Налаштування Memcached для кешування веб-застосунку

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Налаштування Memcached для кешування веб-застосунку
Середня
від 1 робочого дня до 3 робочих днів
Часті питання

Наші компетенції:

Етапи розробки

Останні роботи

  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    874
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Розробка веб-сайту для компанії ФІКСПЕР
    851

Налаштування Memcached для кеширування веб-застосунку

Memcached — це розподілений кеш у пам'яті з мінімальною моделлю: ключ, значення, TTL. Без транзакцій, персистентності або pub/sub. Саме ця простота робить його швидшим за Redis в сценаріях читання з кешу — затримка при влученні в кеш становить 0,1–0,5 мс проти 1–3 мс у Redis з AOF-персистентністю.

Використовується там, де потрібно кешувати великі обсяги однорідних даних: результати SQL-запитів, серіалізовані об'єкти, HTML-фрагменти, API-відповіді.

Встановлення та базова конфігурація

# Ubuntu/Debian
apt install memcached libmemcached-dev

# Редагуємо /etc/memcached.conf
-d                          # режим демона
-m 2048                     # 2GB ОЗУ
-p 11211                    # порт
-u memcache                 # користувач
-l 127.0.0.1               # тільки localhost (не виставляти назовні!)
-c 2048                     # макс з'єднання
-t 8                        # потоки (= кількість ядер CPU)
-I 10m                      # макс розмір елемента (за замовчуванням 1MB, збільшуємо до 10MB)
-o modern                   # сучасні опції slab allocator

Перезапуск і перевірка:

systemctl restart memcached
echo "stats" | nc 127.0.0.1 11211 | grep -E "curr_items|bytes|hit_rate|evictions"

Інтеграція з PHP через php-memcached

pecl install memcached
echo "extension=memcached.so" > /etc/php/8.2/mods-available/memcached.ini
phpenmod memcached

Базове використання:

$mc = new Memcached();
$mc->addServer('127.0.0.1', 11211);

// Налаштування клієнта
$mc->setOptions([
    Memcached::OPT_CONNECT_TIMEOUT    => 50,    // мс
    Memcached::OPT_RETRY_TIMEOUT      => 300,
    Memcached::OPT_SEND_TIMEOUT       => 100,
    Memcached::OPT_RECV_TIMEOUT       => 100,
    Memcached::OPT_POLL_TIMEOUT       => 100,
    Memcached::OPT_COMPRESSION        => true,
    Memcached::OPT_SERIALIZER         => Memcached::SERIALIZER_IGBINARY,
    Memcached::OPT_TCP_NODELAY        => true,
    Memcached::OPT_NO_BLOCK           => true,   // асинхронний I/O
]);

Кеширування SQL-запитів

Паттерн cache-aside — найпоширеніший:

class ProductRepository
{
    private Memcached $cache;
    private PDO $db;
    private int $defaultTtl = 300; // 5 хвилин

    public function findById(int $id): ?array
    {
        $key = "product:v2:{$id}";

        $product = $this->cache->get($key);
        if ($this->cache->getResultCode() === Memcached::RES_SUCCESS) {
            return $product;
        }

        $stmt = $this->db->prepare('SELECT * FROM products WHERE id = ? AND active = 1');
        $stmt->execute([$id]);
        $product = $stmt->fetch(PDO::FETCH_ASSOC) ?: null;

        if ($product !== null) {
            $this->cache->set($key, $product, $this->defaultTtl);
        }

        return $product;
    }

    public function findByCategoryWithPagination(int $categoryId, int $page, int $perPage): array
    {
        $offset = ($page - 1) * $perPage;
        $key = "products:cat:{$categoryId}:p{$page}:pp{$perPage}";

        $result = $this->cache->get($key);
        if ($this->cache->getResultCode() === Memcached::RES_SUCCESS) {
            return $result;
        }

        $stmt = $this->db->prepare('
            SELECT p.*, c.name as category_name
            FROM products p
            JOIN categories c ON c.id = p.category_id
            WHERE p.category_id = ? AND p.active = 1
            ORDER BY p.created_at DESC
            LIMIT ? OFFSET ?
        ');
        $stmt->execute([$categoryId, $perPage, $offset]);
        $result = [
            'items' => $stmt->fetchAll(PDO::FETCH_ASSOC),
            'page'  => $page,
        ];

        $this->cache->set($key, $result, 120);
        return $result;
    }

    public function invalidateProduct(int $id): void
    {
        $this->cache->delete("product:v2:{$id}");
        // Інвалідація пагінації по категорії — через паттерн тегів
    }
}

Інвалідація через теги (емуляція)

Memcached не підтримує теги нативно. Стандартний прийом — версіоновані простори імен:

class CacheTagManager
{
    private Memcached $mc;

    public function getTagVersion(string $tag): int
    {
        $version = $this->mc->get("tag_version:{$tag}");
        if ($this->mc->getResultCode() !== Memcached::RES_SUCCESS) {
            $version = time();
            $this->mc->set("tag_version:{$tag}", $version, 0); // без вислідження
        }
        return (int)$version;
    }

    public function buildKey(string $base, array $tags): string
    {
        $versions = array_map(
            fn($tag) => $this->getTagVersion($tag),
            $tags
        );
        return $base . ':' . implode(':', $versions);
    }

    public function invalidateTag(string $tag): bool
    {
        return $this->mc->increment("tag_version:{$tag}", 1, time()) !== false;
    }
}

// Використання
$tagManager = new CacheTagManager($mc);

// Ключ залежить від версії тега категорії
$key = $tagManager->buildKey("products:cat:5:p1", ['category:5', 'products']);
$data = $mc->get($key);

// При змінах категорії — всі залежні кеші стають "неіснуючими"
$tagManager->invalidateTag('category:5');

Розподілений кеш — конгруентне хешування

З кількома серверами критично використовувати конгруентне хешування, щоб при додаванні/видаленні вузла інвалідувалося мінімум ключів:

$mc = new Memcached('persistent_pool'); // постійний пул з'єднань
$mc->addServers([
    ['memcached-1.internal', 11211, 40], // вага 40
    ['memcached-2.internal', 11211, 40],
    ['memcached-3.internal', 11211, 20], // менша вага — менше трафіку
]);
$mc->setOption(Memcached::OPT_DISTRIBUTION, Memcached::DISTRIBUTION_CONSISTENT);
$mc->setOption(Memcached::OPT_LIBKETAMA_COMPATIBLE, true);
$mc->setOption(Memcached::OPT_REMOVE_FAILED_SERVERS, true);
$mc->setOption(Memcached::OPT_SERVER_FAILURE_LIMIT, 3);
$mc->setOption(Memcached::OPT_RETRY_TIMEOUT, 2);

Моніторинг і діагностика

# Статистика сервера
echo "stats" | nc 127.0.0.1 11211

# Важливі метрики:
# get_hits / (get_hits + get_misses) = коефіцієнт влучання (мета > 90%)
# evictions > 0 = недостатньо пам'яті, потрібно збільшити -m
# curr_connections — поточні з'єднання

# Переглядаємо всі ключі (тільки для налагодження, ніколи на виробництві)
echo "stats cachedump 1 100" | nc 127.0.0.1 11211

Prometheus + memcached_exporter:

docker run -d --name memcached-exporter \
  -p 9150:9150 \
  prom/memcached-exporter:latest \
  --memcached.address=127.0.0.1:11211

Дошка Grafana id: 7603 — готовий дашборд для Memcached.

Типовий таймлайн

День 1 — встановлення, конфігурація розміру пам'яті та кількості потоків, налаштування брандмауера (порт 11211 має бути закритий ззовні).

День 2 — інтеграція з застосунком, реалізація cache-aside для важких SQL-запитів, інвалідація при записі.

День 3 — перевірка коефіцієнта влучання, налаштування моніторингу, настройка TTL за типами даних. Якщо коефіцієнт влучання нижче 80% — аналізуємо промахи та виправляємо стратегію кеширування.