Firebase Cloud Firestoreをバックアップする場合のファイル形式
Firebase Cloud Firestoreをバックアップする場合のファイル形式
前回は、Firebase Cloud FirestoreのデータをFirebase admin SDKの管理ツールを使ってバックアップする基本について解説しました。 実際にバックアップをする場合、FirebaseのCloud Firestoreにリストアする必要があります。
今回は実際にリストアする場合を考慮した場合にどんなファイル形式で保存したらよいかを考えてみます。
Firebase Cloud FirestoreのコレクションのデータはJSON形式
MySQLなどのSQLのデータベースの場合、データの基本が「テーブル」になります。従ってテーブル形式のデータを維持できるファイル形式がバックアップする場合には都合が良いことになります。 シンプルに作る場合は、テーブルの行(row)の各要素を「,」で区切って、最初に各項目の見出しをつけたCSV形式のデータが便利です。ただ、CSVファイルだと一つの表しか保存できないのでちょっと不便です。全ての表を一つのCSVファイルに保存することも可能ですが、扱いもちょっと面倒です。Microsoftの表計算ソフトのExcel形式で保存すると、Excelで保存したテーブルが参照できるのに加えて、複数の表を「sheet」として一つのファイルにまとめる事ができるので便利です。(実際にこうしたバックアップをするかは別の話です)
一方でFirebase Cloud Firestoreの場合、コレクションに格納されるドキュメントのデータはJSON形式のデータの配列になります。仮に全てのドキュメントが同じ項目のデータを持つ場合でも、Firebaseから読み出される各項目の順序は全てのドキュメントで同じにならない場合があります。
これは、通常JavascriptでFirebase Cloud Firestoreからデータを読み出して使う場合は余り大きな問題ではないのですが、CSVやExcelの表形式で保存しようとするとちょっと処理が面倒になります。
従って、シンプルで便利な形式は、JSON形式のテキストとして保存してしまうのが便利です。
保存の手順
まずは保存する手順です。
* Firebase Cloud Firestoreのコレクションのリファレンスを指定
* Firebase Cloud Firestoreの指定されたコレクションから全てのドキュメントを読み出す
* 読み出したデータ(QuerySnapshot)の中の各ドキュメント(DocumentData)をテキスト化(JSON.stringify)
* ファイルにテキストデータとして書き出して保存
という処理をすると、JSON形式のテキストファイルとして保存できます。
前回のコードを少し手直しをすると、JSON形式の配列のテキストとして保存できます。
import * as admin from "firebase-admin";
const fs: any = require("fs");
const serviceAccount = require("../serviceAccountKey.json");
const firebaseAdmin = admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: "https://sample-xxxxx.firebaseio.com",
});
firebaseAdmin
.firestore()
.collection("address")
.get()
.then((querySnapshot: admin.firestore.QuerySnapshot) => {
let contents: Array<string> = [];
querySnapshot.forEach(
(
doc: admin.firestore.QueryDocumentSnapshot<admin.firestore.DocumentData>
) => {
const entry: any = doc.data();
entry.docId = doc.id;
contents.push(JSON.stringify(entry));
}
);
let text: string = "";
for (let i = 0; i < contents.length; i++) {
if (i === 0) {
text = contents[i];
} else {
text = text + ",\n" + contents[i];
}
}
// 作成したテキストの前後に「[」と「]」を付けて配列として扱う
fs.writeFile("backup.txt", "[" + text + "]", (err: any) => {
if (err) {
console.log(err);
}
});
});
各ドキュメントごとに改行を入れたテント、作成したテキストの前後に「[」と「] 」を付けて配列であることを示しています。
生成したファイルの例です。
[{"name":"Taro Yamada","email":"taro.yamada@xxxx.xxx"},
{"email":"hanako.yamada@gmail.com","name":"Hanako Yamada"}]
ファイルをリストアする例
では、このファイルをリストアする例ですが、生成したファイルを読み込んでJSON形式のデータに変換して、Firebase Cloud Firestoreに書き出すという処理をすればリストアできます。
ファイルの読み込みは、Javascriptが標準でサポートしているモジュール「fs」を使います。このモジュールは読み込みでも使用しています。
以下のサンプルコードは、JSON形式のデータに変換するところまでです。これに、Firebase Cloud Firestoreのコレクションに配列内の各JSONデータを保存すればリストア完了です。
Firebase admin SDKの初期化のやり方は、データを読み出す例と全く同じです。あとは、リストアするコレクションのリファレンスを指定して、書き出せは良いだけです。
const fs = require("fs");
fs.readFile("backup.txt", "utf8", (err: any, data: string) => {
if (err) {
console.log(err);
} else {
const json = JSON.parse(data);
console.log(json);
}
});
完全な復元をするには
基本的なバックアップとリストアはこれまでした方法でできます。 ただし、完全に同じ状態のデータベースを復元する場合、ドキュメントのIDも同じように復元しなければいけない場合があります。特にセキュリティルールでドキュメントのIDを使ってルールを作っている場合はドキュメントのIDが変わってしまうとルールが機能しなくなってしまいます。
そこで、最初のバックアップを作成する際に、ドキュメントのIDを「docId」として保存しておくと完全な復元が可能です。
const fs: any = require("fs");
const serviceAccount = require("../serviceAccountKey.json");
const firebaseAdmin = admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: "https://sample-xxxxx.firebaseio.com",
});
firebaseAdmin
.firestore()
.collection("address")
.get()
.then((querySnapshot: admin.firestore.QuerySnapshot) => {
let contents: Array<string> = [];
querySnapshot.forEach(
(
doc: admin.firestore.QueryDocumentSnapshot<admin.firestore.DocumentData>
) => {
// ドキュメントIDを保存
const entry: any = doc.data();
entry.docId = doc.id;
contents.push(JSON.stringify(entry));
}
);
let text: string = "";
for (let i = 0; i < contents.length; i++) {
if (i === 0) {
text = contents[i];
} else {
text = text + ",\n" + contents[i];
}
}
// 作成したテキストの前後に「[」と「]」を付けて配列として扱う
fs.writeFile("backup.txt", "[" + text + "]", (err: any) => {
if (err) {
console.log(err);
}
});
});
リストアのサンプルコードは以下のようになります。この例は、Firebaseの処理も入れてあります。
import * as admin from "firebase-admin";
const fs = require("fs");
const serviceAccount = require("../serviceAccountKey.json");
const firebaseAdmin = admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: "https://sample-xxxxx.firebaseio.com",
});
fs.readFile("backup.txt", "utf8", (err: any, data: string) => {
if (err) {
console.log(err);
} else {
const json: any = JSON.parse(data);
console.log(json);
for (let i = 0; i < json.length; i++) {
const entry: any = json[i];
let docId: string | undefined = undefined;
const data: any = {};
for (let key in entry) {
if (key !== "docId") {
data[key] = entry[key];
} else {
docId = entry[key];
}
}
if (docId) {
firebaseAdmin
.firestore()
.collection("restore")
.doc(docId)
.set(data)
.then(() => {
console.log("success");
})
.catch((error: any) => {
console.log(error.message);
});
} else {
console.log("No document Id");
}
}
}
});
まとめ
この記事では前回の簡単なNode.jsによるFirebase admin SDKの使い方に加えてもう少し実践的なバックアップとリストアの機能を紹介しました。FirebaseのCloud FirestoreのドキュメントIDも含めて保存することで、FirebaseのCloud Firestoreをバックアップしたり、複製したりすることが簡単にできます。
こうした管理ツールの機能は、開発中にデータベースの内容をおかしくしてしまった場合などの復旧にも使えますし、実際に稼働しているシステムの機能改善やデバッグなどでテスト用の複製が必要な場合にも役に立ちます。 一旦WebサービスやWebアプリをインターネット上で稼働させてしまうと、同じデータベースを使って、デバッグや機能の変更はアプリやサービスの機能に影響を与える場合もあるので完全に検証が済むまでは触れたくないものです。 そうした場合に、別のテスト、開発用のプロジェクトを作ってデータベースが複製できると便利です。
Firebaseを利用してWebサービスやWebアプリを開発する場合、管理ツールは開発の助けにもなります。余裕があれば、Firebase admin SDKも使いこなせるとより効率的で安全な開発が可能になります。