性能測試工具的 Coordinated Omission 問題

性能測試工具的 Coordinated Omission 問題

來自專欄 TiDB 的後花園

作者:唐劉

很早之前就看過 Gil 大神的一篇文章《Your Load Generator Is Probably Lying To You - Take The Red Pill And Find Out Why》,裡面提到了性能測試工具 coordinated omission 的問題,但當時並沒有怎麼在意。這幾天有人在我們自己的性能測試工具 go-ycsb (github.com/pingcap/go-y)上面問了這個問題,我才陡然發現,原來我們也有。

什麼是 coordinated omission

首先來說說什麼是 coordinated omission。對於絕大多數 benchmark 工具來說,通常都是這樣的模型——啟動多個線程,每個線程依次的發送 request,接受 response,然後繼續下一次的發送。我們記錄的 latency 通常就是 response time - request time。這個看起來很 make sense,但實際是有問題的。

一個簡單的例子,當我們去 KFC 買炸雞,然後我們排在了一個隊伍後面,前面有 3 個人,開始 2 個人都好快,30 秒搞定,然後第三個墨跡了半天,花了 5 分鐘,然後到我了,30 秒搞定。對於我來說,我絕對不會認為我的 latency 是 30 秒,而是會算上排隊的時間 2 x 30 + 300,加上服務時間 30 秒,所以我的總的時間耗時是 390 秒。這裡不知道大家看到了區別了沒有,就是市面上大多數的性能測試工具,其實用的是服務時間,但並沒有算上等待時間。

再來看一個例子,假設我們需要性能測試工具按照 10 ops/sec 的頻繁發送請求,也就是希望每 100 ms 發送一個。前面 9 個請求,每個都是 50 us 就返回了,但第 10 個請求持續了 1 s,而後面的又是 50 us。可以明顯地看到,在 1 s 那裡,系統出現了卡頓,但這時候其實只有 1 個請求發上去,並沒有很好地對系統進行測試。

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 還是沒解決上面說的第二個問題,如果系統真的出現了卡主,測試客戶端仍然會跟著卡主,因為是同步發送請求的。在網上搜索了一下,看到了一篇 Paper《Coordinated Omission in NoSQL Database Benchmarking》,裡面提到了將同步改成非同步的方式,也就是說,每次的任務是一個 Future,首先根據 target 按照頻率發 Future 就行,至於這個 Future 什麼時候完成,後面再說。而且因為是非同步的,所以並不會卡主後面的請求。

Go YCSB

那麼具體到 go-ycsb,我們如何解決這個問題呢?我現在唯一能想到的就是利用 Go 的 goroutine,按照一定的頻率去生成 goroutine,執行測試。當然 Go 自身也會有調度的開銷,這裡也需要排除。如果要測試的服務出現了卡頓,就會導致大量的 goroutine 沒法釋放,最終 OOM。雖然這樣子看起來比較殘暴,但這才是符合預期的。

這個只是一個想法,具體還沒做。一個原因是不同於其他語言,Go 的 goroutine 其實天生就能開很多,所以通常我都是上千並發進行測試的,假設我們有 1000 個並發,按照 1 ms 一次的頻率,其實也就等同於每個 goroutine 依次發送了。當然,有總比沒有好,如果你對這塊感興趣,歡迎給我們提交 PR,或者給我發郵件詳細討論 tl@pingcap.com。

原文:性能測試工具的 Coordinated Omission 問題

推薦閱讀:

TiDB 源碼閱讀系列文章(七)基於規則的優化
TiDB會不會出現「加拉帕戈斯」?
關於MongoDB安全事件的一些思考
TiDB 2.0 GA Release Notes
TiDB RC1 Release

TAG:性能測試 | TiDB | NewSQL |