Шаблон проектирования для кэширования данных в memcached - PullRequest
10 голосов
/ 10 ноября 2008

Легко обернуть необязательное кэширование в 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;

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

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

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

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

Ответы [ 4 ]

4 голосов
/ 10 ноября 2008

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

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

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

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

3 голосов
/ 10 ноября 2008

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

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

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

1 голос
/ 24 августа 2010

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

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

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

1 голос
/ 10 ноября 2008

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

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

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

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

EDIT:

Я бы сказал об этом так:

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...