Qiita Django Advent Calendar 2021 6日目は、DjangoのデータベースバックエンドにGoogle Cloud Spannerを使えるようにするdjango-google-spannerに関する話を書きます。
django-google-spannerの公式ドキュメントには本物のCloud Spannerインスタンスと接続する使い方しか書かれていませんが、この方法だとmigrate
コマンドの実行にかなり時間がかかります(私が試したときは10分ぐらいかかりました)。
ローカルのエミュレータを使うようにすれば、もっと早くなって楽に開発できるのでは? と考えて、実際にやってみました。
結論から先に書くと、この試みはうまくいきませんでした…
migrate
コマンドの実行は高速化できるのですが、データの登録に失敗するケースがあります。
うまくいかなかったのですが、検証内容をここに公開します。 以下Cloud Spanner公式ドキュメントを参考に、django-google-spannerでエミュレータを使うサンプルアプリを作ってみます。
Cloud Spanner エミュレータの使用 | Google Cloud
サンプルアプリで使っているPyhtonとライブラリのバージョンは以下のとおりです。
- Python 3.10.0
- Django==3.2.9
- django-google-spanner==3.0.0
また、開発環境にGoogle Cloud SDKをインストールする必要があります。
最初にCloud Spannerエミュレータを使う準備を行います。
以下の環境変数を設定します(<
>
で囲った値は任意の文字列を入れてください)。
|
|
gcloudにCloud Spannerエミュレータ用configを作成します。
|
|
上記を実行すると、gcloudのconfigがemulator
に切り替わります。
Cloud Spannerエミュレータの使用を止めてデフォルトのconfigに戻したい場合はgcloud config configurations activate default
を実行してください。
Cloud Spannerエミュレータを起動します。開発中はエミュレータのプロセスを立ち上げっぱなしにします。
|
|
Cloud Spannerエミュレータ上でインスタンスとデータベースを作成します。
|
|
これでCloud Spannerエミュレータが使えるようになりました。
次に、Djangoプロジェクトを作成します。
|
|
settings.py
は以下のように編集します。
|
|
これでDjangoアプリケーションからCloud Spannerエミュレータに繋げるようになりました。
migrate
コマンドを実行してみましょう。1秒程度でテーブルの作成が完了するはずです。
見事、高速化に成功しましたね! と言いたいところですが…
createsuperuser
で管理者ユーザーを作ろうとすると、以下のエラーが発生します。
|
|
なお、このエラーはエミュレータを使っている場合のみ発生するもので、本物のCloud Spannerインスタンスでは普通に登録できます。
django-google-spannerのGitHub Issueに、このエラーに関する報告が挙がっていました。
Fix missing inferred types in insert statements · Issue #566 · googleapis/python-spanner-django
どうやら、Cloud Spannerエミュレータ上のテーブルにデータをinsertする際、デフォルト値nullのカラムに指定する値を省略すると発生するエラーのようです。DjangoのデフォルトのUser
モデルはlast_login
フィールドがblank=True, null=True
で、createsuperuser
コマンド実行時にlast_login
の値を省略して登録しようとしてエラーになっています。
このエラーを回避する方法を色々考えてみましたが、私が思いつくのは以下の2つでした。
- Cloud Spannerエミュレータを本物の挙動に合わせる
- django-google-spannerが依存しているgoogle-cloud-spannerを変更して、デフォルト値nullのカラムに必ず値を指定する 1
自分でPRを作って華麗に解決してからこの記事を書けばかっこよかったのですが、ここで力尽きてしまいました…
django-google-spanner==3.0.0で開発する場合は、以下の選択肢しかないと思います。
- 開発環境でも本物のCloud Spannerインスタンスを使う
- 開発環境のみSQLiteを使う
ただし、SQLiteを使う場合、Cloud Spanner独自の制限を理解していないと、本番環境のみで起こるエラーの発生に繋がります。
たとえば、Cloud SpannerではALTER TABLE 〜 RENAME
をサポートしていません。ところが、Djangoは普通のRDBを想定した設計になっているため、モデル名やフィールド名を変更してmakemigrations
コマンドを実行すると、ALTER TABLE 〜 RENAME
を実行するマイグレーションファイルが作られます。この状態で本番環境でmigrate
コマンドを実行すると、エラーが発生します。
Cloud Spannerを使うDjangoプロジェクトでは、モデル名やフィールド名の変更時に以下の手順でマイグレーションファイルを分けるしかなさそうです。
- 新しいモデル・フィールドを追加して
makemigrations
コマンドを実行 --empty
オプション付きのmakemigrations
コマンドで空のマイグレーションファイルを作成2.
で作ったマイグレーションファイルにデータを移行するPythonコードを記述(こんな感じで)- 古いモデル・フィールドを削除して
makemigrations
コマンドを実行
Cloud Spanner独自の制限についての詳細は、以下ドキュメントを参照してください。
python-spanner-django / limitations.rst at main・googleapis / python-spanner-django
このアプローチは既にPRが存在しますが、「エミュレータの場合のみ値を指定するコードにしたほうがいい」というレビューコメントがついていて、未対応のまま開発が止まっています(2021/12/06時点)。 https://github.com/googleapis/python-spanner/pull/200 ↩︎