ここで紹介するベスト プラクティスは、Cloud Firestore を使用するアプリケーションを構築する際のクイック リファレンスとしてご利用ください。
データベースのロケーション
データベース インスタンスを作成するときは、ユーザーとコンピューティング リソースに最も近いデータベースのロケーションを選択してください。広範囲に及ぶネットワーク ホップはエラーが発生しやすく、クエリのレイテンシを増加させます。
アプリケーションの可用性と耐久性を最大化するには、マルチリージョン ロケーションを選択し、重要なコンピューティング リソースを少なくとも 2 つのリージョンに配置します。
アプリケーションでレイテンシが重要な場合や、他の GCP リソースとのコロケーションが必要な場合は、費用や書き込みレイテンシを抑えるためにリージョン ロケーションを選択します。
ドキュメント ID
- ドキュメント ID に
.
と..
は使用しないでください。 - ドキュメント ID に
/
(スラッシュ)は使用しないでください。 次のように、単調に増加するドキュメント ID を使用しないでください。
Customer1
,Customer2
,Customer3
, ...Product 1
,Product 2
,Product 3
, ...
このように連続した ID を使用すると、レイテンシに影響を与えるホットスポットが生じる可能性があります。
フィールド名
追加のエスケープが必要になるため、フィールド名に次の文字は使用しないでください。
- ピリオド(
.
) - 左角かっこ(
[
) - 右角かっこ(
]
) - アスタリスク(
*
) - バッククォート(
`
)
- ピリオド(
インデックス
インデックスを過剰に使用しないでください。インデックスの数が多すぎると、書き込みレイテンシが増加し、インデックス エントリのストレージ コストが増加する可能性があります。
読み取りレートや書き込みレートが高いアプリケーションの場合、タイムスタンプのように単調に増加する値を持つフィールドにインデックスを作成すると、レイテンシに影響を与えるホットスポットが生じる可能性があります。
インデックスの除外
ほとんどのアプリでは、インデックスを自動的に作成し、エラー メッセージのリンクでインデックスを管理できます。ただし、次のようなケースでは、単一フィールド除外を追加したほうがよい場合があります。
ケース | 説明 |
---|---|
大きな文字列フィールド | クエリには使用しない長い文字列値を保持することが多い文字列フィールドがある場合は、インデックス作成からそのフィールドを除外することでストレージの費用を削減できます。 |
コレクションへの書き込みレートが高く、連続した値を持つドキュメントが含まれる | コレクション内のドキュメント間で順次に増加または減少するフィールド(タイムスタンプなど)のインデックスを作成する場合は、コレクションへの最大書き込みレートは 500 回/秒です。連続した値を持つフィールドに基づいてクエリを実行することがない場合は、そのフィールドをインデックス作成から除外すれば、この上限を回避できます。 たとえば、書き込みレートが高い IoT のユースケースでは、タイムスタンプ フィールドを持つドキュメントを含むコレクションは、500 回/秒の書き込み上限に近づく可能性があります。 |
大規模な配列フィールドまたはマップ フィールド | 大規模な配列フィールドまたはマップ フィールドの場合、インデックス エントリの上限となっている、ドキュメントあたり 40,000 件に近づく可能性があります。大規模な配列フィールドまたはマップ フィールドに基づいてクエリを実行することがない場合は、そのフィールドをインデックス作成から除外してください。 |
読み取り / 書き込みオペレーション
ドキュメントへの書き込みを 1 秒間に複数回行わないでください。詳しくは、同一ドキュメントに対する更新をご覧ください。
可能な場合は、同期呼び出しではなく非同期呼び出しを使用します。非同期呼び出しでは、レイテンシの影響が最小限に抑えられます。たとえば、ドキュメントの検索結果とクエリの結果に従ってレスポンスをレンダリングするアプリケーションについて考えてみましょう。検索とクエリにデータの依存関係がない場合、検索が完了するまでクエリの開始を同期的に待機する必要はありません。
オフセットは使用しないでください。その代わりにカーソルを使用します。オフセットを使用すると、スキップされたドキュメントがアプリケーションに返されなくなりますが、内部ではスキップされたドキュメントも引き続き取得されています。スキップされたドキュメントはクエリのレイテンシに影響し、このようなドキュメントの取得に必要な読み取りオペレーションは課金対象になります。
トランザクションの再試行
Cloud Firestore の SDK とクライアント ライブラリは、一時的なエラーに対処するために、失敗したトランザクションを自動的に再試行します。アプリケーションが SDK を使用せずに REST API または RPC API を介して直接 Cloud Firestore にアクセスする場合は、信頼性を高めるために、アプリケーションによるトランザクションの再試行を実装してください。
リアルタイム アップデート
最適なスナップショット リスナーのパフォーマンスを得るには、ドキュメントを小さくしてクライアントの読み取りレートを制御します。次の推奨事項は、パフォーマンスを最大化するためのガイドラインです。これらの推奨事項を超えると、通知レイテンシが増加する可能性があります。
最適化案 | 詳細 |
---|---|
スナップショット リスナーのチャーンレートを下げる |
リスナーのチャーンは、特にデータベースに大きな書き込み負荷がかかっている場合は、頻繁に行われないようにしてください。 理想的には、Cloud Firestore への接続を開いた直後に、アプリケーションで必要なスナップショット リスナーがすべて設定されるようにします。最初のスナップショット リスナーを設定した後は、同じ接続でのスナップショット リスナーの追加または削除はすぐに行わないようにします。 データの整合性を維持するために、Cloud Firestore は新しいスナップショット リスナーをそれぞれのソースデータからプライミングしてから、新しい変更に追いつく必要があります。これは、データベースの書き込みレートによっては、負荷の大きいオペレーションになることがあります。 参照のスナップショット リスナーの追加または削除を頻繁に行うと、スナップショット リスナーのレイテンシが長くなることがあります。一般に、常時アタッチされているリスナーのほうが、その場でアタッチ、デタッチされるリスナーよりも、同じ量のデータに対するパフォーマンスが高くなります。最適なパフォーマンスを得るために、スナップショット リスナーの存続期間は 30 秒以上に設定してください。アプリでリスナーのパフォーマンスに関する問題が発生した場合は、アプリのリッスン回数とリッスン停止回数を追跡して、頻度が高すぎていないかどうか判断してください。 |
クライアントごとにスナップショット リスナーを制限する |
100 クライアントごとのスナップショット リスナー数を 100 未満にします。 |
コレクションの書き込みレートを制限する |
1,000 オペレーション/秒 個々のコレクションの書き込みオペレーションのレートを 1,000 オペレーション/秒未満にします。 |
個々のクライアントの push レートを制限する |
1 ドキュメント/秒 データベースが個々のクライアントに push するドキュメントのレートを 1 ドキュメント/秒未満にします。 |
グローバル クライアント push レートを制限する |
1,000,000 ドキュメント/秒 データベースがすべてのクライアントに push するドキュメントのレートを 1,000,000 ドキュメント/秒未満にします。 これはソフトリミットです。Cloud Firestore でこのしきい値の超過が禁止されることはありませんが、超過するとパフォーマンスに大きく影響します。 |
個々のドキュメントのペイロードを制限する |
10 KiB/秒 個々のクライアントによってダウンロードされる最大ドキュメント サイズを 10 KiB/秒未満にします。 |
グローバル ドキュメント ペイロードを制限する |
1 GiB/秒 すべてのクライアントでダウンロードできる最大ドキュメント サイズを 1 GiB/秒未満にします。 |
ドキュメントごとのフィールド数を制限する |
100 ドキュメントは 100 フィールド未満にする必要があります。 |
Cloud Firestore の標準的な上限について |
Cloud Firestore の標準的な上限に注意してください。 特に、ドキュメントの書き込み上限は 1 秒あたり 1 回、同時接続数の上限はデータベースあたり 1,000,000 件であることに注意してください。これらの上限はソフトリミットであり、Cloud Firestore によって超過が制限されるものではありません。ただし、これらの上限を超えると、全体的な読み取りと書き込みのレートによっては、パフォーマンスに影響することがあります。 |
Cloud Functions
1 秒あたり 2,000 を超える Cloud Firestore イベントが発生する Cloud Functions 関数を有効にすると、一時的にエラー率が上昇し、レイテンシが数分間一時的に増加する可能性があります。トラフィックの多い関数を有効にする前に、サポートにお問い合わせください。トラフィックの多い関数に合わせてデータベースを準備し、レイテンシの増加を回避できます。
スケールを考慮して設計する
ここでは、競合の発生を防ぐためのベスト プラクティスについて説明します。
同一ドキュメントに対する更新
同じドキュメントを 1 秒間に何度も更新しないでください。ドキュメントを頻繁に更新しすぎると、アプリケーションで競合が発生します。レイテンシの増加やタイムアウト、その他のエラーなども発生します。
狭いドキュメント範囲に対する高頻度の読み取り、書き込み、削除
辞書順で近い一連のドキュメントに対して、高頻度で読み取りや書き込みを行わないでください。この問題はホットスポットといいます。次のいずれかを行うと、アプリケーションにホットスポットが生じる可能性があります。
非常に高い頻度で新しいドキュメントを作成し、単調に増加する ID を割り当てる。
Cloud Firestore では、散布アルゴリズムでドキュメント ID が割り当てられます。新しいドキュメントのドキュメント ID を自動的に割り当てている場合、書き込み時にホットスポットは生じません。
ドキュメント数が少ないコレクションで、新しいドキュメントを頻繁に作成する。
タイムスタンプのように単調に増加するフィールドを持つ新しいドキュメントを非常に高い頻度で作成する。
コレクション内のドキュメントを高頻度で削除する。
トラフィックを徐々に増やすことなく、高頻度でデータベースに書き込みを行う。
削除されたデータをスキップするようなクエリを避ける
最近削除されたデータをスキップするようなクエリの使用を避けてください。前のクエリの結果が直前に削除された場合、その後のクエリで多数のインデックス エントリをスキップしなければならない場合があります。
大量の削除データをスキップしなければならない可能性があるワークロードの例としては、キューに含まれている最も古い作業アイテムを見つけるという操作が挙げられます。そのようなクエリの例を以下に示します。
docs = db.collection('WorkItems').order_by('created').limit(100)
delete_batch = db.batch()
for doc in docs.stream():
finish_work(doc)
delete_batch.delete(doc.reference)
delete_batch.commit()
このクエリを実行するたびに、直前に削除されたドキュメントであるかどうかを調べるために、created
フィールドのインデックス エントリをスキャンします。これにより、クエリの処理速度が低下します。
パフォーマンスを向上させるには、start_at
メソッドを使用して開始するのに適した場所を見つけます。次に例を示します。
completed_items = db.collection('CompletionStats').document('all stats').get()
docs = db.collection('WorkItems').start_at(
{'created': completed_items.get('last_completed')}).order_by(
'created').limit(100)
delete_batch = db.batch()
last_completed = None
for doc in docs.stream():
finish_work(doc)
delete_batch.delete(doc.reference)
last_completed = doc.get('created')
if last_completed:
delete_batch.update(completed_items.reference,
{'last_completed': last_completed})
delete_batch.commit()
注: 上記の例では単調増加するフィールドを使用していますが、これは書き込みレートが高い場合にはアンチパターンです。
トラフィックを徐々に増やす
Cloud Firestore がトラフィックの増加に合わせてドキュメントを準備できるように、新しいコレクションまたは辞書順で近いドキュメントに対するトラフィックを徐々に増やしていく必要があります。新しいコレクションに対するオペレーションは、毎秒 500 回を上限とし、その後、5 分ごとにトラフィックを 50% 増やしていくことをおすすめします。書き込みトラフィックも同様に増やすことができますが、Cloud Firestore の標準アカウントには上限があります。オペレーションがキー範囲全体に比較的均等に分散するよう注意してください。これは「500/50/5」ルールといいます。
新しいコレクションへのトラフィックの移行
アプリのトラフィックをコレクション間で移行する場合、段階的な増加は特に重要になります。この移行を簡単に処理するには、まず古いコレクションから読み取りを行い、ドキュメントが存在しない場合に新しいコレクションから読み取りを行います。ただし、新しいコレクション内で辞書的に近いドキュメントのトラフィックが急増する可能性があります。Cloud Firestore では、トラフィックの増加に合わせて新しいコレクションを効率的に準備できない可能性があります(特にドキュメントが少ない場合)。
同じコレクション内で多くのドキュメントの ID を変更した場合も、同様の問題が発生する可能性があります。
トラフィックを新しいコレクションに移行する最善の方法はデータモデルによって異なります。以下では「同時読み込み」という方法について説明します。この方法が実際のデータに対して効果的かどうかは、ご自身で判断する必要があります。また、移行中の同時操作によるコスト増も考慮する必要があります。
同時読み取り
トラフィックを新しいコレクションに移行するときに同時読み取りを行うには、まず古いコレクションから読み取りを行います。ドキュメントが見つからない場合は、新しいコレクションから読み取ります。存在しないドキュメントを高頻度で読み取るとホットスポットが生じる可能性があるため、新しいコレクションへの負荷を徐々に増やしていく必要があります。より良い方法としては、古いドキュメントを新しいコレクションにコピーしてから古いドキュメントを削除する方法があります。Cloud Firestore が新しいコレクションへのトラフィックを確実に処理できるように、同時読み取りを徐々に増やしていきます。
また、新しいコレクションへの読み取りや書き込みを徐々に増やす方法として、ユーザー ID の決定論的ハッシュを使用して、新しいドキュメントに書き込みを行うユーザーの割合をランダムに選択する方法も考えられます。この場合、ユーザー ID ハッシュの結果が関数やユーザーの行動によって偏らないようにする必要があります。
古いドキュメントから新しいコレクションにすべてのデータをコピーするバッチジョブを実行する方法もあります。ホットスポットを防ぐため、バッチジョブでは連続したドキュメント ID への書き込みを避ける必要があります。バッチジョブが終了すると、新しいコレクションからのみ読み取りが可能になります。
この方法を改善してみましょう。まず、一度に移行するユーザーを小さいバッチにまとめます。そのユーザーの移行ステータスを追跡するフィールドをユーザー ドキュメントに追加します。ユーザー ID のハッシュに基づいて、移行するユーザーのバッチを選択します。このユーザーのバッチのドキュメントを移行するバッチジョブを使用して、移行の途中でユーザーの同時読み取りを行います。
ロールバックは簡単にはできません。ロールバックを行うには、移行段階が完了するまで新旧両方のエンティティに二重に書き込む必要があります。ただし、これを行うと Cloud Firestore の使用料が増加します。
不正アクセスを防止する
Cloud Firestore セキュリティ ルールを使用して、データベースに対する不正オペレーションを防止します。たとえば、ルールを使用することによって、悪意のあるユーザーがデータベース全体を繰り返しダウンロードする行為を防止できます。
詳しくは、Cloud Firestore セキュリティ ルールの使用をご覧ください。