# Firestore のルールが参照するドキュメントは、書き込み順序を決める ([English](firestore-rules-write-order.en.md)) Firestore のセキュリティルールで、こういう書き方をしていませんか。 ``` function isMember(groupId) { return exists(/databases/$(database)/documents/groups/$(groupId)/members/$(request.auth.uid)); } match /groups/{groupId} { allow read: if isMember(groupId); } ``` **このとき `members/{uid}` は、ただのドキュメントではなくなります。** **後続の読み書きすべての前提**になり、**他のドキュメントと並列に扱えなくなります。** エラーメッセージは `PERMISSION_DENIED` としか出ないので、 ルールの書き方を疑って時間を使うことになります。**ルールは正しく、順序だけが違います。** --- ## 参加のとき — 前提を先に置く グループへの参加で、複数のドキュメントに書くとします。 **触る対象が違うので、並列にしてよさそうに見えます。** ```dart // 通らないことがある await Future.wait([ writeMembership(), // ← これだけルールが見ている writeProfile(), writeSomethingElse(), ]); ``` `Future.wait` は**どれが先に届くかを決めません。** メンバー記録が届く前にグループを読みにいくと、**ルールから見ればまだメンバーではない**ので弾かれます。 ```dart // 前提を先に確定させる await writeMembership(); await Future.wait([writeProfile(), writeSomethingElse()]); ``` ### 症状が分かりにくい **再起動すると成功します。** そのころには書き込みが届いているからです。 「処理が終わらないが、アプリを立ち上げ直すと参加できている」という状態は、 **データの書き込みではなく、その直後の読み取りが失敗している**と考えると当たります。 --- ## 削除のとき — 前提を最後まで残す **同じルールが削除にも効きます。** 記録を消すにも「メンバーである」ことが条件だからです。 ```dart // 消し残しが出る await Future.wait([ deleteMembership(), // ← 先に届くと、この時点で権限を失う deleteRecords(), deleteHalls(), ]); ``` メンバー記録が先に消えると、**残りの削除が `PERMISSION_DENIED` で弾かれます。** そして**もうメンバーではないので、二度と消せません。** ```dart // 前提を最後に外す await Future.wait([deleteRecords(), deleteHalls()]); await deleteMembership(); ``` ### 画面上は消えたように見える 一覧から消えるのは**自分の参照が切れたから**で、実体は残っています。 **気づくのは、別の端末で見たときや、再参加したときです。** --- ## 見分け方 **`rules` ファイルで `exists(` と `get(` を検索します。** ``` grep -nE "exists\(|get\(" firestore.rules ``` **そこに出てくるパスが、順序を守る必要のあるドキュメント**です。 ルールの中でドキュメントを参照すると、それは**「このドキュメントがあるかどうかで、 他の操作の可否が変わる」**という宣言になります。**依存関係がルール側に書かれている**ので、 アプリのコードだけを読んでも気づけません。 --- ## 原則 **並列化してよいかを「触る対象が違うか」で判断しない。** Firestore では、**ドキュメントどうしが対等ではありません。** ルールが参照しているものは、他のすべての前提です。 | 操作 | 前提となるドキュメント | | --- | --- | | 作成・参加 | **最初に、単独で書く** | | 通常の読み書き | 並列でよい | | 削除・脱退 | **最後に、単独で消す** | 「作るときは最初、消すときは最後」——どちらも、 **前提が成立している時間を、それを必要とする処理より長くする**という同じ形です。 --- ## 症状から引く表 | 症状 | 疑うところ | | --- | --- | | 処理が終わらないが、再起動すると成功している | 書き込みの完了前に読み取りが走っている | | `PERMISSION_DENIED` が出るのにルールは正しい | ルールが見ているドキュメントがまだ無い | | 削除したのに別の端末では残っている | 権限を先に失って、残りが消せていない | **3つとも「設定が間違っている」ようには見えません。** ルールも権限も正しく、**順序だけが違います。** --- 2026-08 時点の記録。Flutter / Cloud Firestore。