Presto 驗證器

Presto 驗證器是一個執行查詢並驗證正確性的工具。它可以用來測試新的 Presto 版本是否產生正確的查詢結果,或是測試成對的 Presto 查詢是否具有相同的語意。

在每個 Presto 版本發布期間,都會執行驗證器,以確保沒有正確性的回歸。

使用驗證器

在 MySQL 資料庫中,建立以下資料表並載入您想要執行的查詢

CREATE TABLE verifier_queries (
    id int(11) unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
    suite varchar(256) NOT NULL,
    name varchar(256) DEFAULT NULL,
    control_catalog varchar(256) NOT NULL,
    control_schema varchar(256) NOT NULL,
    control_query text NOT NULL,
    control_username varchar(256) DEFAULT NULL,
    control_password varchar(256) DEFAULT NULL,
    control_session_properties text DEFAULT NULL,
    test_catalog varchar(256) NOT NULL,
    test_schema varchar(256) NOT NULL,
    test_query text NOT NULL,
    test_username varchar(256) DEFAULT NULL,
    test_password varchar(256) DEFAULT NULL,
    test_session_properties text DEFAULT NULL)

接下來,建立一個 config.properties 檔案

source-query.suites=suite
source-query.database=jdbc:mysql://127.0.0.1:3306/mydb?user=my_username&password=my_password
control.hosts=127.0.0.1
control.http-port=8080
control.jdbc-port=8080
control.application-name=verifier-test
test.hosts=127.0.0.1
test.http-port=8081
test.jdbc-port=8081
test.application-name=verifier-test
test-id=1

驗證器可以透過設定組態 running-mode,在 query-bankcontrol-test 模式下執行。

  • control-test:這是預設模式。控制查詢和測試查詢都會被執行,並且它們的結果檢查碼會被比較。

  • query-bank:在此模式下,控制查詢會被跳過,並且比較會在儲存的快照結果和測試結果之間進行。

建立 verifier_snapshots 資料表

CREATE TABLE verifier_snapshots (
    id int(11) unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
    suite varchar(256) NOT NULL,
    name varchar(256) NOT NULL DEFAULT '.',
    is_explain BOOLEAN NOT NULL DEFAULT false,
    snapshot json NOT NULL,
    updated_at datetime NOT NULL DEFAULT now(),
    UNIQUE(suite, name, is_explain));

下載 presto-verifier-0.289-executable.jar 並將其重新命名為 verifier.jar。要執行驗證器

java -Xmx1G -jar verifier.jar verify config.properties

query-bank 模式下執行之前,必須儲存快照。新增組態

running-mode=query-bank
save-snapshot=true

執行驗證器,快照將會儲存到資料表 verifier_snapshots

若要以 query-bank 模式執行,請設定 save-snapshot=false 或直接刪除它

running-mode=query-bank
#save-snapshot=true

驗證器程序

以下步驟總結了驗證器的工作流程。

  • 匯入來源查詢
    • 從 MySQL 資料表讀取來源查詢(具有組態的查詢配對)的清單。

  • 查詢前處理和篩選
    • 將覆寫套用至每個查詢的目錄、綱要、使用者名稱和密碼。

    • 根據允許清單和封鎖清單篩選查詢。允許清單會在封鎖清單之前套用。

    • 篩選掉具有無效語法的查詢。

    • 篩選掉不支援驗證的查詢。SelectInsertCreateTableAsSelectcreate tablecreate view 都受到支援。

  • 查詢重寫
    • 在執行之前重寫查詢,以確保生產資料不會被修改。

    • Select 查詢重寫為 CreateTableAsSelect
      • 欄位名稱是透過執行帶有 LIMIT 0Select 查詢來決定。

      • 人工名稱會用於未命名的欄位。

    • 重寫 InsertCreateTableAsSelect 查詢,以使其資料表名稱被取代。
      • 建構一個設定查詢,以建立 Insert 查詢所需的資料表。

    • 如果設定組態,則根據 nondeterministic-function-substitutes 重寫函式呼叫。

  • 查詢執行
    • 從概念上來說,驗證器會組態有控制叢集和測試叢集。然而,對於某些測試,它們可能會指向相同的 Presto 叢集。在 Query-bank 模式下,控制叢集會被跳過,並改用已儲存的快照。

    • 對於每個來源查詢,依序執行以下查詢。
      • 控制設定查詢

      • 控制查詢

      • 測試設定查詢

      • 測試查詢

      • 控制和測試拆解查詢

    • 查詢會受到逾時和重試的限制。
      • 叢集連線失敗和暫時性 Presto 失敗會被重試。

      • 查詢重試可能會隱藏可靠性問題,因此驗證器會記錄所有發生的 Presto 查詢失敗,包括重試。

    • 某些查詢失敗會自動提交以重新驗證,例如在查詢期間分割區或資料表被捨棄。

    • 請參閱 失敗處理 以取得自動解決查詢失敗的方法。

  • 結果比較
    • 對於 SelectInsertCreateTableAsSelect 查詢,結果會寫入暫時性資料表。

    • 建構並執行控制和測試的檢查碼查詢。如果在 query-bank 模式下執行,控制檢查碼將會從儲存在 mysql 資料庫中的快照還原。

    • 如果將組態 save-snapshot 設定為 true,控制檢查碼將會儲存到 mysql 資料庫。

    • 驗證控制和測試結果資料表的資料表綱要和列數是否相同。

    • 驗證每個欄位的檢查碼是否符合。請參閱 欄位檢查碼,以取得不同欄位類型的特殊處理。

    • 請參閱 決定性 以取得處理非決定性查詢的方法。

  • 發出結果
    • 驗證結果可以匯出為 JSON 或人類可讀的文字。

欄位檢查碼

對於控制/測試查詢中的每個欄位,檢查碼查詢中會產生一個或多個欄位。

  • 浮點數欄位
    • 對於 DOUBLEREAL 欄位,會產生 4 個欄位以進行驗證
      • 欄位中有限值的總和

      • 欄位的 NAN 計數

      • 欄位的正無限大計數

      • 欄位的負無限大計數

    • 檢查 NAN 計數、正無限大計數和負無限大計數是否符合。

    • 檢查控制總和和測試總和的空值性。

    • 如果控制平均值或測試平均值非常接近 0,則檢查兩者是否都接近 0。

    • 檢查控制總和與測試總和之間的相對誤差。

  • VARCHAR 欄位
    • 對於 VARCHAR 欄位,會使用 checksum() 產生一個簡單的檢查碼欄位以進行驗證。

    • 如果設定 validate-string-as-double,則會產生以下七個欄位。如果在將所有值轉換為 DOUBLE 之後,NULL 計數相等,則套用浮點數驗證。否則,請檢查簡單的檢查碼是否符合。
      • 檢查碼

      • NULL 值的計數

      • 將所有值轉換為 DOUBLE 後,NULL 值的計數

      在將所有值轉換為 DOUBLE 之後
      • 有限值的總和

      • 值的 NAN 計數

      • 值的正無限大計數

      • 值的負無限大計數

  • 陣列欄位
    • 對於類型為 array(E) 的陣列欄位 arr,會產生三個欄位以進行驗證
      • 基數的總和

      • 基數的檢查碼

      • 陣列校驗和
        • 如果 E 不可排序,則陣列校驗和為 checksum(arr)

        • 如果 E 可排序,則陣列校驗和為 coalesce(checksum(try(array_sort(arr))), checksum(arr))

    • 如果設定了 use-error-margin-for-floating-point-arrays 且 E 為 DOUBLEREAL,則會產生以下六個欄位。檢查基數總和是否匹配,以及基數的校驗和是否匹配。將浮點數驗證應用於其餘結果。
      • 基數的總和

      • 基數的檢查碼

      • 所有陣列值的有限元素總和

      • 所有陣列值的 NAN 元素計數

      • 所有陣列值的正無限大元素計數

      • 所有陣列值的負無限大元素計數

    • 如果設定了 validate-string-as-double 且 E 為 VARCHAR,則會產生以下九個欄位。檢查基數的總和與校驗和是否匹配。如果在將所有陣列元素轉換為 DOUBLE 之前和之後,NULL 的計數相等,則應用浮點數驗證。否則,檢查陣列校驗和是否匹配。
      • 基數的總和

      • 基數的檢查碼

      • 陣列校驗和 checksum(array_sort(arr))

      • 所有陣列值的 NULL 元素計數

      • 將所有陣列元素轉換為 DOUBLE 後的 NULL 元素計數

      將所有陣列元素轉換為 DOUBLE
      • 所有陣列值的有限元素總和

      • 所有陣列值的 NAN 元素計數

      • 所有陣列值的正無限大元素計數

      • 所有陣列值的負無限大元素計數

  • 對應欄位
    • 對於類型為 map(K, V) 的對應欄位,會產生四個欄位進行驗證
      • 基數的總和

      • 對應的校驗和

      • 鍵集合的陣列校驗和

      • 值集合的陣列校驗和

    • 如果設定了 validate-string-as-double 且 K 為 VARCHAR,則會產生六個額外欄位
      • 所有鍵集合的 NULL 元素計數

      • 將所有對應鍵轉換為 DOUBLE 後,鍵集合的 NULL 元素計數

      將所有對應鍵轉換為 DOUBLE
      • 所有鍵集合的有限元素總和

      • 所有鍵集合的 NAN 元素計數

      • 所有鍵集合的正無限大元素計數

      • 所有鍵集合的負無限大元素計數

    • 如果設定了 validate-string-as-double 且 V 為 VARCHAR,則會產生六個額外欄位
      • 所有值集合的 NULL 元素計數

      • 將所有對應值轉換為 DOUBLE 後,值集合的 NULL 元素計數

      將所有對應值轉換為 DOUBLE
      • 所有值集合的有限元素總和

      • 所有值集合的 NAN 元素計數

      • 所有值集合的正無限大元素計數

      • 所有值集合的負無限大元素計數

  • 列的資料列
    • 根據欄位的類型遞迴計算資料列欄位的校驗和。

對於所有其他欄位類型,使用 checksum() 函數產生一個簡單的校驗和。

決定性

結果不匹配(資料列計數不匹配或欄位不匹配)可能是由非決定性的查詢功能引起的。為了避免錯誤警報,我們會對控制查詢執行決定性分析。如果發現查詢不具決定性,我們會跳過驗證,因為它無法提供洞察力。

決定性分析遵循以下步驟。如果在任何時候發現查詢不具決定性,分析將會結束。

  • 可以使用 determinism.non-deterministic-catalog 指定非決定性目錄。如果查詢引用這些目錄中的任何表格,則該查詢會被視為不具決定性。

  • 再次執行控制查詢,並將結果與初始控制查詢執行結果進行比較。

  • 如果查詢在頂層有 LIMIT n 子句,但沒有 ORDER BY 子句
    • 執行查詢以計算控制查詢在沒有 LIMIT 子句的情況下產生的資料列數量。

    • 如果產生的資料列計數大於 n,則將控制查詢視為不具決定性。

失敗解決

組態的差異,包括叢集大小,可能會導致查詢在控制叢集上成功,但在測試叢集上失敗。校驗和查詢也可能會失敗,這可能是由於 Presto 或 Presto Verifier 的限制所致。因此,我們允許 Verifier 自動解決某些查詢失敗。

  • EXCEEDED_GLOBAL_MEMORY_LIMIT:如果控制查詢使用的記憶體多於測試查詢,則會解決。

  • EXCEEDED_TIME_LIMIT:無條件解決。

  • TOO_MANY_HIVE_PARTITIONS:如果測試叢集沒有足夠的工作節點來確保分配給每個工作節點的分割區數量保持在限制內,則會解決。

  • COMPILER_ERRORGENERATED_BYTECODE_TOO_LARGE:如果控制校驗和查詢因這個錯誤而失敗,則會解決。如果控制查詢的欄位過多,則在某些情況下,產生的校驗和查詢可能會過大。

在結果不匹配的情況下,Verifier 可能會發出雜訊訊號,我們允許 Verifier 自動解決某些不匹配。

  • 結構化類型欄位:如果陣列元素或對應鍵/值包含浮點類型,則欄位校驗和不太可能匹配。
    • 對於陣列欄位,如果元素類型包含浮點類型且基數校驗和匹配,則會解決。

    • 對於對應欄位,當以下兩個條件都成立時,則會解決不匹配問題
      • 基數校驗和匹配。

      • 不包含浮點類型的鍵或值的校驗和匹配。

    • 僅當所有欄位都解決時,才解決測試案例。

  • 已解決的函式:在結果不匹配的情況下,如果查詢在

    指定的清單中使用函式,則會將測試案例標記為已解決。

說明模式

在說明模式中,Verifier 會檢查是否可以說明來源查詢,而不是檢查它們是否產生相同的結果。當可以說明控制查詢和測試查詢時,會將驗證標記為成功。

輸出事件中的 matchType 欄位可以用來指示控制執行和測試執行之間是否存在計畫差異。

對於非 DML 查詢,會跳過控制查詢和計畫比較。

擴充 Verifier

除了組態屬性之外,還可以擴充 Verifier 以進行進一步的行為變更。

AbstractVerifyCommand 顯示了可以擴充的元件。實作抽象類別,並建立類似於 PrestoVerifier 的命令列包裝器。

組態參考

一般組態

名稱

描述

白名單

一個以逗號分隔的清單,指定要驗證的套件中的查詢名稱。

黑名單

一個以逗號分隔的清單,指定要從套件中排除的查詢名稱。blacklist 會在 whitelist 之後套用。

來源查詢供應商

來源查詢供應商的名稱。支援 mysql

source-query.table-name

保存驗證器查詢的表格名稱。僅當 source-query-suppliermysql 時可用。

事件用戶端

一個以逗號分隔的清單,指定應在何處發出輸出事件。支援 jsonhuman-readable

json.log-file

JSON 事件的輸出檔案。如果未設定,則 JSON 事件會發送到 stdout

human-readable.log-file

人類可讀事件的輸出檔案。如果未設定,則人類可讀事件會發送到 stdout

control.table-prefix

要附加到控制目標表格的表格前置詞。

test.table-prefix

要附加到測試目標表格的表格前置詞。

control.reuse-table

如果為 true,則重複使用控制來源 Insert 和 CreateTableAsSelect 查詢的輸出表格。否則,執行控制來源查詢並寫入暫時表格。

test.reuse-table

如果為 true,則重複使用測試來源 Insert 和 CreateTableAsSelect 查詢的輸出表格。否則,執行測試來源查詢並寫入臨時表格。

test-id

附加到輸出事件的字串。

max-concurrency

同時驗證的最大數量。

suite-repetition

驗證套件的次數。

query-repetition

驗證來源查詢的次數。

relative-error-margin

浮點數欄位的控制總和與測試總和之間可容忍的最大相對誤差。

absolute-error-margin

低於此臨界值的浮點數平均值將被視為 0

run-teardown-on-result-mismatch

在結果不符的情況下是否執行 teardown 查詢。

verification-resubmission.limit

來源查詢重新提交以進行驗證的次數限制。

running-mode

設定為 query-bank 使驗證器以 query-bank 模式執行。支援 query-bankcontrol-test。預設為 control-test

save-snapshot

設定為 true 將檢查碼儲存到 mysql 資料庫。

extended-verification

設定為 true 以對已寫入的表格執行擴充的表格佈局驗證。僅適用於 InsertCreateTableAsSelect 查詢。如果插入的表格已分割,它將驗證每個分割區的資料檢查碼。如果插入的表格已分桶,它將驗證每個桶的資料檢查碼。

function-substitutes

函數替換的規格,格式為 /foo(c0,_)/bar(c0)/,/fred(c0,c1)/baz(qux(c1,c0))/,/foobar(c0)/if(qux(c1),bar(c0),baz(c1))/,...,其中 foo(c0, _) 將被 bar(c0) 取代,並將宣告的參數應用於對應的位置。

用逗號串連函數替換。

選擇一個與原始函數的回傳類型和引數類型相容的函數替換,以產生有效的來源查詢。例如,/array_agg(z)/array_sort(array_agg(z))/,/approx_percentile(x,y)/avg(x)/

如果需要將函數引數應用於函數替換,則將其宣告為識別碼。

查詢覆寫配置

以下配置控制開始驗證之前查詢元資料修改的行為。測試查詢也有對應的配置,將字首 control 替換為 test

名稱

描述

control.catalog-override

如果指定,則將此目錄套用至所有查詢。

control.schema-override

如果指定,則將此綱要套用至所有查詢。

control.username-override

如果指定,則將此使用者名稱套用至所有查詢。

control.password-override

如果指定,則將此密碼套用至所有查詢。

control.session-properties-override-strategy

支援 3 個值。NO_ACTION:使用每個查詢指定的會話屬性。OVERRIDE:將每個查詢的會話屬性與覆寫合併,以覆寫為主。SUBSTITUTE:每個查詢的會話屬性會被覆寫取代。

control.session-properties-override

要套用至所有查詢的會話屬性。

查詢執行配置

以下配置控制控制叢集上查詢執行的行為。測試叢集也有對應的配置,將字首 control 替換為 test

名稱

描述

control.hosts

以逗號分隔的控制叢集主機名稱或 IP 位址清單。

control.jdbc-port

控制叢集的 JDBC 連接埠。

control.http-host

控制叢集的 HTTP 連接埠。

control.jdbc-url-parameters

代表控制 JDBC 的其他 URL 參數的 JSON 對應。

control.query-timeout

控制查詢和測試查詢的執行時間限制。

control.metadata-timeout

DESC 查詢和 LIMIT 0 查詢的執行時間限制。

control.checksum-timeout

檢查碼查詢的執行時間限制。

control.application-name

要在 ClientInfo 中傳遞的 ApplicationName。可用於設定來源。

決定性分析器配置

名稱

描述

determinism.run-teardown

是否針對決定性分析中產生的表格執行 teardown 查詢。

determinism.max-analysis-runs

檢查控制查詢決定性的其他控制執行次數上限。

determinism.handle-limit-query

是否啟用針對具有最上層 LIMIT 子句的查詢的特殊處理。

determinism.non-deterministic-catalogs

以逗號分隔的非決定性目錄清單。參考這些目錄中的表格的查詢將被視為非決定性。

失敗解決配置

名稱

描述

exceeded-global-memory-limit.failure-resolver.enabled

是否為具有 EXCEEDED_GLOBAL_MEMORY_LIMIT 的測試查詢失敗啟用失敗解析器。

exceeded-time-limit.failure-resolver.enabled

是否為具有 EXCEEDED_TIME_LIMIT 的測試查詢失敗啟用失敗解析器。

verifier-limitation.failure-resolver.enabled

是否啟用由於驗證器限制而導致的失敗的失敗解析器。

too-many-open-partitions.failure-resolver.enabled

是否為具有 HIVE_TOO_MANY_OPEN_PARTITIONS 的測試查詢失敗啟用失敗解析器。

too-many-open-partitions.max-buckets-per-writer

在控制叢集和測試叢集上設定的每個寫入器的最大儲存桶計數。

too-many-open-partitions.cluster-size-expiration

快取測試叢集大小的時間限制。

structured-column.failure-resolver.enabled

是否為結構化類型欄位的欄位不符啟用失敗解析器。

ignored-functions.failure-resolver.enabled

是否啟用 IgnoredFunctions 結果不符失敗解析器。

ignored-functions.functions

以逗號分隔的函數清單。如果查詢使用清單中的任何函數,則會解決不符問題。