メインコンテンツにスキップ
OpenAI

2026年6月30日

エンジニアリング

コアダンプの疫学:18年間見過ごされていたバグを修正

母集団レベルの分析を活用して、データインフラで発生する原因究明が難しいクラッシュを解明します。

読み込んでいます...

OpenAI のモデルやエージェントは、推論時、つまりユーザーの質問について考えている間に関連データを検索するため、スケーラブルなデータインフラへの依存をますます強めています。これらのサービスの一部は C++ で実装されています。C++ はシステムを低レベルで制御できるため、高い性能と少ないメモリ使用量を実現できます。規模を拡大するうえで、こうした効率面での利点は重要ですが、C++ はメモリ安全性を備えていないため、バグによって誤ったメモリアドレスや存在しないメモリアドレスへの書き込みが発生し、クラッシュを引き起こす可能性があります。

数か月前、ChatGPT のデータインフラの一部として独自に構築した Rockset サービス内部で、いくつかのクラッシュが発生していることを確認しました。Rockset は、多くのデータプラグインや会話検索を支える重要なコンポーネントです。これらのクラッシュのいずれにおいても、通常の C++ 関数が終了したように見えた後、不正なアドレスに戻ったため、命令ポインターがコードを指さなくなり、カーネルがプログラムを停止させました。ときどき、スタックフレーム内の戻りアドレススロットが NULL になっていました。また、CPU のスタックポインタレジスタ自体が8バイトずれているように見えることもありました。まるで通常の実行中に、何らかの理由で %rsp がデクリメントされたかのようでした。どちらの場合も、クラッシュはリターン時に発生しました。

これらは、アプリケーションコードで通常想定される障害モードではありません。保存された戻りアドレスだけに意図しない書き込みが当たることはあり得ますが、可能性は極めて低いです。インラインアセンブリ、setcontext、または longjmp(いずれも使用していません)を伴わずに、%rsp のアラインメントを8バイトずらすバグは、さらに奇妙です。というのも、コンパイルされたコードがそのレジスタを直接調整するのは、関数プロローグとエピローグに限られるためです。私たち(または ChatGPT)が思いつく限りの仮説はすべて、それを否定する有力な証拠があったため、そのバグは起こり得ないように思えました。

当初は1つの問題だと思っていたものが、最終的には偶然同時に発見された無関係な2つのバグであることが判明しました。まず、ある Azure ホストでサイレントなハードウェア障害が発生し、CPU が正しく計算を実行していませんでした。2つ目は、GNU libunwind に18年にわたって存在していた競合状態であり、広く利用されているオープンソースライブラリで見過ごされていたバグです。

本記事では、疫学者のような発想で、発生したクラッシュ全体を網羅する高品質なデータセットを構築し、一見原因不明に思えたクラッシュをどのように特定し、修正したのかをご紹介します。

最初のデバッグ:いくつかのコアダンプを詳しく調べる

まずは、Rockset についてもう少し詳しく見てみましょう。Rockset は、検索とリアルタイム分析のためのクラウドネイティブなデータシステムです。同期コネクターをはじめ、OpenAI のさまざまな社内用途で利用されています(Rockset は2024年に OpenAI に買収されました)。ストリーミング更新によって、ワークスペースのナレッジベースのインデックスを常に最新の状態に保ち、ChatGPT が質問への回答やアクションの実行時に関連情報を検索できるようにしています。

Rockset の実行レイヤーは C++ で実装されています。C++ は CPU を低レベルで制御できるため、パフォーマンスと効率の面では有利ですが、その一方で、アプリケーションのバグによって不正なメモリアクセスやセグメンテーションフォールトが発生する可能性があります。こうした問題の原因を追跡するため、クラッシュが発生すると folly の致命的シグナルハンドラーでスタックトレースを記録し、対応するコアダンプ(クラッシュ時点のプログラム状態のスナップショット)を後から分析できるよう Azure Blob Storage にアップロードしています。Rockset のクエリ処理リーフはすべてレプリケートされているため、クラッシュがクライアントへ与える影響は最小限に抑えられます。しかし、セグメンテーションフォールトはいずれも修正すべきバグであり、信頼性と品質の目標を達成するには、その原因を解明する必要があります。

当初は、これらのコアダンプを通常のデバッグと同じように扱いました。いくつかのコアダンプを詳しく調べ、仮説を立て、それらを1つずつ検証して除外していくというアプローチです。

クラッシュの大半は、DocumentTree::updateDocument というメソッド内で発生していました。これらのクラッシュでは、updateDocument が何らかの未知の関数 X を呼び出し、X の実行中にスタックが破損し、その後 X が実行可能コードではないアドレスへリターンしたように見えました。場合によっては、X のポップされた直後のスタックフレームは、保存されていた戻りアドレスが NULL になっていることを除けば、有効に見えました。別のケースでは、スタックポインタ自体が異常に見えましたが、その次の有効なフレームは依然として updateDocument のフレームであるように見えました。

スタックがいつ破損しているのか分からず、調査対象は非常に広範囲に及びました。updateDocument は多くのインライン化が行われる大規模なメソッドであり、X の候補は膨大な数に上りました。

原因は私たちの C++ コードのバグなのか。コンパイラやリンクの問題なのか。ランタイムライブラリのどこかに問題があるのか。シグナルの送達やコンテキストスイッチ周りの Linux カーネルのバグなのか。それとも、さらにまれな原因なのか。もし意図しない書き込みが原因なら、なぜ ASAN のステージング環境で検出されなかったのか。

問題の発生事例をすべて特定するため、私たちはアプリケーションレベルのログを活用しようとしました。しかし、スタック破損バグでは、記録されたスタックトレース自体が破損または欠落しているため、ログだけで分類することは困難でした。誤検知と検知漏れの両方を避けられるログクエリは作成できませんでした。さらに多くのコアダンプを手作業で調査した結果、追加の事例がいくつか見つかりました。しかし、この方法では手間がかかりすぎるため、信頼できるデータセットを得ることはできませんでした。

この時点では、複数のリージョンと複数種類のハードウェアでクラッシュが発生していたことから、(誤って)ハードウェア障害の可能性を除外し、引き続きソフトウェアだけに原因を求めていました。数日間にわたり、%rsp のアラインメントがずれた1件のクラッシュを徹底的に調査し、スタックとレジスタの内容からクラッシュ前の実行履歴を再構築しました。その結果、有力な手がかりがいくつか得られました。しかし、「すべてのバグは同じ原因によるもの」という当初の考えに固執していたため、行き詰まりを打開するには至りませんでした。

スタックから得られた手がかり

調査の転機について説明する前に、コアファイルからどのような情報を抽出していたのかを説明しておきます。

Rockset は -fno-omit-frame-pointer を指定してコンパイルされているため、現在アクティブなスタックフレームには常に %rbp を介して到達でき、呼び出し元はフレームポインタのリンクリストを形成します。

Linux の x86_64 では、AMD64 System V ABI により、%rsp の下位128バイトがレッドゾーンとして予約されています。この領域はユーザー空間のコードから利用できます。さらに重要な点として、ABI の仕様により、カーネルはシグナルを送達する際にこの領域を上書きしないことが保証されています。

レッドゾーンは、リターン後に発生するクラッシュを調査するうえで重要な手がかりとなりました。これは、リターン前の情報の一部が保持されているためです。SIGSEGV が発生すると、folly の致命的シグナルハンドラーは、クラッシュしたスレッドのスタック上で実行されます。すでにアクティブではなくなったスタックフレーム(関数がすでにリターンしているため)は、最後の128バイトを除いて、シグナルハンドラーによって上書きされます。そのため、「X のポップされた直後のスタックフレームは、戻りアドレスが NULL である点を除けば有効に見えた」と判断できます。レッドゾーンには、非アクティブなスタックフレームの一部、あるいは場合によっては1つの非アクティブなスタックフレームの末尾だけが保持されます。

戻りアドレスを上書きし、クラッシュを引き起こす可能性のある破損したスタックフレームを示したスタック図。

スタックのアラインメント不整合によるクラッシュが1件見つかりました。そのクラッシュに関与していた関数はいずれも非常に小規模なものでした。それによって、比較的単純な関数の実行中に %rsp のアラインメントが崩れており、その後さらにいくつかの呼び出しが成功していたことが分かりました。プログラムがクラッシュしたのは、実行中の関数が最終的にリターンしようとしたときだけでした。それらのコードパスでは、例外、インラインアセンブリ、setcontextlongjmp のいずれも使用されていませんでした。そのため、コアダンプが示すとおりにスタックポインタが実際に変化していたのであれば、この問題を説明できるようなユーザー空間コードのバグは考えられませんでした。

そこで私たちは、原因はカーネルにあるのではないかと考えるようになりました。

Rockset は、ほとんどのプログラムよりも積極的にシグナルを使用します。クエリの実行は、データを交換する多数の軽量タスクに分割されます。これは高 QPS ワークロードを効率よく処理するうえで重要ですが、多数のクエリが同じスレッドプール上で多重化されるため、クエリ単位で CPU 使用時間を計測するのが難しくなります。

そこで私たちは、coarse_thread_cputime_clock と呼ぶ仕組みを導入しました。これは、clock_gettime(CLOCK_THREAD_CPUTIME_ID, ...) を、すべてのタスク境界でサンプリングできる程度の低コストで近似するものです。timer_create API を使用すると、CPU 時間の累積を含む複数の時間基準に基づいて、周期的なシグナル送達をスケジュールできます。CPU 時間で数ミリ秒ごとに SIGUSR2 が送達されるよう設定し、そのたびにシグナルハンドラーがスレッドローカルな値を更新します。多くのタスクでは、実行中に coarse clock が進まないように見える場合でも、それぞれの差分を合計すると、クエリの実際の CPU 時間を偏りなく推定できます。

シグナルを非常に高い頻度で送達していることから、コンテキストスイッチやシグナル送達に関するまれなカーネルバグも十分考えられました。そこで、バグレポートやカーネルのソースコード、Azure 固有のカーネルパッチを詳しく調査しました。ストレステストも実施しましたが、関連がありそうなものは見つかりませんでした。

そこで、一歩引いて別のアプローチを試すことにしました。

医師か、それとも疫学者か

このような問題をデバッグするアプローチは、大きく分けて2つあります。

1つは、いわば医師のようなアプローチです。1人の患者に焦点を当て、多くの検査を行い、詳細な証拠をもとに個々の症例を診断します。

もう1つは、疫学者のようなアプローチです。集団全体を対象に、1つの症例だけでは見えてこないパターンがないかを探ります。このバグは、特定のリリースから発生し始めたのか。特定のハードウェア SKU(CPU やサーバーモデル)、リージョン、あるいはカーネルバージョンとの相関はあるのか。1つの症候群に見えるものの中に、実は複数の異なるクラスターが隠れているのではないか。

それまでの私たちは、主に「医師」のアプローチを取っていました。転機となったのは、質の高い母集団データを収集する必要があると判断したことでした。

データのクリーニング

以前、問題の発生箇所をすべて自動で特定しようとしましたが、ログに対するテキスト検索に頼っていたため、うまくいきませんでした。コアダンプ自体にははるかに多くの情報が含まれていましたが、手作業で確認する方法では対応しきれませんでした。そこで、コアダンプを自動的に解析できるパイプラインの構築に取り組むことにしました。

各コアファイルの先頭部分をダウンロードし、レジスタを抽出し、ログを使って既知の誤検知を除外したうえで、クラッシュを return-to-null、misaligned-stack、または other に自動分類するスクリプトを、ChatGPT に作成してもらいました。次に、そのスクリプトを並列実行し、前年に本番環境で発生した Rockset のすべてのコアダンプに適用しました。

これが転機となりました。

クリーンなデータセットが得られると、相関関係はすぐに見えてきました。これまで1つの不可解なバグだと考えていたものは、実際には2つの異なるクラッシュ群だったのです。

return-to-null コアは、多数のクラスターおよび地理的リージョンに分散していました。発生頻度は最近増加していましたが、増え始めた時期を明確に特定できず、インフラ上の明確な境界も見当たりませんでした。

スタックのアラインメント不整合によるクラッシュは、まったく様相が異なっていました。それらはすべて1つのリージョンで発生しており、発生し始めた時期も明確で、長期間稼働していたノードでは一度も発生していませんでした。複数の Azure VM(クラウドでホストされる仮想マシン)で発生していたものの、そのパターンからは、欠陥のあるハードウェアを搭載した1台の物理マシンが、その上でたまたま稼働していた VM に問題を引き起こしているように見えました。

クラスターごとのクラッシュ率の推移を示したドットプロット。クラッシュの大半はクラスター2、3、6に集中しており、期間末近くにはクラスター1で急増が見られます。

その瞬間、私たちは2つのバグを頭の中で混同していたことに気づきました。2つのバグの反例を混同していたため、それらを一貫して説明できる仮説を見つけることができませんでした。

バグ #1:問題のあるホスト

整理された Kubernetes ノードとタイムスタンプのリストをもとに、スタックのアラインメント不整合によるクラッシュを単一の物理ホストまで追跡でき、そのホストを拒否リストに追加するのは容易でした。

管理された環境で数週間にわたるストレステストを実施した後でも、当該ホストでのレジスタ破損を再現することはできませんでした。しかし、問題のあるホストをサービスから外すと、スタックのアラインメント不整合によるクラッシュは発生しなくなりました。

問題のあるホストを取り除いても、同じ問題の再発を防げるわけではないため、恒久的な解決策にはなりません。ただし、同様の問題が再発した場合に容易に検出して対処できるよう、ソフトウェアを変更することは可能です。致命的シグナルハンドラーを改善してレジスタ状態も記録するようにしたことで、ログだけで再発を検出できるようになりました(コアダンプは不要です)。コントロールプレーンを変更し、VM を破棄して再作成するのではなく、通常は再利用するようにしました。これにより、インフラスタックにおける私たちのレイヤーでも、不良ノードの検出が大幅に容易になりました。また、この可能性を反映するよう、ランブック(およびチームのメンタルモデル)も更新しました。

問題のあるホストに起因するクラッシュを切り分けたことで、残りの return-to-null コアははるかに分析しやすくなりました。以前は、反例があると考えていたため、例外アンワインドを原因から除外していました。つまり、例外が使われていないことが明らかなコードパスでもクラッシュが発生していたのです。しかし、それらの反例はいずれもハードウェア障害によるクラッシュ群に属するものでした。

その点を踏まえて残りのコアを改めて調査したところ、結論はまったく逆だったことが分かりました。クラッシュはすべて例外アンワインド中に発生していたのです。

例外処理は動的な制御移譲

C++ が例外をスローすると、ランタイムは、その例外を受け取るべき catch ブロックと、その過程で実行すべきデストラクターまたはクリーンアップハンドラーを特定する必要があります。コンパイラはこのメタデータを生成しますが、実際のマッチングは実行時に動的に行われます。

例外アンワインドは、throw を実行する関数自身が行うのではなく、コンパイル済みコードから呼び出されるヘルパー関数によって実行されます。これらのランタイムルーチンは、スタックを調べ、そこにある関数のメタデータを取得し、クリーンアップハンドラーや catch ブロックを動的に探索したうえで、それらのいずれかへ制御を移します。この制御の移譲では、途中にあるすべてのスタックフレーム(ヘルパー関数のものを含む)のアンワインドも行われます。

動作としては、通常の関数呼び出しやリターンよりも、longjmp やファイバーの切り替えに近いものです。呼び出し先保存レジスタに加え、スタックフレームレジスタである %rbp%rsp も復元する必要があります。

このバイナリは、C++ 例外アンワインドを実装する関数を含む2つのライブラリ、libgcc と GNU libunwind にリンクしています。動的リンカーによって選択されたのは、GNU libunwind 側の定義でした。これは予想外でした。シンボルバージョニングの規則から、libgcc の実装が選ばれると考えていましたが、実行中のバイナリを調べたところ、実際にはそうではありませんでした。

最後の前提を覆す

この時点で、私たちは作業仮説を見直しました。それは、バグが1つだけだと考えていた頃に置いていた、もう1つの前提を捨てたためです。

私たちが見ていたのは、通常の関数が NULL にリターンするケースではなかったのかもしれません。見ていたのは、アンワインド時の制御移譲、つまり実質的には setcontext のようなレジスタ復元処理だったのかもしれません。その際、制御が移る前に、遷移先の命令ポインタが NULL に書き換わっていた可能性があります。言い換えれば、問題はスタック上の戻りアドレススロットではなく、アンワインドライブラリが生成したデータにあったのかもしれません。

これにより、問題の範囲は大幅に絞り込まれました。考えられるのは、GNU libunwind が誤った遷移先状態を計算していたか、あるいは正しい状態を計算していたものの、それが適用される前に何らかの要因で破損していたか、そのどちらかでした。

GNU libunwind のソースコードを調べたところ、スタック上に ucontext_t を生成し、クリーンアップハンドラーのフレームに必要なレジスタ状態を設定したうえで、その構造体へのポインタを内部アセンブリルーチンである _Ux86_64_setcontext に渡していることが分かりました。

この時点で、必要な手掛かりはすべてそろいました。

生成された ucontext_t は、_Ux86_64_setcontext の実行中にアンワインドされるスタックフレームの1つに配置されます。_Ux86_64_setcontext は、%rsp を変更した後で、その構造体を読み出していたのでしょうか。その時点では、その構造体はすでにアクティブなスタック領域の外にありました。もしそうであれば、頻繁に送達される SIGUSR2 のようなシグナルによって上書きされる可能性があります。

バグ #2:libunwind のバグ

答えは「はい」でした。

以下は、私たちが使用していた GNU libunwind の _Ux86_64_setcontext における最後の6命令です。これらは主に、メモリからレジスタへ値をロードする mov 命令で構成されています。

プレーンテキスト

1
74: mov UC_MCONTEXT_GREGS_RSP(%rdi),%rsp
2
75:
3
76: /* push the return address on the stack */
4
77: mov UC_MCONTEXT_GREGS_RIP(%rdi),%rcx
5
78: push %rcx
6
79:
7
80: mov UC_MCONTEXT_GREGS_RCX(%rdi),%rcx
8
81: mov UC_MCONTEXT_GREGS_RDI(%rdi),%rdi
9
82: retq

%rdi はスタック上に確保された ucontext_t を指しており、UC_MCONTEXT_* マクロは、各レジスタが格納されている固定オフセットへ展開されます。)

最初の命令が、この競合ウィンドウの始まりです。この命令は、%rsp をアクティブなスタックの新しい先頭へ更新します。この時点で、%rdi が指す構造体はアクティブなスタック(およびレッドゾーン)の一部ではなくなり、カーネルが保護すべき領域ではなくなります。

通常は問題になりませんが、シグナルがまさに都合の悪いタイミングで到達すると、カーネルは %rsp-128 にシグナルフレームを構築します。その結果、%rdi が指すメモリが上書きされる可能性があります。

それが、次の命令で UC_MCONTEXT_GREGS_RIP(%rdi) が読み取られる前に発生すると、復元される命令ポインタが破損する可能性があります。私たちの環境で発生したクラッシュでは、その値が NULL になっていました。

これがバグの正体です。

コアダンプが通常の不正なリターンに見えた理由

このアセンブリを見ると、私たちを困惑させたもう1つの点、つまり関数 X の直前のスタックフレームのリターンアドレススロットが NULL になっていた理由も分かります。

setcontext%rdi を含むすべてのレジスタを復元するよう実装されているため、制御を移す最後の段階で、UC_MCONTEXT_GREGS_RIP(%rdi) を読み取るために %rdi を使用することはできません。その代わりに、この値を事前に読み取ってスタックに保存し、さらにいくつかのレジスタを復元した後、retq を使って保存した値を読み取り、制御を移します。

コアダンプ上では「関数が NULL にリターンした」ように見えていたものは、実際には「アンワインダーがスタック上に戻り先アドレスを生成したものの、そのアドレスが制御の移行が完了する前に破損していた」ということでした。私たちは、リターンアドレススロットの破損はその場で発生したものだと考えていました。というのも、(破損し得る)データを意図的にリターンアドレススロットへ書き込む箇所を把握していなかったためです。

1命令分の競合ウィンドウ

このバグが信じがたいのは、この競合ウィンドウがあまりにも狭いことです。この種の競合状態では、外部イベント(シグナル)が、別のスレッドによる2つの処理の間に発生する必要があります。それらのステップ同士の間隔が短いほど、競合状態が発生する可能性は低くなります。

この場合、脆弱な時間枠は文字どおり1命令分しかありません。シグナルは、%rsp が変更された後、次の命令が %rip をロードする前までの間に送達される必要があります。このような単純な命令は、最新のスーパースカラ・アウトオブオーダー CPU では1サイクルあたり複数実行できるため、この競合ウィンドウはおよそ100ピコ秒しかありません。

この競合状態を発見したとき、最初は、観測されたクラッシュ率を説明するには発生頻度が低すぎるに違いないと考えました。フリート全体で、return-to-null クラッシュが1日あたり12件を超えて発生していました。例外クリーンアップ中の1命令分しかない競合状態だけで、本当にこれを説明できるのだろうか。

そこで、フェルマー推定を用いて概算してみました。脆弱な時間枠がおよそ101010^{-10}秒で、SIGUSR2 が CPU 時間の10210^{-2}秒ごとに到着するとすると、各例外クリーンアップハンドラーまたは catch ブロックが競合に負ける確率は、およそ10810^{-8}になります。

Rockset は、内部のインジェスト・バックプレッシャー機構の一部として例外を使用します。過負荷状態のホスト1台だけで、1秒あたりおよそ10410^{4}件の例外をスローします。つまり、バックプレッシャーを使用するホストの平均故障間隔(MTBF)は10410^{4}秒、すなわち数時間に1回のクラッシュとなります。フリート規模では、それだけで観測されたクラッシュ頻度を説明するのに十分です。

なぜ libunwind のバグは今になって表面化したのか

GNU libunwind のバグは古く、18年以上前から存在しています。C++ の例外アンワインドをサポートした最初の x86_64 版にすでに含まれていました。

では、なぜ今になって表面化したのでしょうか。

クラッシュ率は、スローされる例外の数と送達されるシグナルの数におおむね比例します。これは、シグナルハンドラーがどれだけのスタックを消費するかにも依存します。

Rockset は、この3つすべての点で特殊でした。Rockset では、通常の過負荷制御の一環として高い頻度で例外をスローします。また、coarse_thread_cputime_clock により SIGUSR2 を非常に高い頻度で送達しています。さらに今年の初めには、マージされたシグナルをカウントできるようにするため、timer_getoverrun の呼び出しを追加し、SIGUSR2 ハンドラーのスタック使用量を増やしました。

最後の変更が重要だったようです。ハンドラーが使用するスタック量が十分に少なければ、古い ucontext_t メモリまで到達して上書きすることはありません。その変更以前は、こうしたクラッシュはまったく確認されていませんでした。変更後もしばらくは発生率は低いままでしたが、バックプレッシャー機構に負荷がかかる一部のユースケースで負荷を引き上げると、発生率も上昇しました。

言い換えれば、libunwind のバグは以前から存在していましたが、例外発生率、シグナル発生率、ハンドラーのスタック使用量が重なり、運用上問題として顕在化する閾値を超えたのは、ごく最近のことでした。

このメカニズムは、ハードウェアのバグと libunwind のバグの両方が、主に DocumentTree::updateDocument 内でクラッシュしていたという偶然も説明できます。libunwind によるクラッシュがこのメソッドに集中していたのは、インジェストのバックプレッシャーを適用するために例外をスローする時点では、このメソッドが常に実行中だからです。また、%rsp のミスアラインメントによるクラッシュも、このメソッドで多く発生していました。これは、問題のあるハードウェアノードがバルクインジェスト向けの SKU であり、その CPU 時間の大半をこのメソッドで費やしていたためです。

当面の対策として、GNU libunwind から libgcc のアンワインダーへ切り替えました。それだけでも十分に価値のある変更でした。libgcc の実装は、ロック競合を減らすための数多くの改善が加えられており、これは大規模な VM へスケールする際に重要です。

また、自己完結型の再現コードと修正(新しいウィンドウで開く)を GNU libunwind のアップストリームへ提供し、他のアンワインダーには同様の問題がないことも確認しました。

母集団レベルの診断の力

このデバッグを通じて、動的リンク、DWARF のアンワインドメタデータ、Linux のシグナル送達、System V ABI、C++ の例外機構など、多くのことを学びました。しかし、最も重要な教訓は、それらよりもずっとシンプルなものでした。

最も重要なステップは、アセンブリコードを巧みに読み解くことでも、細部に関する深い知識でもありませんでした。それは、高品質なデータセットを構築することでした。このデータセットがなければ、私たちは2つの異なる現象を1つのものとして捉え、その混乱を推論だけで解消しようとしていました。正確で網羅的な母集団データが得られると、問題の構造は明らかになりました。クラッシュ群の一方は不良ホストに起因し、もう一方は libunwind の競合状態に起因していました。データの質が向上すると、デバッグも容易になりました。

Rockset のようなインフラシステムでは、これは非常に重要です。今回の調査を通じて、より詳細な計測、自動化された調査、そして運用ツールの継続的な改善に、より一層取り組んでいく重要性を再認識しました。信頼性とは、問題が発生してからバグを修正することだけではありません。不可能に思える問題を、診断・解決できる問題へと変えるためのデータ、ワークフロー、そしてスキルを構築することでもあります。

著者

By Nathan Bronson、Member of Technical Staff