O benchmark Couchbase revela INSERTs e GETs muito lentos (usando operações KeyValue); mais lento do que dados MySQL persistentes
Fiz um pequeno teste de benchmark para comparar Couchbase (rodando em Win) com Redis e MySql (EDITAR: adicionado Aerospike para teste)
Estamos inserindo 100.000 "documentos" JSON em três db / armazéns:
- Redis (basta inserir, não há mais nada)
- Couchbase (baldes efêmeros na memória, índice JSON em JobId)
- MySql (Tabela simples; Id (int), Dados (Texto Médio), índice no Id)
- Aerospike (armazenamento na memória)
O arquivo JSON tem 67 linhas, cerca de 1800 bytes.
INSERIR:
- Couchbase: 60-100 segundos (EDITAR: parece variar um pouco!)
- MySql: 30 segundos
- Redis: 8 segundos
- Aerospike: 71 segundos
LEIA: estamos lendo 1000 vezes, e fazemos isso 10 vezes e olhamos as médias.
- Couchbase: 600-700 ms para 1000 GETs (usando operações KeyValue, não Query API. Usando Query API, isso leva cerca de 1500 ms)
- MySql: 90-100 ms para 1000 GETs
- Redis: 50-60 ms para 1000 GETs
- Aerospike: 750 ms por 1000 GETs
Conclusão: Couchbase parece mais lento (os tempos de INSERT variam muito ao que parece), Aerospike também é muito lento. Ambos estão usando armazenamento na memória (Couchbase => depósito efêmero, Aerospike => memória do mecanismo de armazenamento).
Pergunta: Por que a gravação e leitura na memória no Couchbase são tão lentas, ainda mais lentas do que usando o MySQL normal (em um SSD)?
CÓDIGO
Nota: Usar Task.WhenAll, ou aguardar cada chamada, não faz diferença.
INSERIR
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 (usando 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();
LER
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();
Configuração do Couchbase:

e

e durabilidade do balde:

Eu tenho apenas 1 nó (sem cluster), é local na minha máquina, executando Ryzen 3900x 12 núcleos, M.2 SSD, Win10, 32 GB de RAM.
Se você chegou até aqui, aqui está um repositório GitHub com meu código de referência: https://github.com/tedekeroth/CouchbaseTests
Respostas
Peguei seus CouchbaseTests, comentei os bits não Couchbase. Corrigida a consulta para selecionar da coleção (myCollection) em vez do jobcache e removida a opção Metrics. E criou um índice no JobId. cria o índice mybucket_JobId no padrão: myBucket.myScope.myCollection (JobId) Insere 100.000 documentos em 19 segundos e obtém os documentos por kv em média 146 usec e consulta por JobId em média 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).
Isso foi no 7.0 build 3739 em um Mac Book Pro com o cbserver rodando localmente.
############################################################### ########################
Eu tenho um pequeno aplicativo LoadDriver para o java sdk que usa a API kv. Com 4 threads, ele mostra um tempo médio de resposta de 54 microssegundos e taxa de transferência de 73238 solicitações / segundo. Ele usa o balde travel-sample em um servidor cb no localhost. [email protected]: mikereiche / loaddriver.git
Executar: segundos: 10, threads: 4, tempo limite: 40000us, limite: 8.000us de solicitações / segundo: 0 (máx.), Intervalo de GC forçado: contagem de 0ms: 729873, solicitações / segundo: 72987, máx .: 2796us média: 54us, rq agregado / s: 73238
Para a API de consulta, recebo o seguinte, que é 18 vezes mais lento.
Execução: segundos: 10, threads: 4, tempo limite: 40000us, limite: 8.000us solicitações / segundo: 0 (máx.), Intervalo GC forçado: contagem de 0 ms: 41378, solicitações / segundo: 4137, máx .: 12032us média: 965us, rq agregado / s: 4144
Eu mesmo teria que fazer essa comparação para fazer uma investigação completa, mas duas coisas se destacam.
Sua execução paralela não é totalmente paralela.
async
os métodos são executados de forma síncrona até o primeiro await, de modo que todo o códigoInsertAsync/GetAsync
anterior ao primeiro await é executado sequencialmente conforme você adiciona suas tarefas, não em paralelo.CouchbaseNetClient faz alguma configuração de conexão lenta em segundo plano, e você está pagando esse custo na seção cronometrada. Dependendo do ambiente, incluindo negociação SSL e outras coisas, isso pode ser uma latência inicial significativa.
Você pode potencialmente resolver o primeiro problema usando Task.Run
para iniciar a operação, mas pode ser necessário pré-dimensionar o tamanho padrão do Threadpool.
Você pode resolver o segundo problema fazendo pelo menos uma operação no balde (incluindo bucket.WaitUntilReadyAsync()
) antes da seção cronometrada.
60 segundos para inserções ainda parecem anormais. Quantos nós e qual configuração de durabilidade você está usando?