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

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

Старый (только для БД):

function getX
    x = get from db
    return x
end

Новое (БД с 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;

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

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

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

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

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Symfony Station Communiqué - 7 июля 2023 г
Symfony Station Communiqué - 7 июля 2023 г
Это коммюнике первоначально появилось на Symfony Station .
Оживление вашего приложения Laravel: Понимание режима обслуживания
Оживление вашего приложения Laravel: Понимание режима обслуживания
Здравствуйте, разработчики! В сегодняшней статье мы рассмотрим важный аспект управления приложениями, который часто упускается из виду в суете...
Установка и настройка Nginx и PHP на Ubuntu-сервере
Установка и настройка Nginx и PHP на Ubuntu-сервере
В этот раз я сделаю руководство по установке и настройке nginx и php на Ubuntu OS.
Коллекции в Laravel более простым способом
Коллекции в Laravel более простым способом
Привет, читатели, сегодня мы узнаем о коллекциях. В Laravel коллекции - это способ манипулировать массивами и играть с массивами данных. Благодаря...
Как установить PHP на Mac
Как установить PHP на Mac
PHP - это популярный язык программирования, который используется для разработки веб-приложений. Если вы используете Mac и хотите разрабатывать...
10
0
7 302
4

Ответы 4

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

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

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

На мой взгляд, это всегда сводится к тому, «какие запросы я хочу кэшировать В самом деле

Обновлено:

Я бы сделал следующее:

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

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

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

Спасибо за ответ, Джем. Предполагая, что я кэширую только то, что мне действительно нужно кешировать. У вас есть идеи, как управлять двумя стратегиями (DB и DB + memcache) с наименьшим дублированием кода?

doekman 10.11.2008 04:37

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

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

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

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

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

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

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

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

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

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

Другие вопросы по теме