PHP ではじめる Cloud Spanner

Hiroki Awata
google-cloud-jp
Published in
10 min readNov 2, 2018

はじめに

株式会社コロプラの粟田です。弊社では Cloud Spanner をゲームサーバー開発に使用しており、その経験から得られた Tips を共有できればと思っています。

今回は PHP ではじめる Cloud Spanner 、ということで PHP と連携して使う場合のスタートガイドを書いていきたいと思います!

Cloud Spanner データベースの準備

Cloud Spanner を使うには、最低限次の2つを準備する必要があります。

  • インスタンス
  • データベース

作る方法はいくつかあるのですが、とりあえず簡単に始めたい場合は、 GCP の Webコンソール上から作る方法がおすすめです。

Cloud Spanner への認証

使い始めるには認証が必要です。とはいっても、他の GCP サービスと同じ方法で、GOOGLE_APPLICATION_CREDENTIALS 環境変数にサービスアカウントの鍵ファイルのパスを設定します。

export GOOGLE_APPLICATION_CREDENTIALS=/path/to/key.json

認証についてより詳しく知りたい場合は、GCP と OAuth2 を読むとよいでしょう。

実行環境の構築

公式ドキュメントにもある通り、PHP から接続する場合は composer や gRPC 拡張が必要です。

そこで、PHP と Cloud Spanner の実行環境を Docker などを使ってコンテナにしておくと何かと便利です。

Dockerfile の例を示します。

FROM php:7.2-cli-alpine

RUN apk add --no-cache --virtual=.build-deps zlib-dev autoconf gcc g++ make \
&& pecl install grpc protobuf \
&& docker-php-ext-install sysvshm \
&& docker-php-ext-enable grpc protobuf \
&& apk del .build-deps \
&& apk del *-dev

COPY --from=composer:latest /usr/bin/composer /usr/bin/composer

COPY composer.json /tmp/composer.json
COPY composer.lock /tmp/composer.lock
ENV COMPOSER_ALLOW_SUPERUSER 1
RUN composer install --no-scripts --no-autoloader -d /tmp

COPY . /src
WORKDIR /src

RUN mv -n /tmp/vendor ./ \
&& composer dump-autoload

Dockerfile を用いると、どのような環境のマシンであっても Docker が導入されていれば実行環境を再現できます。

さらに、docker-compose を使うことで認証の鍵ファイルの転送も含め、コンテナの起動を楽に行なえます。

docker-compose.yml の例を示します。

version: '3.6'
services
:
app:
build: .
environment:
GOOGLE_APPLICATION_CREDENTIALS: /key.json
volumes:
- ${GOOGLE_APPLICATION_CREDENTIALS}:/key.json:ro

ここまで準備することで、次のコマンドで簡単に環境の整った状態で PHP を実行できます。

$ docker-compose run app php -vPHP 7.2.11 (cli) (built: Oct 15 2018 18:55:39) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies

とりあえず SELECT してみる

認証が通ればすぐに Cloud Spanner を使いはじめることができます!

まずは小さなコードから始めてみましょう。

<?php
require __DIR__
.'/vendor/autoload.php';

use Google\Cloud\Spanner\SpannerClient;

$instanceId = 'spanner-instance-id';
$databaseId = 'spanner-database-id';

$spanner = new SpannerClient();
$db = $spanner->connect($instanceId, $databaseId);

$row = $db->execute('SELECT "Hello, world"')->rows()->current();
var_dump($row);

実行すると、Hello, world が SELECT されます!

array(1) {
[0] =>
string(12) "Hello, world"
}

初回のクエリが遅い問題

実行してみると、「SELECT 1回するだけなのに、なんだか遅い…」と感じます。

SELECT にかかった時間を計測してみます。

$start = microtime(true);

$row = $db->execute('SELECT "Hello, world"')->rows()->current();

printf('Query time: %.2f ms'.PHP_EOL, (microtime(true) - $start) * 1000);

試しに手元の開発マシンで実行したところ、驚くことに 1000 ミリ秒以上かかっていました。

Query time: 1217.67 ms

ループを使って 2回以上クエリを実行すると、2回めからは高速になっています。

Query time: 1359.55 ms
Query time: 9.00 ms
Query time: 10.28 ms

とはいえ、php-fpm のようなリクエスト毎に新しいコンテキストを生成する仕組みの場合、リクエスト毎に 1000 ミリ秒以上かかると非常に困ります。初回のクエリが遅いのはなぜでしょうか。

原因のひとつは Cloud Spanner のセッションの作成です。公式ドキュメントによると、セッションをキャッシュして維持することが推奨されています。

セッションの作成には高額なコストがかかります。データベース オペレーションごとのコストの発生を避けるため、クライアントは使用可能なセッションのプールであるセッション キャッシュを維持する必要があります。

セッションプールを使う

PHP のクライアントライブラリには CacheSessionPool という仕組みが用意されています。上に示した図のように一度作ったセッションをキャッシュしておき、空いているものを使い回す(pooling)ことができます。

CacheSessionPool のキャッシュ方式は種類が選べますが、まずはクライアントライブラリに同梱されている SysVCacheItemPool を使います。

<?php
require __DIR__
.'/vendor/autoload.php';

use Google\Cloud\Spanner\SpannerClient;
use Google\Cloud\Spanner\Session\CacheSessionPool;
use Google\Auth\Cache\SysVCacheItemPool;

$instanceId = 'spanner-instance-id';
$databaseId = 'spanner-database-id';

$cacheItemPool = new SysVCacheItemPool();
$sessionPool = new CacheSessionPool($cacheItemPool);
$options = [
'sessionPool' => $sessionPool,
];

$spanner = new SpannerClient();
$db = $spanner->connect($instanceId, $databaseId, $options);

$start = microtime(true);

$row = $db->execute('SELECT "Hello, world"')->rows()->current();

printf('Query time: %.2f ms' . PHP_EOL, (microtime(true) - $start) * 1000);

実行してみると、初回クエリの時間が 500 ミリ秒程度まで減りました。

Query time: 487.47 ms

認証キャッシュを使う

さらに、Cloud Spanner クライアントではもう一箇所キャッシュを指定できる箇所があります。 authCacheという名前で、GCP への access token をキャッシュしてくれるものです。

authCache は次のように、SpannerClient のコンストラクタで指定します。

$authCache = new SysVCacheItemPool();
$spanner = new SpannerClient([
'authCache' => $authCache;
]);

authCache を入れて実行すると、200 ミリ秒程度まで減りました。

Query time: 219.40 ms

この結果は手元のノートPC の Docker で実行したものですが、Google Compute Engine や Google Kubernetes Engine 上であれば GCP 内の通信になるため、さらに速くなりそうです。

ためしに、Google Compute Engine 上に 同じコンテナをつくり、同じ処理を curl で何度か実行してみました。

Query time: 17.82 ms

やはり GCP 内で通信にすることで大幅な高速化が実現できました。

ここから、opcache を有効にするとよりさらなる高速化が期待できます。

Query time: 9.21 ms

出力からわかる通り、GCP 上でアプリケーションを動作させることでリクエストあたり 10 ミリ秒前後でクエリを発行できるパフォーマンスが出ることがわかりました。

まとめ

PHP で Cloud Spanner に接続するためには、composer や gRPC の拡張が必要となるため、実行環境の構築に手間がかかります。そこで Docker などで実行環境をコンテナ化しておくことで、複数人で開発を行う際などに楽になるのでおすすめです。

また、Cloud Spanner への初回の通信はコストが大きめです。そこで、 CacheSessionPool と authCache を利用して高コストの通信が連続して起こらないように工夫できます。

--

--