はじめに
データベースの停止が疑われる事態は、誰にとってもストレスの多い出来事となり得ます。ユーザーはデータが失われるのではないかと不安になり、開発者は原因究明に奔走し、企業関係者は収益損失につながる可能性のある毎分を数えます。
停止が発生した場合にデータベースを正常な状態に戻すことが最優先事項です。しかし、このようなストレスの多い状況の中で、問題が何であるかを推測することさえ難しい場合があります。この記事は、データベースが停止する最も一般的な理由のいくつかについて、その対処法をガイドすることを目的としています。
この記事では、データベース停止に対処する際に調べるべきさまざまな領域について説明します。
問題 | 潜在的な問題 | 対策 |
---|---|---|
データベースが接続を受け付けていないように見える | データベースユーザーの認証情報に関する問題、接続文字列の問題、接続制限に達した | 原因を特定するためにデータベースサーバーのログを調査する |
ネットワーク通信に障害があるように見える | VPCとファイアウォールの問題、アプリケーションとデータベース間のレイテンシーとタイムアウト | ファイアウォールルールを確認し、レイテンシーとタイムアウトを探し、接続制限の枯渇を確認する |
データベースサーバーまたはアプリケーションサーバーがクラッシュしている | データ量に関する潜在的な問題により、データベースサーバーとアプリケーションサーバーがリソース制限に達している | 返されるデータが大量にないか確認し、クエリを最適化し、インデックスを追加する |
データベースサーバーは起動しているが、アプリがデータを表示していない | スキーマ変更、未適用マイグレーション、不正な形式のクエリなど、最近のコード変更の可能性 | 最近の変更を確認するためにソース管理履歴を調査し、必要に応じて元に戻す |
この記事を読む最適なタイミングは、データベースが停止するずっと前です。もしデータベースがすでに停止しているなら、次に良いタイミングは今です。
データベース接続の問題
アプリケーションログからの手がかり
データベースが停止しているように見える場合、アプリケーションログは、リクエストの受け入れやデータベースへの接続に関する問題の手がかりとなる可能性があります。アプリケーションサーバーはクライアントからのリクエストとデータベースへのリクエストの両方を処理するため、データベースに問題がある場合は通常エラーをログに記録します。
Prisma Clientを使用している場合、ログの生成方法を制御するためにログ設定を行うことができます。
アプリケーションサーバーは接続を正常に処理していますか?
APIを公開し、データベースにクエリを実行してリクエストを処理するアプリケーションサーバーには、2種類の接続があります。
- アプリケーションサーバーが公開するAPIへのユーザーリクエスト
- アプリケーションサーバーからデータベースへのリクエスト
停止のデバッグ時には、両方の種類の接続の問題が停止を引き起こす可能性があることを念頭に置いてください。
アプリケーションサーバーのフレームワークには通常、組み込みのロガーが付属しています。サーバーが起動すると、接続を受け入れられることをログに記録するのが一般的です。
例えば、このログ行を見てください。
{"level":30,"time":1617808854673,"pid":96741,"hostname":"do-server-1","msg":"Server listening at http://0.0.0.0:8000"}
ログは、サーバーが起動し、ポート8000で接続を受け付けていることを示しています。しかし、サーバー起動後にエラーが発生したかどうかを判断するには、これだけでは不十分です。
次のステップは、最新のログを確認し、サーバーへの最新のリクエストのHTTPステータスコードをチェックすることです。通常、リクエストログは次のようになります。
{"level":30,"time":1617809865718,"pid":97326,"hostname":"do-server-1","reqId":5,"req":{"method":"POST","url":"/graphql","hostname":"0.0.0.0:3000","remoteAddress":"127.0.0.1","remotePort":53540},"msg":"incoming request"}{"level":30,"time":1617809865719,"pid":97326,"hostname":"do-server-1","reqId":5,"res":{"statusCode":200},"responseTime":1.1810400485992432,"msg":"request completed"}
各受信リクエストログには、タイムスタンプ、URL、およびリクエストに関するその他の情報が含まれます。最初のログは受信リクエストを示し、2番目のログはHTTPステータスコード200でリクエストが成功したことを示しています。
最近、データベースエラーはありましたか?
データベース関連の問題をデバッグしようとしているので、失敗したリクエストを特に探すべきです。これらは、サーバーエラーを示す5xx(例:500)の応答ステータスコードを持つリクエストをフィルタリングすることで見つけることができます。
500ステータスコードを持つ複数のリクエストに気づいた場合、それらがいつから始まったかを探し、他のログエラーがないか確認してください。
データベースが停止しているか、データベースの認証情報が間違っている場合、それがログに記録されているのを見ることができます。例えば:
Can't reach database server at `db-postgresql-563564.b.db.ondigitalocean.com`:`5432`
上記のログは、アプリケーションサーバーがデータベースに到達できないことを示しています。そのような場合は、アプリケーションサーバーのデータベースURLを確認してください。
Prisma Clientを使用している場合、エラーコードリファレンスはエラーの意味と修正方法の診断に役立ちます。
データベース問題のトラブルシューティング
アプリケーションサーバーが動作しているがデータベースに到達できないことがわかったので、次のステップはその理由を特定することです。この問題には、潜在的な問題源を排除することで対処するのが最善です。
データベースにマネージドサービスを使用している場合は、まずクラウドプロバイダーのステータスページを確認してください。ほとんどのクラウドプロバイダーは、インフラストラクチャが稼働しているかどうかを確認する方法を提供しています(例:Digital Ocean、Google Cloud Platform、AWS)。
クラウドプラットフォームの問題を除外したら、次に確認すべきはデータベースインスタンスのログまたはステータスです。
マネージドデータベースのログ
使用しているマネージドデータベースプロバイダーが稼働しているように見える場合、次のステップは、特定のデータベースインスタンスのログを確認することです。
ほとんどのクラウドプラットフォームは、スループットやクエリ統計などの一部のメトリクスに加えて、データベースログを表示する方法を提供しています。ログのレイアウトと詳細レベルはクラウドプロバイダーによって異なりますが、ほとんどのクラウドプロバイダーは問題の検索に役立つ十分な詳細レベルを提供しています。
各クラウドプロバイダーは、データベースログにアクセスするための異なる方法も持っています。ほとんどのプロバイダーは、デプロイ管理ページからログに簡単にアクセスできるようにしています。
例えば、DigitalOceanはデプロイ管理メニューから直接アクセスできる「ログとクエリ」というタブを提供しています。
このセクションには、一般的な「最近のログ」セクションを含む、いくつかの異なる種類のログ情報が含まれています。データベースへの接続の問題や、発生している可能性のあるその他の問題の兆候が見つかるかもしれません。
ネットワークの問題
ネットワーク関連の問題により、データベースが利用できなくなったり、停止しているように見えたりすることがあります。クライアント層、アプリケーション層(バックエンド)、データ層(データベース)の3層からなるアプリケーションでは、これら3つの層間でネットワークの問題が発生する可能性があります。
ネットワーク関連の問題には、以下が含まれます。
- VPCとファイアウォールポリシーの問題
- レイテンシーとアプリケーションとデータベース間のタイムアウト
データベースが停止しているか応答しないように見え、それがネットワーク問題に関連している可能性があると疑われる場合、最初にトラフィックがファイアウォールポリシーによってブロックされているのか、それとも実際のネットワーク問題があるのかを判断するのが最善です。
注:以下の推奨事項は、お客様のアーキテクチャに適用されない可能性がある前提に基づいています。したがって、データベースの問題をデバッグする際には、障害モードとその原因を理解することをお勧めします。
VPC
クラウドプラットフォーム上でデータベースなどのクラウドリソースをプロビジョニングする際、それらは仮想プライベートクラウド(VPC)内に隔離されます。実際には、VPCはアプリケーションリソースのプライベートネットワークとして機能し、パブリックインターネットから隔離されます。データベースとアプリケーションが同じVPC(または同じクラウド)にない場合、通常、アプリケーションのIPがデータベースにアクセスできるようにVPCのファイアウォールルールを設定する必要があります。
ファイアウォールルール
アプリケーションとデータベースを異なるクラウドプラットフォームまたは異なるVPCにデプロイする場合、アプリケーションからデータベースへのアクセスを許可するようにファイアウォールを設定する必要があります。
一部のファイアウォールポリシーは許可されていないトラフィックを黙って破棄するブラックホールポリシーを使用しますが、その他のポリシーは拒否応答を送信します。これは、ファイアウォールが接続障害の原因であるかどうかを判断するのに役立ちます。同様に、「no route to host」タイプのエラーが発生した場合、それはネットワークセグメントがダウンしているか、ルーティングロジックが間違っているかのいずれかを意味する可能性があります。
さらに、アプリケーションのIPが動的である場合、いつでも変更される可能性があります。アプリケーションのIPが変更されると、新しいIPアクセスを許可するようにファイアウォールが更新されるまで、データベースに接続できない場合があります。
ファイアウォールルールの対策
アプリケーションとデータベースを同じVPCと同じリージョンにデプロイして、両者がプライベートネットワーク経由で通信できるようにするのが一般的により良い方法です。これにより、パブリックネットワークが引き起こす可能性のあるボトルネックも回避できます。しかし、それが不可能な場合は、いくつかの解決策があります。
最初のアプローチは、アプリケーションに専用IPを使用し、そのIPからのアクセスを許可するファイアウォールルールを追加することです。このアプローチにより、IPが同じままであるため、ファイアウォールルールを更新する必要がなくなります。
クラウドプラットフォームの制限やデプロイアプローチ(例:サーバーレス)により専用IPの使用が実現できない場合、パブリックネットワーク経由でのデータベースアクセスを有効にすることを検討してください。このアプローチはデータベースのセキュリティを低下させますが、アプリケーションのIPが変更されたときにファイアウォールルールを更新するまで、アプリケーションが突然ダウンすることはありません。さらに、データベースの認証メカニズムが主要な防御線として機能し続けます。
パブリックインターネットからデータベースへの接続を開放することは推奨されません。可能な場合は、特定のIPアドレスへの接続を制限するか、VPCピアリングなどの技術を使用することをお勧めします。
レイテンシーとタイムアウト
アプリケーションとデータベース間の地理的およびネットワーク的距離が大きい場合、リクエストのレイテンシー増加やタイムアウトエラーが問題となることがあります。このようなシナリオでは、タイムアウトエラーを避けるために、アプリケーションのデータベース接続タイムアウトを増やす必要があるかもしれません。
ORMとクエリビルダーは、データベースへの接続プールを保持しています。これらの接続には通常、接続確立時にタイムアウトするまでの待機時間を制御する接続タイムアウト設定があります。
まず初期の基準となるタイムアウト値を設定し、アプリケーションのロードテストを行ってパフォーマンスを確認することをお勧めします。ロードテストでの失敗したリクエスト数に基づいて、タイムアウトを調整し、タイムアウトが問題の原因ではないと確信するまで再試行すると良いでしょう。接続タイムアウトの設定が低すぎると、これらのタイムアウトがリクエストの失敗につながるリスクがあります。
接続制限の枯渇
MySQLやPostgreSQLのような接続ベースのデータベースにおけるもう一つの一般的な課題は、データベースの接続制限をすぐに使い果たしてしまう可能性があることです。接続指向のデータベースは、データベースへの開いている接続の数に制限を課します。
従来の長時間実行プロセスモデルを使用してアプリケーションをデプロイする場合、少数のDB接続プールで多数の受信リクエストを多重化できます。例えば、単一のサーバーインスタンスが20のDB接続プールで100の同時HTTPリクエストを処理する可能性があります。
しかし、サーバーレス関数では、各関数インスタンスは一度に1つのHTTPリクエストしか処理できません。各受信リクエストはデータベースへの少なくとも1つの接続を必要とするため、データベース接続を多重化する方法はありません。
これを踏まえると、データベースの接続制限を使用し、データベースの接続制限を使い果たさないように、接続するサーバーインスタンス間でそれらを分散させることをお勧めします。例えば、データベースの接続制限が20だとします。アプリケーションサーバーのインスタンスが2つある場合、各サーバーインスタンスの接続プールを最大10接続に設定する必要があります。
サーバーレスデプロイメントの場合、PgBouncerのような外部のコネクションプーラーを実行することを検討してください。これは、データベースへの接続を保持し、サーバーレス関数からの受信データベースクエリを多重化する追加のインフラストラクチャコンポーネントです。
データ量に関する問題
アプリケーションが成長するにつれて、そのアプリのデータ量もおそらく増加するでしょう。パフォーマンスとデータベースの稼働時間の両方にとって重要な考慮事項は、特定のリクエストを処理するために処理されるデータ量です。データ総量が少ないときにアプリのために書かれた非効率なクエリは、データが増加するとパフォーマンスを低下させ、停止を引き起こすボトルネックに変わる可能性があります。
大量のデータは、3つの場所で影響を与える可能性があります。
- データベースサーバー
- アプリケーションサーバー
- クライアント
データベースサーバー
アプリケーションサーバーからデータベースクエリが発行されると、データベースサーバーの役割は要求されたデータを取得し、それをアプリケーションサーバーに送り返すことです。この際、データベースサーバーはまず取得したデータを自身のメモリにスキャンする必要があります。データ量が少ない場合、データをメモリにスキャンしてアプリケーションサーバーに転送するプロセスは取るに足りません。しかし、クエリから非常に大量のデータが返される場合、プロビジョニングが不十分なデータベースサーバーは負荷に耐えきれなくなる可能性があります。
このシナリオは、データベースの合計サイズが小さいときに当初は十分だったクエリが、データサイズの増加に伴って適切に最適化されていない場合に頻繁に発生します。
アプリケーションがユーザーの連絡先リストを表示する必要があるシナリオを考えてみましょう。このデータを提供する典型的なデータベースクエリは、そのユーザーに限定されたデータを選択する可能性が高いです。
SELECT * from contacts WHERE userId = 123;
アプリケーションのニーズに応じて、アプリケーションサーバーは結果をクライアントに転送する以外に何もする必要がない場合があります。このクエリがデータ量に関する問題を引き起こす可能性は低いでしょう。
しかし、上記のクエリにWHERE
句がなかったらどうなるかを考えてみましょう。
SELECT * from contacts;
もし代わりに、クエリがデータベース内の_すべての_連絡先を要求し、アプリケーションサーバーが結果をクライアントに転送する前にuserId
に基づいてリストをフィルタリングする責任があった場合、データ量の違いは天文学的になる可能性があります。
全体的なデータ量が比較的少ない場合、この不適切なクエリは検出されない可能性があります。データ量が増加するにつれて、パフォーマンスの低下が目立つようになるかもしれませんが、アプリケーションを完全に壊すほどではないかもしれません。しかし、この状態になると、データベースサーバーはプロビジョニングされた以上の負荷を受けることになります。データ量がさらに増え続けると、データベースサーバーが負荷を処理できなくなる転換点に達する可能性があります。
アプリケーションサーバー
データベースサーバーが大量のデータを処理する無限の能力を持たないのと同様に、アプリケーションサーバーについても同じことが言えます。データベースサーバーよりもアプリケーションサーバーの容量とサイジングをより細かく制御できる可能性はありますが、アプリケーションサーバーの容量を増やすことは不必要な場合があります。
データベースサーバーから返される大量のデータをアプリケーションサーバーで処理するには時間とリソースがかかります。アプリケーションサーバーがクライアントアプリケーションからのリクエストに応答している場合、アプリケーションサーバーでの長い待機は問題となる可能性があります。多くのクラウドホスティングプロバイダーは、数分経過するとリクエストタイムアウトを強制します。Webブラウザは、リクエストが「保留中」の状態になってから数分後に自動的にリクエストを再試行し、システムにさらなる負担をかけます。
クライアント
クライアントアプリケーションは、大量のデータによって引き起こされるボトルネックに最も影響を受けやすい可能性があります。容量を増やすことができるアプリケーションサーバーやデータベースサーバーとは異なり、ブラウザやモバイルデバイスで実行されるクライアントアプリケーションは、ブラウザ、オペレーティングシステム、またはその両方の制限を受けます。
クライアントアプリケーションに過度に大量のデータを送信すると、処理に遅延が生じ、応答性が著しく低下する可能性があります。特定のデータサイズに達すると、ブラウザが完全に反応しなくなり、最終的にはクラッシュする可能性があります。
非常に大量のデータがデータベースからクライアントまで転送される場合、パフォーマンスの低下は途中のすべてのステップで感じられるでしょう。この3つの要因が合わさることで、ロード時間が長くなり、これらのいずれかの時点で停止しているように感じられる可能性があります。問題をさらに複雑にするのは、ボトルネックがどこにあるのかを特定することが難しくなることです。
データサイズの対策
データ量に関する問題によって引き起こされるパフォーマンスの低下や停止の解決策は、ほぼ常にデータベースサーバーから返されるデータ量を制限することです。そうすることで、データベースサーバー、アプリケーションサーバー、およびクライアントにおける問題が緩和されます。
`WHERE`句でデータを絞り込む
上記の架空のクエリ例では、データベースの連絡先テーブル全体がクエリされました。これは、データベースサーバーが目的の結果にフィルタリングする前にテーブル全体をメモリにスキャンする必要があることを意味し、データベースサーバーとアプリケーションサーバーに莫大な負荷をかける可能性があります。
まず、広すぎる範囲を対象としているクエリを探すことから始めます。これらのクエリはデータベースサーバーで処理に長い時間がかかっている可能性があります。長時間実行されているクエリのログを確認すると、これがどこで発生しているかの兆候が得られます。多数の結果を返してからアプリケーションコードでフィルタリングするのではなく、真に必要とされる結果のサブセットを返すようにクエリを構築してください。SQLでは、WHERE
句を使用して、実際に必要なデータを具体的に指定します。
`LIMIT`と`OFFSET`でデータをページ分割する
クライアントアプリケーションで本当に大量のデータが必要な場合は、ページネーションを導入することをお勧めします。
ページネーションは、特定の時点でクエリおよび返されるレコードの総数を制限する設計パターンです。ユーザーがさらに多くのレコードを見たい場合は、明示的にそれらを要求する必要があります。
この設計パターンは、LIMIT
句とOFFSET
句を使用して実装されることが最も多いです。特定の数のレコードをスキップし、返される結果の数を制限することを選択することで、クライアントとアプリケーションサーバーはページネーション体験を実現できます。
最初のページのクエリは次のようになります。
SELECT * from contacts WHERE userId = 123 LIMIT 25 OFFSET 0;
2ページ目のクエリは、OFFSET
値にLIMIT
の値を設定するだけで、次の「ページ」を取得します。
SELECT * from contacts WHERE userId = 123 LIMIT 25 OFFSET 25;
データ量に関する問題のトラブルシューティング
ほとんどのデータベースは、データ量に関する問題のトラブルシューティングに役立つツールを提供しています。特に、多くのデータベースはEXPLAIN
コマンドを提供しており、これによりクエリの実行計画が明らかになり、関連する問題についての洞察が得られます。
例えば、PostgreSQLでは、EXPLAIN
コマンドは検討中のステートメントのクエリプランを表示し、推定実行コストを示します。これは、クエリの実行にかかる時間の見積もりです。
この情報は、ボトルネックを引き起こしている特定のテーブルやクエリステートメントを絞り込むのに役立ちます。
EXPLAIN
コマンドを手動で呼び出すだけでなく、多くのクラウドデータベースプロバイダーは、問題を引き起こしているクエリを自動的に視覚化する方法を提供しています。この情報は、クラウドプロバイダーの「低速クエリ」ビューでよく見られます。アプリケーションに悪影響を与えている可能性のあるクエリを見つけるには、データベースプロバイダーの低速クエリセクションを確認してください。
インデックスの追加
大量のデータに関連する問題は、インデックスを使用することでしばしば解決できます。
データベーステーブルのインデックスは、本の索引と同じように概念化できます。本の中で特定のトピックに関する情報を探している場合、本全体を読み通して見つけるには長い時間がかかります。代わりに、索引を参照して関心のある特定のトピックを調べることができます。もし存在すれば、そのトピックが本の中で議論されている特定のページを教えてくれます。
同じ概念がデータベースインデックスにも適用されます。一般的なアクセスパターンに基づいてデータベーステーブルにインデックスを追加することで、高速なルックアップが可能になります。
例えば、email
列を含むユーザーのデータベーステーブルがあり、そのテーブルがメールアドレスに基づいてユーザーを検索するためにクエリされる場合、email
列に基づいてそのテーブルのインデックスを作成するとパフォーマンスが向上します。インデックスがないと、メールアドレスでユーザーを見つけるためにテーブル全体がスキャンされます。テーブルサイズが非常に大きい場合、これは重大なパフォーマンス問題を引き起こす可能性があります。しかし、その列にインデックスが作成されている場合、特定のメールを検索すると必要な正確な行がすぐに示されるため、ルックアップは高速になります。
データベースにインデックスを追加するには計画と検討が必要であり、データベースがアプリケーションの停止を引き起こす前に実行すべきことです。ただし、停止を引き起こしている特定のクエリが見つかった場合、すぐにインデックスを追加して負荷を軽減することが役立つ場合があります。
互換性のないコード変更
データベースの停止が疑われる場合、クライアントまたはサーバーコードに対して最近行われた互換性のない変更に原因がある可能性があります。これらのケースでは、データベース自体が停止しているのではなく、データを取得し、処理し、クライアントに返すために使用されるコードが壊れている可能性があります。
中断を引き起こす可能性のあるコード変更は、一般的に3つのカテゴリに分類されます。
- データベースクエリ
- サーバーコード
- クライアントコード
データベースクエリ
データベースクエリへの変更は、たとえそれが小さなものであっても、アプリケーションに影響を与える可能性があります。生のSQLを使用している場合、最近のコード変更でクエリステートメントが無効になっている可能性があります。クエリ自体がまだ有効であっても、アプリケーションの他の部分で処理できなくなった結果を返すように変更されている可能性があります。
ソース管理の変更を調べて、データベースアクセス(SQLステートメントやORMの使用)への最近の変更を探します。影響を受けているアプリケーションの特定の領域に関連するクエリを特定するようにしてください。
データベースアクセスへの変更の兆候がない場合、別の可能性として、データベーススキーマが変更されたが、マイグレーションが実行されていないことが考えられます。本番データベースにまだ適用する必要がある可能性のある最近のマイグレーションを探してください。
クライアントおよびサーバーコード
クライアントまたはサーバーのアプリケーションコードへのコード変更は、たとえ小さなものであっても、データベース停止のように見える状況を作り出す可能性があります。問題の根本には多くの具体的な問題がある可能性がありますが、いくつかの例としては次のようなものがあります。
- クライアントが存在しないサーバーエンドポイントを呼び出している可能性がある
- サーバーエンドポイントがペイロードまたはクエリパラメータの検証ルールを変更した
- サーバーが、最近のスキーマ変更によって無効になった返されたデータのプロパティにアクセスしようとしている
まとめ
データベースの停止は、根本的な原因に関わらず、非常にストレスの多い出来事です。アプリケーションを稼働状態に戻そうと急ぐあまり、データベースの問題がどこから来ているのかを適切に推測することは困難になることがあります。
データベースの停止はそれぞれ異なり、発生した際に万能の解決策はありません。しかし、起こりうる問題とそれに関連する解決策について精通しておくことで、問題の原因をより効率的に特定し、稼働状態に戻すまでの時間を短縮することができます。
ほとんどの問題解決と同様に、問題が発生する前に潜在的な解決策に慣れておくことが最善です。このガイドが、あなたがまさにそれを達成するのに役立つことを願っています。