コンテンツにスキップ

GAE

WordPress on GAE

GCP の事前準備

以下の準備が事前に必要。ここでは割愛。

  • GCP アカウントの作成
  • GCP プロジェクトの作成
  • 課金の有効化
  • GCS(Google Cloud Storage)に default bucket を作成
    • default bucket: YOUR_PROJECT_NAME.appspot.com
  • Google Cloud SDK のインストール
    • https://cloud.google.com/sdk/

Composer のインストール

Composer は PHP のパッケージ管理ツール

Composer is strongly inspired by node's npm and ruby's bundler.

node の npm、ruby の bundler に強く影響を受けているようだ。

手順

PHP がインストールされていることを確認

$ php -v
PHP 5.5.38 (cli) (built: Aug 21 2016 21:48:49) 

こちら の手順通り Composer をインストール

$ php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
$ php -r "if (hash_file('SHA384', 'composer-setup.php') === '669656bab3166a7aff8a7506b8cb2d1c292f042046c5a994c43155c0be6190fa0355160742ab2e1c88d40d5be660b410') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"
$ php composer-setup.php
$ php -r "unlink('composer-setup.php');"

グローバルに呼び出せるようにしておく

$ mv composer.phar /usr/local/bin/composer

GCS へのアクセス権限変更

作成したプロジェクトでログイン

$ gcloud auth login
$ gcloud config set project YOUR_PROJECT_NAME

GCS のアクセス権更新

$ gsutil defacl ch -u AllUsers:R gs://YOUR_PROJECT_NAME.appspot.com

DB の setup

Cloud SQL を使うので GCP の SQL メニューへ

  • wp という名前で MySQL のインスタンスを立てる
    • このとき第2世代を選択可
    • 日本向けがメインであればリージョンは asia-northeast1
    • マシンタイプは安さ重視なら db-f1-micro とする (このタイプで 30円/日 という印象)
    • 他の設定はお好みで...
  • 立てたインスタンスに wp という名前のデータベースを作る
  • wp というユーザーを作る
    • アクセス制御 → ユーザー → ユーザーアカウントを作成、とたどる

WordPress の setup

ローカルに WordPress 構築に必要な素材を用意する

$ git clone https://github.com/GoogleCloudPlatform/php-docs-samples.git
$ cd php-docs-samples/appengine/wordpress/
$ composer install

setup の開始

$ php wordpress-helper.php setup
# ここでいろいろインタラクティブに聞かれるので答える

※ リージョンを聞かれるが、 asia-northeast1 がない場合は適当に答えておく

DB_HOST 設定を変更

上記手順で WordPress setup 時に適切なリージョンが指定できなかった場合には自分で一部設定を変更する

cd YOUR_PROJECT_NAME/
vim wordpress/wp-config.php

/** Production login info */ 以下の DB_HOST を適切なリージョンを用いて変更する

Deploy

GAE インスタンスに WordPress を deploy する

$ gcloud app deploy --promote --stop-previous-version app.yaml cron.yaml

最初はかなり多くのファイルを GCS にあげるのでそこそこ時間がかかる

参照

https://github.com/GoogleCloudPlatform/php-docs-samples/tree/master/appengine/wordpress

Could not open the requested socket エラーが出たら

Android Studio で GAE に乗せるアプリを開発しているとき、ローカルで立ち上げようとすると以下のようなエラーが出た

Could not open the requested socket: Address already in use
Try overriding --address and/or --port.

どうやら以前立ち上げた際のプロセスが生き残っていて邪魔しているらしい

以下のように対応した

Android Studio のメニューから RunEdit Configurations... とたどり、対象 module が使っている port を確認

ポートを指定してプロセスを確認、そして kill

$ lsof -i:8080
COMMAND   PID    USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
java    74792  sojiro   48u  IPv6 0xXXXXXXXXXXXXXXXX      0t0  TCP *:http-alt (LISTEN)
$ kill -9 74792

GAE Task Queue について

GAE の Task Queue についてざっくり。

具体的な実装に関しては別エントリに書こうと思います。

Overview

  • アプリケーションの処理(task)を Task Queue API に渡す(queue)ことでユーザーからのリクエスト外で非同期に処理させることができる
  • task はスケーラブルな GAE の Worker モジュールによってバックグラウンドで処理される

Task Queue には以下の2つのタイプがある

Push queues

  • queue を処理する間隔をあらかじめスケジュールすることができる
  • queue は GAE のモジュールへのリクエストとして処理される
  • task の処理には期限がある
    • 自動的にスケーリングするモジュールで処理するものは 10 分以内
    • そうでないモジュールで処理するものは 24 時間以内

Pull queues

  • GAE は queue を処理せず、外部の Worker が queue を取得(lease)して処理する
  • 外部の Worker で、どのような間隔で queue を処理するか管理する必要がある
  • lease のタイミングで外部 Worker に対して queue の処理期限が渡される
  • 期限内に queue の処理が完了するか、 queue が削除されない場合は同一 queue に対する他の Worker プロセスからの lease を許可する

queue の処理は非同期で行われるので task を作成したアプリケーションは処理の結果を知ることができないが、処理に失敗した場合は自動でリトライ処理が走る

Use cases

Push queues

  • SNS メッセージアプリケーションでユーザーがメッセージを送るたびにフォロワーの更新を非同期で行う
  • キャンペーン広告の送信をあらかじめスケジュールしておいて決められた時間に送る

Pull queues

  • バッチジョブに効果がある
  • task に tag をつけることで外部 worker が lease する時に同じ tag がつく task をまとめて処理することができる
  • 実装例としては複数のゲームのリーダーボードが典型的
    • ハイスコアの更新があるたびに game id を tag として、 score と user 名を enqueue する

Push queue

  • Push queue は GAE の Worker モジュールに HTTP リクエストで task を渡す
  • このリクエストは一定の間隔で実行され、失敗すると新たなリクエストをもってリトライされる
  • タスクの種類ごとにハンドラを書く必要がある
  • 1つのモジュールに各種類ごとのハンドラを用意することができる
  • task の種類ごとに別々のモジュールを使うこともできる

Working with push queues

  • task を作って push queue に enqueue するプログラムを書く
  • task リクエストを受け取って GAE モジュールに渡すハンドラを書く
  • quota を気にする必要がある

Pull queue

  • Pull queue を使うことで独自のシステムから GAE の task を処理することができる
  • ここでいう独自のシステムには GAE で構成されたシステムも含まれる
  • GAE アプリケーションからは com.google.appengine.api.taskqueue パッケージを使って task を処理できる
  • その他のアプリケーションからは Task Queue REST API を使う
  • Push queue では GAE がやってくれる以下の処理を自前で用意する必要がある
  • アプリケーション側で worker スケールの管理をする
  • アプリケーション側で処理の終わった task を削除する
  • Pull queue は queue.xml という設定ファイルが必要

Pull queue による task 処理の流れ

  1. アプリケーションが task を lease する
  2. GAE が task データを返す
  3. アプリケーションが task を処理する
    1. もし lease の期限以内に処理を完了できなかった場合は再度 lease できる
    2. retry できる最大の回数はあらかじめ設定できる
    3. この回数を超えると GAE が task を削除する
  4. アプリケーションは task への処理が完了したら必ずその task を削除する

参照

GAE Datastore について

GAE の Datastore についてざっくり

Overview

  • スキーマのないオブジェクトデータベース
  • オートスケーリング
  • ディスクへの書き込み時に自動で暗号化、読み出し時に自動で復号
  • 計画的ダウンタイムなし
Concept Cloud Datastore Relational database
オブジェクトが所属するカテゴリ Kind Table
オブジェクト Entity Row
オブジェクトがもつデータ Property Field
オブジェクトを特定する ID Key Primary key
  • 同一 kind の entity でも違う property を持ちうる
  • それぞれの entity は同名の property でも型の違うデータを持ちうる

Other storages

  • 複数の table の join や 複数のカラムに対する不等号比較など、すべての SQL 操作が必要なら Google Cloud SQL
  • ACID transaction を必要としないスキーマレスなデータを扱うなら Google Bigtable
  • オンラインで分析されるデータを扱うなら Google BigQuery
  • 画像や動画などの変更がない大きなデータを扱うなら Google Cloud Storage

Entities

  • ひとつの entity は1つ以上の property をもつ
  • property は1つ以上の値を取りうる

Keys

  • key は entity を特定する
  • key は以下を含む
    • entity の kind
    • 以下のいずれかの識別子
      • 文字列の key name
      • 数値の ID
    • ancestor path (optional)
  • 識別子は entity が生成されたタイミングで設定される
  • 一度設定された識別子は変更されない
  • 識別子は以下の2つの方法で設定できる
    • アプリケーションで特定の key name を設定する
    • Datastore が自動で発行する数値の ID を使う

Ancestor paths

  • Datastore はファイルシステムのディレクトリ構成に似た階層構造をもつ
  • entity 作成時に parent entity を指定することができる
  • parent entity が指定されない entity は root entity と呼ぶ
  • 一度親子関係ができた entity はその関係が変更されることはない
  • 同じ parent をもつ2つの entity に対して Datastore は同一の ID を払い出すことはない
  • 同様に2つの root entity に対して同一の ID を払い出すこともない
  • parent や parent の更に parent を ancestor と呼ぶ
  • 逆に children や children の更に children を descendant と呼ぶ
  • root entity とその descendant は同一の entity group に属する
  • ancestor path は root entity からたどって該当の entity に到達するまでの親子関係で構成される

Transactions and entity groups

  • entity に対する create, update, delete はトランザクションで管理される
  • トランザクションにはこれらの複数の操作が内包される
  • トランザクションは一貫性の担保のため、そこに含まれる操作をひとまとまりとして適用する、あるいはいずれかの操作が失敗するとすべての操作を適用しない
  • commit を試みてエラーが返ってきたとしても、トランザクションが失敗したとは限らない
    • DatastoreTimeoutExceptionDatastoreFailureException といったエラーが返ってきたとしても、 commit は成功している可能性がある
    • このため、 Datastore は可能な限り同一のトランザクションを複数回適用しても最終的な結果が変わらないように設計すべきである
  • entity group とトランザクションの関係
    • 1つのトランザクションで扱うデータは 25 の entity group 内に収まっていなければならない
    • トランザクション内でクエリを発行する場合には正しいデータにマッチする ancestor filter を指定できるように entity group 内のデータを設計する必要がある
    • 広域に渡って各 entity group をレプリケーションするために、1つの entity group ごとに1秒あたりの書き込みスループットの上限値が定められている

Understanding write costs

  • 発生する書き込み
    • entity 自身
    • EntitiesByKind という index
    • 1つの property value ごとに EntitiesByPropertyEntitiesByPropertyDesc の各 index

以下のような entity を考える

Key: 'Foo:1' (kind = 'Foo', id = 1, no parent)
A: 1, 2
B: null
C: 'this', 'that', 'theOther'

このとき発生する書き込み

  • 1: entity 自身
  • 1: EntitiesByKind index
  • 4: A property の2つの値にそれぞれ2つの index
  • 2: B property の値に2つの index (null でも必要)
  • 6: C property の3つの値にそれぞれ2つの index

よって発生する書き込みの合計は 1 + 1 + 4 + 2 + 6 = 14

上記の entity に複合 index を追加することを考える

Kind: 'Foo'
A ▲, B ▼, C ▼

このとき、各 property の値の組み合わせ分の書き込みが発生する

(1, null, 'this') (1, null, 'that') (1, null, 'theOther')
(2, null, 'this') (2, null, 'that') (2, null, 'theOther')

したがって発生する書き込みの合計は 1 + 1 + 4 + 2 + 6 + 6 = 20

次にこれまでと同様に ancestor が存在する以下のような entity を考える

Key: 'GreatGrandpa:1/Grandpa:1/Dad:1/Foo:1' (kind = 'Foo', id = 1, parent = 'GreatGrandpa:1/Grandpa:1/Dad:1')
A: 1, 2
B: null
C: 'this', 'that', 'theOther'
Kind: 'Foo'
A ▲, B ▼, C ▼
Ancestor: True

このとき各 property の値と各 ancestor 及び自身の entity の組み合わせ分の書き込みを必要とする

(1, null, 'this', 'GreatGrandpa') (1, null, 'this', 'Grandpa') (1, null, 'this', 'Dad') (1, null, 'this', 'Foo')
(1, null, 'that', 'GreatGrandpa') (1, null, 'that', 'Grandpa') (1, null, 'that', 'Dad') (1, null, 'that', 'Foo')
(1, null, 'theOther', 'GreatGrandpa') (1, null, 'theOther', 'Grandpa') (1, null, 'theOther', 'Dad') (1, null, 'theOther', 'Foo')
(2, null, 'this', 'GreatGrandpa') (2, null, 'this', 'Grandpa') (2, null, 'this', 'Dad') (2, null, 'this', 'Foo')
(2, null, 'that', 'GreatGrandpa') (2, null, 'that', 'Grandpa') (2, null, 'that', 'Dad') (2, null, 'that', 'Foo')
(2, null, 'theOther', 'GreatGrandpa') (2, null, 'theOther', 'Grandpa') (2, null, 'theOther', 'Dad') (2, null, 'theOther', 'Foo')

したがって発生する書き込みの合計は 1 + 1 + 4 + 2 + 6 + 24 = 38

参照

Android StudioからGAE for Javaアプリケーションをdeployするのに必要なFacet

こちらの記事を参考にGAE for JavaアプリケーションをAndroid Studio + Gradleでセットアップし、サンプルアプリケーションを開発してみました。

早速GAEにdeployしてみようと、メニューバーの Build から Deploy Module to App Engine... を選択してdeployを実行...ところがタスクが走らずうんともすんとも言わないので調べてみました。

結論

以下の設定を app.iml ファイルに追記する

<facet type="android-gradle" name="Android-Gradle">
  <configuration>
    <option name="GRADLE_PROJECT_PATH" value=":app" />
  </configuration>
</facet>
<facet type="java-gradle" name="Java-Gradle">
  <configuration>
     <option name="BUILD_FOLDER_PATH" value="$MODULE_DIR$/build" />
     <option name="BUILDABLE" value="true" />
  </configuration>
</facet>

こちらは少々特殊な方法でmoduleを作成しているのでFacetの設定が不十分となってしまった模様。

Facetとは

FacetはIntelliJ IDEAに用意された機能で、使用するフレームワークや言語に合わせたFacetを設定することでIntelliJ IDEAが必要なコンポーネントのダウンロードや各種補完機能の設定などを行ってくれるもの。

Android StudioはIntelliJ IDEAをベースとして開発されたIDEなのでFacetの機能を継承している。

参照