Разбираемся, что такое кэш, для чего он нужен, зачем и как его чистят
Статью писали как ликбез для маркетологов и новичков в IT. Но поняли, что это нужно знать каждому.
Иллюстрация: Polina Vari для Skillbox Media
Кэш есть на любом цифровом устройстве. Если вы понимаете, как он работает, то сможете ускорить работу программ и предотвратить ошибки в браузере.
В этом материале Skillbox Media разберёмся:
- что такое кэш;
- какие есть типы кэш-памяти;
- для чего нужно чистить кэш; компьютера и браузера.
Что такое кэш простыми словами
Кэш — это память программы или устройства, которая сохраняет временные или часто используемые файлы для быстрого доступа к ним. Это увеличивает скорость работы приложений и операционной системы. Процесс сохранения таких файлов в специальном месте называется кэшированием.
Для примера рассмотрим кэш браузера. Это папка с файлами, которые браузер загрузил в память устройства. В файлах могут быть видео, музыка, изображения или скрипты с какого-то сайта. Когда вы в следующий раз вернётесь на сайт, то браузер не будет запрашивать эти файлы, а возьмёт их из кэша. Нужная вам страница загрузится быстрее.
Увидеть, как кэшируются файлы, можно при просмотре онлайн-видео. Пока вы смотрите видеоролик, он загружается в кэш вашего устройства. Если видео загрузится полностью, его можно будет досмотреть даже с отключённым интернетом.
Кэш-память нужна, чтобы приложения и система работали быстрее. Также она:
- снижает нагрузку на основное хранилище;
- даёт системе возможность выполнять больше действий одновременно;
- экономит трафик.
Термин «кэш» произошёл от французского слова cache — тайник. Его придумал в 1967 году редактор журнала IBM Systems Journal Лайл Джонсон. При подготовке статьи про усовершенствование памяти в новых компьютерах он попросил заменить сложное определение «высокочастотный буфер», а затем сам предложил слово cache. После выхода статьи слово стали использовать в компьютерной литературе как общепринятый термин.
Типы кэш-памяти
Есть два типа кэш-памяти: аппаратная и программная.
Аппаратная кэш-память — память системы. Свой кэш есть у жёсткого диска, графического ускорителя и процессора.
Чаще всего, когда говорят про аппаратную кэш-память, имеют в виду память процессора. Иногда её ещё называют сверхоперативной памятью. Она загружает данные из оперативной памяти для самого быстрого доступа к ним. Она энергозависима — работает, пока устройство включено. Если вы выключите компьютер, кэш процессора автоматически очистится.
Программная кэш-память — это папки на диске устройства, в которых программы и сервисы сохраняют свои файлы для быстрого доступа. У каждой программы своя папка.
Например, кэш «Яндекс Браузера» на компьютере можно найти по такому пути: C: → Пользователи → Имя вашей учётной записи → AppData → Local → Yandex → YandexBrowser → User Data → Default → Cache. AppData — скрытая папка. Вам придётся включить отображение скрытых папок, чтобы её увидеть.
Размер программного кэша ограничивают, чтобы не ухудшалось быстродействие всей системы. Когда место заканчивается, то удаляется часть старой информации и записывается новая.
Для чего нужно чистить кэш
Есть четыре причины очищать кэш.
Первая — медленная работа программ. Если кэш-память будет переполнена, то производительность программ снизится.
Вторая — старые файлы из кэша могут привести к ошибкам в программах. Например, браузер может хранить в кэше старые скрипты сайта. Если на сайте выйдет обновление, браузер продолжит брать данные из своего кэша и не сможет корректно отобразить сайт.
Третья — с помощью кэшированных файлов посторонний человек может проследить за вами, если воспользуется вашим устройством. Также это может произойти, если вы пользуетесь общедоступным устройством — например, компьютером в интернет-кафе.
Четвёртая — кэш занимает место на диске. Память вашего устройства может переполниться. Очистка кэша позволит освободить немного места.
Как происходит очистка кэша
Чтобы не возникало проблем, кэш стоит очищать раз в 2–4 недели. Во время этой процедуры будут удалены только временные файлы, ваши личные данные останутся.
Кэш на компьютере можно очистить с помощью специальных программ. Например, использовать CCleaner для Windows и CleanMyMacX для компьютеров Apple. Также можно сделать это в настройках самому.
Чтобы удалить программный кэш на Windows самостоятельно, откройте меню «Пуск». Наберите на клавиатуре «параметры хранилища». Нажмите «Параметры хранилища» → «Временные файлы» → «Удалить файлы».
Также можно чистить кэш отдельно в программах, которыми вы пользуетесь.
Чаще всего есть необходимость очистить кэш браузера. Во время работы на странице сайта можно нажать комбинацию клавиш Ctrl + F5. Её кэш сбросится, и страница обновится. Чтобы удалить весь сохранённый кэш всех сайтов, вам нужно будет зайти в настройки браузера.
Рассмотрим эту процедуру на примере «Яндекс Браузера». Вам нужно будет зайти в настройки «Яндекс Браузера» (три горизонтальные полоски) → «Дополнительно» → «Очистить историю». После этого установите флажок рядом с «Файлы, сохранённые в кеше» и нажмите «Очистить».
What is Cache?
I am pretty sure that this is not the first time you are hearing or seeing this word — cache. In fact we see this word being used at multiple places in our day to day life. Examples — Browser cache, App cache. In fact, In DSA Memoization is also a type of cache.
But what is cache? As per wikipedia — a cache is a component that stores data so that future requests for that data can be served faster; the data stored in a cache might be the result of an earlier computation or a copy of data stored elsewhere.
Simply put, Cache is a way of storing frequently demanded things closer to those asking for it.
Ok. Now we know what is cache but we still don’t know why do we need it and what does this have to do with software engineering and backend?
Why Caching?
As said in the wikipedia definition — “so that future requests for that data can be served faster”.
Well that is it. We need cache, so that our APIs can respond faster.
Albeit that is the most popular & important use-case of cache but that is not the only one. Some other reasons for using cache includes —
Performance — Performance is improved by making data easier to be accessed through the cache and reduces workloads for database.
Scalability — Workload of backend query is distributed to the cache system which is lower costs and allow more flexibility in processing of data.
Availability — If backend database server is unavailable, cache can still provide continuous service to the application, making the system more resilient to failures.
Nowadays, we also use cache to solve our business problems where transient data is required.
What to Cache?
Well now we know that we need caching to server our requests faster. So should we cache everything? Obviously NOT..
Technically we can cache everything but that has some costs associated with it — both monetary and performance.
As a general rule — “Cache it if you are going to need the data again in a short interval of time” or “if the data is not going to change for a long period of time”.
- Homepage
- Raw data (non-user specific) without personalisations
It is also important to know what shouldn’t be cached —
As a general rule — “Don’t cache if cache hit ratio is very less”.
Cache hit ratio is ratio of how many requests a cache is able to fill successfully and how many requests it receives
Examples when we shouldn’t use cache—
- Low cache hit ratio: when data is requested using filters and filters have very high cardinality (example: Price Range filter)
- High cardinality: User personalised search results. In this case the cache hit might be high if the user comes to the app often but the total number of cache keys that would be required would be very large.
Where to Cache?
In a backend system, there are multiple places where we can cache data. The choice of which depends upon the type of data and usecase. For example —
- Front End cache is used to store the assets and API responses that don’t change much. This cache is present in your browser.
- CDN (content delivery network) is used for caching front end assets like images, fonts, javascripts, css etc
- Nginx(reverse proxy) is used to cache HTTP responses.
Nginx cache is not distributed and harder to manage and invalidate, and therefore not used that much
In-memory cache VS Centralised cache
In-memory cache: In case of small, predictable number of objects that have to be read multiple times, an in-process cache is a good solution because it will perform better than a distributed cache. Example: Guava Cache
Centralised cache: In cases where the number of objects that needs to be cached is unpredictable and large, and read-consistency is a must in between servers. Even if 100 percent read consistency might not be required, there might be cases where we want a single source of truth. Not same performance benefits as an in-process cache. Example: Redis, Memcached etc.
It goes without saying that your application can use both schemes for different types of objects depending on what suits the scenario best.
Caching Strategies
- Cache Aside
- Read-Through
Write
- Write-Through
- Write-Behind
Read + Write
- Write Around + Cache Aside
- Write Around + Read Through
Cache Updation
There are also multiple methods of refreshing the cache
— Refreshing on TTL expiry
— Refreshing on DB update
The second method is also called cache invalidation and is probably the best one for most use cases.
Redis (https://redis.io/)
Redis is an open source, in-memory data structure store, used as a database, cache, and message broker.
Redis provides data structures such as strings, hashes, lists, sets, sorted sets with range queries.
Why Redis?
- Low Response time: Keeps data in-memory instead of disk or SSD. So retrieval is fast.
- Data persistence: Redis can provide persistence in case of process outages or network bottlenecks. Redis takes regular snapshots of data and appending them with changes as they become available.
Redis can be configured to generate backups on-demand or after regular intervals of time to ensure database durability and integrity. - Data Expiration: Redis allows us to set a TTL (time to live) for the data.
- Battle tested: Already being used at high scale by companies like Twitter, Github, Stack overflow etc.
Redis Deployment Configurations
Standalone
There is only 1 server node. If that node goes down, cache is down.
Master Slave
Redis server can operate in one of 2 modes — as a master node, or as a slave node.
- The master slave replication is done asynchronously.
- Recommendation: Serve writes through Redis Master and reads through Redis Slaves.
- By default, all redis servers are master nodes. We need to explicitly specify if we need a node to work in slave mode.
Now the next logical questions would be — what if master goes down? what if slave goes down?
Master node failure: Well, there are 2 options — we can add a new master node OR we can promote an existing slave node to be master.
Here, the second option is better than first as in the first one, the newly added master node will not be having any existing data. Whereas in the second option slave already has almost all of the data.
Slave node failure: If one of the slaves go down, we have other slaves that can server data.
Redis Cluster
Redis scales horizontally with a deployment topology called Redis Cluster.
Redis Cluster provides a way to run a Redis installation in such a way that data is automatically sharded across multiple Redis nodes.
Sharding is the method for distributing data across multiple machines.
Redis cluster also provides us with a degree of availability i.e. it continues to function if some of the nodes in the cluster goes down. However, it stops working in case of a large failures (when multiple master nodes are down).
For a cluster to work properly, we need to open the 2 TCP ports for each node in cluster-
- 6379: So that clients can communicate with nodes
- 16379: For inter-node communications.
Redis Cluster does not use consistent hashing, but a different sharding technique where every key is conceptually part of a hash slot.
There are 16384 hash slots in Redis Cluster, and to compute the hash slot for a given key, we simply take the CRC16 of the key modulo 16384.
Every node in a Redis Cluster is responsible for a subset of the hash slots.
For both adding and removing nodes, we only need to change the hash slots associated with each node.
Moving hash slots does not require stopping any service; therefore, adding and removing nodes, or changing the percentage of hash slots held by a node, requires no downtime.
Redis Cluster with master-slave replication
This deployment configuration is used when we want to be available even when a subset of master nodes are down. Here, even if Node B master fails, the cluster can continue to serve requests from Node B replica 1 or Node B Replica 2.
The cluster will go down, if all the nodes for a hash slot are down.
Twemproxy — Client Side Sharding in Redis
Twemproxy (aka nutcracker) is a fast and lightweight proxy developed by twitter before redis cluster was a thing.
It was built primarily to reduce the number of connections to the caching servers on the backend. This, together with protocol pipelining and sharding enables us to horizontally scale our applications distributed caching architecture.
Twemproxy support consistent hashing with different strategies and hashing functions.
Time to Live (TTL) for Cache
TTL for a cache determines, for how long a particular data needs to be cached and its value varies from usecase to usecase.
Low TTL will remove the key from cache more frequently, resulting in more number of requests to the underlying system. Low TTL can reduce the chances of inconsistencies but can result in high latencies and reduced performance.
Large TTL on the other hand will keep the system performant but increases the chances of inconsistencies.
TTL with Jitter
This is a very common technique that is used in case where we cache multiple items at once. If we were to add a fixed TTL for all the keys (lets say 10 minutes) then all the keys will expire at once, resulting in large load on the database at once.
Instead, we add a delta to the Cache Keys’ TTL so that not all the keys expire at once.
Redis Eviction policies
The exact behaviour that Redis must follow when the maxmemory limit is reached is configured using the maxmemory-policy configuration.
The maxmemory configuration directive configures Redis to use a specified amount of memory for the data set. You can set the configuration directive using the redis.conf file
The following policies are available:
- noeviction: New values aren’t saved when memory limit is reached. When a database uses replication, this applies to the primary database
- allkeys-lru: Keeps most recently used keys; removes least recently used (LRU) keys
- allkeys-lfu: Keeps frequently used keys; removes least frequently used (LFU) keys
- volatile-lru: Removes least recently used keys with the expire field set to true
- volatile-lfu: Removes least frequently used keys with the expire field set to true
- allkeys-random: Randomly removes keys to make space for the new data added
- volatile-random: Randomly removes keys with expire field set to true .
- volatile-ttl: Removes least frequently used keys with expire field set to true and the shortest remaining time-to-live (TTL) value.
Cache Warmup — solution
Cold Cache — problem
This is a problem that you can face with both In-memory and Centralised cache. The problem is the time interval during which the cache is yet to be populated, so the cache hit ratio is very less.
To solve this problem, we simply update the cache node with data before it starts serving online traffic with the help of startup scripts or schedulers. To avoid all cache miss at once, we use TTL with Jitter is recommended.
Achieving consistent behaviour using redis
Redis can also be used to store transient data that is required within subsequent requests to ensure consistent behaviour.
Example
Consider a booking flow where the price is being calculated on the basis of many dynamic inputs that can change anytime. Now, the booking flow can consist of multiple APIs that are needed to be invoked serially.
Our expectation here is that the user should be given a consistent price on all the pages and since the price calculation is dynamic, we somehow need to store the price that was shown to the user for this specific session.
We can use cache here to store the price for that session in the first API call and then use the same data in all the subsequent requests.
Error Handling
Cache plays an important role when we are building applications at scale as it helps us drastically improve the performance of our system.
The important thing to question here is — what if the entire cache cluster is down?
Consider an application that is heavily dependent on cache and the cache hit percentage for the service is 90 percent. Meaning that 90 percent of the overall traffic is being handled by the cache and 10 percent is being handled by the database.
Now let us say the redis cluster goes down or the application is unable to connect to the cluster. In this case how should the application behave? There are 3 options —
- Option 1: Retry the requests
- Option 2: We can ask the database for the data and continue serving the request
- Option 3: We can terminate the request and give error to the client
In case the error is due to some temporary network fluctuation, with Option 1 we will continue to serve traffic.
Option 2 seems like a good option but in fact is a Devil in disguise. It can do more harm than good. Why you ask?
If cache is indeed down and we call the database with all that traffic that was handled by cache. There is a 90 percent increase in the traffic to the database in a short span of time. This spike traffic could easily choke the database resources and can lead to failures in other systems.
So, it is always crucial to think about the error handling in case of cache down or any other system for that matter.
Liked what you read? Motivate me to write more by upvoting (clapping) this article. Thanks!!
[По полочкам] Кэширование
Всем привет! Меня зовут Илья Денисов, я занимаюсь backend разработкой уже более пяти лет и сейчас пишу на языке go. Сегодня я предлагаю вам поговорить о кэшировании. Постараюсь рассказать о базовых концепциях, а также затронуть ряд особенностей, неочевидных на первый взгляд.
Что такое кэширование?
Кэширование – это способ хранения данных как можно ближе к месту их использования. Как правило, для этого используется быстродействующая память (RAM).
Для чего нужно кэширование?
Кэширование появилось давно и использовалось для ускорения работы процессора с оперативной памятью. Наверняка каждый из вас слышал об иерархии кэша L1, L2 и L3, применяемого в процессорах. Добавление кэша значительно ускорило работу с памятью, но и принесло дополнительные проблемы. Самая известная и сложная из них – это инвалидация данных. Мы уделим ей особое внимание чуть позже.
С этой проблемой (и рядом других, о которых мы тоже поговорим) связан главный принцип работы с кэшем. Он очень прост: если вы можете обойтись без кэширования, то именно так и сделайте.
Например, если у вас простое приложение или небольшая нагрузка, то кэширование вам не нужно. Важно понимать, что кэширование само по себе “не бесплатное”, оно привносит в систему дополнительную сложность: появляются дополнительные компоненты, которые надо сопровождать, усложняется структура кода.
Но если у вас большая частота запросов (Requests per Second, RPS), если запросы эти “тяжелые ”, если вам слишком дорого масштабировать основное хранилище – любой из этих причин и, тем более, их сочетания достаточно, чтобы задуматься о кэшировании всерьез. При грамотном подходе оно поможет уменьшить в разы время ответа при обращении к данным.
Поэтому более продвинутый вариант принципа работы с кэшем можно сформулировать, перефразируя знаменитую цитату немецкого богослова XVIII века Карла Фридриха Этингера: “Дай мне удачу, чтобы без кэша можно было обойтись, дай мне сил, если обойтись без него нельзя – и дай мне мудрости отличить одну ситуацию от другой”.
Как работает кэширование?
Логически кэш представляет из себя базу типа ключ-значение. Каждая запись в кэше имеет “время жизни”, по истечении которого она удаляется. Это время называют термином Time To Live или TTL. Размер кэша гораздо меньше, чем у основного хранилища, но этот недостаток компенсируется высокой скоростью доступа к данным. Это достигается за счет размещения кэша в быстродействующей памяти ОЗУ (RAM). Поэтому обычно кэш содержит самые “горячие” данные.
Вот самый базовый пример работы кэша:
На первой схеме изображено первое обращение за данными:
Пользователь запрашивает некие данные
Кэш приложения ПУСТ, поэтому приложение обращается к базе данных (БД)
БД возвращает запрошенные данные приложению
Приложение сохраняет полученные данные в кэше
Пользователь получает данные
Вторая схема иллюстрирует последующие обращения за данными:
Пользователь запрашивает данные
Приложение уже имеет эти данные в кэше (ведь они были записаны туда при первом обращении) и поэтому НЕ ОБРАЩАЕТСЯ за ними к БД
Пользователь получает данные
Из этого простого примера видно, что только первый запрос данных приводит к обращению к БД. Все последующие запросы попадают в кэш до тех пор, пока не истечет TTL. Как только это произойдет, новый запрос снова обращается к БД и заново кэширует данные. Так будет продолжаться все время, пока приложение работает.
В результате мы экономим время не только на обработке запросов в БД, но и на сетевом обмене с БД. Все это значительно ускоряет время получения ответа пользователем.
Метрики кэша
Работу кэша можно оценивать при помощи множества метрик разной степени полезности. Я опишу те, которые считаю базовыми и наиболее полезными.
Объем памяти, выделенной под кэш. Это базовый показатель, по которому можно судить, сколько используется ресурсов
RPS чтения/записи – количество операций чтения/записи за единицу времени. В обычной ситуации количество операций чтения должно быть в разы больше количества операций записи. Обратное соотношение свидетельствует о проблемах в работе кэша
Количество элементов в кэше. Его полезно знать в дополнение к объему памяти, чтобы обнаруживать большие записи
Hit rate – процент извлечения данных из кэша. Чем он ближе к 100%, тем лучше. Этот параметр буквально определяет то, насколько наш кэш полезен и эффективен
Expired rate – процент удаления записей по истечении TTL. Этот показатель помогает обнаружить проблемы с производительностью, вызванные большим количеством записей с одновременно истекшим TTL
Eviction rate – процент вытеснения записей из кэша при достижении лимита используемой памяти. Важный показатель при выборе стратегий вытеснения, о которых мы поговорим чуть позже
Что можно кэшировать?
Строго говоря, кэшировать можно что угодно, но не всегда это целесообразно. Все сильно зависит от данных и паттерна их использования.
Все данные можно условно разделить на 3 группы по частоте изменений:
Меняются часто. Такие данные изменяются в течение секунд или нескольких минут. Их кэширование чаще всего бессмысленно, хотя иногда может и пригодится.
Пример: ошибки (кэширование ошибок может быть настолько важным, что мы посвятили ему целую главу ближе к концу статьи)
Меняются нечасто. Такие данные изменяются в течение минут, часов, дней. Именно в этом случае вы чаще всего задаетесь вопросом “Стоит ли мне кэшировать это?”
Примеры: списки товаров на сайте, описания товаров
Меняются крайне редко или не меняются никогда. Такие данные меняются в течение недель, месяцев и лет. В этом случае данные можно спокойно кэшировать. НО! Ни в коем случае нельзя усыплять бдительность верой в то, что какие-либо данные никогда не изменятся. Рано или поздно они изменятся, поэтому всегда выставляйте всем данным разумный TTL. ВСЕГДА!
Пример: картинки, DNS
Типы кэшей
С точки зрения архитектуры, можно выделить два типа кэшей: встроенный кэш (inline) и отдельный кэш (sidecar).
Встроенный кэш – это кэш, который “живет” в том же процессе, что и основное приложение. Они делят один сегмент памяти, поэтому за данными можно обращаться напрямую. В качестве такого кэша может выступать map – обычный инструмент из арсенала go – или другие структуры данных (подробнее об этом можно почитать здесь: https://github.com/hashicorp/golang-lru). Вот принципиальная схема работы inline cache:
Отдельные кэши (sidecar) – это обособленный процесс со своей выделенной памятью. Как правило, это хранилище типа ключ-значение, которое используется как кэш для основного. Примеры такого типа кэша – Redis, Memcached и другие хранилища типа ключ-значение. Вот принципиальная схема их работы:
Сравнение двух типов кэшей:
Характеристика
Встроенный кэш (inline)
Отдельные кэши (sidecar)
Выше: обращаемся напрямую к памяти
Ниже: есть сетевые вызовы, а также оверхед на работу самого хранилища
Кэш и приложение делят одну область памяти, поэтому ее тратится меньше
Под кэш выделена отдельная память, поэтому ее тратится больше
Плохая: каждая копия приложения содержит только свои данные
Хорошая: все копии приложения обращаются к единому хранилищу
Кэш нельзя масштабировать отдельно от приложения
Кэш и приложение можно масштабировать независимо друг от друга
Ресурсы (память, ЦПУ и т.д.)
Общие, поскольку “живут” в одном процессе
Выделенные, поскольку “живут” в разных процессах
Проще: кэш – просто структура данных, предоставляемая библиотекой
Сложнее: кэш – отдельно разворачиваемый компонент, требующий отдельного мониторинга и экспертизы
Низкая: проводить отдельно работы над кэшем крайне сложно
Высокая: мы можем проводить любые работы над кэшем независимо от приложения
Сложнее: приложение обычно не общается с диском напрямую
Проще: например, redis может периодически сбрасывать данные на диск и восстанавливать состояние после рестарта
Не стоит думать, что один тип кэша хуже другого. У каждого из них свои достоинства и недостатки и каждый из них нужно применять с умом. Более того, их можно комбинировать. Например, вы можете использовать встроенный кэш как L1, а отдельный кэш как L2 перед основным хранилищем. В результате, такая схема может в разы сократить время ответа и снизить нагрузку на основное хранилище.
Стратегии работы с кэшем
Рассмотрим стратегии чтения и записи при работе с кэшем. В приведенных далее примерах кэш может быть как встроенным, так отдельным. Под “приложением” подразумевается некая бизнес-логика. Упор делается на описание взаимодействия компонентов друг с другом.
Cache through (Сквозное кэширование)
В рамках этой этой стратегии все запросы от приложения проходят через кэш. В коде это может выглядеть как связующее звено или “декоратор” над основным хранилищем. Таким образом, для приложения кэш и основная БД выглядят как один компонент хранилища.
Read through (Сквозное чтение)
Приходит запрос на получение данных
Пытаемся прочитать данные из кэша
В кэше нужных данных нет, происходит промах (miss)
Кэш перенаправляет запрос в БД. Это важный нюанс стратегии: к БД обращается именно кэш, а не приложение
Хранилище отдает данные
Сохраняем данные в кэш
Отдаем запрошенные данные приложению. Для приложения это выглядит так, как если бы хранилище просто вернуло ему данные, то есть шаги 3-6 скрыты от основной бизнес-логики
Возвращаем результат запроса
Write through (Сквозная запись)
Приходит запрос на вставку каких-либо данных
Отправляем запрос на запись через кэш. В этот момент кэш выступает только как прокси и сам по себе ничего не делает
Сохраняем данные в БД
БД возвращает результат запроса
Сохраняем данные в кэш. Делаем мы это специально после вставки, чтобы кэш и БД были консистентны. Если бы мы писали данные в кэш на шаге 2, а при этом на шаге 3 произошла бы ошибка, то кэш содержал бы данные, которых нет в БД, что может привести к печальным последствиям
Отдаем запрошенные данные приложению
Возвращаем результат запроса
Очень простая схема работы с точки зрения организации кода
В коде легко добавить/убрать кэш, так как обычно это простая “обертка” над основным хранилищем
Схема негибкая, поскольку записывать в кэш мы можем строго после выполнения основного запроса и в целом кэш скрыт от бизнес-логики
Cache aside (Кэширование на стороне)
В этой стратегии, приложение координирует запросы в кэш и БД и само решает, куда и в какой момент нужно обращаться. В коде это выглядит как два хранилища: одно постоянное, второе – временное.
Read aside (Чтение на стороне)
Приходит запрос на получение данных
Пытаемся читать из кэша
В кэше нужных данных нет, происходит промах (miss)
Приложение само обращается к хранилищу. В этом главное отличие от сквозного подхода: бизнес-логика в любой момент времени сама решает, куда обращаться – к кэшу или к БД
БД отдает данные
Сохраняем данные в кэш
Возвращаем результат запроса
Write aside (Запись на стороне)
Приходит запрос на вставку каких-либо данных
Сохраняем данные в БД
БД возвращает результат запроса
Сохраняем данные в кэш. Опять-таки, мы намеренно делаем это после вставки, чтобы кэш и БД были консистентны. Сохранение данных в кэше перед шагом 2 и ошибка на шаге 2 привели бы к появлению в кэше данных, которых нет в БД. Результат был бы все тот же – печальные последствия
Возвращаем результат запроса
Гибкость. Управление полностью в руках приложения. Оно может сохранить данные в кэш сразу после обращения к БД, а может предварительно сделать еще ряд манипуляций с данными и только затем сохранить их в БД
Схема работы немного сложнее с точки зрения организации кода
В коде сложнее добавить/убрать кэш, поскольку это отдельный компонент, с которым взаимодействует бизнес-логика. Изменить этого взаимодействие может быть сложно
Cache ahead (Опережающие кэширование)
Эта стратегия предназначена только для запросов на чтение. Они всегда идут только в кэш, никогда не попадая в БД напрямую. По факту мы работаем со снимком состояния БД и обновляем его с некоторой периодичностью. Для приложения это выглядит просто как хранилище, как и в случае со сквозным кэшированием.
Входящие запросы на чтение:
Приходит запрос на получение данных
Читаем из кэша. Если в кэше нет нужных данных, то возвращаем ошибку. К БД в случае промаха не обращаемся
Если данные есть, то отдаем их приложению
Возвращаем результат запроса
Периодически запускается фоновый процесс, который читает данные из БД.
Читаем актуальные данные из БД
БД отдает данные
Сохраняем данные в кэш
Минимальная и полностью контролируемая нагрузка на БД. Клиентские запросы не могут повлиять на БД
В коде легко добавить/убрать кэш, поскольку можно просто заменить кэш на основное хранилище и обращаться уже к нему
Простота, так как не приходится иметь дело с двумя хранилищами
Кэш отстает от основного хранилища на период между запусками обновления кэша. Нужно помнить, что на момент обращения свежие данные могут еще “не доехать” до кэша. Эта проблема может быть решена использованием сквозной записи или записи на стороне. Тогда, при обновлении данных в БД, данные будут обновляться и в кэше
Вы можете использовать описанные стратегии в любых комбинациях. Например, вы можете взять опережающие кэширование, добавить туда сквозную запись и чтение на стороне, чтобы добиться максимальный актуальности данных и избежать промахов по максимуму!
Стратегии инвалидации
Инвалидация – это процесс удаления данных из кэша или пометка их как недействительных. Делается это для того, чтобы гарантировать актуальность данных, с которыми работает приложение.
Инвалидация по TTL
TTL (Time To Live) – время жизни данных в кэше. При сохранении данных в кэш для них устанавливается TTL и данные будут обновляться с периодичностью не менее TTL.
Это самый простой способ инвалидации данных. Тем не менее, у этой стратегии есть свои подводные камни.
Самый главный из них – вопрос длительности TTL. Если TTL слишком короткий, то запись может “протухнуть” и стать недействительной раньше, чем обновление было бы необходимо, что приведет к отправке повторного запроса в источник данных. Если TTL слишком длинный, то запись может содержать устаревшие данные, что может привести к ошибкам или неправильной работе приложения. Обычно ответ на этот вопрос подбирается эмпирическим путем.
Есть, впрочем, и другой вариант. Источник данных может присылать TTL сам, тогда клиенту не придется выбирать TTL, а просто брать предлагаемый. Такой подход, например, можно использовать в HTTP.
Сложность иного рода возникает, если записи становятся недействительными одновременно в большом количестве. В таком случае возникает множество запросов в источник данных, что может привести к проблемам с производительностью, а то и вовсе “положить” его. Для избежания подобной ситуации, можно использовать jitter.
Jitter – это случайное значение, добавляемое к TTL. Если в обычном случае все записи имеют, например, TTL = 60 сек., то при использовании jitter с диапазоном от 0 до 10 сек. TTL будет принимать значение от 60 до 70 сек. Это позволит сгладить количество записей, переходящих в состояние недействительных одновременно.
Jitter позволил нам сгладить нагрузку на источник данных, когда “протухает” много записей сразу. Но что делать, если есть одна запись, которую интенсивно используют? Ее инвалидация приведет к тому, что все запросы, которые не нашли данных в кэше, одновременно обратятся к источнику. Тогда нам нужно схлопнуть все эти запросы в один. В go есть для этого отличная библиотека singleflight. Она определяет одинаковые запросы, возникающие одномоментно, выполняет лишь один запрос в источник, а затем отдает результат всем изначальным запросам. Таким образом, если у нас возникли десять запросов, библиотека выполнит только один из них, а результат вернет всем десяти. Стоит отметить, что эта библиотека работает только в рамках одного экземпляра приложения. Если у вас их несколько, то даже с использованием этой библиотеки в источник может уйти больше одного запроса.
Инвалидация по событию
При таком подходе данные инвалидируют при наступлении некоего события – обычно это обновление данных в источнике. На самом деле, мы уже рассмотрели этот способ, когда говорили про стратегии использования кэширования, а именно write through и write aside.
Также в качестве события для инвалидации данных может выступать время последней модификации данных. Такой способ используется в HTTP.
Стратегии вытеснения
Размер кэша ограничен, он гораздо меньше основного хранилища, а значит, мы не может разместить в нем все данные. Что делать, когда память, выделенная под кэш, полностью заполнена, а новые записи продолжают поступать?
Ничего не делать
Конечно, можно игнорировать все новые записи и ждать, пока существующие истекут по TTL. Однако в общем случае это крайне неэффективно, поскольку все запросы идут в источник данных и все преимущества кэша нивелируются. Поэтому обычно используются более эффективные стратегии вытеснения.
Random
Random – стратегия вытеснения, при которой удаляются случайные записи. Это самая простая стратегия: просто удаляем то, что первым попалось под руку. Но этот способ недалеко ушел от стратегии “ничего не делать”.
TTL – стратегия вытеснения, предусматривающая удаление той записи, которой осталось меньше всего “жить”, то есть у которой TTL истечет раньше всех. Как и random, эта стратегия немного лучше, чем “ничего не делать”, но все еще недостаточно эффективна.
LRU (Least Recently Used) – стратегия вытеснения, которая опирается на время последнего использования записи. Она удаляет записи, у которых время последнего использования старше остальных. Таким образом, в кэше остаются записи, которые использовались недавно. Эта стратегия опирается уже не на случай, а на паттерн использования данных, поэтому она гораздо эффективнее предыдущих.
Эта стратегия хорошо подходит, когда:
недавно использованные данные, скорее всего, будут использованы снова в ближайшем будущем
нет данных, которые используются чаще остальных
вы не знаете, что именно вам нужно
LFU (Least Frequently Used) – стратегия вытеснения, опирающаяся на частоту использования записи. Она удаляет записи, которые использовались реже всего. Так в кэше остаются данные, которые использовались чаще других. Эта стратегия тоже опирается не на случай, а на паттерн использования данных, поэтому она тоже эффективнее остальных и является альтернативой LRU.
Эта стратегия хорошо подходит, когда есть данные, которые используются значительно чаще остальных. Такие данные разумно не вытеснять из кэша, чтобы избежать лишних “походов” в источник.
Кэширование ошибок
Ранее я упомянул, что мы можем кэшировать ошибки. На первый взгляд это может показаться странным: зачем нам вообще кэшировать ошибки? На самом деле, это крайне полезная штука.
Представим себе, что клиент запрашивает данные, которых нет в источнике. Пусть это будем информация о товаре по id. Казалось бы, нет данных и ладно: просто сходим в источник, ничего не получим и сообщим клиенту. Но что, если таких запросов много? А что, если кто-то делает это специально?
Это типичная схема так называемой атаки через промахи кэша (cache miss attack). Ее суть в запрашивании данных, которых заведомо не может быть в кэше, поскольку их нет в источнике. Вал таких запросов может привести к проблемам с производительностью источника и даже к его “падению”. Этого можно избежать, если кэшировать ошибку, тогда последующие запросы того же рода будут попадать в кэш и источник не пострадает.
Но тут тоже нужно быть осторожным. Если хранить в одном кэше и полезные данные, и ошибки, то в случае атаки полезные данные могут вытеснены из кэша. Поэтому я бы рекомендовал иметь выделенный кэш под ошибки. Он может быть меньшего объема, чем основной кэш.
Также кэширование ошибок полезно, если сервис, к которому вы обращаетесь, “почувствовал себя плохо”. Чтобы не забивать его запросами, которые, скорее всего, не будут выполнены, а лишь усугубят проблему, лучше кэшировать ошибки на несколько секунд. Таким образом, мы перестанем оказывать негативное воздействие на сервис и дадим ему возможность нормализоваться. Но с этой задачей лучше справляется паттерн Circuit Breaker, который мы не будем рассматривать в рамках этой статьи.
Заключение
На этом пока все. Я не ставил перед собой целью раскрыть тему кэширования исчерпывающе: наверняка многие вещи остались не рассмотренными в рамках этой статьи. Я хотел лишь предоставить структурированную основу и хочу верить, что у меня это получилось.
Надеюсь, данный материал оказался вам полезен, вы открыли для себя что-то новое или структурировали уже известное.
Что такое кэш в телефоне
Память мобильных устройств, которые подключены к интернету, накапливает много информации. Чтобы смартфон работал корректно и без сбоев, лишние данные рекомендуется периодически удалять. Если знать, что такое кэш в телефоне, можно не бояться вместе с мусором стереть нужные сведения.
Что такое кэшированные данные
Слово Cache с английского языка – «тайник». Когда человек впервые посещает веб-сайт, открывает приложение или программу, телефон автоматически начинает запись появившейся страницы – кэширование. Загруженные данные остаются на устройстве в виде временных или постоянных файлов. При повторном просмотре сохраненная информация позволяет быстро воспроизвести исходную страницу.
Dalvik-Cache
Временные файлы кодов установленных приложений хранятся на промежуточном буфере и для быстрого доступа могут запрашиваться виртуальной машиной Dalvik. На таком программном обеспечении осуществляется работа всех телефонов на базе Андроид.
Удалять такие сведения рекомендуется только при возникновении серьезных проблем с телефоном. Действия по очистке проводят, когда устройство включено в специальном режиме, где осуществляется сброс всех настроек.
Системный кэш
На мобильном устройстве находятся предустановленные программы. Их данные называют системными. Они необходимы, чтобы программы правильно функционировали. При удалении временных файлов возможен сбой в их работе.
Кэш в приложениях на телефоне
В программах, которые пользователь скачивает из интернета самостоятельно, кэшированные данные содержат все сведения о работе приложения и настройках. Они занимают определенное количество памяти.
Временный кэш сохраняется на устройстве при просмотре видеороликов, музыкальных и графических файлов в установленных программах. Например, при входе на страничку в соцсетях, старые изображения в ленте появляются сразу и могут отображаться даже без интернета.
Где находятся и зачем нужны
Временные файлы не только увеличивают скорость загрузки, экономят трафик, но и помогают пользователю сохранить прогресс в приложениях. Кэшированные данные сторонних программ и системные сведения находятся в отдельной папке на внутреннем накопителе и иногда занимают значительный объем телефонной памяти. Исключение составляют приложения, которые можно установить или переместить на съемное запоминающее устройство. Информация копируется на внешний накопитель.
Можно ли удалить кэш в телефоне
Операция по удалению временных файлов не принесет вреда установленным приложениям. Они продолжат работать, но при входе потребуется некоторое время, чтобы сведения заново загрузились из интернета.
Как очистить кэш на смартфоне
Из-за огромного количества кэшированных данных внутренняя память смартфона забивается. Начинаются сбои в работе телефона, появляются устаревшие сведения в браузере, замедляется воспроизведение музыки, загрузка фото из галереи. Если освободить устройство от ненужного мусора, оно начнет функционировать быстрее и эффективней.
Вручную
- Войти в настройки телефона и найти раздел «Приложения». В открывшемся окне отобразятся все системные, установленные и активные программы.
- Выбрать ту, которой нужна очистка, и кликнуть по ней. Графа «Память» будет отображать данные о размере приложения.
- Внизу появится кнопка «Очистить кэш». После нажатия система попросит подтвердить действие.
Автоматическая очистка
Использование специальных программ помогает найти кэш в телефоне и стереть лишние данные автоматически. Их желательно скачивать из магазина, который предусмотрен на устройстве.