Aller au contenu principal

Comparaison des performances : Redis vs KeyDB

KeyDB vs Redis KeyDB et Redis sont deux solutions de stockage de données en mémoire très populaires, mais avec des caractéristiques de performances différentes. Voici une analyse comparative de leurs…

coolibra08 juil. 2026393 vues

KeyDB vs Redis

KeyDB et Redis sont deux solutions de stockage de données en mémoire très populaires, mais avec des caractéristiques de performances différentes. Voici une analyse comparative de leurs performances respectives.

Architecture et conception

Redis est une base de données en mémoire monothread key-value qui a fait ses preuves comme solution de cache et de stockage de données à faible latence.

KeyDB est un fork de Redis(6) conçu spécifiquement pour résoudre les limitations de performances du modèle monothread de Redis. Sa caractéristique la plus notable est son architecture multithread.

 

Prérequis

 

Tests de performances

  • Script python de tests: redis_keydb_benchmark.py
#!/usr/bin/env python3
"""
Script de benchmark comparatif entre Redis et KeyDB
Amélioré pour bénéficier du multithreading de KeyDB avec plusieurs connexions
"""

import time
import json
import argparse
import statistics
import os
import datetime
import concurrent.futures
import redis
from prettytable import PrettyTable
import matplotlib.pyplot as plt
import numpy as np
from typing import Dict, List, Tuple, Any

class DatabaseBenchmark:
    """Classe pour effectuer des benchmarks sur Redis et KeyDB"""
    
    def __init__(self, 
                host_redis: str='localhost',
                port_redis: int=6379,
                password_redis: str=None,
                host_keydb: str='localhost',
                port_keydb: int=6379,
                password_keydb: str=None,
                iterations: int=10000,
                value_size: int=1000,
                pipeline_size: int=1000,
                cleanup: bool=True,
                prefix: str='bench',
                keydb_connections: int=8):  # Nombre de connexions parallèles pour KeyDB
        """Initialise le benchmark avec les configurations pour Redis et KeyDB"""
        
        self.redis_config = {
            'host': host_redis,
            'port': port_redis,
            'password': password_redis,
            'decode_responses': True
        }
        
        self.keydb_config = {
            'host': host_keydb,
            'port': port_keydb,
            'password': password_keydb,
            'decode_responses': True
        }
        
        self.iterations = iterations
        self.value_size = value_size
        self.pipeline_size = pipeline_size
        self.cleanup = cleanup
        self.prefix = prefix
        self.test_value = 'x' * value_size
        self.results = {}
        self.keydb_connections = keydb_connections
        
    def connect(self) -> Tuple[redis.Redis, List[redis.Redis]]:
        """Établit les connexions avec Redis et KeyDB (plusieurs connexions pour KeyDB)"""
        
        try:
            redis_client = redis.Redis(**self.redis_config)
            redis_client.ping()
            print(f"✅ Connexion établie avec Redis sur {self.redis_config['host']}:{self.redis_config['port']}")
        except Exception as e:
            print(f"❌ Erreur de connexion à Redis: {str(e)}")
            return None, None
        
        # Créer plusieurs connexions pour KeyDB
        keydb_clients = []
        try:
            for i in range(self.keydb_connections):
                keydb_client = redis.Redis(**self.keydb_config)
                keydb_client.ping()
                keydb_clients.append(keydb_client)
            
            print(f"✅ {self.keydb_connections} connexions établies avec KeyDB sur {self.keydb_config['host']}:{self.keydb_config['port']}")
        except Exception as e:
            print(f"❌ Erreur de connexion à KeyDB: {str(e)}")
            return redis_client, None
            
        return redis_client, keydb_clients
        
    def cleanup_keys(self, client: redis.Redis):
        """Nettoie les clés de test de la base de données"""
        
        if not client:
            return
            
        pattern = f"{self.prefix}:*"
        keys = client.keys(pattern)
        if keys:
            client.delete(*keys)
            print(f"Nettoyage des clés de test effectué ({len(keys)} clés supprimées)")
    
    def get_info(self, client: redis.Redis) -> Dict:
        """Récupère les informations du serveur"""
        
        if not client:
            return {}
            
        info = client.info()
        stats = {
            'version': info.get('redis_version', 'N/A'),
            'mode': info.get('redis_mode', 'N/A'),
            'os': info.get('os', 'N/A'),
            'used_cpu_sys': float(info.get('used_cpu_sys', 0)),
            'used_memory_human': info.get('used_memory_human', 'N/A'),
            'connected_clients': int(info.get('connected_clients', 0)),
            'instantaneous_ops_per_sec': int(info.get('instantaneous_ops_per_sec', 0)),
            'total_commands_processed': int(info.get('total_commands_processed', 0)),
            'evicted_keys': int(info.get('evicted_keys', 0)),
            'maxmemory_human': info.get('maxmemory_human', '0B'),
            'uptime_in_seconds': int(info.get('uptime_in_seconds', 0))
        }
        return stats
        
    def format_uptime(self, seconds: int) -> str:
        """Formatte le temps d'activité en jours/heures"""
        
        days = seconds // (24 * 3600)
        hours = (seconds % (24 * 3600)) // 3600
        return f"{days} jours, {hours} heures"
    
    def run_single_test_redis(self, client: redis.Redis, test_name: str) -> Dict:
        """Exécute un test spécifique sur Redis et retourne les résultats"""
        
        if not client:
            return {'duration': 0, 'ops_per_sec': 0}
            
        start_time = time.time()
        
        if test_name == "SET":
            for i in range(self.iterations):
                client.set(f"{self.prefix}:set:{i}", self.test_value)
                
        elif test_name == "GET":
            # D'abord insérer des données
            for i in range(self.iterations):
                client.set(f"{self.prefix}:get:{i}", self.test_value)
            
            # Puis mesurer les GET
            start_time = time.time()
            for i in range(self.iterations):
                client.get(f"{self.prefix}:get:{i}")
                
        elif test_name == "Pipeline SET":
            pipeline_count = self.iterations // self.pipeline_size
            for p in range(pipeline_count):
                pipe = client.pipeline(transaction=False)
                for i in range(self.pipeline_size):
                    index = p * self.pipeline_size + i
                    pipe.set(f"{self.prefix}:pipe_set:{index}", self.test_value)
                pipe.execute()
                
        elif test_name == "Pipeline GET":
            # D'abord insérer des données
            for i in range(self.iterations):
                client.set(f"{self.prefix}:pipe_get:{i}", self.test_value)
            
            # Puis mesurer les GET pipelinés
            start_time = time.time()
            pipeline_count = self.iterations // self.pipeline_size
            for p in range(pipeline_count):
                pipe = client.pipeline(transaction=False)
                for i in range(self.pipeline_size):
                    index = p * self.pipeline_size + i
                    pipe.get(f"{self.prefix}:pipe_get:{index}")
                pipe.execute()
                
        elif test_name == "INCR":
            for i in range(self.iterations):
                client.incr(f"{self.prefix}:incr:{i % 1000}")
                
        elif test_name == "LPUSH":
            for i in range(self.iterations):
                client.lpush(f"{self.prefix}:list:{i % 1000}", self.test_value)
                
        elif test_name == "LPOP":
            # D'abord insérer des données dans les listes
            for i in range(self.iterations):
                client.lpush(f"{self.prefix}:pop_list:{i % 1000}", self.test_value)
            
            # Puis mesurer les LPOP
            start_time = time.time()
            for i in range(self.iterations):
                client.lpop(f"{self.prefix}:pop_list:{i % 1000}")
                
        end_time = time.time()
        duration = end_time - start_time
        ops_per_sec = self.iterations / duration if duration > 0 else 0
        
        return {
            'duration': duration,
            'ops_per_sec': ops_per_sec,
            'pipeline_size': self.pipeline_size if 'Pipeline' in test_name else None
        }
    
    def worker_task(self, client: redis.Redis, test_name: str, start_idx: int, end_idx: int) -> Dict:
        """Tâche pour un worker dans le test parallèle de KeyDB"""
        
        start_time = time.time()
        
        if test_name == "SET":
            for i in range(start_idx, end_idx):
                client.set(f"{self.prefix}:set:{i}", self.test_value)
                
        elif test_name == "GET":
            for i in range(start_idx, end_idx):
                client.get(f"{self.prefix}:get:{i}")
                
        elif test_name == "Pipeline SET":
            pipeline_size = self.pipeline_size
            iterations = end_idx - start_idx
            pipeline_count = iterations // pipeline_size
            
            for p in range(pipeline_count):
                pipe = client.pipeline(transaction=False)
                for i in range(pipeline_size):
                    index = start_idx + p * pipeline_size + i
                    if index < end_idx:
                        pipe.set(f"{self.prefix}:pipe_set:{index}", self.test_value)
                pipe.execute()
                
        elif test_name == "Pipeline GET":
            pipeline_size = self.pipeline_size
            iterations = end_idx - start_idx
            pipeline_count = iterations // pipeline_size
            
            for p in range(pipeline_count):
                pipe = client.pipeline(transaction=False)
                for i in range(pipeline_size):
                    index = start_idx + p * pipeline_size + i
                    if index < end_idx:
                        pipe.get(f"{self.prefix}:pipe_get:{index}")
                pipe.execute()
                
        elif test_name == "INCR":
            for i in range(start_idx, end_idx):
                client.incr(f"{self.prefix}:incr:{i % 1000}")
                
        elif test_name == "LPUSH":
            for i in range(start_idx, end_idx):
                client.lpush(f"{self.prefix}:list:{i % 1000}", self.test_value)
                
        elif test_name == "LPOP":
            for i in range(start_idx, end_idx):
                client.lpop(f"{self.prefix}:pop_list:{i % 1000}")
                
        end_time = time.time()
        duration = end_time - start_time
        operations = end_idx - start_idx
        ops_per_sec = operations / duration if duration > 0 else 0
        
        return {
            'duration': duration,
            'operations': operations,
            'ops_per_sec': ops_per_sec
        }
        
    def run_parallel_test_keydb(self, clients: List[redis.Redis], test_name: str) -> Dict:
        """Exécute un test en parallèle sur KeyDB avec plusieurs connexions"""
        
        if not clients:
            return {'duration': 0, 'ops_per_sec': 0}
        
        # Préparation des données pour certains tests
        if test_name == "GET" or test_name == "Pipeline GET":
            # Utiliser le premier client pour insérer les données
            client = clients[0]
            for i in range(self.iterations):
                key = f"{self.prefix}:get:{i}" if test_name == "GET" else f"{self.prefix}:pipe_get:{i}"
                client.set(key, self.test_value)
        
        if test_name == "LPOP":
            # Préparer les listes pour les LPOP
            client = clients[0]
            for i in range(self.iterations):
                client.lpush(f"{self.prefix}:pop_list:{i % 1000}", self.test_value)
        
        # Répartir les itérations entre les workers
        chunk_size = self.iterations // len(clients)
        tasks = []
        
        with concurrent.futures.ThreadPoolExecutor(max_workers=len(clients)) as executor:
            for i, client in enumerate(clients):
                start_idx = i * chunk_size
                end_idx = (i + 1) * chunk_size if i < len(clients) - 1 else self.iterations
                tasks.append(executor.submit(self.worker_task, client, test_name, start_idx, end_idx))
        
        # Collecter les résultats
        results = [task.result() for task in concurrent.futures.as_completed(tasks)]
        
        # Calculer les performances globales
        total_operations = sum(r['operations'] for r in results)
        total_duration = max(r['duration'] for r in results)  # Prendre la durée la plus longue
        ops_per_sec = total_operations / total_duration if total_duration > 0 else 0
        
        return {
            'duration': total_duration,
            'ops_per_sec': ops_per_sec,
            'pipeline_size': self.pipeline_size if 'Pipeline' in test_name else None
        }
    
    def run_benchmark(self) -> Dict:
        """Exécute tous les tests de benchmark et retourne les résultats"""
        
        redis_client, keydb_clients = self.connect()
        if not redis_client or not keydb_clients:
            print("Impossible de continuer sans connexion aux deux bases de données")
            return {}
            
        # Nettoyer les bases avant les tests si demandé
        if self.cleanup:
            self.cleanup_keys(redis_client)
            self.cleanup_keys(keydb_clients[0])  # Utiliser la première connexion pour le nettoyage
            
        print("Exécution des tests...")
        
        tests = ["SET", "GET", "Pipeline SET", "Pipeline GET", "INCR", "LPUSH", "LPOP"]
        results = {
            'redis': {'tests': {}, 'info': {}},
            'keydb': {'tests': {}, 'info': {}}
        }
        
        # Tests Redis
        print("\nExécution des tests sur Redis...")
        for test in tests:
            print(f"  - Test {test}... ", end="", flush=True)
            results['redis']['tests'][test] = self.run_single_test_redis(redis_client, test)
            print(f"Terminé: {results['redis']['tests'][test]['ops_per_sec']:.2f} ops/sec")
            
        # Tests KeyDB en parallèle
        print(f"\nExécution des tests sur KeyDB (avec {self.keydb_connections} connexions parallèles)...")
        for test in tests:
            print(f"  - Test {test}... ", end="", flush=True)
            results['keydb']['tests'][test] = self.run_parallel_test_keydb(keydb_clients, test)
            print(f"Terminé: {results['keydb']['tests'][test]['ops_per_sec']:.2f} ops/sec")
            
        # Récupération des informations sur les serveurs
        results['redis']['info'] = self.get_info(redis_client)
        results['keydb']['info'] = self.get_info(keydb_clients[0])
        
        # Ajouter le nombre de connexions KeyDB aux résultats
        results['keydb']['connections'] = self.keydb_connections
        
        # Nettoyage final si demandé
        if self.cleanup:
            self.cleanup_keys(redis_client)
            self.cleanup_keys(keydb_clients[0])
            
        # Fermer les connexions
        redis_client.close()
        for client in keydb_clients:
            client.close()
        
        self.results = results
        return results
        
    def print_results(self):
        """Affiche les résultats des tests sous forme de tableau"""
        
        if not self.results:
            print("Aucun résultat disponible. Exécutez d'abord le benchmark.")
            return
            
        print("\n" + "="*60)
        print(f"RÉSULTATS COMPARATIFS (KeyDB: {self.results['keydb'].get('connections', 1)} connexions parallèles)")
        print("="*60)
        
        results_table = PrettyTable()
        results_table.field_names = ["Test", "Redis (ops/sec)", "KeyDB (ops/sec)", "Différence (%)", "Plus rapide"]
        
        for test in self.results['redis']['tests'].keys():
            redis_ops = self.results['redis']['tests'][test]['ops_per_sec']
            keydb_ops = self.results['keydb']['tests'][test]['ops_per_sec']
            
            if redis_ops > 0 and keydb_ops > 0:
                diff_pct = ((keydb_ops / redis_ops) - 1) * 100  # Modifié: KeyDB par rapport à Redis
                faster = "KeyDB" if diff_pct > 0 else "Redis"
                if abs(diff_pct) < 1:
                    faster = "Égalité"
            else:
                diff_pct = 0
                faster = "N/A"
                
            results_table.add_row([
                test,
                f"{redis_ops:.2f}",
                f"{keydb_ops:.2f}",
                f"{abs(diff_pct):.2f}%",
                faster
            ])
            
        print(results_table)
        
        # Informations sur les serveurs
        print("\n" + "="*60)
        print("INFORMATIONS SUR LES SERVEURS")
        print("="*60)
        
        info_table = PrettyTable()
        info_table.field_names = ["Caractéristique", "Redis", "KeyDB"]
        
        redis_info = self.results['redis']['info']
        keydb_info = self.results['keydb']['info']
        
        info_fields = [
            ('Version', 'version'),
            ('Mode', 'mode'),
            ('Système d\'exploitation', 'os'),
            ('Utilisation CPU', 'used_cpu_sys'),
            ('Mémoire utilisée', 'used_memory_human'),
            ('Connexions clients', 'connected_clients'),
            ('Opérations par seconde', 'instantaneous_ops_per_sec'),
            ('Commandes traitées', 'total_commands_processed'),
            ('Évictions de clés', 'evicted_keys'),
            ('Mémoire maximale', 'maxmemory_human'),
            ('Temps de disponibilité', 'uptime_in_seconds')
        ]
        
        for label, field in info_fields:
            redis_value = redis_info.get(field, "N/A")
            keydb_value = keydb_info.get(field, "N/A")
            
            if field == 'uptime_in_seconds':
                redis_value = self.format_uptime(redis_value)
                keydb_value = self.format_uptime(keydb_value)
                
            info_table.add_row([label, redis_value, keydb_value])
        
        # Ajouter le nombre de connexions parallèles dans les informations
        info_table.add_row(["Connexions parallèles", "1", str(self.keydb_connections)])
            
        print(info_table)
        
    def export_results(self):
        """Exporte les résultats en JSON et génère des graphiques"""
        
        if not self.results:
            print("Aucun résultat disponible. Exécutez d'abord le benchmark.")
            return
            
        # Création du répertoire pour les résultats si nécessaire
        results_dir = "benchmark_results"
        os.makedirs(results_dir, exist_ok=True)
        
        # Nom de fichier avec horodatage
        timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
        json_filename = f"{results_dir}/redis_keydb_benchmark_{timestamp}.json"
        chart_filename = f"{results_dir}/redis_keydb_chart_{timestamp}.png"
        
        # Exporter en JSON
        with open(json_filename, 'w') as f:
            json.dump(self.results, f, indent=2)
        print(f"\nRésultats exportés dans {json_filename}")
        
        # Créer un graphique ade comparaison
        tests = list(self.results['redis']['tests'].keys())
        redis_ops = [self.results['redis']['tests'][test]['ops_per_sec'] for test in tests]
        keydb_ops = [self.results['keydb']['tests'][test]['ops_per_sec'] for test in tests]
        
        # Configuration du graphique
        plt.figure(figsize=(12, 8))
        x = np.arange(len(tests))
        width = 0.35
        
        plt.bar(x - width/2, redis_ops, width, label='Redis (1 connexion)', color='#D82C20')
        plt.bar(x + width/2, keydb_ops, width, label=f'KeyDB ({self.keydb_connections} connexions)', color='#00CCFA')
        
        plt.title(f'Comparaison Redis vs KeyDB ({self.keydb_connections} connexions) - Opérations par seconde', fontsize=16)
        plt.xlabel('Tests', fontsize=14)
        plt.ylabel('Opérations par seconde', fontsize=14)
        plt.xticks(x, tests, rotation=45, ha='right')
        plt.grid(axis='y', linestyle='--', alpha=0.7)
        plt.legend()
        plt.tight_layout()
        
        # Échelle logarithmique pour mieux visualiser les différences importantes
        plt.yscale('log')
        
        # Enregistrer le graphique
        plt.savefig(chart_filename)
        print(f"Graphique de comparaison exporté dans {chart_filename}")
        
        # Créer un graphique supplémentaire pour la comparaison directe en pourcentage
        diff_chart = f"{results_dir}/redis_keydb_diff_{timestamp}.png"
        
        plt.figure(figsize=(12, 6))
        diff_pct = []
        colors = []
        
        for test in tests:
            redis_ops = self.results['redis']['tests'][test]['ops_per_sec']
            keydb_ops = self.results['keydb']['tests'][test]['ops_per_sec']
            
            if redis_ops > 0 and keydb_ops > 0:
                pct = ((keydb_ops / redis_ops) - 1) * 100  # Modifié: pourcentage relatif à Redis
                diff_pct.append(pct)
                colors.append('#00CCFA' if pct > 0 else '#D82C20')
            else:
                diff_pct.append(0)
                colors.append('gray')
                
        plt.bar(tests, diff_pct, color=colors)
        plt.axhline(y=0, color='black', linestyle='-', alpha=0.3)
        
        plt.title(f'Différence de performance KeyDB ({self.keydb_connections} connexions) vs Redis (%)', fontsize=16)
        plt.xlabel('Tests', fontsize=14)
        plt.ylabel('Différence en % (positif = KeyDB plus rapide)', fontsize=14)
        plt.xticks(rotation=45, ha='right')
        plt.grid(axis='y', linestyle='--', alpha=0.7)
        plt.tight_layout()
        
        # Enregistrer le graphique
        plt.savefig(diff_chart)
        print(f"Graphique de différence exporté dans {diff_chart}")

def main():
    """Fonction principale qui parse les arguments et lance le benchmark"""
    
    parser = argparse.ArgumentParser(description='Benchmark comparatif entre Redis et KeyDB')
    
    # Arguments Redis
    parser.add_argument('--redis-host', type=str, default='localhost',
                        help='Hôte Redis (défaut: localhost)')
    parser.add_argument('--redis-port', type=int, default=6379,
                        help='Port Redis (défaut: 6379)')
    parser.add_argument('--redis-password', type=str, default=None,
                        help='Mot de passe Redis (facultatif)')
    
    # Arguments KeyDB
    parser.add_argument('--keydb-host', type=str, default='localhost',
                        help='Hôte KeyDB (défaut: localhost)')
    parser.add_argument('--keydb-port', type=int, default=6379,
                        help='Port KeyDB (défaut: 6379)')
    parser.add_argument('--keydb-password', type=str, default=None,
                        help='Mot de passe KeyDB (facultatif)')
    parser.add_argument('--keydb-connections', type=int, default=8,
                        help='Nombre de connexions parallèles pour KeyDB (défaut: 8)')
    
    # Arguments généraux
    parser.add_argument('--iterations', type=int, default=10000,
                        help='Nombre d\'itérations pour chaque test (défaut: 10000)')
    parser.add_argument('--value-size', type=int, default=1000,
                        help='Taille des valeurs en octets (défaut: 1000)')
    parser.add_argument('--pipeline-size', type=int, default=1000,
                        help='Taille des pipelines (défaut: 1000)')
    parser.add_argument('--no-cleanup', action='store_true',
                        help='Ne pas nettoyer les clés de test avant et après les tests')
    parser.add_argument('--prefix', type=str, default='bench',
                        help='Préfixe pour les clés de test (défaut: bench)')
    
    args = parser.parse_args()
    
    print("\n" + "="*60)
    print(f"BENCHMARK COMPARATIF REDIS VS KEYDB - {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
    print("="*60)
    print(f"Redis: {args.redis_host}:{args.redis_port} (1 connexion)")
    print(f"KeyDB: {args.keydb_host}:{args.keydb_port} ({args.keydb_connections} connexions)")
    print(f"Itérations: {args.iterations}, Taille des valeurs: {args.value_size} octets")
    print(f"Taille des pipelines: {args.pipeline_size}")
    print("="*60 + "\n")
    
    benchmark = DatabaseBenchmark(
        host_redis=args.redis_host,
        port_redis=args.redis_port,
        password_redis=args.redis_password,
        host_keydb=args.keydb_host,
        port_keydb=args.keydb_port,
        password_keydb=args.keydb_password,
        iterations=args.iterations,
        value_size=args.value_size,
        pipeline_size=args.pipeline_size,
        cleanup=not args.no_cleanup,
        prefix=args.prefix,
        keydb_connections=args.keydb_connections
    )
    
    benchmark.run_benchmark()
    benchmark.print_results()
    benchmark.export_results()

if __name__ == "__main__":
    main()

 

  • Dockerfile
FROM python:3.9.22-slim

WORKDIR /app

# Installation des dépendances nécessaires
RUN pip install --no-cache-dir redis prettytable matplotlib numpy

# Création du répertoire pour les résultats
RUN mkdir -p /app/results

# Copie des scripts existants
COPY *.py /app/

# Rendre tous les scripts exécutables
RUN chmod +x /app/keydb_perf.py
RUN chmod +x /app/redis_perf.py
RUN chmod +x /app/redis_keydb_benchmark.py
RUN chmod +x /app/redis_key_db_bench.py

# Définition d'un volume pour persister les résultats
VOLUME ["/app/results"]

 

  • Build d'image
docker build -t keydb-performance-test:0.0.1 .

 

  • Lancer le container de test
docker run --rm -it -v ./perf/results:/app/results keydb-performance-test:0.0.1 bash

cd /app/results

 

Lancer test-01 avec une seule connexion pour keydb

python3 .././redis_keydb_benchmark.py --redis-host=10.10.10.23 --redis-password='fddaad00d6c9485KD7485' \
    --keydb-host=10.10.10.24 --keydb-password='fddaad00d6c9485KD7485' \
    --iterations=20000 --value-size=1000 --pipeline-size=1000 --keydb-connections=1

 

============================================================
RÉSULTATS COMPARATIFS (KeyDB: 1 connexions parallèles)
============================================================
+--------------+-----------------+-----------------+----------------+-------------+
|     Test     | Redis (ops/sec) | KeyDB (ops/sec) | Différence (%) | Plus rapide |
+--------------+-----------------+-----------------+----------------+-------------+
|     SET      |      668.96     |      366.04     |     45.28%     |    Redis    |
|     GET      |     1379.64     |      823.85     |     40.28%     |    Redis    |
| Pipeline SET |     19585.55    |     6856.01     |     64.99%     |    Redis    |
| Pipeline GET |     33130.34    |     22496.89    |     32.10%     |    Redis    |
|     INCR     |      880.60     |      486.95     |     44.70%     |    Redis    |
|    LPUSH     |      927.98     |      512.28     |     44.80%     |    Redis    |
|     LPOP     |      851.02     |      511.42     |     39.91%     |    Redis    |
+--------------+-----------------+-----------------+----------------+-------------+

============================================================
INFORMATIONS SUR LES SERVEURS
============================================================
+------------------------+-----------------------------+-----------------------------+
|    Caractéristique     |            Redis            |            KeyDB            |
+------------------------+-----------------------------+-----------------------------+
|        Version         |            7.4.2            |            6.3.4            |
|          Mode          |          standalone         |          standalone         |
| Système d'exploitation | Linux 6.1.0-32-amd64 x86_64 | Linux 6.1.0-32-amd64 x86_64 |
|    Utilisation CPU     |          111.486628         |          159.332488         |
|    Mémoire utilisée    |           106.81M           |           120.97M           |
|   Connexions clients   |              7              |              3              |
| Opérations par seconde |              8              |             2536            |
|   Commandes traitées   |            460383           |           1676247           |
|   Évictions de clés    |              0              |              0              |
|    Mémoire maximale    |              0B             |              0B             |
| Temps de disponibilité |      0 jours, 2 heures      |      0 jours, 2 heures      |
| Connexions parallèles  |              1              |              1              |
+------------------------+-----------------------------+-----------------------------+

 

 

Dans ce test-01, KeyDB est configuré avec 1 seule connexion parallèle (comme Redis), et on constate un renversement complet de la tendance. Redis surpasse KeyDB dans tous les scénarios :

  • Les opérations SET sont 45% plus rapides avec Redis
  • Les opérations GET sont 40% plus rapides avec Redis
  • Les opérations par pipeline sont significativement plus efficaces avec Redis (65% pour SET, 32% pour GET)
  • Les opérations INCR, LPUSH et LPOP montrent toutes une supériorité de Redis d'environ 40-45%

Ces résultats sont révélateurs car ils montrent que :

  1. Lorsque limité à un seul thread, KeyDB est moins performant que Redis
  2. L'avantage de KeyDB observé dans les tests précédents provient principalement de sa capacité à exploiter le multithreading

Fait intéressant, malgré la configuration à une seule connexion parallèle, KeyDB traite toujours plus de commandes au total (1,67 million contre 460 383) et affiche 2536 opérations par seconde contre 8 pour Redis, ce qui semble contradictoire avec les autres métriques de performance. Cela pourrait suggérer des différences dans la façon dont ces métriques sont calculées entre les deux systèmes.

KeyDB utilise également plus de CPU (159% contre 111%) et légèrement plus de mémoire (120.97M contre 106.81M), ce qui indique probablement une surcharge liée à son architecture prête pour le multithreading, même lorsqu'elle n'est pas pleinement exploitée.

 

Lancer test-02 avec 8 connexions pour keydb

python3 .././redis_keydb_benchmark.py --redis-host=10.10.10.23 --redis-password='fddaad00d6c9485KD7485' \
    --keydb-host=10.10.10.24 --keydb-password='fddaad00d6c9485KD7485' \
    --iterations=20000 --value-size=1000 --pipeline-size=1000 --keydb-connections=8

 

============================================================
RÉSULTATS COMPARATIFS (KeyDB: 8 connexions parallèles)
============================================================
+--------------+-----------------+-----------------+----------------+-------------+
|     Test     | Redis (ops/sec) | KeyDB (ops/sec) | Différence (%) | Plus rapide |
+--------------+-----------------+-----------------+----------------+-------------+
|     SET      |      597.33     |     1703.28     |    185.15%     |    KeyDB    |
|     GET      |     1354.96     |     2688.02     |     98.38%     |    KeyDB    |
| Pipeline SET |     17618.66    |     23806.06    |     35.12%     |    KeyDB    |
| Pipeline GET |     27846.44    |     33186.71    |     19.18%     |    KeyDB    |
|     INCR     |      878.18     |     1636.22     |     86.32%     |    KeyDB    |
|    LPUSH     |      844.10     |     1515.94     |     79.59%     |    KeyDB    |
|     LPOP     |      863.61     |     1953.35     |    126.18%     |    KeyDB    |
+--------------+-----------------+-----------------+----------------+-------------+

============================================================
INFORMATIONS SUR LES SERVEURS
============================================================
+------------------------+-----------------------------+-----------------------------+
|    Caractéristique     |            Redis            |            KeyDB            |
+------------------------+-----------------------------+-----------------------------+
|        Version         |            7.4.2            |            6.3.4            |
|          Mode          |          standalone         |          standalone         |
| Système d'exploitation | Linux 6.1.0-32-amd64 x86_64 | Linux 6.1.0-32-amd64 x86_64 |
|    Utilisation CPU     |          68.982286          |          79.533901          |
|    Mémoire utilisée    |           106.80M           |           105.91M           |
|   Connexions clients   |              7              |              5              |
| Opérations par seconde |              8              |             8068            |
|   Commandes traitées   |            251805           |            832606           |
|   Évictions de clés    |              0              |              0              |
|    Mémoire maximale    |              0B             |              0B             |
| Temps de disponibilité |      0 jours, 1 heures      |      0 jours, 1 heures      |
| Connexions parallèles  |              1              |              8              |
+------------------------+-----------------------------+-----------------------------+

Les tests montrent clairement que KeyDB surpasse Redis dans tous les scénarios testés, avec des différences de performance significatives :

  • Les opérations SET sont 185% plus rapides avec KeyDB
  • Les opérations GET sont 98% plus rapides
  • Le traitement par pipeline est également plus efficace avec KeyDB (35% pour SET, 19% pour GET)
  • Les opérations INCR, LPUSH et LPOP montrent toutes des gains substantiels avec KeyDB

Une différence majeure dans votre configuration de test est le nombre de connexions parallèles : KeyDB utilise 8 connexions tandis que Redis n'en utilise qu'une seule. Cette différence explique en grande partie l'écart de performance, car KeyDB est conçu pour exploiter le multithreading, contrairement à Redis qui est principalement single-threaded.

Il est également intéressant de noter que KeyDB traite un volume beaucoup plus important d'opérations par seconde (8068 contre 8 pour Redis) et a géré plus de commandes au total (832606 contre 251805).

Si vous cherchez à optimiser les performances pour des charges de travail intensives, ces résultats suggèrent que KeyDB pourrait être un meilleur choix, particulièrement dans des environnements multi-cœurs où ses capacités de multithreading peuvent être pleinement exploitées.

 

Lancer test-03 avec 16 connexions pour keydb

python3 .././redis_keydb_benchmark.py --redis-host=10.10.10.23 --redis-password='fddaad00d6c9485KD7485' \
    --keydb-host=10.10.10.24 --keydb-password='fddaad00d6c9485KD7485' \
    --iterations=20000 --value-size=1000 --pipeline-size=1000 --keydb-connections=16

 

============================================================
RÉSULTATS COMPARATIFS (KeyDB: 16 connexions parallèles)
============================================================
+--------------+-----------------+-----------------+----------------+-------------+
|     Test     | Redis (ops/sec) | KeyDB (ops/sec) | Différence (%) | Plus rapide |
+--------------+-----------------+-----------------+----------------+-------------+
|     SET      |      585.57     |     1768.70     |    202.05%     |    KeyDB    |
|     GET      |      945.81     |     2009.10     |    112.42%     |    KeyDB    |
| Pipeline SET |     12231.15    |     19462.77    |     59.12%     |    KeyDB    |
| Pipeline GET |     19741.58    |     35587.11    |     80.26%     |    KeyDB    |
|     INCR     |      669.85     |     2058.40     |    207.29%     |    KeyDB    |
|    LPUSH     |      610.43     |     2249.38     |    268.49%     |    KeyDB    |
|     LPOP     |      663.18     |     2058.59     |    210.41%     |    KeyDB    |
+--------------+-----------------+-----------------+----------------+-------------+

============================================================
INFORMATIONS SUR LES SERVEURS
============================================================
+------------------------+-----------------------------+-----------------------------+
|    Caractéristique     |            Redis            |            KeyDB            |
+------------------------+-----------------------------+-----------------------------+
|        Version         |            7.4.2            |            6.3.4            |
|          Mode          |          standalone         |          standalone         |
| Système d'exploitation | Linux 6.1.0-32-amd64 x86_64 | Linux 6.1.0-32-amd64 x86_64 |
|    Utilisation CPU     |          160.011791         |          204.234688         |
|    Mémoire utilisée    |           106.83M           |           117.06M           |
|   Connexions clients   |              7              |              8              |
| Opérations par seconde |              6              |             5788            |
|   Commandes traitées   |            664419           |           2403673           |
|   Évictions de clés    |              0              |              0              |
|    Mémoire maximale    |              0B             |              0B             |
| Temps de disponibilité |      0 jours, 2 heures      |      0 jours, 2 heures      |
| Connexions parallèles  |              1              |              16             |
+------------------------+-----------------------------+-----------------------------+

 

 

L'analyse du test-03 avec KeyDB configuré à 16 connexions parallèles (contre 1 pour Redis) montre des résultats encore plus impressionnants :

Les performances de KeyDB surpassent encore davantage celles de Redis par rapport au test précédent. On observe :

  • Pour les opérations SET : +202% (contre +185% dans le test précédent)
  • Pour les opérations GET : +112% (contre +98%)
  • Pour INCR : +207% (contre +86%)
  • Pour LPUSH : +268% (contre +80%)
  • Pour LPOP : +210% (contre +126%)

Ces améliorations significatives s'expliquent principalement par l'augmentation du nombre de connexions parallèles de KeyDB (passé de 8 à 16). Le volume de commandes traitées est également bien plus important avec KeyDB (2,4 millions contre 664 419 pour Redis).

Quelques observations supplémentaires :

  • L'utilisation CPU est plus élevée dans les deux cas (204% pour KeyDB, 160% pour Redis)
  • KeyDB utilise légèrement plus de mémoire (117.06M contre 106.83M)
  • Le nombre d'opérations par seconde pour KeyDB est de 5788, en baisse par rapport au test précédent (8068), ce qui pourrait indiquer un certain niveau de contention ou de surcharge avec 16 threads

Ces résultats confirment la supériorité de KeyDB dans des environnements multi-cœurs exploitant le multithreading. L'augmentation des connexions parallèles de 8 à 16 a amélioré significativement les performances pour la plupart des opérations, particulièrement pour les opérations de liste (LPUSH/LPOP).

 

Conclusion

Ces tests mettent en évidence plusieurs points importants :

  1. Architecture et conception : Redis, avec son modèle single-threaded, est plus efficace que KeyDB lorsqu'on limite les deux systèmes à une seule connexion. Cela suggère que l'architecture de base de Redis est plus optimisée pour les opérations séquentielles.
  2. Scalabilité : KeyDB démontre une excellente capacité à exploiter les ressources multi-cœurs. Ses performances s'améliorent considérablement avec l'augmentation du nombre de connexions parallèles.
  3. Cas d'utilisation :
    • Pour des déploiements sur des machines à processeur unique ou avec des charges de travail légères, Redis pourrait être le choix le plus adapté.
    • Pour des environnements à haute charge nécessitant une exploitation maximale des ressources matérielles multi-cœurs, KeyDB offre un avantage considérable.
  4. Ressources : KeyDB consomme généralement plus de CPU et de mémoire, ce qui est le coût de son architecture multithreading.

Commentaires

Connectez-vous pour rejoindre la discussion.

Aucun commentaire pour le moment.