はじめに
データベースの停止が疑われる場合、誰にとってもストレスの多い出来事になります。ユーザーは不満を募らせ、データが失われるのではないかと心配し、開発者は原因を突き止めようと奔走し、企業の利害関係者は収益の損失につながる可能性のあるすべての時間を数えます。
停止が発生した場合にデータベースを正常な状態に戻すことが最優先事項です。しかし、そのようなストレスの多い状況の中では、問題が何であるかを推論することさえ困難な場合があります。この記事は、データベースがダウンしている最も一般的な理由と、それを修正するためにできることについて、皆様を導くことを目的としています。
この記事では、データベースの停止に対処する際に確認すべきさまざまな領域について説明します。以下を含みます。
問題 | 潜在的な問題 | アクション |
---|---|---|
データベースが接続を受け入れないように見える | データベースユーザーの認証情報の問題、接続文字列の問題、接続制限に達した | データベースサーバーのログを調べて原因を特定する |
ネットワーク通信が損なわれているように見える | 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 にデプロイする場合は、アプリケーションからデータベースへのアクセスを許可するようにファイアウォールを設定する必要があります。
一部のファイアウォールポリシーは、許可されていないトラフィックをサイレントにドロップするためにブラックホールポリシーを使用しますが、他のポリシーは拒否応答を送信します。これは、ファイアウォールが接続の中断の原因であるかどうかを判断するのに役立ちます。同様に、「ホストへのルートなし」タイプのエラーが発生した場合、ネットワークセグメントがダウンしているか、ルーティングロジックが正しくないかのいずれかを意味する可能性があります。
さらに、アプリケーションの IP アドレスが動的な場合、いつでも変更される可能性があります。アプリケーションの IP アドレスが変更されると、ファイアウォールが更新されて新しい IP アドレスのアクセスが許可されるまで、データベースへの接続に失敗する可能性があります。
ファイアウォールルール対策
一般的に、アプリケーションとデータベースを同じ VPC および同じリージョンにデプロイして、2 つがプライベートネットワーク経由で通信するようにすることをお勧めします。また、パブリックネットワークで発生する可能性のあるボトルネックを防ぎます。ただし、それが不可能な場合は、いくつかの可能な修正方法があります。
最初のアプローチは、アプリケーション専用の IP アドレスを使用し、その IP アドレスからのアクセスを許可するファイアウォールルールを追加することです。このアプローチにより、IP アドレスは変わらないため、ファイアウォールルールを更新する必要がなくなります。
クラウドプラットフォームの制限またはデプロイメントアプローチ(例:サーバーレス)により、専用 IP アドレスの使用が実現可能でない場合は、パブリックネットワーク経由でのデータベースへのアクセスを有効にすることを検討してください。このアプローチはデータベースのセキュリティを低下させますが、アプリケーションの IP アドレスが変更されたときにファイアウォールルールを更新するまで、アプリケーションが突然ダウンタイムを経験することはありません。さらに、データベースの認証メカニズムが主要な防御線として残ります。
パブリックインターネットからデータベースへの接続を開放することは推奨されないことに注意してください。可能な限り、接続を特定の IP アドレスに制限するか、VPC ピアリングなどの手法を使用することをお勧めします。
遅延とタイムアウト
アプリケーションとデータベース間の地理的およびネットワーク距離が大幅に大きい場合、リクエストの遅延の増加とタイムアウトエラーが問題になる可能性があります。このようなシナリオでは、タイムアウトエラーを回避するために、アプリケーションのデータベース接続タイムアウトを長くする必要がある場合があります。
ORM および クエリビルダーは、データベースへの接続プールを保持します。これらの接続には通常、接続を確立するときにタイムアウトするまでの待機時間を制御する接続タイムアウト構成があります。
最初にベースラインタイムアウト値を設定し、アプリケーションをロードテストしてパフォーマンスを確認することをお勧めします。ロードテストからの失敗したリクエストの数に基づいて、タイムアウトが問題の原因ではないと確信するまで、タイムアウトを調整して再試行することをお勧めします。接続タイムアウト構成が低すぎると、これらのタイムアウトがリクエストの失敗につながるリスクがあります。
接続制限の枯渇
MySQL や PostgreSQL などの接続ベースのデータベースのもう 1 つの一般的な課題は、データベースの接続制限をすぐに使い果たしてしまう可能性があることです。接続指向のデータベースは、データベースへのオープン接続数に制限を課します。
従来からの長時間実行プロセスモデルを使用してアプリケーションをデプロイする場合、プール内のより少ない 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 ブラウザーは、「保留中」状態のリクエストが数分間続くと、リクエストを自動的に再試行し、システムにさらに負荷をかけます。
クライアント
クライアントアプリケーションは、大量のデータによって引き起こされるボトルネックの影響を最も受けやすい可能性があります。容量を増やすことができるアプリサーバーやデータベースサーバーとは異なり、ブラウザーまたはモバイルデバイスで実行されるクライアントアプリケーションは、ブラウザー、オペレーティングシステム、またはその両方の制限を受けます。
過度に大量のデータをクライアントアプリケーションに送信すると、処理の遅延が発生し、応答性が著しく低下する可能性があります。特定のデータサイズに達すると、ブラウザーが完全に応答しなくなり、最終的にクラッシュする可能性があります。
非常に大量のデータがデータベースからクライアントまでずっと転送される場合、パフォーマンスの低下はすべてのステップで感じられます。このトリフェクタは、ロード時間の長期化、およびこれらのポイントのいずれかでの認識される停止につながります。問題を悪化させるのは、ボトルネックがどこにあるかのトラブルシューティングが困難になる可能性があることです。
データサイズ対策
データ量の問題によって引き起こされるパフォーマンスの低下と停止の修正は、ほとんどの場合、データベースサーバーから返されるデータ量を制限することです。そうすることで、データベースサーバー、アプリサーバー、およびクライアントでの問題を軽減できます。
WHERE
句でデータの範囲を絞り込む
上記の架空のクエリの例では、データベースの連絡先テーブル全体がクエリされました。これは、データベースサーバーが目的の結果にフィルタリングする前にテーブル全体をメモリにスキャンする必要があることを意味し、データベースサーバーとアプリサーバーに大きな負荷がかかる可能性があります。
広すぎる範囲をキャストしているクエリを探すことから始めます。これらのクエリは、データベースサーバーでの処理に時間がかかっている可能性があります。長時間実行されているクエリのログを見ると、どこで発生しているかを示すことができます。多くの結果を返してからアプリコードでフィルタリングする代わりに、実際に必要な結果のサブセットを返すようにクエリを構築します。SQL では、実際に必要なデータについて具体的に指定するために WHERE
句を使用します。
LIMIT
および OFFSET
でデータをページネーションする
大量のデータがクライアントアプリケーションに本当に必要な場合は、ページネーションを導入することをお勧めします。
ページネーションは、一度にクエリおよび返されるレコードの総数を制限する設計パターンです。ユーザーがより多くのレコードを表示したい場合は、明示的にリクエストする必要があります。
この設計パターンは、LIMIT
および OFFSET
句を使用して実装されることが最も一般的です。特定の数のレコードをスキップし、返される結果の数を制限することを選択することで、クライアントとアプリサーバーはページネーションエクスペリエンスを実現できます。
最初のページのクエリは次のようになるかもしれません。
SELECT * from contacts WHERE userId = 123 LIMIT 25 OFFSET 0;
次の「ページ」を取得するために、2 ページ目のクエリは LIMIT
の値を OFFSET
値に設定するだけです。
SELECT * from contacts WHERE userId = 123 LIMIT 25 OFFSET 25;
データ量に関する問題のトラブルシューティング
ほとんどのデータベースは、データ量に関する問題のトラブルシューティングに役立つツールを提供しています。特に、多くのデータベースは EXPLAIN
コマンドを提供しており、クエリの実行計画を表示し、関連する問題に関する洞察を提供します。
例えば、PostgreSQL では、EXPLAIN
コマンドは検討中のステートメントのクエリプランを表示し、推定実行コストを表示します。これは、クエリの実行にかかる時間の見積もりです。
この情報は、ボトルネックの原因となっている特定のテーブルまたはクエリステートメントを絞り込むのに役立ちます。
EXPLAIN
コマンドを手動で呼び出すことに加えて、多くのクラウドデータベースプロバイダーは、問題を引き起こしているクエリを自動的に視覚化する方法を提供しています。この情報は、クラウドプロバイダーの「低速クエリ」ビューによくあります。データベースプロバイダーの低速クエリセクションを確認して、どのクエリがアプリケーションに悪影響を与えている可能性があるかを確認してください。
インデックスの追加
大量のデータ量に関連する問題は、多くの場合、インデックスを使用することで改善できます。
データベーステーブルのインデックスは、本の索引と同じように概念化できます。本の中で特定のトピックに関する情報を探している場合、それを見つけるために本全体を読むのに長い時間がかかるでしょう。代わりに、索引を参照して、目的の特定のトピックを調べることができます。存在する場合、そのトピックが本の中で議論されている特定のページを教えてくれます。
同じ概念がデータベースインデックスにも適用されます。一般的なアクセスパターンに基づいてデータベーステーブルにインデックスを追加すると、高速な検索が可能になります。
例えば、email
の列を含むユーザーのデータベーステーブルがあり、そのテーブルがメールアドレスに基づいてユーザーを検索するためにクエリされる場合、email
列に基づいてそのテーブルのインデックスを作成すると、パフォーマンスが向上します。インデックスがない場合、メールアドレスでユーザーを見つけるためにテーブル全体がスキャンされます。テーブルサイズが非常に大きい場合、これは重大なパフォーマンス問題を引き起こす可能性があります。しかし、その列にインデックスが作成されている場合、特定のメールアドレスを検索すると、必要な正確な行がすぐに示されるため、検索は高速になります。
データベースにインデックスを追加するには、計画と検討が必要であり、データベースがアプリケーションで停止を引き起こす前に実行する必要があることです。ただし、停止を引き起こしている特定のクエリを特定できる場合は、インデックスを追加してすぐに負荷を軽減することが役立ちます。
コード変更による障害
疑われるデータベースの停止は、クライアントまたはサーバーコードに加えられた最近の破壊的な変更に遡る可能性があります。これらの場合、停止を経験しているのはデータベース自体ではなく、データを取得、処理し、クライアントに返すために使用されるコードが破損している可能性があります。
中断を引き起こす可能性のあるコード変更は、一般的に 3 つのカテゴリに分類されます。
- データベースクエリ
- サーバーコード
- クライアントコード
データベースクエリ
データベースクエリへの変更は、たとえ小さなものであっても、アプリケーションに影響を与える可能性があります。生の SQL を使用している場合、クエリステートメントは最近のコード変更で無効になった可能性があります。クエリ自体がまだ有効な場合でも、アプリケーションの他の部分で処理できなくなった結果を返すように変更されている可能性があります。
ソース管理の変更を調べて、データベースアクセス(SQL ステートメントまたは ORM の使用)への最近の変更を探してください。影響を受けているアプリケーションの特定の領域に関連するクエリを特定してみてください。
データベースアクセスへの変更の兆候がない場合、別の可能性は、データベーススキーマが変更されたが、マイグレーションが実行されていないことです。まだ本番データベースに適用する必要がある可能性のある最近のマイグレーションを探してください。
クライアントおよびサーバーコード
クライアントまたはサーバーのアプリコードへのコード変更は、たとえ小さなものであっても、データベースの停止のように見えるものを作成する可能性があります。問題の根本原因となる可能性のある特定の多くの問題がありますが、いくつかの例を挙げます。
- クライアントが存在しなくなったサーバーエンドポイントを呼び出している可能性があります。
- サーバーエンドポイントがペイロードまたはクエリパラメータの検証ルールを変更しました。
- サーバーが、最近のスキーマ変更で無効になった返されたデータからプロパティにアクセスしようとしています。
結論
データベースの停止は、根本的な原因に関係なく、非常にストレスの多いイベントになる可能性があります。アプリケーションを復旧させようと急いでいると、データベースの問題がどこから発生しているのかを適切に推論することが難しくなる場合があります。
すべてのデータベースの停止は固有であり、発生した場合に万能な解決策はありません。ただし、考えられる問題とそれに関連する解決策をよく知っていると、問題の原因をより効率的に特定し、復旧にかかる時間を短縮するのに役立ちます。
ほとんどの問題解決と同様に、問題が発生する前に問題の潜在的な解決策に慣れておくのが最善です。このガイドが、まさにそれを達成するためのお役に立てれば幸いです。