私は現在 HERP のデータプラットフォームの構築や運用を行っているが、このデータプラットフォームは以前、信頼性や障害検知に大きな問題を抱えていた。
障害が頻発しており、障害の発生を自分たちで検知することもできないような状況だった。
以下の記事で少し触れたように、ニーズを満たすために機能要件を優先し続けた結果だと理解している。
しかし機能要件を優先し続けたトレードオフとして、非機能要件の欠如が顕著になり、それにより様々な問題が起きていた。この頃に私がデータプラットフォームに関わるようになり、信頼性や可観測性などを高めていった。
この記事では、具体的にどのような状況であり、それに対してどのように対応していったのかを書いていく。
なお、この記事で書いている課題や取り組みは全て 2024 年のものである。今は状況が違うし、現在の知識や経験を持っていれば違う方法を採用していた可能性もある。
あくまでも当時の自分の取り組みを記している。
また、この記事における「障害」は主に、データパイプラインによるデータの更新に失敗し、それにより閲覧できるべきデータをできなくなってしまうこと、データが想定より古くなってしまうこと、などを指す。
どのような状況だったのか
HERP のデータパイプラインは大きく分けると 2 つの要素で構成されている。
ひとつは、各種データソース(アプリケーションのデータベースなど)からデータを集める処理。データウェアハウスとして BigQuery を採用しているので、集めたデータを BigQuery に投入している。データソース毎にいくつかの技術を使っているが、メインとなっているのはとある SaaS 。この記事ではこれを「データ転送サービス」と呼ぶことにする。
もうひとつはデータの加工であり、これは dbt を使ってる。
データ転送サービスでデータを集めてそれを dbt で加工、そうして作られたデータを様々な用途で利用している、というのが大まかな流れである。
前述した通りニーズには応えておりそれはよかったのだが、障害が多かった。時期によってバラツキはあるが、 2023/11 から 2024/10 の 1 年間は、ユーザーに影響が出るレベルの障害が月に 1 回のペースで発生していた。
そしてそれを検知する仕組みが何もないため、問い合わせがあって初めて気付くという状況だった。発生から数日経過してようやく気付く、ということもあった。
当然ユーザーには迷惑を掛けることになるし、本来やるべき業務が障害対応によってストップしてしまう。そしてそれは開発者には閉じず、様々な立場の人間に影響を与えてしまう。
さすがにまずいだろうということで、対応を始めた。この時点での自分は少し dbt を書けるようになったくらいで、データエンジニアリングの経験は特にない。BigQuery など Google Cloud のサービスもほとんど触ったことがなく、よく分かっていない状況だった。
通知を改善する
まずは、データパイプラインでエラーが発生したときに気付けるようにしたかった。エラー通知さえ機能していれば問い合わせが来る前に気付けた障害が、何度も発生していた。
データ転送サービスと dbt 、どちらも機能していなかったので、それぞれ対応する必要があった。
データ転送サービスについては、転送が失敗した際に Slack に通知する仕組みに既になっていた。しかし、毎日常に失敗しその通知が来るため、オオカミ少年になっており、機能していなかった。
複数の転送設定を組み合わせてワークフローを組んでいたが、そのうちの一部の転送設定が常に失敗しているのが原因だった。当該転送設定は削除してしまって問題なかったので(なにせ一度も成功したことがなかった)、削除した。これにより通知が正常化した。
dbt については、通知が何も存在しない状態だった。
HERP ではモニタリングに Datadog を使っており、 dbt のログも見れる状態にはなっていた。しかしログレベルの設定が上手くいっておらず、 dbt の吐き出すエラーログが Datadog 上ではエラー扱いになっていなかった。
そのため、 Datadog の Status Remapper を使ってエラーログとして扱われるようにした。これにより Datadog の機能を使って Slack に通知を送れるようになった。
これによりエラー通知が改善され、障害発生に気付きやすくなった。
CI で dbt compile を行うようにする
次は、動作確認してから dbt をデプロイするようにしたかった。
この時点では、 dbt のテストや動作確認は本当に何もなく、動く保証が何もないコードを毎日ぶっつけ本番で動かしていた。
変更を加える際に開発者が手元で動作確認するが、それだけでは保証が難しい。
例えば、自分たちで定義している dbt のマクロの仕様が変わったことがあった。変更者は当然、問題が起きないように確認し必要な対応を行った。しかし同時期に別の開発者がそのマクロを利用するコードを書き、その時点の仕様、つまり変更前の仕様で利用した。それぞれの変更そのものは間違ったことをしていないが、両方のコードを取り込むと、当然エラーが発生するようになる。コードレビューで気付ければよかったが見落とされ、障害が発生した。
そもそもこういった問題は人間の注意力ではなくプログラムによって検知されるべきで、 CI で然るべきチェックを行っていれば防げたはずだった。
調べたところ、dbt compileを実行することでチェックできそうだったので、 Pull Request の際に GitHub Actions でdbt compileすることにした。
dbt compileするだけでも BigQuery への接続情報が必要だったので、サービスアカウントキーを発行しそれを GitHub Actions で使えるようにした。
先程のように間違ったマクロの使い方をした場合はdbt compileが失敗するので、 Pull Request の段階で気付けるようになった。
しかしdbt compileでは、「存在しないカラムを参照している」などのケースには気付けない。これに気付くためにはdbt runなどを実行するべきなのだが、この時点では見送っている。dbt runを実行できるようにするために解決すべき論点が色々と存在したことが理由。具体的には、dbt runを実行するためには GitHub Actions のプロセスに BigQuery に対する各種権限を付与する必要があり、それに関しては社内調整が必要だった。そして当時の自分では、この議論に関してイニシアチブを発揮するのが難しかった。前提となる知識や理解が欠如していたためである。
最善ではないかもしれないが、それを行うことで再発を防げる障害があるのだから、まずはdbt compileを実行するようにした。
過去の障害について整理する
障害を減らすためのアプローチのひとつとして、「過去に起きた障害のポストモーテムや再発防止策を愚直にやる」は有効だと思った。
実際に発生した具体的な障害について考えることで、抽象的な議論に終始することなく具体的なアクションにつなげられそうだと思ったため。
また、様々な理由でポストモーテムや再発防止策の実施が有耶無耶になってしまうことが多く、やり切れていなかったので、一人ででもやっておきたかった。
直近 3 ヶ月に発生した障害について振り返り、その内容を以下の観点で整理していった。
- 障害の直接的な要因となった事象は何か
- その事象が発生するとなぜ障害が起きるのか
- その事象を発生させないようにする方法はないか
- その事象が発生しても障害を起こさないようにする方法や、影響をより狭く小さなものにする方法はないか
これにより、障害の発生要因やその対策についてある程度の類型化ができた。
そしてそのなかには、再発防止策がまだ打たれておらず、確率は高くないが明日にでも再発しかねないものがあることが明確になった。
その対策のために具体的に何ができるか調べ、すぐにできそうなもの、費用対効果が高さそうなものから、取り組んでいくことにした。
データ転送サービスのリトライ設定を修正する
この時期は、データ転送サービスの転送失敗に起因する障害が複数回起きていた。一定の確率で発生するが対策はまだ行われておらず、いつ再発するか分からない。それでいて、単に手動で再実行すればデータ転送に成功することが多い。
そのため、「すぐにできそうなもの、費用対効果が高さそうなもの」として、データ転送サービスのリトライ設定を行うことにした。
リトライ設定は以前から行っているはずなのに、なぜか上手くいっていないという状況だったため、運営会社に問い合わせた。その結果、設定方法が間違っていたことがわかった。あるページで有効にしても他のページの設定内容によっては機能しない、というような状態であり、我々が意図した設定内容には無っていなかったのだった。かなり分かりづらい仕様であり当時のドキュメントにもちゃんと書かれておらず、ちょっとどうなんだという気持ちはあったが、単純にもっと早く問い合わせておけばよかったなと思う。
ちなみにその後、このデータ転送サービスが提供する実行環境が強化され、「一定の確率でエラーが発生する」という事象自体が起きなくなった。
SaaS を利用すると自由度は減っていくし振り回されることもあるが、自分たちで何もしなくても勝手に性能がよくなっていくこともある。
dbt エラーに対応し続ける
リトライ設定が機能するようになったことでデータ転送サービスによる処理は安定し始めたが、 dbt による処理はエラーが頻発していた。
その多くは影響範囲が限定的で障害に直結するものではない。しかしエラーを放置し続ければ、それは必ず割れ窓になる。事実、dbt エラーが放置されることで、それが障害の発見の遅れなどにつながっていた。エラーが発生しそれによって障害が起きたが、従前から起きていた「問題ないエラー、いつものエラー」に埋もれ気付くのに遅れてしまった、ということが実際に起きていた。
そのため、エラーをゼロにしてそれを維持する、という活動を行い、「エラーが出ていることは異常であり、何らかの対応や判断をしなければならない」という状態を作ることにした。
日々発生するエラーの対応をひたすら行うことでそれを実現できたが、この取り組みは、データエンジニアリングの知識や業務知識を得るよい機会となった。エラー対応を続けることで、既存のデータパイプラインに対する理解が深まっていった。
CI での dbt run を実現する
データ転送サービスが安定したことでしばらく障害は起きていなかったが、残念ながら dbt のエラーに起因してまた障害が起きてしまった。
このときもポストモーテムを行い、「どうすれば防げたか」「どうすれば影響を小さくできたか」「日々の運用や監視に改善の余地はないか」などを整理した。
その結果、やはり「dbt runが失敗するコードをデフォルトブランチに入れない、デプロイしない」という当たり前のことをやるしかない、と判断した。
それとは別に、どの dbt model がどのように依存されているのか、どこから使われているのかが分からない、という課題も浮かび上がった。
冒頭で触れたようにデータ活用は進んでおり、様々な用途でデータが利用されていた(だからこそ、障害対策が終わるまでデータ周りの日々の開発やデプロイを止める、といった判断はしづらかった)。しかし、具体的に何がどのデータに対して依存しているのか、用途は何なのか、という全貌はまったく管理できていなかった。そのため、 dbt で何らかのエラーが起きたとき、その影響範囲を把握するのが難しかった。
まずは、緊急度の高い「dbt runが失敗するコードをデフォルトブランチに入れない、デプロイしない」から取り組むことにした。
Kubernetes の CronJob を使って日次でdbt runを実行していたが、その失敗は、以下のように分類することができる。
- どの環境でも
dbt runが失敗するケース - Kubernetes の CronJob でのみ
dbt runに失敗するケース
1は、 dbt のコードの書き方そのものに問題があるケース。
2は、 Kubernetes の実行環境や設定に起因するケース。例えば、 dbt が必要としている環境変数の設定漏れなどが該当する。
「日次で実行しているdbt runが失敗しないか確認したい」がやりたいことなので、1だけでなく2もカバーできるのが望ましい。
ただ、現状はdbt runのチェックは何も行っていないため、1をカバーできるようになるだけでも効果は大きい。そのため2をカバーするのが大変そうならばまずは1のみを対応することにした。
論点を整理してドキュメントをまとめたり、関係者とのミーティングを設定したりしながら進め、素朴に GitHub Actions で dbt を実行することにした。
GitHub Actions から Argo Workflow や CronJob を呼び出す案もあり、それは2もカバーできるはずだが、見送った。スピーディーに1を実装できることを優先した。実装の難易度というより、論点の少なさが主な理由。弊社にとって未知の要素が少なく枯れている手法を採用した。知見が少ない技術は未知の要素が多いし、利用するにあたってセキュリティなどの観点からの調査や確認も行う必要が出てくる。そのため、当時既に弊社において枯れていた方法を採用することで、比較的スムーズに成果を生めるようにした。
これまで CI でdbt runを実行できていなかった主な理由は、認証や認可。
BigQuery に対する権限の他、 Python model でやり取りしている外部 SaaS のトークンなども必要だった。
そういった課題を解決していき、 Pull Request の際に、変更があった model とそのダウンストリームを対象に(のちにアップストリームも対象にした)dbt runを実行するようにした。そしてそれがパスしないとマージできないようにした。
議論すること、決めること、実装すること、依頼すること、相談すること、いろいろあったが、無事に実現できた。
dbt compileの節で「イニシアチブを発揮するのが難しい」と書いたが、さすがにこの頃には、ある程度は発揮できるようになっていた。これまでの取り組みによってデータパイプラインに対する理解が深まっていたし、別の業務でも Google Cloud について学んでいたのでそれについても少しは知識を得ていた。そのため、例えばこれまでは「dbt runを GitHub Actions で実行したい」としか言えなかったところが、「そのためにこの権限が欲しい」「この処理でこういう問題が起きているため対応が必要で、こういう手法で解決するつもりでいる」のようにより具体的な会話ができるようになった。
次の手を考える
「CI でのdbt runを実現する」を進めつつ、それが終わったあとに何をするとよいか考えていた。
やはり、見送った2のカバーはやりたい。CI は通るが Kubernetes では失敗する、というケースはどうしても存在する。そもそも CI ではあくまでも「変更があった model とそのダウンストリーム」のみが対象であり、全ての model に対してdbt runしているわけではない。また、様々な理由で GitHub Actions で実行するのが難しく、 CI でのdbt runをスキップしている model も、少数だが存在している。
production (本番環境)と同等の環境で動作確認できるようになれば、安全性が高まる。
障害の検知にも課題感があった。
上述したようにデータ転送サービスや dbt のエラーは Slack に通知されるようになった。ノイズも大幅に減った。
しかしそもそも「データ転送サービスや dbt がエラーを出していない」と「障害が起きていない」は、イコールではない。「エラー」にはなっていないがデータの更新や提供に失敗している、ということはあり得る。
同様に「データ転送サービスや dbt がエラーを出している」と「障害が起きている」もイコールではない。例えば、実態としてほとんど利用されていないテーブルの更新が失敗したところで、影響はほとんどない。
障害を防ぐための取り組みは大切だが、 100 % 防ぐということは不可能なのだから、障害検知も改善していきたかった。それができれば、より迅速に障害対応を行えるようになる。
既に触れた「どの dbt model がどのように依存されているのか、どこから使われているのかが分からない」という課題はここに関係してくる。依存関係を把握できていないため、あるテーブルの更新に失敗したときに、それが重大なことなのか、即時の対応が必要なものなのか、判断に時間と労力を要してしまう。
この 2 つに取り組むことには価値がある、今後の積極的な開発を進めていくための足場となる、と判断し、この 2 つに取り組むことにした。
production と同等の環境で動作確認できるようにする
「production と同等の環境で動作確認できるようにする」は、私ではなく、当時稼働して頂いていた業務委託の方をアサインした。
自分は「障害検知の改善」に取り組みたく、それと並行して進めるためである。
結果として、順調に進んだ。業務委託の方が上手くやってくれたのだが、なかでも以下の点が個人的によかったし助かった。
- 思考や議論のログを含め、ドキュメントを大量に残す
- 私がドキュメンテーションを重視し好むタイプなので、それに合わせてくれたのかもしれない
- 私とだけやり取りするのではなく、自ら進んで、私以外のチームメンバーや有識者に質問や相談を行う
- 段階的にコンセンサスを作っていく
- そもそもの課題や実現したいこと、細かい要件や制約、方針、それぞれを丁寧に確認しながら進めていった
具体的には、要件の整理、現状の整理、どのような選択肢があるかの調査とそれらの検討、 GitHub Actions や Kubernetes などの技術要素に対する学習や検証、運用フロー案の設計、などをやってもらった。
最終的に、 Kubernetes の CronJob を使った動作確認を事前に行い、それから production にデプロイする運用を導入することができた。
障害検知を改善する
取り敢えずまず、データプラットフォームに依存しているサービスやプロダクトとして何があるのか、それらはどのように依存しているのかを、把握することにした。
調査や関係者への質問を行って把握していき、それをドキュメントにまとめていった。
このときの調査やドキュメンテーションが後に、以下の記事に書いた「dbt のexposureとtagを使った依存関係の管理とその自動更新」や「利用されていないサービスの停止」につながっていく。
「障害検知」を改善するためには、そもそもここでいう「障害」とは何なのか、何を検知したいのか、整理していく必要がある。
それを考えるにあたり、まずデータプラットフォームに依存しているサービスやプロダクトを調べたのは、有益だった。依存関係や内容について具体的に把握することで、考えやすくなった。
役割分担としてデータチームは何を担うのかということも含めて考えを進めた結果、原則的には BigQuery に置かれているデータが「正しい」かどうかさえ観測できればいいはず、と思うに至った。
各サービスは BigQuery に置いてあるデータを参照しているだけだし、そのデータをどう使うかは各サービスが面倒を見るのでありデータチームが首を突っ込むような話ではないように思えた。
これについては絶対的な答えはないし状況によっても変化していくとは思う。だが何にせよ、 BigQuery に置かれているデータが「正しい」かを監視すること、そのための仕組みを用意することは、やったほうがよいことのはずなので、取り組むことにした。
とはいえ何の知見もなかったので、「Data Observability」などのキーワードでざっと調べたりしながら、考えを整理していった。
最終的に、「作られたデータに問題が起きている可能性を示すシグナル」を定義し、それが発生したときは Slack で通知させるようにすればよさそうだとなった。
具体的には、以下のような事象が発生したことを検知したい。
- データが 24 時間以上更新されていない
- テーブルの中身が空である
データ転送サービスや dbt がエラーを出していなかったとしても、上記のような状態になっていた場合、何か問題が起きている可能性がある。
他にも検知するとよさそうな事象は色々とあるが、取り敢えずこの 2 つをチェックできれば、これまでに発生した障害と同種の障害は検知できるようになる。
そして上記の事象はdbt testで検知できることを確認できたので、重要な dbt model (プロダクトから直接依存されている model)についてこれらのテストを書いていった。
最後に、以下の内容をドキュメントに記し、プロダクトから依存されている model を書いている各チームに共有した。
- どのようなテストをどのような意図で書いたのか
- 今後はこれらのテストを書くことを推奨すること
- 具体的なテストの書き方
特に言及してこなかったが、ドキュメンテーションについては他の施策やプロジェクトでも行っている。
そうすることで当時の意思決定の内容や背景を後から参照できるし、こうやってブログにまとめることもできる。
結び
データプラットフォームの信頼性を改善するために 2024 年に行った主な取り組みを書いてきたが、これらの取り組みの成果として、障害の頻度をある程度下げることができた。
冒頭に書いたように 2023/11 から 2024/10 は月 1 回のペースだったが、 2024/11 から 2025/10 は 3 ヶ月に 1 回のペースだった。
障害がゼロにはなっていないが(そんな日が来ることはない)、障害の理由は変化しており、 2024 年に多発していたような内容は起きなくなってきている。
そしてさらに、早期発見できることで、すぐに復旧させ、影響を小さくできるようになった。
ベストではないかもしれないが、一定の成果は出せた。
その理由のひとつは、課題から考えていたことだと思う。「データ転送サービスのリトライ設定」のような自明な施策以外は、「何が課題なのか」「どうなっていて欲しいのか」「実現するとどんな効果があるのか」といったことを考え整理して進めるようにしていた。
ともすれば「流行っている技術やアーキテクチャを導入すること」や「余所がやっていることを模倣すること」、単に「今までとは違う仕組みに変えること」自体が目的になってしまい、手段の目的化が起きかねないが、そうはならずに済んだ。
この記事に書いた施策はどれも、導入した後から見れば、普通のこと、やって当然のことに見えるものばかりだが、そう感じるのは大きく的を外してはいないからだと思う。そしてそれは、ツールやテクニックを目的とせず、課題に意識を向けて取り組んでいたからだと思う。