很早之前就看過 Gil 大神的一篇文章 Your Load Generator Is Probably Lying To You - Take The Red Pill And Find Out Why岛心,里面提到了性能測試工具 coordinated omission 的問題来破,但當時并沒有怎么在意。這幾天有人在我們自己的性能測試工具 go-ycsb 上面問了這個問題忘古,我才陡然發(fā)現(xiàn)徘禁,原來我們也有。
什么是 coordinated omission
首先來說說什么是 coordinated omission 問題髓堪,對于絕大多數(shù) benchmark 工具來說送朱,通常都是這樣的模型 - 啟動多個線程,每個線程依次的發(fā)送 request干旁,接受 response驶沼,然后繼續(xù)下一次的發(fā)送。然后我們記錄的 latency 通常就是 response time - request time争群。這個看起來很 make sense回怜,但實際是有問題的。
一個簡單的例子祭阀,當我們去 KFC 買炸雞(或者也可以來個高大上的 - 去星巴克買咖啡)鹉戚,然后我們排在了一個隊伍后面鲜戒,前面有 3 個人专控,開始 2 個人都好快,30 秒搞定遏餐,然后第三個墨跡了半天伦腐,花了 5 分鐘,然后到我了失都,30 秒搞定柏蘑。對于我來說,我絕對不會認為我的 latency 是 30 秒粹庞。而是會算上排隊的時間 2 x 30 + 300咳焚,加上服務時間 30 秒,所以我的總的時間耗時是 390 秒庞溜。這里不知道大家看到了區(qū)別了沒有革半,就是市面上大多數(shù)的性能測試工具,其實用的是服務時間流码,但并沒有算上等待時間又官。
再來看一個例子,假設我們需要我們的性能測試工具按照 10 ops/sec 的頻繁發(fā)送請求漫试,也就是我們希望每 100 ms 發(fā)送一個六敬,前面 9 個請求每個請求都是 50 us 就返回了,但第 10 個請求持續(xù)了 1 s驾荣,而后面的又是 50 us外构。我們可以明顯的看到普泡,在 1 s 那個時候,系統(tǒng)出現(xiàn)了卡頓审编,但這時候我們其實只有 1 個請求發(fā)上去劫哼,并沒有很好的對系統(tǒng)進行測試。
YCSB
對于第一個排隊的例子割笙,為了更好的計算 latency权烧,YCSB 引入了一個 intended time 的概念。也就是會記錄下操作實際的排隊時間伤溉。它使用了一個 local thread 變量般码,在 throttle 的時候,記錄:
private void throttleNanos(long startTimeNanos) {
//throttle the operations
if (_targetOpsPerMs > 0)
{
// delay until next tick
long deadline = startTimeNanos + _opsdone*_targetOpsTickNs;
sleepUntil(deadline);
_measurements.setIntendedStartTimeNs(deadline);
}
}
然后每次操作的時候乱顾,使用 intended time 計算排隊時間:
public Status read(String table, String key, Set<String> fields,
Map<String, ByteIterator> result) {
try (final TraceScope span = tracer.newScope(scopeStringRead)) {
long ist = measurements.getIntendedtartTimeNs();
long st = System.nanoTime();
Status res = db.read(table, key, fields, result);
long en = System.nanoTime();
measure("READ", res, ist, st, en);
measurements.reportStatus("READ", res);
return res;
}
}
需要注意板祝,只有 YCSB 開啟了 target,intended time 才有作用走净。
這也就是我當初在看 YCSB 代碼的時候券时,一直沒搞明白為啥會有兩種時間,而且也不知道 intended time 到底是什么鬼伏伯,后來重新回顧了 coordinated omission橘洞,才清楚。也就是說 YCSB 通過 intended time 來計算排隊時間说搅。
但其實 YCSB 還是沒解決上面說的第二個問題炸枣,如果系統(tǒng)真的出現(xiàn)了卡主,測試客戶端仍然會跟著卡主弄唧,因為是同步發(fā)送請求的适肠。在網(wǎng)上搜索了一下,看到了一篇 Paper Coordinated Omission in NoSQL Database Benchmarking候引,里面提到了將同步改成異步的方式侯养,也就是說,我每次的任務是一個 Future澄干,首先根據(jù) target 按照頻率發(fā) Future 就行逛揩,至于這個 Future 啥時候完成,后面再說傻寂。而且因為是異步的息尺,所以并不會卡主后面的請求。因為這個代碼是 Scala 的疾掰,我看 Java 已經(jīng)夠吃力了搂誉,所以也就懶得深入了。
Go YCSB
那么具體到 go-ycsb静檬,我們如何解決這個問題呢炭懊?我現(xiàn)在唯一能想到的就是利用 Go 的 goroutine并级,按照一定的頻率去生成 goroutine,執(zhí)行測試侮腹。當然 Go 自身也會有調度的開銷嘲碧,這里也需要排除。如果要測試的服務出現(xiàn)了卡頓父阻,就會導致大量的 Goroutine 沒法釋放愈涩,最終 OOM。雖然這樣子看起來比較殘暴加矛,但這才是符合預期的履婉。
這個只是一個想法,具體還沒做斟览,一個原因就是懶毁腿,另一個原因是不同于其他語言,Go 的 goroutine 其實天生就能開很多苛茂,所以通常我都是上千并發(fā)進行測試的已烤,假設我們有 1000 個并發(fā),按照 1 ms 一次的頻率妓羊,其實也就等同于每個 Goroutine 依次發(fā)送了胯究。當然,有總比沒有好侍瑟,如果你對這塊感興趣唐片,歡迎給我們提交 PR丙猬,或者給我發(fā)郵件詳細討論 tl@pingcap.com涨颜。