Introduction : pourquoi la mise en cache est cruciale pour les performances en C++
\nDans le développement moderne à forte charge en C++, la mise en cache n'est plus une option — c'est un élément obligatoire de l'architecture. Sans elle, tout système CRUD ou microservice atteint rapidement les limites de la base de données, et la latence des requêtes augmente de façon exponentielle. Au niveau avancé, nous sommes confrontés à un dilemme : utiliser des systèmes externes (Redis, Memcached) ou déployer notre propre solution en mémoire en C++ ? Dans cet article, nous analyserons les avantages, les inconvénients et les implémentations concrètes de chaque approche, en nous appuyant sur la pratique des files d'attente réseau, des verrous et de la sérialisation.
\n\n1. Redis : flexibilité et structures de données
\nRedis n'est pas qu'un simple key-value store. Son principal avantage est son riche ensemble de types de données : chaînes, hachages, listes, ensembles, sorted sets. Pour C++, il existe plusieurs clients : redis-plus-plus (recommandé) et hiredis (bas niveau).
Exemple de connexion et d'opérations de base avec redis-plus-plus
\n#include <sw/redis++/redis++.h>\n#include <iostream>\n\nint main() {\n // Connexion à Redis (par défaut localhost:6379)\n sw::redis::Redis redis("tcp://127.0.0.1:6379");\n\n // Définition d'une valeur avec TTL de 60 secondes\n redis.set("user:1001", "John Doe", std::chrono::seconds(60));\n\n // Lecture\n auto val = redis.get("user:1001");\n if (val) {\n std::cout << "Utilisateur : " << *val << std::endl;\n }\n\n // Travail avec les hachages\n redis.hset("session:abc", "token", "xyz123");\n redis.hset("session:abc", "expires", "3600");\n auto token = redis.hget("session:abc", "token");\n std::cout << "Token : " << *token << std::endl;\n\n return 0;\n}\n\nFonctionnalités avancées : utilisation de transactions via redis.multi() et redis.exec(), ainsi que Pub/Sub pour les notifications d'invalidation de cache. Redis est idéal lorsqu'il faut stocker des structures complexes avec des opérations atomiques (par exemple, incrémentation d'un compteur).
2. Memcached : vitesse et simplicité
\nMemcached est un cache distribué léger, optimisé pour le stockage de données plates (chaînes et blobs binaires). Il ne prend pas en charge les transactions ni les structures complexes, mais il gagne en vitesse et en consommation mémoire minimale. Pour C++, nous utilisons la bibliothèque libmemcached.
Exemple de travail avec Memcached via libmemcached
\n#include <libmemcached/memcached.h>\n#include <iostream>\n#include <cstring>\n\nint main() {\n memcached_st *memc = memcached_create(nullptr);\n memcached_server_st *servers = nullptr;\n memcached_return_t rc;\n\n // Ajout d'un serveur\n servers = memcached_server_list_append(servers, "127.0.0.1", 11211, &rc);\n rc = memcached_server_push(memc, servers);\n if (rc != MEMCACHED_SUCCESS) {\n std::cerr << "Erreur : " << memcached_strerror(memc, rc) << std::endl;\n return 1;\n }\n\n // Écriture avec TTL de 30 secondes\n const char *key = "config:db_host";\n const char *value = "10.0.0.1";\n rc = memcached_set(memc, key, strlen(key), value, strlen(value), \n std::time(0) + 30, (uint32_t)0);\n if (rc != MEMCACHED_SUCCESS) {\n std::cerr << "Échec de l'écriture : " << memcached_strerror(memc, rc) << std::endl;\n }\n\n // Lecture\n size_t val_len;\n uint32_t flags;\n char *retrieved = memcached_get(memc, key, strlen(key), &val_len, &flags, &rc);\n if (rc == MEMCACHED_SUCCESS) {\n std::cout << "Hôte DB : " << retrieved << std::endl;\n free(retrieved);\n }\n\n memcached_free(memc);\n return 0;\n}\n\nQuand choisir Memcached ? Si vous avez besoin d'un cache simple pour des objets sérialisés, sans nécessité de stocker des structures complexes. Memcached est idéal pour la mise en cache des résultats de requêtes SQL ou de fragments HTML.