Пока в голове перваривается идеология кеширования в memcached, там же, в голове же, возникают идеи как это можно было сделать лучше.
ПостановкаЦель — кеширование данных в режиме "ключ-значение": предельно быстрое на чтение, желательно быстрое и на запись, но ненадёжное по определению (ибо тот же memcached тоже ничего не гарантирует); и обязательно распределённое, по постановке вопроса.
В случае APC мы теряем распределённость, вынуждая каждый процесс иметь свой собственный кеш с одними и теми же значениями. В лучшем случае мы можем задействовать shared memory, и делить кеш в рамках сервера, но это нас тоже не устраивает. Так что идеология memcached была выбрана примерно правильной: кучка демонов, которые хранят значения, а распределение производится хешированием ключа по списку серверов (алгоритмы разнятся, но они нас сейчас не касаются).
Как можно улучшить идеологию memcached чтоб достичь ещё большей быстроты?
ПервоеМожно переписать этот демон на протокол (бинарный, само собой), работающий по UDP. Да-да, по тому самому UDP, "ненадёжному" транспорту. Как это выглядит сейчас? Сейчас клиент (скрипт страницы, например) коннектится к серверу по TCP. Это уже само по себе непозволительно дорогая в плане быстродействия операция из-за её трёхшаговости: клиентский SYN, ожидание, серверный SYN-ACK, клиентский ACK, и далее уже сам протокол. Случай с UNIX-сокетами не рассматриваем, ибо оно уже заведомо не распределённое.
А вот сделай мы на UDP, это выглядело бы так: шлём пакет с запросом или командой (сразу! без всяких SYN&ACK'ов!); затем каким-либо способом ждём ответа определённое время от сервера, и либо считаем запрос неотвеченным по таймауту, либо реагируем н полученный отклик.
Да, UDP-пакеты могут теряться. Но и кеш сам по себе — слой весьма ненадёжный, без гарантий возврата данных, даже тех, которые ещё не устарели. Оно именно ускоряющее, а не хранящее. В случае потери UDP мы получим чуть большую нагрузку на DB-сервера, но это будет поводом сделать так, чтоб пакеты не терялись.
ВтороеНо по-прежнему остаётся слабое звено — ожидание отклика после отправки запроса. Оно есть и сейчас в TCP-протоколе. Но и от него можно избавиться, причём как в случае UDP, так и в случае TCP. Просто и банально, надо изменить клиентский API так, чтоб отправка запроса-команды была асинхронной. То есть не ожидала ответа немедленно на выходе из функции отправки.
Но такая асинхронность требует многопоточности. Мы в этом плане ограничены, и наш клиент (скрипт) и так уже работает в одном из потоков веб-сервера, и язык не даёт функционала для параллелизма. Но нам и не нужна вычислительная параллельность. Мы можем её сэмулировать не-блокирующими сокетами.
В общем случае, сценарий чтения данных однопотоковым клиентом мог бы выглядеть так:
1. Отправляем UDP-запрос на нужные нам кешированные значения по ключам. В случае TCP — коннектимся к TCP, и отправляем запрос.
2. Делаем всякие алгоритмы, которые не зависят от данных из кеша. Это и есть главная фичка: не ждать тупо как коровы, а заниматься полезными вещами во время отработки кеша и его протоколов.
3. Спрашиваем клиентский API, не готовы ли данные. API нам либо возвращает данные, либо говорит что отклика нет и время истекло, либо ждёт до истечения изначально отведённого времени.
4. Продолжаем далее как обычно обрабатывать данные из кеша.
Запись могла бы выглядеть вообще вот так:
1. Отправляем UDP-команду записи данных (set/add/replace). Если нас волнует результат (add/replace), то:
2. ... делаем независящие от кеша дела.
3. ... проверяем отклик.
4. После чего (либо сразу, если нас отклик не волнует (set)) продолжаем делать дела.
Очевидно, что последовательная, можно даже сказать что сихронная модель клиентских библиотек memcached сейчас не допускает существования пунктов №2 в обоих операциях, и не позволяет игнорировать отклик после записи (перейти от №1 сразу к №4). И это не считая лишних издержек на TCP SYN-ACK, о которых уже говорилось.
ЗаключениеКак минимум нужно избавиться от задержек, возникающих от TCP-коннекта, и от пустого ожидания отклика в клиентских библиотеках.
Естественно, придётся написать сервер, который будет обслуживать этот UDP. Можно за основу взять тот же memcached, ибо там наверняка есть какая-либо математика с управлением памяти (ещё не вник что такое slab и как оно работает и зачем оно надо, кроме уменьшения фрагментации).
И естественно, под новый сервер придётся написать клиентские API (в случае PHP — это extension). В принципе, ни то, ни другое, не является чем-то заоблачно сложным. Просто было бы зачем, и для чего.
И естественно, архитектура и алгоритмы клиенского скрипта должны быть идеологически выстроены под такое кеширование, а не сводиться к втискиванию get/set'ов на манер memcached в уже работающие скрипты.
PS: Я вот не знаю. По рыночной идеологии надо было эту идею поднести моему будущему работодателю в сфере хайлоадов: репутацию себе повысил бы, и авось это и было бы первым заданием (хотя оно сишное в основном). Это с одной стороны. С другой стороны, я пока ни на кого из них формально не работаю ещё; да и неизвестно ещё как потом работа сложится, и идея может утопнуть в пунктиках о коммерческой тайне, know-how и пр, и не достигнуть воплощения. Так что идею дарю всем, реализацию каждый сделает сам, если сочтёт оправданным; а я, а я... А я, вообще-то, идеи генерирую только так — потом ещё много чего напридумываю, если будет где развернуться ;-)
PPS: И вообще, мне никогда не нравилось быть винтиком механизма. Я всегда хотел чтобы я как винтик имел имя, и моя роль была более чем очевидна снаружи. Это называется "работать на своё имя". Хотя я всё-таки соблюдаю установленные в фирме границы know-how и прочие секреты и правила. Вопрос именно в балансе засекреченного внутреннего и дозволенного наружного. Но это так, реплика в сторонку, ни с того, ни с сего ;-)