コンテンツに移動
データベース

Django ORM の Cloud Spanner サポートのご紹介

2021年3月1日
Google Cloud Japan Team

※この投稿は米国時間 2021 年 2 月 17 日に、Google Cloud blog に投稿されたものの抄訳です。

このたび、Django ORMGoogle Cloud Spanner のベータサポートが開始されました。django-google-spanner パッケージは Cloud Spanner Python クライアント ライブラリを使用した、Cloud Spanner 用のサードパーティ製データベース バックエンドです。Django ORM は、リレーショナル データに Python オブジェクトをマップする、Django ウェブ フレームワークの強力なスタンドアロン コンポーネントです。基礎となるデータベースに対する優れた Pythonic インターフェースを提供し、スキーマ変更を自動生成するツールと、スキーマのバージョン履歴を管理するツールが含まれています。この統合によって、Django アプリケーションで大規模環境に対応した Cloud Spanner の高可用性と整合性を利用できるようになりました。

ここでは以下の Django チュートリアルに沿って、新しいプロジェクトを作成し、Cloud Spanner へのデータ書き込みを開始します。別のデータベース バックエンドですでに Django をお使いの場合は、Cloud Spanner への切り替えを説明した「既存の Django プロジェクトの Cloud Spanner への移行」までスキップしてください。

インストール

「django-google-spanner」を使用するには、Python がインストールされていることと、Django プロジェクトが必要です。このライブラリには「Django 2.2 系」および「Python 3.6 以上」が必要です。Django を初めてお使いの場合は、Django 2.2 スタートガイドをご覧ください。

Cloud Spanner API が有効になったアクティブな Google Cloud プロジェクトも必要です。Cloud Spanner の開始方法の詳細については、Cloud Spanner スタートガイドをご覧ください。

通常、Django アプリケーションは単一のデータベースを使用するよう構成されています。既存の Cloud Spanner のお客様は、Django アプリケーションでの使用に適したデータベースをすでにお持ちのはずです。まだ Cloud Spanner データベースをお持ちでない場合や、新しい Django アプリケーションを新規に開始する場合は、Google Cloud SDK を使用して新しいインスタンスデータベースを作成してください。

読み込んでいます...

Cloud Spanner データベース バックエンド パッケージをインストールするには:

読み込んでいます...

「django-google-spanner」には「django_spanner」という Django アプリケーションが用意されています。Cloud Spanner データベース バックエンドを使用するには、作成するアプリケーションの settings.py ファイルで、このアプリケーションを「INSTALLED_APPS」の最初のエントリにする必要があります。

読み込んでいます...

「django_spanner」アプリケーションは Django の AutoField のデフォルト動作を変更して、(自動的に増分される連番ではなく)乱数を生成するようにします。このようにするのは、Cloud Spanner の使用における一般的なアンチパターンを回避するためです。

プロジェクト、インスタンス、データベース名を設定して、データベース エンジンを構成します。

読み込んでいます...

開発中およびテスト中にコードをローカルで実行するには、アプリケーションのデフォルト認証情報を使用して認証するか、GOOGLE_APPLICATION_CREDENTIALS 環境変数を設定し、サービスアカウントを使用して認証する必要があります。このライブラリは、認証を Cloud Spanner Python クライアント ライブラリに委任します。このライブラリや他のクライアント ライブラリをすでに正しく使用できている場合は、Django アプリケーションでの認証のために新たにすべきことは何もありません。詳細については、クライアント ライブラリのドキュメントで認証の設定をご覧ください。

仕組み

「django-google-spanner」の内部では Cloud Spanner Python クライアント ライブラリを使用して、gRPC API によって Cloud Spanner との通信が行われます。このクライアント ライブラリは、Cloud Spanner セッションのライフタイム管理も行い、正常なリクエストのタイムアウト再試行のデフォルトを提供します。

Django ORM をサポートするため、Google は google.cloud.spanner_dbapi パッケージのクライアント ライブラリに、Python Database API 仕様(DB-API)の実装を追加しました。このパッケージは Cloud Spanner データベースの接続を処理し、ストリーミング結果の反復処理のための標準カーソルを提供して、中断されたトランザクションのクエリと DML ステートメントをシームレスに再試行します。将来はこのパッケージを使用して、SQLAlchemy など、DB-API と互換性のある他のライブラリおよび ORM をサポートできるようになることでしょう。

Django は マイグレーションと呼ばれる強力なスキーマ バージョン管理システムを提供しています。それぞれのマイグレーションには、スキーマ変更につながる Django モデルの変更が記述されます。Django は内部「django_migrations」テーブルのマイグレーションを追跡します。スキーマ バージョン間でのデータ移行用ツールが付属し、アプリのモデルから自動的にマイグレーションを生成します。「django-google-spanner」は、Django マイグレーションを移行時に実行する DDL ステートメント(具体的には「CREATE TABLE」と「ALTER TABLE」)に変換することで、Cloud Spanner のバックエンド サポートを提供します。

Django チュートリアルに沿って、クライアント ライブラリと Cloud Spanner API のインタラクションを見ていきましょう。以下の例は、チュートリアル 2 の「データベース設定」ステップから始まり、チュートリアルの前半で「mysite」および「polls」アプリを作成済みであることを前提としています。

前述のとおりデータベース バックエンドを構成すると、プロジェクトの初期移行を実行できるようになります。

読み込んでいます...

https://storage.googleapis.com/gweb-cloudblog-publish/images/image7_BL5kzxX.max-1900x1900.png

移行すると、Django が作成したテーブルとインデックスが GCP Cloud Console に表示されます。

https://storage.googleapis.com/gweb-cloudblog-publish/images/image42_EjOnuvr.max-2000x2000.png

または、「information_schema.tables」を調べると、Django が Google Cloud SDK を使用して作成したテーブルを確認できます。

読み込んでいます...

https://storage.googleapis.com/gweb-cloudblog-publish/images/image28.max-1600x1600.png

これには「SPANNER_SYS」および「INFORMATION_SCHEMA」テーブルなど、Spanner 内部のテーブルも表示されます。前述の例ではこれらを省略しています。

Cloud Spanner 内のテーブル スキーマが、「sqlmigrate」でライブラリによって生成された DDL と一致することを確認できます。この例では「polls」アプリを確認しますが、「INSTALLED_APPS」の他のアプリ(「admin」、「auth」、「contenttypes」、「sessions」など)で作成されたテーブルでも同じ手順を使用できます。

読み込んでいます...

https://storage.googleapis.com/gweb-cloudblog-publish/images/image33.max-1200x1200.png

Cloud Console のテーブルの詳細ページにある [同等な DDL を表示] リンクをクリックして、テーブル スキーマを確認します。

https://storage.googleapis.com/gweb-cloudblog-publish/images/image3_iVRZKpq.max-2000x2000.max-2000x2000.png
https://storage.googleapis.com/gweb-cloudblog-publish/images/image31_3BrgYQy.max-2000x2000.png

今度は、チュートリアルの API の利用セクションに沿って、「polls」アプリでいくつかのオブジェクトを作成および修正して、変更が Cloud Spanner 内で維持される様子を見てみましょう。以下の例では、チュートリアルの各コード セグメントの後ろに、Django で実行される SQL ステートメント、および得られた Cloud Spanner API リクエストのリストの一部と引数が続いています。

生成された SQL ステートメントを自分で確認するには、「django.db.backends」ロガーを有効にします。

空の Questions テーブルのクエリ

lang-py
読み込んでいます...

https://storage.googleapis.com/gweb-cloudblog-publish/images/image9_6g3l1Er.max-1200x1200.png

このコードによって SQL ステートメントが得られます。

https://storage.googleapis.com/gweb-cloudblog-publish/images/image40.max-600x600.png

また、次の Cloud Spanner API 呼び出しが行われます。

  1. google.spanner.v1.Spanner/CreateSession」。Cloud Spanner への最初のリクエストで新しいセッションを作成します。以降のリクエストでは、「google.spanner.v1.Spanner/GetSession」とここで返されたセッションの名前を呼び出すことで、このセッションを再使用します。スペース上の都合により、ここではその後の「GetSession」の呼び出しを省略してあります。

リクエストにはいくつかのフィールドが追加されています。「metadata」は、使用するデータベースとセッションの追跡のために Cloud Spanner が使用します。「retry」と「timeout」は、名前が示すとおり、リクエストの再試行およびタイムアウト動作を記述するものです。一般的にこれらの引数はユーザー構成可能ではないため、以下で省略してあります。

https://storage.googleapis.com/gweb-cloudblog-publish/images/image37.max-1100x1100.png

2. 「google.spanner.v1.Spanner/ExecuteStreamingSql」。ここではデータを修正する必要がないため、オペレーションはブロックなしの読み取り専用トランザクション内で行われます。ここではデフォルトで強力な読み取りを行い、これの開始前に commit されたトランザクションの結果を確認できることが保証されます。

https://storage.googleapis.com/gweb-cloudblog-publish/images/image36.max-800x800.png

新しい Question オブジェクトの作成と保存

lang-py
読み込んでいます...

このコードによって SQL ステートメントが得られます。

https://storage.googleapis.com/gweb-cloudblog-publish/images/image21.max-1000x1000.png

また、次の Cloud Spanner API 呼び出しが行われます。

  1. google.spanner.v1.Spanner/BeginTransaction」。「model.save」では、ブロックありの読み取りおよび書き込みトランザクションを開始します(リクエスト ペイロードには「options: {read_write: {}}」が含まれます)。リクエストには前述のセッション ID が含まれます。

https://storage.googleapis.com/gweb-cloudblog-publish/images/image16.max-1300x1300.png

2. 「google.spanner.v1.Spanner/ExecuteSql」。トランザクションの中で「INSERT INTO polls_question ...」を実行します。リクエストには前述のトランザクション ID が含まれます。

https://storage.googleapis.com/gweb-cloudblog-publish/images/image41.max-1500x1500.png

3. 「google.spanner.v1.Spanner/Commit」。「INSERT」トランザクションを commit します。

https://storage.googleapis.com/gweb-cloudblog-publish/images/image30.max-1200x1200.png

既存の Question の修正

読み込んでいます...

このコードによって SQL ステートメントが得られます。

https://storage.googleapis.com/gweb-cloudblog-publish/images/image2_85GiYl9.max-700x700.max-700x700.png

また、次の Cloud Spanner API 呼び出しが行われます。

  1. google.spanner.v1.Spanner/BeginTransaction」。「model.save」の最後の呼び出しによって、別の読み取りおよび書き込みトランザクションが開始されます。

https://storage.googleapis.com/gweb-cloudblog-publish/images/image16.max-1300x1300.png

2. 「google.spanner.v1.Spanner/ExecuteSql」。「UPDATE polls_question ...」を実行します。行がすでに存在しているため、今回は「INSERT」ではなく「UPDATE」ステートメントのみを実行します。

https://storage.googleapis.com/gweb-cloudblog-publish/images/image41.max-1500x1500.png

3. 「google.spanner.v1.Spanner/Commit」。「UPDATE」トランザクションを commit します。

https://storage.googleapis.com/gweb-cloudblog-publish/images/image30.max-1200x1200.png

4. 「google.spanner.v1.Spanner/DeleteSession」。最後に、シェルの終了時にセッションを削除します。

https://storage.googleapis.com/gweb-cloudblog-publish/images/image12_015fXIF.max-1200x1200.png

この背後でどれだけの作業が行われていたでしょうか。Django モデル オペレーションを Cloud-Spanner 準拠の SQL に変換する以外に、データベース ドライバもセッションとトランザクションの管理を担っています。前述の例で見てきたとおり、「model.save」の 1 回の呼び出しで多数の Cloud Spanner API 呼び出しが行われます。

既存の Django プロジェクトの Cloud Spanner への移行

Django プロジェクトを別のデータベースから Cloud Spanner に移行するには、複数のデータベース接続に対する Django の組み込みサポートを使用できます。この機能を使用すると、一度に 2 つのデータベースに接続し、一方から読み取ってもう一方に書き込むことができます。

アプリケーションのデータを SQLite から Cloud Spanner に移動する場合を考えてみましょう。既存のデータベース接続は「デフォルト」として構成済みであると仮定すると、Cloud Spanner に第 2 のデータベース接続を追加できます。この接続の名前は「spanner」です。

lang-py
読み込んでいます...

チュートリアルのとおり、「python manage.py migrate」を実行するとプロジェクト内のすべてのモデル用のテーブルとインデックスが作成されます。デフォルトでは、構成済みのすべてのデータベース接続に対して「migrate」が実行され、それぞれのデータベース バックエンドに固有の DDL が生成されます。「migrate」の実行後は両方のデータベースが同等のスキーマを持ちますが、新しい Cloud Spanner データベースは空のままです。

Django はプロジェクトのモデルから自動的にスキーマを生成するので、生成された DDL が Cloud Spanner のベスト プラクティスに沿っているのを確認することをおすすめします。Cloud Spanner にデータをコピーした後、マイグレーションごとにプロジェクトのモデルを調整できます。

Cloud Spanner へのデータのコピーには、HarbourBridge を使用してデータを PostgreSQL または MySQL データベースからインポートするDataflow を使用して Avro ファイルをインポートするなど、いくつかのオプションがあります。インポートしたデータが新しいスキーマと一致している限り、どのオプションも使用できますが、データベース間でデータをコピーする(最速ではないが)最も簡単な方法は Django 自体を使用することです。

チュートリアルで作成したモデルについて考えてみましょう。このコード スニペットでは、SQLite データベースからすべての Question と Choice を読み取ってから、それらを Cloud Spanner に書き込みます。

lang-py
読み込んでいます...

既存のデータベースの各テーブルの各行で、以下を行います。

  • 行を読み取り、それを Django モデル オブジェクトとしてメモリに格納します。

  • 主キーを設定解除します。

  • それを新しいデータベースに書き戻すと、それが新しい主キーとして割り当てられます。

新たに生成された主キーも使用するには、外部キーを更新する必要があります。また、「question」の主キーを変更する前に「question.choice_set.all()」を呼び出します。そうしないと、間違ったキーを使用して QuerySet が遅延と評価されてしまいます。

これはネイティブな例であり、理解しやすく作られていますが、必ずしも高速ではありません。Question ごとに個別に「SELECT … FROM polls_choice」クエリを行います。データベースですべての Choices を読み取ることがあらかじめわかっているため、「Choice.objects.all().select_related('question')」で 1 つのクエリに減らすことができます。一般的には、個別のリクエストで各行に書き込む代わりに、bulk_update を使用するなどの方法で、プロジェクトのスキーマを利用してマイグレーション ロジックを書き込めます。このロジックは、Django シェルで実行するコード スニペット(前述のもの)、独立したスクリプト、Django のデータのマイグレーションの形式にすることができます。

以前のデータベースから Cloud Spanner に移行した後は、以前のデータベース接続用の構成を削除し、Cloud Spanner 接続の名前を「default」に変更できます。

lang-py
読み込んでいます...

制限事項

これはベータ版リリースであり、本番環境での使用にはまだおすすめしません。SLA またはテクニカル サポートの保証はなく、現在のリリースの機能セットが後日の一般提供バージョンと異なる可能性があります。とはいえ、ライブラリは安定し、機能は十分に有用であると思われますので、お客様がお使いになってフィードバックをお寄せいただけますと幸いです。

一部の Cloud Spanner 機能(NUMERIC データ型など)は、このライブラリと Cloud Spanner Python クライアント ライブラリのとちらでもまだ利用できません。Cloud Spanner API と互換性がないなどの理由で、いくつかの Django データベース機能は無効化されています。Django には総合テストスイートが付属しています。「python-spanner-django」でまだサポートされていない Django の機能の詳細なリストは、スキップした Django テストのリストをご覧ください。

Cloud Spanner エミュレータをお使いのお客様は、特にエミュレータは同時実行トランザクションをサポートしていないことから、Cloud Spanner サービスとは異なった動作となることがあります。制限事項および Cloud Spanner サービスとの違いのリストについては、Cloud Spanner エミュレータのドキュメントをご覧ください。

当面、このライブラリは Django バージョン 2.2.x をサポートしますが、今後は 3.x をサポートする予定です。必要な Python の最小バージョンは 3.6 です。

知識を深める

お客様からのご意見をお待ちしております。特に、現在 Django アプリケーションで Cloud Spanner Python クライアント ライブラリをお使いの場合、または既存の Cloud Spanner のお客様で、新しいプロジェクトのために Django の使用をお考えの場合は、ぜひご意見をお寄せください。このプロジェクトはオープンソースです。GitHub でコメント、バグレポート、pull リクエストのオープンを行うことができます

プロジェクト初期に作業していただいた Emmanuel Odeke 氏Tim Graham 氏、DB-API 実装およびベータ版の準備をしていただいた Maxim Factourovich 氏Ilya Gurov 氏Hemang Chothani 氏これまで貢献していただいた方々や、ライブラリをテストして早期のフィードバックを寄せていただいたすべての方々に感謝いたします。

関連情報



-Cloud Spanner 担当ソフトウェア エンジニア Chris Kleinknecht

投稿先