Presto Verifier

Presto Verifier は、クエリを実行して正確性を検証するためのツールです。新しい Presto バージョンが正しいクエリ結果を生成するかどうかをテストしたり、Presto クエリペアが同じセマンティクスを持つかどうかをテストするために使用できます。

各 Presto リリース時に、Verifier を実行して、正確性の回帰がないことを確認します。

Verifier の使用

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://localhost: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

Verifier は、設定 running-mode を設定することで、query-bank モードまたは control-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 に名前を変更します。Verifier を実行するには

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

query-bank モードで実行する前に、スナップショットを保存する必要があります。設定を追加します。

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

Verifier を実行すると、スナップショットが verifier_snapshots テーブルに保存されます。

query-bank モードで実行するには、save-snapshot=false に設定するか、削除します。

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

Verifier プロシージャ

次の手順では、Verifier のワークフローをまとめます。

  • ソースクエリのインポート
    • MySQL テーブルからソースクエリのリスト(設定を含むクエリペア)を読み取ります。

  • クエリの事前処理とフィルタリング
    • 各クエリのカタログ、スキーマ、ユーザー名、パスワードに上書きを適用します。

    • ホワイトリストとブラックリストに従ってクエリをフィルタリングします。ブラックリストの前にホワイトリストが適用されます。

    • 無効な構文を持つクエリを除外します。

    • 検証でサポートされていないクエリを除外します。SelectInsertCreateTableAsSelectcreate tablecreate view がサポートされています。

  • クエリのリライト
    • 本番データが変更されないように、実行前にクエリを書き換えます。

    • Select クエリを CreateTableAsSelect に書き換えます。
      • 列名は、LIMIT 0 を使用して Select クエリを実行することで決定されます。

      • 名前のない列には人工的な名前が使用されます。

    • Insert クエリと CreateTableAsSelect クエリは、テーブル名が置き換えられるように書き換えます。
      • Insert クエリに必要なテーブルを作成するための設定クエリを構築します。

    • 設定が設定されている場合、nondeterministic-function-substitutes に従って関数呼び出しを書き換えます。

  • クエリの実行
    • 概念的には、Verifier は制御クラスタとテストクラスタで構成されています。ただし、特定のテストでは、同じ Presto クラスタを指す場合があります。Query-bank モードでは、制御クラスタはスキップされ、代わりに保存されたスナップショットが使用されます。

    • 各ソースクエリについて、次のクエリを順番に実行します。
      • 制御設定クエリ

      • 制御クエリ

      • テスト設定クエリ

      • テストクエリ

      • 制御とテストのティアダウンクエリ

    • クエリはタイムアウトと再試行の対象となります。
      • クラスタ接続障害と一時的な Presto 障害は再試行されます。

      • クエリの再試行により、信頼性の問題が隠される可能性があるため、Verifier は再試行を含む発生したすべての Presto クエリ障害を記録します。

    • クエリの失敗の中には、クエリ中にパーティションが削除されたり、テーブルが削除されたりするなど、自動的に再検証のために送信されるものもあります。

    • 障害解決 を参照して、クエリの失敗を自動的に解決してください。

  • 結果の比較
    • SelectInsertCreateTableAsSelect クエリの場合、結果は一時テーブルに書き込まれます。

    • 制御とテストの両方に対してチェックサムクエリを構築して実行します。query-bank モードで実行している場合、制御チェックサムは mysql データベースに保存されているスナップショットから復元されます。

    • 設定 save-snapshottrue に設定されている場合、制御チェックサムは mysql データベースに保存されます。

    • 制御結果テーブルとテスト結果テーブルのテーブルスキーマと行数が同じであることを検証します。

    • 各列のチェックサムが一致することを検証します。さまざまな列タイプの特別な処理については、列チェックサム を参照してください。

    • 非決定論的クエリの処理については、決定性 を参照してください。

  • 結果の出力
    • 検証の結果は、JSON または人間が読めるテキストとしてエクスポートできます。

列チェックサム

制御/テストクエリの各列について、チェックサムクエリで 1 つ以上の列が生成されます。

  • 浮動小数点列
    • DOUBLE 列と REAL 列の場合、検証のために 4 つの列が生成されます。
      • 列の有限値の合計

      • NAN の列数

      • 正の無限大の列数

      • 負の無限大の列数

    • NAN の数、正の無限大の数、負の無限大の数が一致するかどうかを確認します。

    • 制御合計とテスト合計の NULL 値を確認します。

    • 制御平均またはテスト平均のいずれかが 0 に非常に近い場合、両方が 0 に近いかどうかを確認します。

    • 制御合計とテスト合計の相対誤差を確認します。

  • VARCHAR 列
    • VARCHAR 列の場合、checksum() を使用して、検証のために単純なチェックサム列が生成されます。

    • validate-string-as-double が設定されている場合、以下の 7 つの列が生成されます。すべての値を DOUBLE にキャストする前と後で NULL の数が等しい場合、浮動小数点検証を適用します。そうでない場合は、単純なチェックサムが一致するかどうかを確認します。
      • チェックサム

      • NULL 値の数

      • すべての値を DOUBLE にキャストした後の NULL 値の数

      すべての値を DOUBLE にキャストした後
      • 有限値の合計

      • NAN の値の数

      • 正の無限大の値の数

      • 負の無限大の値の数

  • 配列列
    • array(E) の配列列 arr の場合、検証のために 3 つの列が生成されます。
      • カーディナリティの合計

      • カーディナリティのチェックサム

      • 配列チェックサム
        • E が順序付けできない場合、配列チェックサムは checksum(arr) です。

        • E が順序付け可能であれば、配列チェックサムは coalesce(checksum(try(array_sort(arr))), checksum(arr)) となります。

    • use-error-margin-for-floating-point-arrays が設定されており、E が DOUBLE または REAL の場合、代わりに以下の6つの列が生成されます。基数の合計と基数のチェックサムが一致するかどうかを確認します。残りの結果には浮動小数点検証を適用します。
      • カーディナリティの合計

      • カーディナリティのチェックサム

      • すべての配列値の有限要素の合計

      • すべての配列値の NAN 要素の数

      • すべての配列値の正の無限大要素の数

      • すべての配列値の負の無限大要素の数

    • validate-string-as-double が設定されており、E が VARCHAR の場合、代わりに以下の9つの列が生成されます。基数の合計とチェックサムが一致するかどうかを確認します。すべての配列要素を DOUBLE にキャストする前後の NULL の数が等しい場合、浮動小数点検証を適用します。そうでない場合、配列チェックサムが一致するかどうかを確認します。
      • カーディナリティの合計

      • カーディナリティのチェックサム

      • 配列チェックサム checksum(array_sort(arr))

      • すべての配列値の NULL 要素の数

      • すべての配列要素を DOUBLE にキャストした後の NULL 要素の数

      すべての配列要素を DOUBLE にキャストした後
      • すべての配列値の有限要素の合計

      • すべての配列値の NAN 要素の数

      • すべての配列値の正の無限大要素の数

      • すべての配列値の負の無限大要素の数

  • マップ列
    • map(K, V) 型のマップ列の場合、検証のために4つの列が生成されます。
      • カーディナリティの合計

      • マップのチェックサム

      • キーセットの配列チェックサム

      • 値セットの配列チェックサム

    • validate-string-as-double が設定されており、K が VARCHAR の場合、さらに6つの列が生成されます。
      • すべてのキーセットの NULL 要素の数

      • すべてのマップキーを DOUBLE にキャストした後のキーセットの NULL 要素の数

      すべてのマップキーを DOUBLE にキャストした後
      • すべてのキーセットの有限要素の合計

      • すべてのキーセットの NAN 要素の数

      • すべてのキーセットの正の無限大要素の数

      • すべてのキーセットの負の無限大要素の数

    • validate-string-as-double が設定されており、V が VARCHAR の場合、さらに6つの列が生成されます。
      • すべての値セットの 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 と同様のコマンドラインラッパーを作成します。

構成リファレンス

一般構成

名前

説明

ホワイトリスト

検証するクエリの名前をコンマ区切りで指定します。

ブラックリスト

スイートから除外するクエリの名前をコンマ区切りで指定します。blacklistwhitelist の後に適用されます。

ソースクエリサプライヤー

ソースクエリサプライヤーの名前。 mysql をサポートします。

source-query.table-name

Verifierクエリを保持するテーブルの名前。 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

結果が一致しない場合にクリーンアップクエリを実行するかどうか。

verification-resubmission.limit

検証のためにソースクエリを再送信できる回数の制限。

running-mode

Verifierをquery-bankモードで実行するには、query-bankに設定します。query-bankcontrol-testをサポートします。デフォルトはcontrol-testです。

save-snapshot

trueに設定すると、チェックサムをmysqlデータベースに保存します。

extended-verification

trueに設定すると、書き込まれたテーブルに対して拡張テーブルレイアウト検証を実行します。InsertクエリとCreateTableAsSelectクエリにのみ適用されます。挿入されたテーブルがパーティション化されている場合は、各パーティションのデータチェックサムを検証します。挿入されたテーブルがバケット化されている場合は、各バケットのデータチェックサムを検証します。

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)/

関数置換に適用する必要がある場合は、関数引数を識別子として宣言します。

クエリ上書き設定

次の設定は、検証開始前にクエリメタデータの変更の動作を制御します。接頭辞controltestに置き換えたテストクエリについても、同様の設定が可能です。

名前

説明

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

すべてのクエリに適用されるセッションプロパティ。

クエリ実行設定

次の設定は、コントロールクラスタでのクエリ実行の動作を制御します。接頭辞controltestに置き換えたテストクラスタについても、同様の設定が可能です。

名前

説明

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

決定性分析で生成されたテーブルに対してクリーンアップクエリを実行するかどうか。

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

Verifierの制限による失敗に対する障害解決を有効にするかどうか。

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

カンマ区切りの関数リスト。クエリがこのリスト内の関数を使用している場合、不一致を解決します。