はじめに
firebaseでアプリを開発しており、ドキュメントに対して採番ルールを設け、新たなドキュメントが作成されるたびに、1ずつ追加され、新たな番号が付与されるというのをcloud funtionsで実装する必要がありました。
最初の実装
主にここはチームメンバーが実装してくれたのですが、いろいろな技術記事を探すと、
- firestoreに採番用のドキュメントを1つ用意
- そのドキュメントを全件取得し、Math.max()で最大値を取得
- 最大値に+1をして、新たなドキュメントを作る
という方法が紹介されており、この方法で実装しました。
我々のチームで実装したコードは以下のような形でした。
let existsTodoNumber;
const todoNumberList = await firestore.collection("TodoNumbers").get();
const todoNumberData = todoNumberList.docs.map((doc) => {
const replaceTodoNumber = doc.data().todoNumber.replace("ABC", "");
return { todoNumber: replaceTodoNumber };
});
if (todoNumberData.length === 0) {
existsTodoNumber = 0;
} else {
//todoNumberDataから一番大きい数値を取得
const maxTodoNumber = Math.max(...todoNumberData.map((d) => d.todoNumber));
existsTodoNumber = maxShipmentNumber;
}
const newTodoNumber = existsTodoNumber + 1;
const convertTodoNumber = "ABC" + String(newTodoNumber);
問題点
先輩エンジニアにレビューを頂いたところ、以下のような指摘をもらいました。
- この実装だと、todoNumberが増えれば増えるほど、最大値を取得するときの、ドキュメント取得サイズが線形的に増えていく(N+1)
- readのコストも、functionsのメモリもサービスの成長に連れて大量消費していく実装になる。
- 単純な連番の数字を保存するためのfieldを用意すれば、クエリ1回で最大値の取得が可能。
実際にリファクタリングしたコード
指摘事項を踏まえ、以下のように修正しました。
let existsTodoNumber;
const existsTodoNumberData = await firestore
.collection("TodoNumbers")
.orderBy("value", "desc")
.limit(1)
.get();
if (existsTodoNumberData.empty) {
existsTodoNumber = 0;
} else {
existsTodoNumber = existNumberData.docs[0].data().number;
}
const newNumber = existsTodoNumber + 1;
const convertTodoNumber = "ABC" + String(newNumber);
所感
やっていることは同じでも、パフォーマンスを考慮すると書き方が変わって非常に勉強になりました。
またこのコペルニクス的転換はプログラミングの極みであると感じ、最高におもしろいと思いました。
メインでの実装は私ではなく、チームメンバーであり、私は頂いたアドバイスをもとに、リファクタリングしただけなので、記事での発信をためらいましたが、
firestoreの採番をクエリを使った実装例の記事がなさそうだったので、記事を書くことに決めました。
同じような採番実装でお悩みの方に少しでもお役に立てれば幸いです。