Firestoreで連番IDを実装する方法

2024/10/20に公開

はじめに

Firestoreで連番IDを扱うのは、その特性上ナンセンスです。
ビジネス上特に必要がないのなら、要件から外してもらうよう動くことをオススメします。

どうしても実装せざるを得なくなってしまった時のために、この記事を残しておくことにします。

環境

  • java 11.0.12
  • firestore-admin 8.1.0

実装方法

連番IDをフィールドに持つコレクションを実装します。
※ここではbooksという名前のコレクションを例に作ります。

- books
  - Firestore自動採番ID #ドキュメントID
    - id #フィールド
      - 1 #連番ID
    - name
      - なんかすごい本
  - Firestore自動採番ID #ドキュメントID
    - id
      - 2
    - name
      - 残念な本

次に、採番用のコレクション(numberings)を実装します。
このbooksIdのid値は採番用の値です。

- numberings
  - booksId #ドキュメントID
    - id
      - 2

データ登録をトランザクションで実装します。
採番用のid値を+1する処理とbooksへのデータ登録を同一トランザクションで行うよう実装します。


var booksRef = db.collection("books");
var numberingsRef = db.collection("numberings");

// トランザクション開始(悲観ロック)
var futureTransaction = db.runTransaction(transaction-> {

    var numberingBooksRef = numberingRef.document("booksId");
    // 現在の採番Id値を取得
    // ここでロックがかかる
    var snapshot = transaction.get(numberingBooksRef).get();
    var nowIdObj = snapshot.get("booksId");
    var nowId = 0;

    if (nowIdObj != null) {
        nowId = Integer.parseInt(nowIdObj.toString());
    }

    // 次のId値を算出
    var nextId = nowId + 1;

    // 採番DocのId値を更新
    var numberingData = new HashMap<>();
    numberingData.put("id", nextId);
    transaction.set(docRef, numberingData);

    //  採番IDを利用し、新しいDocを登録
    var newDataDocRef = booksRef.document();
    var docData = new HashMap<>();
    docData.put("name", "book" + nextId);
    docData.put("id", nextId);
    transaction.set(newDataDocRef, docData);

    return null;
})

これでbooksにデータを登録するごとに、連番IDがフィールドに追加されます。

Firestoreで連番IDが推奨されない理由について

オートインクリメント機能がない

RDBだとスキーマにAutoIncrementを設定するだけで、データ登録時に自動で連番を入れてくれます。
ただしFirestoreにはそんな機能はありません。連番を入れたい場合はプログラム側で実装してあげる必要があります。

ホットスポット

ドキュメントIDを連番にするとホットスポットと呼ばれる問題が発生する可能性があります。

ホットスポットとは特定の箇所にアクセスが集中することでパフォーマンスが低下する問題のことです。

連番IDを実装するにあたり一番に思いつく実装は、以下のようにドキュメントIDを連番IDにする方法だと思います。

- books
  - 1 #ドキュメントID
    - name
      - なんかすごい本
  - 2 #ドキュメントID
    - name
      - 残念な本

FirestoreではドキュメントIDをある種の乱数値にすることで、特定のストレージ領域にアクセスが集中しないようにしています。

ドキュメントIDを連番にしてしまうと、データが特定領域に保存されてしまいます。この状態でクライアントからの高頻度アクセスが発生した場合、特定ストレージ領域にアクセスが集中してしまい、結果パフォーマンスの低下を起こしてしまいます。

今回は連番IDをフィールドに持たせることでホットスポットを回避しました。

別解として連番IDをビット順逆転したものをドキュメントIDに設定するという考え方もあるようです。

おわりに

RDBだと一瞬でできることがFirestoreだとこんなにめんどくさいと思わなかったです。
NoSQLはほんと難しい。

Discussion