Le benchmark Couchbase révèle des INSERT et GET très lents (en utilisant les opérations KeyValue); plus lent que les données MySQL persistantes

Nov 25 2020

J'ai fait un petit test de référence pour comparer Couchbase (fonctionnant sous Win) avec Redis et MySql (EDIT: Ajout d'Aerospike pour tester)

Nous insérons 100 000 "documents" JSON dans trois db / stores:

  • Redis (il suffit d'insérer, il n'y a rien d'autre)
  • Couchbase (buckets éphémères en mémoire, index JSON sur JobId)
  • MySql (Simple table; Id (int), Data (MediumText), index sur Id)
  • Aerospike (stockage en mémoire)

Le fichier JSON est de 67 lignes, environ 1800 octets.

INSÉRER:

  • Couchbase: 60-100 secondes (EDIT: semble varier un peu!)
  • MySql: 30 secondes
  • Redis: 8 secondes
  • Aerospike: 71 secondes

LIRE: Nous lisons 1000 fois, et nous le faisons 10 fois et regardons les moyennes.

  • Couchbase: 600 à 700 ms pour 1000 GET (en utilisant les opérations KeyValue, pas l'API de requête. Avec l'API de requête, cela prend environ 1 500 ms)
  • MySql: 90-100 ms pour 1000 GET
  • Redis: 50-60 ms pour 1000 GET
  • Aerospike: 750 ms pour 1000 GET

Conclusion: Couchbase semble le plus lent (les temps INSERT varient beaucoup semble-t-il), Aerospike est également très lent. Les deux utilisent le stockage en mémoire (Couchbase => seau éphémère, Aerospike => mémoire du moteur de stockage).

Question: Pourquoi l'écriture et la lecture en mémoire sur Couchbase sont-elles si lentes, voire plus lentes qu'en utilisant MySQL normal (sur un SSD)?

CODE

Remarque: l'utilisation de Task.WhenAll ou l'attente de chaque appel ne fait aucune différence.

INSÉRER

Couchbase:

IBucket bucket = await cluster.BucketAsync("halo"); // <-- ephemeral 
IScope scope = bucket.Scope("myScope");
var collection = scope.Collection("myCollection");

// EDIT: Added this to avoid measuring lazy loading:
JObject t = JObject.FromObject(_baseJsonObject);
t["JobId"] = 0;
t["CustomerName"] = $"{firstnames[rand.Next(0, firstnames.Count - 1)]} {lastnames[rand.Next(0, lastnames.Count - 1)]}"; await collection.InsertAsync("0", t); await collection.RemoveAsync("0"); List<Task> inserTasks = new List<Task>(); sw.Start(); foreach (JObject temp in jsonObjects) // jsonObjects is pre-created so its not a factor in the test { inserTasks.Add(collection.InsertAsync(temp.GetValue("JobId").ToString(), temp)); } await Task.WhenAll(inserTasks); sw.Stop(); Console.WriteLine($"Adding {nbr} to Couchbase took {sw.ElapsedMilliseconds} ms");

Redis (en utilisant ServiceStack!)

sw.Restart();
using (var client = redisManager.GetClient())
{
    foreach (JObject temp in jsonObjects)
    {
        client.Set($"jobId:{temp.GetValue("JobId")}", temp.ToString()); } } sw.Stop(); Console.WriteLine($"Adding {nbr} to Redis took {sw.ElapsedMilliseconds} ms");
sw.Reset();

Mysql:

MySql.Data.MySqlClient.MySqlConnection mySqlConnection = new MySql.Data.MySqlClient.MySqlConnection("Server=localhost;Database=test;port=3306;User Id=root;password=root;");
mySqlConnection.Open();
sw.Restart();
foreach (JObject temp in jsonObjects)
{
    MySql.Data.MySqlClient.MySqlCommand cmd = new MySql.Data.MySqlClient.MySqlCommand($"INSERT INTO test (id, data) VALUES ('{temp.GetValue("JobId")}', @data)", mySqlConnection); cmd.Parameters.AddWithValue("@data", temp.ToString()); cmd.ExecuteNonQuery(); } sw.Stop(); Console.WriteLine($"Adding {nbr} to MySql took {sw.ElapsedMilliseconds} ms");
sw.Reset();

LIS

Couchbase:

IBucket bucket = await cluster.BucketAsync("halo");
IScope scope = bucket.Scope("myScope");
var collection = scope.Collection("myCollection");


    Stopwatch sw = Stopwatch.StartNew();
    for (int i = 0; i < 1000; i++)
    {
        string key = $"{r.Next(1, 100000)}"; var result = await collection.GetAsync(key); } sw.Stop(); Console.WriteLine($"Couchbase Q: {q}\t{sw.ElapsedMilliseconds}");

Redis:

    Stopwatch sw = Stopwatch.StartNew();
    using (var client = redisManager.GetClient())
    {
        for (int i = 0; i < nbr; i++)
        {
            client.Get<string>($"jobId:{r.Next(1, 100000)}"); } } sw.Stop(); Console.WriteLine($"Redis Q: {q}\t{sw.ElapsedMilliseconds}");

MySQL:

MySqlConnection mySqlConnection = new MySql.Data.MySqlClient.MySqlConnection("Server=localhost;Database=test;port=3306;User Id=root;password=root;");
mySqlConnection.Open();
            
Stopwatch sw = Stopwatch.StartNew();
for (int i = 0; i < nbr; i++)
{
    MySqlCommand cmd = new MySql.Data.MySqlClient.MySqlCommand($"SELECT data FROM test WHERE Id='{r.Next(1, 100000)}'", mySqlConnection); using MySqlDataReader rdr = cmd.ExecuteReader(); while (rdr.Read()) { } } sw.Stop(); Console.WriteLine($"MySql Q: {q} \t{sw.ElapsedMilliseconds} ms");
sw.Reset();

Configuration de Couchbase:

et

et durabilité du godet:

Je n'ai qu'un seul nœud (pas de cluster), il est local sur ma machine, exécutant Ryzen 3900x 12 cœurs, SSD M.2, Win10, 32 Go de RAM.

Si vous êtes arrivé jusqu'ici, voici un repo GitHub avec mon code de référence: https://github.com/tedekeroth/CouchbaseTests

Réponses

2 MichaelReiche Dec 07 2020 at 18:52

J'ai pris vos CouchbaseTests, commenté les bits non-Couchbase. Correction de la requête pour sélectionner dans la collection (myCollection) au lieu de jobcache et suppression de l'option Métriques. Et créé un index sur JobId. create index mybucket_JobId par défaut: myBucket.myScope.myCollection (JobId) Il insère les 100 000 documents en 19 secondes et kv-récupère les documents en moyenne 146 usec et interroge par JobId en moyenne 965 usec.

Couchbase Q: 0 187
Couchbase Q: 1 176
Couchbase Q: 2 143
Couchbase Q: 3 147
Couchbase Q: 4 140
Couchbase Q: 5 138
Couchbase Q: 6 136
Couchbase Q: 7 139
Couchbase Q: 8 125
Couchbase Q: 9 129
average et: 146 ms per 1000 -> 146 usec / request

Couchbase Q: 0 1155
Couchbase Q: 1 1086
Couchbase Q: 2 1004
Couchbase Q: 3 901
Couchbase Q: 4 920
Couchbase Q: 5 929
Couchbase Q: 6 912
Couchbase Q: 7 911
Couchbase Q: 8 911
Couchbase Q: 9 927
average et: 965 ms per 1000 -> 965 usec / request. (coincidentally exactly the same as with the java api).

C'était sur 7.0 build 3739 sur un Mac Book Pro avec le cbserver fonctionnant localement.

################################################ ####################

J'ai une petite application LoadDriver pour le sdk java qui utilise l'api kv. Avec 4 threads, il affiche un temps de réponse moyen de 54 micro-secondes et un débit de 73238 requêtes / seconde. Il utilise le bucket travel-sample sur un serveur cb sur localhost. [email protected]: mikereiche / loaddriver.git

Exécution: secondes: 10, threads: 4, timeout: 40000us, seuil: 8000us requêtes / seconde: 0 (max), intervalle GC forcé: 0ms count: 729873, requêtes / seconde: 72987, max: 2796us avg: 54us, agrégat rq / s: 73238

Pour l'API de requête, j'obtiens ce qui suit qui est 18 fois plus lent.

Exécution: secondes: 10, threads: 4, timeout: 40000us, seuil: 8000us demandes / seconde: 0 (max), intervalle GC forcé: 0ms count: 41378, demandes / seconde: 4137, max: 12032us avg: 965us, agrégat rq / s: 4144

1 RichardP Nov 25 2020 at 18:03

Je devrais effectuer moi-même une telle comparaison pour faire une enquête complète, mais deux choses ressortent.

  1. Votre exécution parallèle n'est pas vraiment totalement parallèle. asyncLes méthodes s'exécutent de manière synchrone jusqu'à la première attente, donc tout le code InsertAsync/GetAsyncavant la première attente s'exécute de manière séquentielle lorsque vous ajoutez vos tâches, et non en parallèle.

  2. CouchbaseNetClient effectue une configuration de connexion paresseuse en arrière-plan, et vous payez ce coût dans la section chronométrée. Selon l'environnement, y compris la négociation SSL et autres, cela peut être une latence initiale importante.

Vous pouvez potentiellement résoudre le premier problème en utilisant Task.Runpour lancer l'opération, mais vous devrez peut-être pré-dimensionner la taille de Threadpool par défaut.

Vous pouvez résoudre le deuxième problème en effectuant au moins une opération sur le compartiment (y compris bucket.WaitUntilReadyAsync()) avant la section chronométrée.

60 secondes pour les inserts semblent toujours anormaux. Combien de nœuds et quel paramètre de durabilité utilisez-vous?