Шаблон проектирования для кэширования кэшированных данных

Легко обернуть дополнительное кэширование memcached вокруг ваших существующих запросов к базе данных. Например:

Старый (только DB):

function getX x = get from db return x end 

Новый (DB с memcache):

 function getX x = get from memcache if found return x endif x = get from db set x in memcache return x end 

Дело в том, что это не всегда так, как вы хотите кэшировать. Например, выполните следующие два запроса:

 -- get all items (recordset) SELECT * FROM items; -- get one item (record) SELECT * FROM items WHERE pkid = 42; 

Если бы я использовал вышеупомянутый псевдокод для обработки кэширования, я бы дважды сохранил все поля элемента 42. Однажды в большой набор записей и один раз сам по себе. В то время как я предпочел бы сделать что-то вроде этого:

 SELECT pkid FROM items; 

и кешировать этот индекс PK. Затем кешируйте каждую запись отдельно.

Таким образом, в целом стратегия доступа к данным, которая лучше всего подходит для БД, не соответствует стратегии memcache. Поскольку я хочу, чтобы уровень memcache был дополнительным (т.е. если memcache не работает, сайт все еще работает), я как бы хочу иметь лучшее из обоих миров, но для этого я уверен, что мне нужно будет поддерживать много запросов в 2 разных формах (1. выборка индекса, затем записи и 2. выборка набора записей в одном запросе). Это сложнее с разбиением на страницы. С помощью БД вы будете делать запросы LIMIT / OFFSET SQL, но с memcache вы просто получите индекс PK, а затем пакетный – получите соответствующий срез массива.

Я не уверен, как аккуратно спроектировать это, есть ли у кого-нибудь предложения?

Еще лучше, если вы сами справитесь с этим. Как вы справляетесь с этим?

Если вы используете кеш, чтобы максимально использовать его, вы должны признать, что ваши данные будут всегда устаревать до некоторой степени и что некоторые части данных будут не синхронизированы друг с другом. Попытка сохранить все записи в актуальном состоянии, поддерживая одну копию, лучше всего оставить в реляционных базах данных, поэтому, если это то, что вам нужно, вам, вероятно, будет лучше с мощным 64-битным сервером БД с большим количеством ОЗУ поэтому он может выполнять собственное внутреннее кэширование.

Если вы можете принять устаревшие данные (которые вам понадобятся, если важна реальная масштабируемость), то один подход состоит в том, чтобы просто выбросить весь набор результатов в кеш; не беспокойтесь о дублировании. ОЗУ дешево. Если вы обнаружите, что ваш кеш заполняется, просто покупайте больше оперативной памяти и / или серверов кеша. Например, если у вас есть запрос, который представляет элементы 1-24 в наборе, отфильтрованном условиями X и Y, используйте ключ кеша, который содержит всю эту информацию, а затем, когда его попросят повторить этот же поиск, просто верните весь набор результатов из кэш. Вы либо получаете полный набор результатов из кеша за один удар, либо переходите в базу данных.

Самое сложное – выяснить, сколько данных может быть устаревшим, и насколько он может быть устаревшим: (а) люди слишком много замечают или (б) нарушают бизнес-требования, такие как минимальные интервалы обновления.

Этот подход хорошо работает для приложений, ориентированных на чтение, особенно тех, которые имеют запрограммированные запросы и / или конечный набор критериев фильтрации для данных. Это также означает, что ваше приложение работает точно так же с кешем вкл. Или выкл., Только с 0% ударом, когда кеш выключен. Это подход, который мы принимаем в blinkBox практически во всех случаях.

Читайте о шаблоне карты идентичности . Это способ убедиться, что вы сохраняете только одну копию данной строки в своем прикладном пространстве. Сохраняете ли вы его в memcached или просто в простых объектах, это способ справиться с тем, что вы хотите. Я бы предположил, что Identity Map лучше всего использовать, когда вы обычно выбираете по одной строке за раз.

Когда вы извлекаете целые подмножества таблицы, вы должны обрабатывать каждую строку отдельно. У вас может часто возникать дилемма: лучше ли вы используете ваш кеш, потому что, если 99% ваших строк находятся в кеше, но требуется выборка из базы данных, вы все равно должны запускать SQL-запрос (по крайней мере один раз).

Вы можете преобразовать SQL-запрос для извлечения только строк, которые не находятся в кеше, но нетривиально выполнять это преобразование автоматически, не делая запрос SQL более дорогостоящим.

Ну, я думаю, это то, с чем вам придется жить. Memcahced будет работать лучше всего, если вы на самом деле не делаете это в партиях. Например, это здорово для таких вещей, как «где вещи для этого пользователя? Вот куча вещей для этого пользователя». Это не означает, что этот запрос не выполняет партии. Конечно, это будет – если некоторые вещи пользователя будут такими же, как и его / ее сообщения.

Я предполагаю, что проблема, с которой вы столкнетесь, – это случаи, когда вы смешиваете запросы, которые должны получить элемент из БД самостоятельно, а некоторые, которые получают кучу того же типа предыдущих элементов.

Ситуация всегда меняется. Если вы действительно хотите получить волосатые с вашей реализацией, вы можете изменить свои пакетные запросы, чтобы не включать элементы, уже присутствующие в memcached.Very очень уродливые …

По-моему, это всегда сводится к тому, «какие запросы я действительно хочу кэшировать?»

РЕДАКТИРОВАТЬ:

То, как я это сделаю, это:

  • Запрос по одному элементу – если в memcached, используйте его, иначе извлечение из БД и обновление memcached.
  • Пакетный запрос – не беспокойтесь о том, какие элементы находятся в memcached, просто получите все и обновите memcached.

Это, конечно же, предполагает, что пакетные запросы уже уходят в ад намного больше времени для завершения, и поэтому я уже трачу столько времени, что могу жить с внешним поиском уже кэшированных элементов.

Однако, в конечном счете, ваш кеш будет содержать много элементов, если вы будете использовать пакетные запросы много. Поэтому вам придется ударить по балансу, чтобы определить, в какой момент вы все еще хотите выполнять поиск базы данных. Хорошо, если пакетный запрос раньше в жизненном цикле ваших приложений, тогда все будет кэшировано ранее. После первого пакетного запроса вы можете сказать себе, что вам больше не нужно извлекать из БД, если данные в кеше не будут аннулированы обновлениями или удалениями.

Вот мое понимание того, как это делает NHibernate (и, следовательно, Hibernate). Он имеет 4 кэша:

  • row cache: это кэширует строки базы данных. Кэш-кеш – имя_билета # id, остальные записи – значения строк.
  • кеш запросов: это кэширует результаты, возвращаемые для конкретного запроса. Кэш-кеш – это запрос с параметрами, данные – это список ключей строки TableName # id, которые были возвращены в качестве результатов запроса.
  • cache collection: он кэширует дочерние объекты любого родителя (NHibernate позволяет лениться). Поэтому, если вы будете обращаться к myCompany.Employees, коллекцию сотрудников будут кэшироваться в кеше коллекций. Кэш-кеш – CollectionName # entityId, данные – это список ключей строки таблицы NameName для дочерних строк.
  • table update cache: список каждой таблицы и время последнего обновления. Если таблица была обновлена ​​после кэширования данных, данные считаются устаревшими.

Это довольно гибкое решение, очень эффективное по размеру и гарантирует, что данные не будут устаревшими. Недостатком является то, что для одного запроса может потребоваться несколько обращений к кешу, что может быть проблемой, если кэш-сервер находится в сети.