高速と噂される SQL リンターツール「sqruff」を実際に試してみた

高速と噂される SQL リンターツール「sqruff」を実際に試してみた

Clock Icon2024.12.03

Google Cloud データエンジニアのはんざわです。

皆さん、SQL のリンターを使っていますか?
過去のブログで SQL のリンターである sqlfluff を紹介しましたが、本ブログでは、sqlfluff よりも高速と噂される新しいツール「sqruff」を試してみたいと思います。

https://dev.classmethod.jp/articles/sqlfluff-2/

(余談ですが、この「sqruff」ってどう発音するんでしょうね、SQL + Ruff で「エスキューラフ」?それとも別の発音?)

検証環境

  • OS とバージョン
    • macOS 13.5.2
  • パッケージ管理システム
    • Homebrew 4.4.8
  • SQL の方言
    • BigQuery

sqruff とは?

https://github.com/quarylabs/sqruff

sqruff は、Rust で開発された SQL リンターおよびフォーマッターのオープンソースツールです。
似たようなツールとして sqlfluff がありますが、sqruff はその sqlfluff よりも高速だと噂されています。

投稿日時点では、以下の SQL の方言に対応しています。将来的には、さらに多くの方言がサポートされる予定とのことです。

  • ANSI SQL(デフォルト)
  • BigQuery
  • Athena
  • Clickhouse
  • DuckDB
  • PostgreSQL
  • Snowflake
  • SparkSql
  • SQLite
  • Trino

ローカル環境で触ってみる

本ブログでは、ローカル環境で sqruff を実際に試してみます。

インストール

brew install コマンドを使って sqruff をインストールします。以下のコマンドを実行します。

$ brew install quarylabs/quary/sqruff

インストール後、バージョンの確認を行い、正常にインストールされているか確認します。

$ sqruff --version
> sqruff 0.20.2

機能の概要

sqruff には、主に以下の 2 つの機能があります。

  • lint:SQL を読み込み、事前に定義されたルールに沿って問題点を指摘します。
  • fix:問題点を指摘するだけでなく、SQL クエリを修正します。

これらの動作は sqlfluff とほとんど同じため、すでに sqlfluff を使ったことがある方には馴染みやすいツールだと思います。

sqruff コマンドの詳細については、以下のドキュメントを参考にしてください。

本ブログでは、lint の機能を試してみます。

ルールファイルの作成

まずは、sqruff のルールファイルを作成します。

ルール設定は .sqruff ファイルで行い、このファイルを sqruff コマンドを実行するディレクトリに配置します。

sqlfluff を使ったことがある方は、sqruff のルールファイルが sqlfluff の .sqlfluff ファイルと非常に似ているため、ほぼ同じ感覚で設定できると思います。

以下は、例として作成した .sqruff ファイルです。この設定では、SQL 方言を BigQuery に指定し、AM01 と AM02 を除くすべてのルールを有効にしています。

.sqruff
[sqruff]
dialect = bigquery
rules = all
exclude_rules = AM01,AM02

[sqruff:indentation]
indent_unit = space
tab_space_size = 4

ルール名(例:AM01, AM02)や各ルールの詳細については、以下のドキュメントを参考にしてください。

また、ルールファイルで設定できる項目(例:dialect, rules など)の一覧については、以下のドキュメントを参考にしてください。

動かしてみる

次に、フォーマットが乱れているサンプルの SQL ファイルを用意します。

sample.sql
with temp AS (
    SELECT    *
    FROM sample.test
)

selecT id,
hoge,
  ifnull(fuga, '')
FROM
  temp t

.sqruff ファイルが配置されているディレクトリで、以下のコマンドを実行します。

$ sqruff lint sql/sample.sql

結果は以下の通りです。

== [sql/sample.sql] FAIL
L:   1 | P:  11 | CP01 | Keywords must be consistently lower case.
                       | [capitalisation.keywords]
L:   2 | P:   5 | CP01 | Keywords must be consistently lower case.
                       | [capitalisation.keywords]
L:   2 | P:  11 | LT01 | Expected only single space before "*". Found "    ".
                       | [layout.spacing]
L:   3 | P:   5 | CP01 | Keywords must be consistently lower case.
                       | [capitalisation.keywords]
L:   6 | P:   1 | CP01 | Keywords must be consistently lower case.
                       | [capitalisation.keywords]
L:   6 | P:   1 | LT09 | Select targets should be on a new line unless there is
                       | only one select target.
                       | [layout.select_targets]
L:   6 | P:   7 | LT02 | Expected line break and indent of 4 spaces before "id".
                       | [layout.indent]
L:   7 | P:   1 | LT02 | Expected indent of 4 spaces
                       | [layout.indent]
L:   8 | P:   1 | LT02 | Line should not be indented.
                       | [layout.indent]
L:   8 | P:   3 | AL03 | Column expression without alias. Use explicit `AS`
                       | clause. [aliasing.expression]
L:   8 | P:   3 | CV02 | Use 'COALESCE' instead of 'IFNULL'.
                       | [convention.coalesce]
L:   9 | P:   1 | CP01 | Keywords must be consistently lower case.
                       | [capitalisation.keywords]
L:  10 | P:   1 | LT02 | Expected indent of 4 spaces.
                       | [layout.indent]
L:  10 | P:   8 | AL01 | Implicit/explicit aliasing of table.
                       | [aliasing.table]
L:  10 | P:   8 | AL05 | Alias 't' is never used in SELECT statement.
                       | [aliasing.unused]
The linter processed 1 file(s).
All Finished 📜 🎉

結果を見ると、ルール違反の箇所がリストアップされています。

L は行番号、P は文字位置を表しています。この例では、次のように修正することでインデントのチェックをパスできます。

sample.sql
with temp as (
    select *
    from
        sample.test
)

select
    t.id,
    t.hoge,
    coalesce(t.fuga, '') as fuga
from
    temp as t

再度、lint を実行すると、問題が解消されていることが確認できます。

$ sqruff lint sql/sample.sql

The linter processed 1 file(s).
All Finished 📜 🎉

このように、sqruff は sqlfluff と同じ感覚で簡単に利用できます。

おまけ:sqlfluff と速度を比較してみる

https://www.quary.dev/blog/sqruff-launch

上記の公式ブログによると、sqruff は sqlfluff と比較して高速であると紹介されています。
実際にどれほどの差があるのか、行数の異なる SQL ファイルを用意して検証してみました。

検証の環境

検証に使用したバージョンは以下の通りです。

$ sqlfluff --version    
> sqlfluff, version 3.2.5

$ sqruff --version                
> sqruff 0.20.2

また、.sqlfluff.sqruff のルールファイルは統一しています。

.sqlfluff
[sqlfluff]
dialect = bigquery
rules = all

[sqlfluff:indentation]
indent_unit = space
tab_space_size = 4
.sqruff
[sqruff]
dialect = bigquery
rules = all

[sqruff:indentation]
indent_unit = space
tab_space_size = 4

各リンターの速度は、以下のコマンドで計測しました。
それぞれのコマンドを 3 回ずつ実行し、速度を比較してみたいと思います。

# sqlfluff lint
start=$(gdate +%s.%N); sqlfluff lint sql/; end=$(gdate +%s.%N); echo "Execution time: $(printf "%.2f" $(echo "$end - $start" | bc)) seconds"

# sqruff lint
start=$(gdate +%s.%N); sqruff lint sql/; end=$(gdate +%s.%N); echo "Execution time: $(printf "%.2f" $(echo "$end - $start" | bc)) seconds"

検証の結果

  • 行数小さいクエリ

まずは、行数が小さいクエリで試してみます。
行数が 25 で文字数が 636 のファイルを 10 個用意しました。

$ find ./short_sql -type f -exec wc -l -m {} +

 25     636 ./short_sql/sample_5.sql
 25     636 ./short_sql/sample_4.sql
 25     636 ./short_sql/sample_6.sql
 25     636 ./short_sql/sample_7.sql
 25     636 ./short_sql/sample_3.sql
 25     636 ./short_sql/sample_2.sql
 25     636 ./short_sql/sample_1.sql
 25     636 ./short_sql/sample_10.sql
 25     636 ./short_sql/sample_9.sql
 25     636 ./short_sql/sample_8.sql
250    6360 total

3 回実行した結果は以下の通りです。sqruff の方が sqlfluff と比べて約 10 倍高速であることが確認できました。

sqlfluff sqruff
1 回目 0.92 0.12
2 回目 0.92 0.10
3 回目 0.93 0.09
  • 行数が大きいクエリ

次に、行数が大きいクエリで試してみます。
行数が 180 で文字数が 8460 のファイルを 10 個用意しました。

$ find ./long_sql -type f -exec wc -l -m {} +

 180    8460 ./long_sql/sample_5.sql
 180    8460 ./long_sql/sample_4.sql
 180    8460 ./long_sql/sample_6.sql
 180    8460 ./long_sql/sample_7.sql
 180    8460 ./long_sql/sample_3.sql
 180    8460 ./long_sql/sample_2.sql
 180    8460 ./long_sql/sample_1.sql
 180    8460 ./long_sql/sample_10.sql
 180    8460 ./long_sql/sample_9.sql
 180    8460 ./long_sql/sample_8.sql
1800   84600 total

3 回実行した結果は以下の通りです。行数が小さいクエリで見られたほどの差はなく、sqlfluff と sqruff の速度は比較的近い結果となりました。

sqlfluff sqruff
1 回目 4.21 3.55
2 回目 4.19 3.72
3 回目 4.30 3.70

考察

以下の Issue にも報告されているように、ファイルの行数が増えると sqruff のパフォーマンスが指数関数的に低下する現象が確認されています。さらに、SQL 方言が BigQuery の場合、他の方言よりもパフォーマンスが悪化する傾向があるようです。

https://github.com/quarylabs/sqruff/issues/939

プロジェクトによって SQL ファイルの行数や規模は異なると思いますが、今回の検証では以下のような特徴が確認できました。

  • sqruff は、小さいクエリファイルを多数処理する場合に特に高速
  • sqlfluff は、大規模なクエリファイルを安定して処理できる点が魅力

この検証結果は、ローカル環境で SQL の方言を BigQuery に設定した場合のものです。例えば、GitHub Actions(GHA)などの CI 環境や、他の SQL 方言を使用する場合には、異なる結果となる可能性があります。

これらの条件によってパフォーマンスがどう変化するかは、別途検証が必要です。

感想

今回のブログでは、sqruff を実際に触ってみました。

sqlfluff は、すでにある程度成熟したツールとして広く利用されているように思います。
一方で、sqruff はまだ発展途上のツールですが、その高速性は非常に魅力的で、これからさらに成長していく可能性があると感じています。

https://github.com/quarylabs/sqruff/issues/331

また、投稿日時点で dbt の方言はサポートされていませんが、上記の Issue にもある通り、今後対応していく動きもあります。これからのアップデートにも目が離せませんね。

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.