一次失败的Couchbase Troubleshooting

type
status
date
slug
summary
tags
category
icon
password

前情提要

大约1年办前,我在某咖啡项目上做一些后端业务,数据库是用的couchbase。(谁选的,为啥选的,一概不知。
在使用过程中发现,并发高了之后,Couchbase在平均响应时间/95%时间依旧非常美观的情况下,会夹杂着一些请求超时,并发越高越明显。
先通过非Java版本(就是Go)的Couchbase SDK做同样的测试,得出问题应该是出在Client端,而非Server端。(这是一个坑
根据Couchbase SDK的源码梳理了一下整个查询的流程。原图
notion image
 

尝试回答之前遇到的一些问题

  1. 为什么会超时?
    1. 在couchbaseNode那一块逻辑的时候,没有足够的Endpoint,会直接进入重试,重试直至超时。
  1. 怎么解决Endpoint不够的问题?
    1. 最直接的办法就是调大Endpoint的连接数,Spring Data默认1,官方推荐6-10,在实际项目中甚至都被调到30-50了。数值调大之后确实缓解了出错情况。
  1. 为什么Endpoint调这么大了,依然会出错?
    1. 因为没来得及释放Endpoint,负责释放Endpoint的IO线程被Block住了。后面的业务逻辑实际上是在IO线程上执行。
  1. 图上publish response给客户端,是在Computation线程池,为什么会出现IO线程的问题?
    1. 按照设计,业务确实应该都是跑在Computation线程池上(或后期切换到其他线程),IO线程对SDK的使用者来说,应该是不可见的。
      问题出在这个代码块:
      queryN1QL(query)之后的业务都是跑在Computation上的,但是flatMap可能会带来线程重新切换回IO的问题。因为asyncN1qlQueryResult.rows()本身是一个跑在IO线程上的热源。
      在QueryEndpoint拿到response的时候,会把publish和decode同时进行,publish会放在Computation去做,decode依然会放到IO
      当代码执行到这一块的时候,如果decode完成,就会继续在Computation进行执行。
      如果decode没有执行完成,后面的一系列操作,包括业务逻辑,就都会在IO上进行操作,在业务操作完成之前,IO线程都无法释放Endpoint。
       

如何改进?

  1. 调换decode response和release endpoint的顺序,理论上拿到返回,就不应该再占用连接,就算后面decode导致io线程泄露,也不会影响endpoint释放。
  1. 在flatMap拿到rows的时候,强制用observeOn 来切换成其他线程。
 

设计一个复现场景

 

一些额外的发现

按道理说,写到这里应该是可以装一波了,然而,经过测试,发现上面的一些改动压根没办法解决空负载压测的报错。(也许能解决真实业务场景中的错误情况
因为整个webflux+couchbase的模型就是一个不带背压的生产消费模型,每个endpoint每次处理请求的时间基本是固定的。(1s/单个endpoint处理单条请求时间)*endpoint数量,就是能正常处理的tps。
但是这个tps和webflux的tps是脱节的,couchbase client已经满负载了,webflux还在接受请求放入队列,最终结果就是超时报错。
这样的结果确实是有点反直觉的,看数据库,发现响应时间都挺好看的,但是API却有大量的报错,给人一种数据库牛逼,客户端拖后腿的感觉。
当把endpoint调到足够大之后,Couchbase 服务端开始出现“响应时间开始明显下降”,整体的有效TPS不升反降。
所以最终的一系列结论如下:
  • Endpoint的设置是为了保护Couchbase Server,让其性能跑在一个甜点区。数据库性能下降和客户端排队你两个总得选一个。
  • Couchbase跑N1QL就是不行。
 
之前Go测试的坑:Go没有endpoint的概念,有点那种来多少就连多少的味道,在Couchbase没有达到极限之前,确实不会报错,TPS也会更好看。但一旦压测力度上去了,Go SDK的数据惨不忍睹。
 
 
 
Loading...

© XGFan 2012-2025