3.06. Справочник по Memcached
Разработчику
Аналитику
Тестировщику
Архитектору
Инженеру
Справочник по Memcached
Архитектурные параметры и опции запуска
Memcached — это демон, не имеющий встроенной конфигурационной системы в виде файлов (типа .conf). Все параметры передаются при запуске через аргументы командной строки либо через переменные окружения в wrapper-скриптах (например, systemd unit или init-скрипты). Поведение демона определяется комбинацией этих параметров, и их корректный подбор критичен для достижения баланса между производительностью, отказоустойчивостью и эффективным использованием ресурсов.
Общие параметры
-d — запуск в режиме демона (background). Без этого флага memcached остаётся в foreground, что удобно для отладки или запуска под supervisord/systemd.
-u <user> — задаёт имя пользователя, от имени которого будет работать процесс после инициализации. Требуется при запуске от root (например, при использовании привилегированных портов < 1024), но не рекомендуется оставлять демон под root в production.
-v, -vv, -vvv — уровни детализации логирования. -v выводит базовые события (старт, остановка, ошибки соединений), -vv добавляет информацию о входящих командах (get/set/cas и т.п.), -vvv включает трассировку сетевых пакетов и внутренних операций памяти. Логи пишутся в stderr или syslog, в зависимости от способа запуска.
-h — вывод справки по аргументам.
Параметры привязки и доступа
-l <ip> — указывает IP-адрес, на котором memcached будет принимать соединения. По умолчанию используется INADDR_ANY (0.0.0.0), что означает приём соединений на всех интерфейсах. Для изоляции в инфраструктуре рекомендуется явно указывать внутренний адрес (например, 127.0.0.1 в single-node конфигурации или 10.0.0.123 в кластере). Возможна передача нескольких адресов через запятую (начиная с версии 1.5.16).
-p <port> — TCP-порт для клиентских подключений. Стандартное значение — 11211. Этот порт зарегистрирован в IANA для memcached, и клиентские библиотеки используют его по умолчанию. Изменение может потребоваться при запуске нескольких инстансов на одном хосте.
-U <port> — UDP-порт для приёма команд. UDP-интерфейс поддерживает только ограниченный набор операций (get/multi-get без cas/touch), и его использование не рекомендуется в production из-за отсутствия контроля целостности и невозможности гарантировать доставку. По умолчанию UDP-порт отключён. Если указано 0, UDP отключается.
-6 — включает поддержку IPv6. Без этого флага демон слушает только IPv4. При совместной работе с -l следует указывать адреса в квадратных скобках (например, -l [::1]).
Параметры памяти
-m <num> — объём выделяемой оперативной памяти в мегабайтах. Это наиболее важный параметр, напрямую влияющий на ёмкость кэша и поведение LRU. Значение по умолчанию — 64 МБ. Рекомендуется выделять не более 50–70 % от свободной RAM на хосте, с учётом того, что memcached не поддерживает свопинг и при исчерпании RAM переходит к агрессивной выгрузке данных.
Механизм выделения памяти реализован через slab-аллокатор (см. ниже). Общая выделенная память фиксирована на весь срок работы процесса и не может быть изменена без перезапуска.
Параметры сетевого взаимодействия
-c <num> — максимальное количество одновременных соединений (TCP-сокетов). По умолчанию — 1024. Превышение приводит к отказу в установке нового соединения (error EMFILE или ENFILE). Значение должно учитывать лимиты ядра (ulimit -n) и ожидаемую нагрузку. В высоконагруженных системах часто устанавливают 10 000 и выше.
-t <num> — количество потоков обработки команд (worker threads). По умолчанию — 4. Это число определяет, сколько потоков будет обслуживать входящие запросы после принятия соединения. Каждый поток имеет собственную event loop (на базе epoll/kqueue/select). Важно: потоки НЕ создаются на каждое соединение — соединения распределяются между потоками по схеме «один поток — множество сокетов», что минимизирует накладные расходы на переключение контекста. Увеличение числа потоков целесообразно только при наличии нескольких ядер и высокой параллельной нагрузке. Слишком большое значение может вызвать конкуренцию за lock slab-аллокатора.
-R <num> — максимальное число запросов, обрабатываемых за один цикл event loop в каждом потоке. По умолчанию — 20. Служит для предотвращения «голодания» других соединений при большом числе запросов от одного клиента. Повышение может улучшить пропускную способность при равномерной нагрузке, но понижение — уменьшить задержки при неравномерной.
-a <mask> — маска прав доступа для Unix-сокета (если используется -s). Например, 0755.
-s <path> — путь к Unix-сокету. Альтернатива TCP-подключению. Обеспечивает минимальные накладные расходы при локальном вызове (например, frontend и memcached на одном хосте). Команды передаются через domain socket, без сетевого стека. Не совместим с кластеризацией.
Параметры безопасности и аутентификации
Memcached изначально разрабатывался как внутренний сервис без аутентификации. В открытых сетях это создаёт риски (см. уязвимость 2018 г., связанную с амплификацией DDoS через UDP). Для повышения безопасности:
- отключается UDP (
-U 0); - ограничивается интерфейс (
-l 127.0.0.1или внутренний VLAN); - используется фаервол (iptables/nftables).
Начиная с версии 1.5.13, появилась опциональная поддержка SASL-аутентификации:
-S — включает SASL (Simple Authentication and Security Layer). Требует сборки с флагом --enable-sasl, настройки /etc/sasl2/memcached.conf, и поддержки на стороне клиента (например, libmemcached с SASL). После включения все команды, кроме quit и version, требуют предварительной аутентификации (auth <user> <password>). Шифрования трафика (TLS) memcached не поддерживает — для этого требуется внешний прокси (stunnel, haproxy с TLS-терминацией).
Параметры slab-аллокатора и управления памятью
Memcached использует slab-аллокатор, чтобы избежать фрагментации кучи и обеспечить O(1)-сложность операций выделения/освобождения. Память делится на страницы (обычно 1 МБ), страницы группируются в slab classes — классы, каждый из которых хранит значения примерно одинакового размера.
-f <factor> — фактор роста размера слотов (chunks) между соседними slab-классами. По умолчанию — 1.25. Например: если slab 1 хранит объекты ≤ 96 Б, slab 2 — ≤ 120 Б (96 × 1.25), slab 3 — ≤ 150 Б (120 × 1.25) и т.д. Уменьшение фактора увеличивает число классов, снижает внутреннюю фрагментацию («wasted bytes per item»), но повышает накладные расходы на управление. Увеличение — наоборот.
-n <size> — минимальный размер резервируемого пространства под метаданные (ключ + заголовок) в байтах. По умолчанию — 48. Это базовый «накладной» объём на каждый элемент. Если реальный ключ + структурные данные (flags, expiry, cas id) занимают меньше — остаток остаётся неиспользованным в чанке.
-L — попытка использовать большие страницы памяти (HugeTLB, 2 МБ или 1 ГБ). Может снизить overhead TLB и повысить производительность на NUMA-системах. Но требует наличия достаточного количества huge pages (/proc/sys/vm/nr_hugepages) и прав. При неудаче демон продолжает работу с обычными страницами.
-- slab_automove <0|1|2> — стратегия балансировки slab-классов при нехватке памяти:
0— отключено (по умолчанию);1— фоновый перенос страниц из малоиспользуемых slab-классов в переполненные (раз в 1–10 сек, по эвристике);2— агрессивный режим: перенос происходит чаще и учитывает статистику hit/miss.
Это помогает бороться с slab fragmentation — ситуацией, когда в одном slab-классе есть свободные чанки, но новый объект не помещается в них из-за размера, тогда как в другом классе свободная память простаивает.
-- slab_chunk_max <size> — максимальный размер чанка (в байтах), который может быть выделен в отдельном slab-классе. По умолчанию — не ограничен, но логически ограничен размером страницы (≈1 МБ). Полезен для ограничения потребления памяти под очень крупные объекты.
Параметры статистики и отладки
-o <opt1,opt2,...> — список дополнительных параметров (comma-separated), вводимых единым флагом. Примеры:
verbose— эквивалент-vv;maxconns_fast— при достижении-cвозвращать ошибкуSERVER_ERROR maxconnsсразу, без попыткиclose()— повышает предсказуемость;hashpower_init=<N>— начальная степень хэш-таблицы (размер =2^Nбакетов). По умолчанию — динамически подбирается, но можно зафиксировать (например,hashpower_init=16→ 65 536 бакетов). Недостаточный размер вызывает коллизии и замедление get/set; избыточный — излишний расход памяти на empty buckets;no_lru_crawler— отключает фоновый LRU crawler, который ищет устаревшие объекты для принудительного удаления (даже до истечения TTL), чтобы поддерживать hit ratio;lru_crawler_sleep=<ms>— задержка между обходами LRU crawler (по умолчанию 100 мс);lru_crawler_tocrawl=<num>— сколько элементов проверять за один проход;idle_timeout=<sec>— автоматическое закрытие «спящих» соединений без активности (по умолчанию 0 — отключено).
Протокол взаимодействия: команды, семантика, форматы данных
Memcached поддерживает два протокола: ASCII-протокол (человекочитаемый, используется по умолчанию) и binary-протокол (более компактный, позволяет избежать парсинга строк, поддерживает дополнительные флаги). Оба протокола передаются поверх TCP (или Unix-сокета); UDP поддерживает только подмножество ASCII-команд (в основном get, gets, quit) и не рекомендуется.
Все операции идемпотентны (кроме incr/decr и CAS-операций при конфликтах), и не имеют транзакционных гарантий. Memcached — это система с eventual consistency, не поддерживающая ACID.
Формат ключа
Ключ (<key>) в протоколе — это последовательность байтов, не содержащая пробелов, управляющих символов (\r, \n, \0) или не-ASCII символов без явного согласования.
- Максимальная длина ключа: 250 байт (не символов!). Это жёсткое ограничение, заложенное на уровне аллокатора метаданных.
- Ключ чувствителен к регистру:
"User:1"и"user:1"— разные объекты. - Рекомендуется использовать только ASCII-символы
[a-zA-Z0-9._-]. Юникод требует кодирования (например, UTF-8 → base64 или urlencode), что увеличивает размер и снижает читаемость. - Использование одинакового префикса (например,
"sess:","user_profile:") облегчает отладку и позволяет эффективно использоватьflush_prefixв некоторых прокси (например,mcrouter), хотя сам memcached не поддерживает префиксы как отдельную сущность.
Формат значения
Значение (<value>) — произвольная последовательность байтов, максимум 1 МБ (1 048 576 байт) на один элемент. Это ограничение введено для предотвращения блокировки event loop на сериализацию/десериализацию, а также для совместимости со slab-аллокатором (chunk size ≤ page size).
- Значение не интерпретируется — memcached хранит его как blob.
- Типизация (строка, число, сериализованный объект) — на стороне клиента.
- Для хранения структурных данных (JSON, protobuf, msgpack) клиент обязан выполнять сериализацию/десериализацию. Memcached не проверяет формат.
- Передача бинарных данных допустима как в ASCII-, так и в binary-протоколе (в ASCII-протоколе значение передаётся «как есть», без base64 — но клиент должен корректно обрабатывать
\r\nвнутри значения).
Управляющие флаги (flags)
Поле flags — 32-битное беззнаковое целое, передаваемое при записи (set, add, replace и др.) и возвращаемое при чтении.
- Memcached не использует
flagsвнутренне — это пользовательское поле. - Типичные применения:
0x01— сжатие (gzip/snappy);0x02— сериализация (JSON/MsgPack);0x04— зашифровано;0x08— требует CAS-проверки на стороне приложения;- Комбинации:
flags = 0x03→ сжато и сериализовано.
- Клиентская библиотека обязана сохранять и интерпретировать
flags; сервер лишь сохраняет и возвращает их.
Время жизни (exptime)
Поле exptime задаёт срок хранения объекта в секундах или как Unix timestamp.
- Если
exptime == 0— объект хранится бессрочно (до eviction или явного удаления). - Если
0 < exptime ≤ 60 × 60 × 24 × 30 = 2 592 000— интерпретируется как относительное время (секунд с момента записи). - Если
exptime > 2 592 000— интерпретируется как абсолютный Unix timestamp (UTC). Это позволяет задавать срок до конкретного момента, независимо от времени записи. - По истечении
exptimeобъект переходит в состояние logically expired, но физически может оставаться в памяти до:- попытки чтения (lazy expiration: при
getобнаруживается просрочка и возвращаетсяNOT_FOUND); - обхода LRU crawler’ом;
- нехватки памяти (eviction).
- попытки чтения (lazy expiration: при
Нет механизма «callback при истечении» — приложение должно само проверять актуальность.
Команды ASCII-протокола
Все команды завершаются \r\n. Ответы также строковые, с кодами состояния.
Команды записи
set <key> <flags> <exptime> <bytes> [noreply]\r\n<value>\r\n
— универсальная команда записи. Перезаписывает существующее значение или создаёт новое.
Опция noreply (если указана) подавляет ответ сервера — используется для ускорения массовой записи, но делает невозможным обнаружение сетевых ошибок.
add <key> <flags> <exptime> <bytes> [noreply]\r\n<value>\r\n
— записывает значение только если ключ отсутствует. Если ключ существует — возвращает NOT_STORED.
replace <key> <flags> <exptime> <bytes> [noreply]\r\n<value>\r\n
— записывает значение только если ключ существует. Если ключ отсутствует — NOT_STORED.
append <key> <exptime> <bytes> [noreply]\r\n<value>\r\n
— дописывает байты в конец существующего значения. Не затрагивает flags и exptime (значение exptime в команде игнорируется, но должен быть указан — историческая причина; в новых версиях можно передавать 0). Если ключ не существует — NOT_STORED.
prepend <key> <exptime> <bytes> [noreply]\r\n<value>\r\n
— дописывает байты в начало существующего значения. Аналогично append.
Команды чтения
get <key1> [<key2> ... <keyN>]\r\n
— запрос одного или нескольких ключей (multi-get). Ответ: для каждого найденного ключа — блок:
VALUE <key> <flags> <bytes>\r\n<value>\r\n, завершается END\r\n.
Для отсутствующих ключей — ничего не возвращается (тихий miss).
Multi-get позволяет уменьшить сетевые round-trip’ы.
gets <key1> [<key2> ...]\r\n
— как get, но возвращает дополнительное поле CAS-идентификатор в ответе:
VALUE <key> <flags> <bytes> <cas_uniq>\r\n...
Используется для последующей CAS-операции.
CAS — проверка и установка
CAS (Check-And-Set) — механизм optimistic concurrency control.
cas <key> <flags> <exptime> <bytes> <cas_uniq> [noreply]\r\n<value>\r\n
— записывает значение только если текущий CAS-идентификатор объекта совпадает с указанным в команде.
Если совпадает — STORED.
Если не совпадает (кто-то изменил объект между gets и cas) — EXISTS.
Если объект удалён — NOT_FOUND.
CAS-идентификатор — 64-битное целое, инкрементируемое при каждом изменении объекта (включая touch, append, prepend). Гарантирует, что клиент работает с актуальной версией.
Команды управления временем жизни
touch <key> <exptime> [noreply]\r\n
— обновляет exptime существующего объекта без изменения значения и флагов.
Если ключ не существует — NOT_FOUND.
Полезно для продления TTL «горячих» объектов.
Арифметические операции
incr <key> <value> [noreply]\r\n
decr <key> <value> [noreply]\r\n
— атомарные операции над целочисленными значениями в десятичной системе.
Требования:
- Значение должно быть неотрицательным 64-битным целым, записанным в виде строки (например,
"42"). valueв команде — тоже целое (1 ≤ value ≤ 2⁶⁴−1).- Результат не может быть отрицательным:
decrостанавливается на0. - Если ключ не существует —
NOT_FOUND(в отличие от Redis, где можно сделатьINCRпо несуществующему ключу как0 + N). - CAS-идентификатор инкрементируется при изменении.
Расширение incr/decr для float не поддерживается — только целые.
Команды удаления и сброса
delete <key> [noreply]\r\n
— удаляет объект немедленно. Ответ: DELETED или NOT_FOUND.
flush_all [<delay>] [noreply]\r\n
— помечает все существующие объекты как устаревшие, но не освобождает память сразу.
Если указан delay (в секундах) — операция применяется асинхронно через указанное время.
После flush_all новые get возвращают NOT_FOUND, но память остаётся занята до eviction или LRU-очистки.
⚠️ Опасная команда: в production её следует блокировать на уровне фаервола или прокси.
Сервисные команды
version\r\n → VERSION 1.6.30\r\n
— возвращает версию демона.
stats\r\n
— выводит ключевые метрики:
STAT pid 12345
STAT uptime 86400
STAT time 1732218000
STAT version 1.6.30
STAT libevent 2.1.12-stable
STAT pointer_size 64
STAT rusage_user 123.456
STAT rusage_system 78.901
STAT curr_connections 42
STAT total_connections 10000
STAT connection_structures 50
STAT reserved_fds 20
STAT cmd_get 1000000
STAT cmd_set 500000
STAT get_hits 950000
STAT get_misses 50000
STAT ...
stats items\r\n
— статистика по каждому slab-классу:
STAT items:1:number 1000
STAT items:1:age 3600
STAT items:1:evicted 10
STAT items:1:evicted_nonzero 5
STAT items:1:evicted_time 120
STAT items:1:outofmemory 0
STAT items:1:tailrepairs 0
...
stats slabs\r\n
— детали по slab-аллокатору:
STAT 1:chunk_size 96
STAT 1:chunks_per_page 10922
STAT 1:total_pages 1
STAT 1:total_chunks 10922
STAT 1:used_chunks 1000
STAT 1:free_chunks 9922
STAT 1:free_chunks_end 0
STAT 1:mem_requested 96000
STAT 1:get_hits 95000
...
STAT active_slabs 5
STAT total_malloced 5242880
stats malloc\r\n (если собран с --enable-mallinfo)
— информация о внутреннем malloc (редко используется).
quit\r\n
— корректное закрытие соединения.
Binary-протокол (кратко)
Binary-протокол использует фиксированный 24-байтный header:
Offset Size Field
0 1 magic (0x80 — request, 0x81 — response)
1 1 opcode
2 2 key length
4 1 extras length
5 1 data type (обычно 0x00)
6 2 vbucket id (для совместимости с membase/Couchbase; в memcached игнорируется)
8 4 total body length
12 4 opaque (копируется в ответ)
16 8 CAS (при наличии)
Преимущества:
- отсутствие парсинга текста → выше производительность;
- поддержка CAS в
setбез отдельногоgets; - флаг
quiet(opcode с| 0x80) —noreplyна уровне протокола; getq(get quiet) — не возвращаетNOT_FOUND, уменьшает трафик при массовых miss’ах;- возможность пакетной отправки запросов без ожидания ответа.
Основные opcodes:
0x00—get0x01—set0x02—add0x03—replace0x04—delete0x05—incr0x06—decr0x07—quit0x08—flush0x09—getq0x0A—noop0x0B—version0x0C—getk(возвращает ключ + значение)0x0D—getkq0x0E—append0x0F—prepend0x10—stat0x11—setq0x12—addq0x13—replaceq0x14—deleteq0x15—incrementq0x16—decrementq0x17—quitq0x18—flushq0x19—appendq0x1A—prependq
Binary-протокол предпочтителен в high-load сценариях и при использовании языков с эффективной работой с бинарными буферами (C, Rust, Java NIO).
Внутренние механизмы
Memcached достигает высокой производительности не только за счёт простоты API, но и благодаря продуманной архитектуре управления памятью. Ключевой элемент — slab-аллокатор, разработанный специально для устранения фрагментации кучи и обеспечения O(1)-сложности операций. Ниже приводится исчерпывающее описание его работы, эвристик, а также сопутствующих механизмов: LRU-политики, eviction, статистики фрагментации и поведения при граничных нагрузках.
Slab-аллокатор: устройство и принцип работы
При запуске демона с параметром -m <N> выделяется единый непрерывный блок виртуальной памяти размером N мегабайт. Этот блок не расширяется динамически — его размер фиксирован на всё время работы процесса.
Память делится на страницы (pages) фиксированного размера — по умолчанию 1 МБ (1 048 576 байт). Каждая страница принадлежит ровно одному slab-классу (slab class).
Slab-класс — это группа страниц, предназначенных для хранения объектов примерно одинакового размера. Внутри класса все страницы структурно идентичны.
Каждая страница разбивается на чанки (chunks) — фиксированные по размеру ячейки хранилища. Размер чанка определяется на этапе инициализации slab-класса и не изменяется в течение работы. Размер чанка всегда не меньше, чем сумма:
- длина ключа (≤ 250 байт);
- размер метаданных (~48 байт:
itemheader — CAS, flags, expiry, next/prev указатели, длина ключа и значения); - размер значения.
Чанк выравнивается по границе, кратной 8 байтам (для совместимости с 64-битными указателями).
Формирование slab-классов
Slab-классы генерируются динамически при старте, на основе параметров:
-n <size>— минимальный размер чанка (по умолчанию 48 байт);-f <factor>— мультипликативный коэффициент роста (по умолчанию 1.25);- ограничение: чанк ≤ размер страницы (≈1 МБ).
Алгоритм:
class_1.chunk_size = max(-n, MIN_CHUNK_SIZE)class_2.chunk_size = ceil(class_1.chunk_size * -f)class_3.chunk_size = ceil(class_2.chunk_size * -f)
… и так до тех пор, покаchunk_size + item_overhead ≤ 1 048 576.
Пример (упрощённый, без выравнивания):
| Slab-class | Chunk size (байт) | Объекты на страницу (≈1 МБ) | Примечание |
|---|---|---|---|
| 1 | 96 | ~10 922 | мелкие объекты: сессии, флаги |
| 2 | 120 | ~8 738 | |
| 3 | 150 | ~6 990 | |
| 4 | 188 | ~5 576 | |
| … | … | … | |
| 32 | 102 360 | 10 | крупные JSON-объекты |
| 33 | 127 950 | 8 | |
| … | … | … | |
| 42 | 1 048 576 | 1 | максимальный размер |
Если клиент пытается сохранить объект размером 151 байт, он попадает в slab 4 (chunk = 188 байт). Разница (188 − 151 = 37 байт) — внутренняя фрагментация, которая не используется и не может быть задействована другими объектами.
Распределение объектов по slab-классам
При выполнении set key ...:
- Вычисляется общий размер:
total = len(key) + len(value) + item_overhead. - Выбирается минимальный slab-класс, для которого
chunk_size ≥ total. - Если в выбранном классе есть свободный чанк — объект помещается туда.
- Если свободных чанков нет:
- выделяется новая страница (если есть свободная память в пуле);
- или инициируется eviction (см. ниже).
Важно: объект никогда не переходит между slab-классами, даже если его размер изменяется (append, prepend). При append к объекту в классе с chunk=120 новое значение в 130 байт не поместится — операция завершится ошибкой SERVER_ERROR object too large for cache. Это ограничение делает append/prepend малоприменимыми на практике.
Фрагментация памяти: типы и измерение
В Memcached различают два вида фрагментации:
-
Внутренняя фрагментация (per-item waste)
— разница между размером чанка и фактически используемым объёмом (chunk_size − (key + value + overhead)).
Измеряется через статистику:stats slabs
→ STAT <class>:mem_requested 96000 # суммарный запрошенный объём
→ STAT <class>:total_malloced 1048576 # объём выделенной памяти (pages × 1MB)Waste =
total_malloced − mem_requested. -
Внешняя фрагментация (slab imbalance)
— ситуация, когда в одних slab-классах есть свободные чанки, а в других — переполнение и eviction, несмотря на наличие свободной общей памяти.
Например: класс 1 заполнен на 99 %, класс 10 — на 10 %, но объект на 500 байт не может использовать свободные чанки класса 10, если его размер требует класса 15.
Оценка внешней фрагментации:
stats items
→ STAT items:15:evicted 1000 # много eviction в классе 15
→ STAT items:10:free_chunks 5000 # но в классе 10 — свобода
Меры борьбы:
- подбор
-f(ближе к 1.1 — больше классов, меньше внутренней фрагментации); --slab_automove 1/2— фоновый перенос страниц;- мониторинг распределения размеров объектов и корректировка ключей (например, разбивка крупных объектов на части:
profile:123:part1,profile:123:part2).
LRU (Least Recently Used) и его реализация
Memcached использует сегментированную LRU-политику, разделённую по slab-классам. Каждый slab-класс имеет собственный LRU-список, состоящий из трёх цепочек:
- HOT LRU — недавно использованные и часто запрашиваемые объекты.
- WARM LRU — регулярно используемые, но менее «горячие».
- COLD LRU — редко используемые; кандидаты на eviction.
Кроме того, в версиях ≥ 1.5.0 появился TEMP LRU — отдельная очередь для объектов с очень коротким TTL (например, < 60 сек), чтобы не «засорять» основные списки.
Алгоритм перемещения между LRU
- При
getобъект перемещается в голову своей LRU-цепочки (HOT → WARM → COLD при остывании). - Периодически (раз в несколько секунд) запускается LRU crawler — фоновый поток, который:
- обходит COLD-список;
- удаляет объекты с истёкшим
exptime; - перемещает «тёплые» объекты из COLD → WARM, если их
last_accessed_timeслишком свежий; - собирает статистику hit ratio по классам.
Команды, влияющие на LRU-позицию:
get,gets— перемещают в голову HOT;touch— обновляетlast_accessed_time, не возвращая данные → «подогрев» без сетевого трафика;flush_all— не сбрасывает LRU-списки, но делает все объекты «невидимыми».
Eviction (вытеснение объектов)
Eviction происходит только при нехватке свободных чанков в нужном slab-классе и отсутствии возможности выделить новую страницу.
Алгоритм:
- Выбирается slab-класс, в который должен быть помещён объект.
- Если в нём нет свободных чанков и нет свободной общей памяти:
- из COLD LRU данного класса удаляется самый «холодный» объект (с самым давним
last_accessed_time); - если COLD пуст — берётся из WARM, затем из HOT (крайне нежелательно).
- из COLD LRU данного класса удаляется самый «холодный» объект (с самым давним
- Объект удаляется без уведомления клиента (silent eviction).
- Метрики обновляются:
evicted,evicted_time,evicted_nonzero.
Важно: eviction не зависит от TTL. Объект с exptime = 1 час может быть вытеснен раньше объекта с exptime = 10 сек, если первый — cold, а второй — hot.
Метрики eviction в stats items:
evicted— общее число вытесненных объектов;evicted_nonzero— из них с ненулевым TTL (то есть «полезных»);evicted_time— время (в секундах), сколько в среднем прожил вытесненный объект до eviction;outofmemory— количество попыток записи, приведших к eviction (не всегда ==evicted, т.к. одинsetможет вызвать несколько eviction при фрагментации).
Если evicted_nonzero > 0 — индикатор неоптимальной конфигурации: либо памяти недостаточно, либо распределение размеров несбалансировано.
Стратегии минимизации eviction
-
Правильный подбор
-m
Используйтеstats slabs→total_mallocedиmem_requested, чтобы оценить эффективность использования. Цель:mem_requested / total_malloced ≥ 0.85. -
Анализ распределения размеров
Соберите выборку реальных(key_len + value_len)и постройте гистограмму. Если пики приходятся на границы между slab-классами (например, 119/120/121), скорректируйте-fили структуру данных. -
Использование префиксов и шардирования на клиенте
Клиентская библиотека (например,libmemcached) может распределять ключи по разным инстансам memcached (consistent hashing). Это снижает нагрузку на один узел и уменьшает вероятность локального переполнения. -
Отказ от крупных объектов
Объекты > 512 КБ резко снижают эффективность slab-аллокатора (1 страница = 1–2 объекта). Лучше хранить ссылки на внешнее хранилище (например,value = "s3://bucket/key"), а не сами данные. -
Настройка
-- slab_automove
При включении (1или2) демон в фоне перераспределяет страницы между классами, если обнаруживает дисбаланс. Требует дополнительного CPU, но снижаетevicted_nonzero.
Поведение при отказах и ограничения
- Сбой инстанса → полная потеря данных в нём. Memcached не обеспечивает персистентность и не реплицирует данные.
- Переполнение памяти → eviction, затем — при исчерпании всех ресурсов — отказ в записи (
SERVER_ERROR out of memory), если отключён eviction (через--enable-shutdown+ OOM-killer). - Переполнение соединений →
SERVER_ERROR maxconns(если-R maxconns_fast) или зависание. - Некорректные клиенты:
- отправка
\r\nвнутри значения в ASCII-протоколе → повреждение потока; - слишком частые
flush_all→ деградация hit ratio; - отсутствие обработки
NOT_STORED/EXISTS→ логические ошибки в приложении.
- отправка
Клиентские библиотеки, интеграция, мониторинг и сравнительный анализ
Memcached как протокол получил широкое распространение благодаря простоте и эффективности. Для большинства языков существуют как официальные, так и сторонние клиентские реализации. Однако качество, поддерживаемые функции и поведение при отказах сильно различаются. Ниже приводится развернутый обзор, ориентированный на эксплуатацию в production-средах: выбор библиотеки, настройка пулов соединений, стратегии отказоустойчивости, метрики и сравнение с Redis в контексте кэширования.
Клиентские библиотеки
Клиентская библиотека не просто инкапсулирует сетевые вызовы — она определяет поведение системы в целом: как обрабатываются ошибки, как распределяются ключи, как управляются соединения, как обеспечивается согласованность при сбоях.
Ключевые требования к production-библиотеке:
- Поддержка binary-протокола — обязательна для снижения overhead парсинга и поддержки CAS в
set. - Connection pooling — повторное использование TCP-соединений для устранения стоимости handshake (
SYN/ACK) иTIME_WAIT. - Consistent hashing (или ketama) — алгоритм распределения ключей по узлам кластера, минимизирующий перераспределение при добавлении/удалении узла.
Простоеhash(key) % Nприводит к ~100 % кэш-промахов при изменении N. Consistent hashing ограничивает перераспределение ~1/N. - Поддержка нескольких инстансов и отказоустойчивость — автоматическое исключение узла при ошибках и возврат после восстановления (через health-check или таймаут).
- Интеграция с метриками — возможность сбора
get_hits,get_misses,connection_errors,timeout_errorsв систему мониторинга. - Безопасная обработка timeout’ов — отмена операции без утечки сокетов.
- Поддержка SASL, если аутентификация включена на сервере.
Обзор по языкам
| Язык | Библиотека | Статус | Особенности | Рекомендация |
|---|---|---|---|---|
| C/C++ | libmemcached | Официальная, активно поддерживается | Поддержка binary, ketama, connection pooling, SASL, memstat, memcp, memcat CLI-утилиты. Используется в PHP-расширении memcached (PECL). | ✅ Рекомендуется |
| C#/.NET | EnyimMemcachedCore | Популярная, open-source | Асинхронная (async/await), поддержка Microsoft.Extensions.Caching.Abstractions, health-check, конфигурация через appsettings.json. Поддерживает ketama и binary. | ✅ Рекомендуется для .NET Core/5+ |
Memcached.ClientLibrary | Устаревшая | Синхронная, .NET Framework 2.0, отсутствие асинхронности. | ❌ Не использовать | |
| Java | XMemcached | Активно развивается | Высокая производительность, NIO, binary, SASL, dynamic node discovery. | ✅ Рекомендуется |
spymemcached | Устарела (последний релиз — 2017) | Поддержка только ASCII, проблемы с таймаутами в высоконагруженных сценариях. | ❌ Избегать | |
| Python | pymemcache | Активно поддерживается (PyPA) | Чистый Python, поддержка binary, SASL (через pymemcache.client.hash), serde для сериализации, hasher=Hasher.KETAMA. | ✅ Рекомендуется |
python-memcached | Устаревшая | Только ASCII, отсутствие connection pooling в стандартной конфигурации. | ❌ Не использовать | |
| Node.js | memjs | Лёгкая, современная | Поддержка binary, connection pooling, consistent hashing, Promise/async. | ✅ Рекомендуется |
node-memcached | Устаревшая | Коллбэки, отсутствие binary, проблемы с большим числом соединений. | ❌ Избегать | |
| PHP | memcached (PECL) | Официальное расширение | Обёртка над libmemcached, поддержка всех функций. Требует компиляции. | ✅ Рекомендуется |
Memcache (PECL) | Устаревшее | Только ASCII, отсутствие CAS, binary, SASL. | ❌ Не использовать |
⚠️ Важно: в PHP расширения
memcachedиMemcache— разные пакеты. Установка:pecl install memcached # ← это правильное
# НЕ pecl install memcache
Типичная интеграция в приложение (на примере C#)
// appsettings.json
{
"Memcached": {
"Servers": [
{ "Address": "10.0.0.11", "Port": 11211 },
{ "Address": "10.0.0.12", "Port": 11211 }
],
"ConnectTimeout": 500,
"ReceiveTimeout": 1000,
"SendTimeout": 1000,
"UseBinaryProtocol": true,
"KeyTransformer": "Enyim.Caching.Memcached.TigerHashKeyTransformer"
}
}
// Startup.cs
services.AddEnyimMemcached(options =>
{
Configuration.GetSection("Memcached").Bind(options);
});
services.AddStackExchangeRedisCache(...); // ← не путать с Redis!
Шаблон «кэш-aside» (cache-aside pattern) — стандартный подход:
public async Task<UserProfile> GetUserProfileAsync(int userId)
{
var cacheKey = $"user:profile:{userId}";
var cached = await _memcachedClient.GetAsync<UserProfile>(cacheKey);
if (cached != null) return cached;
// Промах → идём в БД
var fromDb = await _userRepository.GetByIdAsync(userId);
if (fromDb == null) return null;
// Сохраняем в кэш с TTL = 5 минут
await _memcachedClient.AddAsync(cacheKey, fromDb, TimeSpan.FromMinutes(5));
return fromDb;
}
public async Task UpdateUserProfileAsync(int userId, UserProfile update)
{
var result = await _userRepository.UpdateAsync(userId, update);
if (result)
{
var cacheKey = $"user:profile:{userId}";
// Вариант 1: перезапись
await _memcachedClient.SetAsync(cacheKey, update, TimeSpan.FromMinutes(5));
// Вариант 2: инвалидация (часто предпочтительнее)
// await _memcachedClient.RemoveAsync(cacheKey);
}
}
Почему AddAsync, а не SetAsync, при первом запросе?
→ Минимизирует race condition: если два запроса одновременно получают miss, оба пойдут в БД, но только один успешно выполнит Add. Второй получит false и может использовать значение, уже записанное первым. Это снижает нагрузку на БД при «холодном старте».
Мониторинг Memcached: ключевые метрики и alert-условия
Memcached предоставляет > 100 метрик через stats. Для production-эксплуатации достаточно 15–20.
| Группа | Метрика | Описание | Порог тревоги |
|---|---|---|---|
| Доступность | curr_connections | Текущие соединения | > 90 % от -c |
connection_structures | Выделено структур под соединения | Рост > 10 %/час → утечка | |
| Операции | cmd_get, cmd_set | Общее число операций | Тренд ↓ при постоянной нагрузке → проблема клиентов |
get_hits, get_misses | Кэш-попадания/промахи | Hit ratio = hits / (hits + misses) • < 90 % — требуется анализ • < 70 % — критично | |
| Память | bytes | Текущий объём данных в байтах | > 90 % от -m |
limit_maxbytes | Значение -m в байтах | Контрольная точка | |
evicted, evicted_nonzero | Число вытесненных объектов | evicted_nonzero > 0 — сигнал к действию | |
| LRU | expired_unfetched, evicted_unfetched | Объекты, удалённые без single get | Высокое значение → короткое TTL или неиспользуемые ключи |
| Сеть | bytes_read, bytes_written | Трафик | Аномальный всплеск → DDoS или ошибка в клиенте |
| Ошибки | listen_disabled_num | Сколько раз слушание отключалось из-за нехватки памяти | > 0 — критично |
accepting_conns | 1/0 — принимает ли соединения | 0 → недоступен |
Grafana dashboard (рекомендуемые панели):
- Hit ratio (5m avg) — линейный график;
- Memory usage % — гистограмма по узлам;
- Eviction rate (per sec) — гистограмма;
- Connections vs limit;
- Top 10 slab classes by
evicted_nonzero.
Alert-правила (Prometheus + Alertmanager):
- alert: MemcachedLowHitRatio
expr: rate(memcached_get_hits_total[5m]) / (rate(memcached_get_hits_total[5m]) + rate(memcached_get_misses_total[5m])) < 0.85
for: 10m
labels:
severity: warning
- alert: MemcachedEvictionNonZero
expr: rate(memcached_evicted_nonzero_total[5m]) > 0.1 # >1 раз в 10 сек
for: 5m
labels:
severity: warning
- alert: MemcachedOutOfMemory
expr: memcached_listen_disabled_num > 0
for: 1m
labels:
severity: critical
Memcached vs Redis в роли кэша
| Критерий | Memcached | Redis |
|---|---|---|
| Модель данных | Только key → value (blob) | Поддержка 5+ типов: string, hash, list, set, zset и др. |
| Макс. размер объекта | 1 МБ | 512 МБ (по умолчанию; можно увеличить) |
| Кластеризация | На стороне клиента (consistent hashing) | Встроенная (Redis Cluster) — шардирование + репликация |
| Персистентность | Нет | RDB, AOF, AOF fsync — настраивается |
| Репликация | Нет | Master-slave, Sentinel, Redis Cluster |
| Высокая доступность | Требует внешнего балансировщика/прокси | Встроена (quorum-based failover) |
| LRU/вытеснение | Slab-based LRU, per-class | Общий LRU/LFU/ volatile-ttl, configurable policy |
| CAS и конкурентность | Поддержка CAS (64-bit) | WATCH/MULTI/EXEC (транзакции), GET+SET атомарно через Lua |
| Метрики и интроспекция | stats — низкоуровневые | INFO, MEMORY USAGE, CLIENT LIST, Slow Log |
| Сетевой протокол | ASCII / Binary | RESP (Redis Serialization Protocol) — binary-like, но человекочитаемый |
| Типичная latency (локально) | 0.1–0.3 мс | 0.2–0.5 мс |
| Потребление памяти на объект | Выше из-за slab-фрагментации | Ниже — динамическое выделение |
| Использование CPU | Очень низкое (event-driven) | Умеренное (однопоточный, но с фоновыми задачами) |
Когда выбирать Memcached:
- Чистое кэширование (read-heavy, временные данные);
- Максимальная latency predictability;
- Простая горизонтальная масштабируемость через клиентский шардинг;
- Минимальные требования к HA (например, stateless frontend-tier).
Когда выбирать Redis:
- Требуется персистентность или репликация;
- Нужны richer data structures (например, лидерборды через ZSET);
- Сценарии beyond caching: очереди, блокировки, rate-limiting;
- Требуется встроенная HA без внешних компонентов.
Примечание: в современных архитектурах часто используют оба:
- Redis — для stateful-компонентов (сессии, очереди, rate-limit);
- Memcached — для volatile-кэша (HTML-фрагменты, API-результаты), где потеря данных допустима.