效能測試工具的 Coordinated Omission 問題

PingCAP發表於2018-06-03

作者:唐劉

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

什麼是 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 問題

相關文章