【问题标题】:Couchbase benchmark reveals very slow INSERTs and GETs (using KeyValue operations); slower than persisted MySQL dataCouchbase 基准测试显示非常慢的 INSERT 和 GET(使用 KeyValue 操作);比持久化 MySQL 数据慢
【发布时间】:2025-12-30 12:05:09
【问题描述】:

我做了一个小型基准测试来比较 Couchbase(在 Win 中运行)与 Redis 和 MySql(编辑:添加 Aerospike 进行测试)

我们正在将 100 000 个 JSON“文档”插入三个 db/stores:

  • Redis(插入即可,没有别的)
  • Couchbase(内存中的临时存储桶,JobId 上的 JSON 索引)
  • MySql(简单表;Id (int)、Data (MediumText)、Id 上的索引)
  • Aerospike(内存存储)

JSON 文件 67 行,约 1800 字节。

插入:

  • Couchbase:60-100 秒(编辑:似乎变化很大!)
  • MySql:30 秒
  • Redis:8 秒
  • 气动尖峰:71 秒

阅读: 我们正在阅读 1000 次,我们这样做了 10 次并查看平均值。

  • Couchbase:1000 次 GET 需要 600-700 毫秒(使用 KeyValue 操作,而不是 Query API。使用 Query API,这大约需要 1500 毫秒)
  • MySql:1000 次 GET 需要 90-100 毫秒
  • Redis:1000 次 GET 需要 50-60 毫秒
  • Aerospike:1000 次 GET 需要 750 毫秒

结论: Couchbase 似乎最慢(插入时间似乎变化很大),Aerospike 也很慢。这两个都使用内存存储(Couchbase => Ephemeral bucket,Aerospike => storage-engine memory)。

问题:为什么在 Couchbase 上的内存读写如此缓慢,甚至比使用普通 MySQL(在 SSD 上)还要慢?

代码

注意:使用 Task.WhenAll 或等待每次调用都没有区别。

插入

沙发床:

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(使用 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();

阅读

沙发床:

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();

沙发底座设置:

和存储桶的耐用性:

我只有 1 个节点(无集群),它在我的机器上是本地的,运行 Ryzen 3900x 12 核、M.2 SSD、Win10、32 GB RAM。

如果你做到了这一点,这里有一个包含我的基准代码的 GitHub 存储库: https://github.com/tedekeroth/CouchbaseTests

【问题讨论】:

  • Redis 和 Mysql 代码有助于确保进行公平比较。例如,是否使用了 REDIS 批量 API?
  • 不,mysql和redis都是顺序插入,甚至没有使用Task.WhenAll,而且速度仍然非常快。根据要求,我也会更新代码。
  • @Ted 获取另一个数据点进行比较,看看使用 cbc-pillowfight 工具 (docs.couchbase.com/sdk-api/couchbase-c-client-2.4.8/…) 运行类似测试的结果会很有趣,该工具将位于 Couchbase 的 bin 目录中安装。
  • 对不起,我直到现在才看到这条评论,很奇怪,这里没有“ping”。是的,这可能很有趣,但不幸的是,我已经“继续前进”并寻找其他解决方案。我希望这对其他人有所帮助。
  • @Ted 很公平。只是想让任何偶然发现这一点的人都知道,您所看到的时间比我们期望从 Couchbase 看到的要慢得多。我对您在其他地方的发现感到非常困惑,即并行执行插入并不比串行快多少。如果您有机会回到这一点,看看调整 SDK NumKvConnections 参数是否有帮助会很有趣。这控制了SDK到每个Key-Value节点的连接数,默认为1。在这样的批量加载场景中,它可以帮助提高吞吐量。

标签: mysql redis couchbase benchmarking aerospike


【解决方案1】:

我拿了你的 CouchbaseTests,注释掉了非 Couchbase 位。修复了从集合( myCollection )而不是 jobcache 中选择的查询,并删除了 Metrics 选项。并在 JobId 上创建了一个索引。 在默认情况下创建索引 mybucket_JobId:myBucket.myScope.myCollection (JobId) 它在 19 秒内插入 100,000 个文档,kv-fetches 平均 146 usec 和 JobId 查询平均 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).

这是在 Mac Book Pro 上的 7.0 build 3739 上,cbserver 在本地运行。

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

我有一个用于使用 kv api 的 java sdk 的小型 LoadDriver 应用程序。使用 4 个线程,平均响应时间为 54 微秒,吞吐量为 73238 个请求/秒。它使用本地主机上的 cb 服务器上的 travel-sample 存储桶。 git@github.com:mikereiche/loaddriver.git

运行:秒:10,线程:4,超时:40000us,阈值:8000us 请求/秒:0(最大值),强制 GC 间隔:0ms 计数:729873,请求/秒:72987,最大值:2796us 平均:54us,聚合 rq/s:73238

对于查询 API,我得到以下 18 倍的速度。

运行:秒:10,线程:4,超时:40000us,阈值:8000us 请求/秒:0(最大值),强制 GC 间隔:0ms 计数:41378,请求/秒:4137,最大值:12032us 平均:965us,聚合 rq/s:4144

【讨论】:

  • 感谢您对此进行测试。你能提供一个 GitHub URL 或类似的东西吗?我想测试你的这个版本。另外,据我了解, KeyValue 操作不应该需要设置索引,我认为 Key 总是被索引?无论如何,不​​添加该索引应该会使插入速度更快,对吧?
  • github.com/mikereiche/CouchbaseTests Ted - 我隐约记得在没有索引的情况下尝试不记得插入速度更快。索引是异步的。
  • @Ted 没错,使用 Key-Value API 不会触及索引。我认为 Michael 提到索引是因为他还测试了 N1QL 查询。
  • 定义了 GSI 后,插入 - 使用 kv-api 或其他方式 - 将导致新文档被索引(异步)。就像我说的那样,这似乎并没有导致插入时间增加——可能是因为测试是串行插入的,而我的机器有 6 个 CPU。
【解决方案2】:

我必须自己进行这样的比较才能进行全面调查,但有两点很突出。

  1. 您的并行执行并不是真正完全并行的。 async 方法在第一次等待之前同步运行,因此在第一次等待之前InsertAsync/GetAsync 中的所有代码在您添加任务时按顺序运行,而不是并行运行。

  2. CouchbaseNetClient 在后台进行一些惰性连接设置,而您在定时部分支付该费用。根据环境的不同,包括 SSL 协商等,这可能是一个显着的初始延迟。

您可以通过使用Task.Run 启动操作来潜在地解决第一个问题,但您可能需要预先设置默认线程池大小。

您可以通过在定时部分之前对存储桶(包括bucket.WaitUntilReadyAsync())执行至少一项操作来解决第二个问题。

60 秒的插入仍然看起来不正常。您正在使用多少个节点和什么持久性设置?

【讨论】:

  • 关于“并行执行”:是的,任务是按顺序创建的,但这只是任务的创建,而不是工作本身。我在上面写了创建任务,然后与等待每个呼叫相比,WhenAll 并没有什么不同。我再次使用WhenAll 和sequantial 重新运行它,等待每次调用。它的时间相同,但现在需要 100 秒...
  • 2:那么,如果我在定时部分之外插入一个,然后再去?这会补救懒惰的东西吗?
  • 我更新了上面的问题,添加了一些延迟加载补救措施,以及 Bucket 的 Durability 设置,设置为“none”。
  • 我用更新的代码重新运行了它(延迟加载补救措施)。现在花了 85 秒。还再次运行 Redis INSERT,耗时 8.5 秒。
  • 更新:如果我 1) 删除集合,重新添加集合,添加 PRIMARY 键; 2)重新运行代码以插入 100k,我再次得到 100 秒。好像有点不一样。 Compter 没有负载,除了 Win10、浏览器、Visual Studio 之外什么都没有运行。尽管如此,Redis 只需要 8 秒...我在上传测试代码的地方添加了 GitHub 存储库。