目次
はじめに
データ基盤チームの鹿野です。
データ基盤チームではソースデータのデータ変換処理、及びデータモデルの管理用途でData build toolのOSSリリース dbt Coreを利用しています。
本記事では、dbt Core 1.5へのアップグレード時に直面したエラーについて、その解決策、解決までのプロセス、そして得られた教訓を共有します。
本記事のメイン部分は前半・後半に分かれています。
前半は1〜2分で読める内容です。同様のエラーでお困りの方は、まず前半をご覧ください。
dbt CoreのEOLバージョンサポートについて
dbt CoreのEOLバージョンサポートはそのマイナーバージョンのリリース日から1年間と定められています。1
サポート期間が終了すると、セキュリティパッチとドキュメントの提供が終了します。
また新機能を利用できなくなるため、特別な理由がない限りは定期的なアップグレード対応が推奨されます。
dbt Coreをアップグレードするときの推奨事項
- アップグレード対象のバージョンは、現在のマイナーバージョンから1つまたは2つの範囲内に留めること
- これはOSSアップグレードに限らず、変更によるリスクを最小限に抑えるため
- アップグレードと関係のない修正は避けること
- 新機能の追加や既存の挙動変更でリファクタリングしたくなっても、それは後回しにすること
環境について
dbt CoreはAmazon Redshiftデータプラットフォームに接続することを前提としています。
dbt Coreバージョン | dbt Redshiftアダプターバージョン | 実行環境 |
---|---|---|
1.4.9 | 1.4.1 | コンテナ(Python 3.9 slimイメージベース) |
Github Actions ホステッドランナー(ubuntu-latest) |
[前半] エラーの解決
エラーの説明と補足
遭遇した複数のエラーの中から、特に解決に時間を要した1つを取り上げます。
dbt run実行中に発生したエラーで、スタックトレースは残っていないため文字情報のみです。
1 |
Connection reset by peer |
見覚えのある方も多いのではないでしょうか。
これはdbt Core固有のエラーではなく、ネットワークスタック(TCPレイヤー)に起因するエラーです。
以下に補足を記載します。
調査の過程で判明したのですが、このエラーはGithub Actionsホステッドランナー環境でのみ発生していました。
どうやって解決したか?
A. 実行環境をGithub Actionsホステッドランナーからセルフホステッドランナーへ移行しました。
具体的には、Github公式が提供するubuntu-latestランナーから、EKSクラスター上のm5.largeタイプのワーカーノード上のランナー(ポッド)へ移行することで解決しました。
補足で既に答えを示唆していますが、特定環境でのみ発生する問題だと判明したため、実行環境の変更で解決を図りました。
内容を深めるため、以降のセクションで補足説明を加えます。
Github Actions セルフホステッドランナー環境について
正式名称は self-hosted runner です。
弊社では、dbtモデルを安全に本番環境へ公開するためのCIをGithub Actionsで構築していました(以降、CIワークフローと呼びます)。
CIワークフローでは、Github公式が提供するubuntu-latestランナーを使用していましたが、これを自前で用意したランナーに切り替えました。
Github Actions セルフホステッドランナーの考慮事項
セルフホステッドランナー環境の構築自体は公式ドキュメントに従えば容易ですが、運用面では以下の懸念事項を考慮する必要があります。
- ランナーを実行するマシンをどうするか?
- マシンのメンテナンス
- マシンのセキュリティ担保
Githubホステッドランナーではこれらの懸念事項はGithub側が対応してくれますが、セルフホステッドランナーは名前の通り実行環境を自己管理する必要があります。
このセクションでは結論のみ記載しますが、Actions Runner Controller (ARC)の導入でこれらの課題を解決しました。
詳細なプロセスは後半で説明します。
[後半] 解決に至るまでのプロセス
前半は前座で、後半がメインです。
ただし、こちらは主に経緯を詳しく説明する内容となるため、文章量が多くなり、より詳細な記述となります。
ご理解いただいた方からお進みください。
以下に、解決までのプロセスを時系列順に記載します。
各ステップについて補足を加え、最後に得られた知見をまとめます。
解決までのプロセスを時系列順に列挙しました。
それぞれのセクションで補足をし、最後に気づきをまとめます。
- dbt Coreを1.5にアップグレード
- dbt run でエラー発生
- Redshiftクラスターのモニタリングデータ、およびエラーログの確認
- dbt-redshiftアダプター設定の見直し
- RedshiftアダプターのCHANGELOG確認
- 類似の事象探索・調査
- 今後の進め方を決める
- Github Actions セルフホステッドランナーでCIワークフロー検証
- Github Actions セルフホステッドランナー環境の選定
dbt Coreを1.5にアップグレード
dbt Coreはpipでインストールをしています。
1.5へのアップグレードと書いていますが、1.8より前のバージョンでは、dbt Core自体を直接インストールするのではなく、各データベース用のアダプターをインストールすることが推奨されています。
dbt Coreは、インストールしたアダプターの依存関係として自動的にインストールされます。
1 |
pip install dbt-redshift==1.5.11 |
この時点では、アダプターのバージョン1.5.11をインストールしていました。
これは単に最新のパッチバージョンを見落としていたためです。
dbt run でエラー発生
Github Actionsホステッドランナー環境で実行されるCIワークフローのdbt runステップにおいて、「Connection reset by peer」エラーが発生しました。
このエラーは、実行コストの高い一部のモデルでのみ発生することが判明しました。
しかし、この段階では実行環境の違いがエラーの発生に関係しているという可能性に気づいていませんでした。
Redshiftクラスターのモニタリングデータ、およびエラーログの確認
RedshiftクラスターはサーバーレスではなくRA3インスタンスのノードで構成されています。
実行コストの高いモデルでのみエラーが発生していたため、Redshiftクラスターに負荷がかかっているのではないかと考え、モニタリングデータとエラーログを確認しました。
- マシンリソース(CPU・メモリ)
- アクティブ接続数
- クエリの同時実行数
- キューに入ったクエリ数と処理されたクエリ数の統計
エラーログには特に目立った問題は見られず、接続数、同時実行数、クエリ統計のいずれもクォータには達していませんでした。
CPUの使用率は高止まりしていたため、スペックアップで解決できる可能性はありましたが、まずは他の解決方法を探ることにしました。
dbt-redshiftアダプター設定の見直し
クライアント側で負荷を軽減できないかという観点からアダプター設定を見直すことにしました。
1.5で非推奨となった設定項目がいくつか見つかりましたが、これらは一旦保留としました。
- threads: 8 -> 4 -> 2
- connect_timeout: 30 -> None
- read operation timed out がIssueにあったのでここで同時対応
- sslmode: prefer -> disable
- keepalives_idle: 非推奨となったので削除
threadsの調整により一部のモデルでは問題が解消されましたが、より処理コストの高いモデルについては依然としてエラーが発生しました。
その他のパラメータ変更では目立った効果は得られませんでした。
keepalives_idleが非推奨になっていたことが気になったため、次にアダプターにどのような変更が入ったのかを確認することにしました。
RedshiftアダプターのCHANGELOG確認
CHANGELOGを確認したところ、以下の重要な点が判明しました。
- Redshiftクラスターへの接続ドライバが psycopg2 から redshift_connector に変更されていること
さらに、この時点で「開発環境ではdbt runのエラーが発生していない」という重要な事実に気づきました。
類似の事象探索・調査
dbt、redshift_connector、environmentをキーワードとして、以下の情報源で類似事象を調査しました。
- dbt-labs/dbt-core のIssue
- dbt-labs/dbt-redshift のIssue
- aws/amazon-redshift-python-driver のIssue
- dbt公式のユーザーコミュニティ
完全に一致する事象は見つかりませんでしたが、ネットワークスタックに関連すると思われるIssueをいくつか発見しました。
- https://github.com/dbt-labs/dbt-redshift/issues/701
- https://github.com/dbt-labs/dbt-redshift/issues/518
- https://github.com/aws/amazon-redshift-python-driver/issues/212
これらのIssueから、Github ActionsやCodeBuildなどの環境で同様のエラーが報告されていることが分かりました。
いずれのケースも明確な対処策やパッチは提供されず、「Redshift Pythonドライバーには安定したネットワーク接続が必要」「実行環境に起因する問題」という結論でクローズされていました。
今後の進め方を決める
実行環境の変更で解決できる可能性が見えてきたため、次の2つの対応を進めることにしました。
- アダプターのバージョンを最新(1.5.12)にアップデートする
- セルフホステッドランナーでCIワークフローの検証を行う
- 検証結果でエラーが発生しなければCIワークフローの実行環境を変更し、エラーが続く場合は別の方針を検討する
パッチ適用だけでは解決できない可能性が高いと予想されましたが、可能な対応は全て試してみることにしました。
Github Actions セルフホステッドランナーでCIワークフロー検証
EC2インスタンス(Amazon Linux 2023、t3.small)にセルフホステッドランナーを構築してCIワークフローを実行したところ、エラーは発生しませんでした。
これにより、実行環境の変更を解決策として進めることを決定しました。
Github Actions セルフホステッドランナー環境の選定
セルフホステッドランナーは自己管理が必要なため、実運用開始前に運用方針とセキュリティ面について十分な検討が必要です。
- マシンの運用
- VMのメンテナンス
- 静的なVM一台の管理負担が発生
- ランナーのバージョン管理、セキュリティパッチ適用、OSアップグレードなどが必要
- スケーリングの課題
- オートスケーリングの実装は過度な工数となるため現実的ではない
- 非稼働時間の扱い
- ワークフローの実行時間は1日のごく一部のみ
- ワークフローのprehook、posthookでマシンの起動・停止を制御することは技術的には可能
- ワークフロー以外のタスクを組み込むとメンテナンスコストが増大するため避けたい
- 実行環境の構成管理
- ランナーのホストマシンのランタイムやライブラリの構成管理が必要
- コンテナによる環境分離は可能だが、管理負担が増加しワークフローの変更も必要となる
- モニタリング
- リソース・ログ・コストの監視が必要
- VMのメンテナンス
- セキュリティ面
- マシンのライフサイクル管理
- ホストマシンとランナーは、ホステッドランナー同様のクリーンな環境を維持することが望ましい(環境汚染やバックドアを防ぐため)
- 必要時のみ起動し、用済み後は速やかに終了する運用が望ましい
- 実行権限管理
- ワークフロー実行時のユーザー権限を適切に制御する必要がある
- セットアップの利便性のために「runner ALL=NOPASSWD」などの安易な設定を行うと、環境が脆弱になるリスクがある
- マシンのライフサイクル管理
要件を整理してみると考慮すべき点が多岐にわたり、さらに検討漏れもありそうです。
マシンの適切な運用には相当な労力が必要となるため、できれば避けたいところです。
これらの課題を検討しながらGithub Actionsの公式ドキュメントを確認していたところ、ほとんどの課題を解決できそうなARCを発見したため、検証を経て実運用への導入を決定しました。
まとめ
今回のdbt Coreアップグレード作業から得られた教訓を以下にまとめました。
この内容が誰かの参考になれば幸いです。
- アップグレード作業における基本
- アップグレード作業の際は、変更点の確認と影響範囲の特定を標準プロセスとして組み込むこと
- また、環境ごとのアップグレードガイドラインを事前に整備することで、担当者によらず安定的なアップグレードを実現できる可能性が高まる
- 問題解決アプローチの改善点
- 詰まっている時ほど客観的な視点での状況把握が重要、そのためには意識的に「リセット」できると良い
- 適切な判断を導くために
- 公式ドキュメントや一次情報を優先的な情報源として参照すること
- 解決策の選択に関する考察
- その場における最適解はあるが、正解はない
- 最適解を追求する姿勢は大切だが、過度に固執すると単なる時間の無駄になってしまうこともあるため、バランスを保つことが重要
- 単一の解決策にとらわれず、複数の選択肢を検討することも重要
参考資料
- https://docs.getdbt.com/docs/dbt-versions/core
- https://docs.getdbt.com/blog/upgrade-dbt-without-fear
- https://docs.getdbt.com/docs/dbt-versions/core ↩︎