ラベル GCP の投稿を表示しています。 すべての投稿を表示
ラベル GCP の投稿を表示しています。 すべての投稿を表示

2024/10/01

Batch実行時のエラー「java.lang.NullPointerException: Cannot invoke "String.lastIndexOf(int)" because "endpoint" is null」の対処

事象

GAE/JでBatchジョブを起動しようとBatchServiceClient.create()をコールすると、以下のエラーが発生しました。

java.lang.NullPointerException: Cannot invoke "String.lastIndexOf(int)" because "endpoint" is null

ソースコードはGoogleのスニペットどおりで、ヌルポなど発生するわけがないはずです。しかし何度実行しても、ビルドしなおしても状況に変化はありません。


調査

BatchServiceClient.create()ではどのようにしてBatchServiceClientが生成されているのかわかりませんが、BatchServiceClient.create(BatchServiceSettings settings)メソッドを使用して自分でBatchServiceSettingsを生成して渡せば、生成されるBatchServiceClientの内容をある程度推測できそうです。
とりあえず以下のようなスニペットでBatchServiceSettings.newBuilder()でデフォルト設定のBatchServiceSettingsを生成し、別のBatchジョブの起動に成功しているプロジェクトとBatchServiceSettingsの内容を比較してみました。

	BatchServiceSettings settings = BatchServiceSettings.newBuilder().create();

生成されたBatchServiceSettingsの内容で、有意な差異は以下のとおりでした。

成功しているプロジェクト :
	endpoint=batch.googleapis.com:443,
失敗するプロジェクト :
	 endpoint=null,

エラーメッセージどおりですが、endpointがnullです。


endpointは文字列ですし、BatchServiceSettings.Builder.setEntpoint(String endpoint)で設定できます。そこで強制的にendpointに成功しているプロジェクトと同じ値をセットしてBatchServiceSettingsを生成し、それを渡してBatchServiceClientを生成してみると、発生するエラーが以下に変わりました。

com.google.api.gax.rpc.PermissionDeniedException: io.grpc.StatusRuntimeException: PERMISSION_DENIED: Batch API has not been used in project プロジェクト名 before or it is disabled.

Batchが有効になっていない...


解決方法

その1

BatchのAPIを有効にします。GCPコンソールからなら以下の手順になります。

  1. GCPコンソールの「APIとサービス」-「ライブラリ」を表示。
  2. 「APIとサービス」にBatchと入力してBatchを探す。
  3. 検索結果のリストから「Batch API」をクリック。
  4. Batch APIのページの「有効にする」をクリック。



Batchは手動で有効化する必要があったのを忘れていただけでした。そして最初に目にするエラーメッセージはそれを連想しにくいものなので、気づけないというワナもあったということでした。


その2

Batchを有効化し、BatchServiceClient.create()のコールをGoogleのスニペットどおりに戻してみたのですが、相変わらずこの記事の最初と同じヌルポが発生したままです。
とりあえず上記調査と同様にendpointを強制的に設定することで、Batchジョブの実行はできるようになりました。

しかしなぜプロジェクトによって挙動が違うのかは不明のままです。

2024/08/26

Firestore/Datastoreで複数の範囲フィルタを使ってクエリが可能に

Firestoreが登場する前の昔のDatastoreから、Firestore/Datastoreのクエリには「不等式はクエリ内で1つだけ」という制限があったのですが、2024/7/29にやっと解除されました。

上記リンクはFirestoreですが、Datastoreも同様のページがあります。Datastoreの日本語ページにはプレビューマークがついていますが、リリースノートにはGAと説明されています。

この制限のためにこれまで「DBに範囲を記録しておいて、指定の値がその範囲内にあるデータを検索」ができませんでした。具体的には以下のようなケースです。
  • 開始・終了日時を保存しておき、指定の日時に対応するデータを検索。
  • 上限・下限の金額を保存しておき、指定の金額に対応するデータを検索。
  • etc.

ただし無制限になったわけではなく、少ないながらも制限事項もあるようです。検索時間などパフォーマンス面も未検証なので、遅くないのかなど気になります。

これまではこの制限を回避するためだけにSearch APIを利用したりElasticsearchをインストールしたりといった対応が必要だったのですが、だいぶFirestore/Datastoreのみで検索ができるようになりそうです。

2024/02/29

Cloud Runのインスタンス起動時間

 動機

Cloud Runのドキュメント「実行環境について」については以下の記述があります。
第 2 世代の実行環境は、一般に持続的な負荷の下では迅速に動作しますが、ほとんどのサービスでは、第 1 世代よりもコールド スタート時間が長くなります。
このように書かれると、第1世代/第2世代ともどの程度インスタンスの起動時間がかかるのか、どの程度違うのか気になります。また第2世代のジョブはバックグラウンドで動作するものなので起動時間はあまり気にされるものではありませんが、これも知りたくなってきます。
実行速度は目的によって測定方法も変えることになるので面倒ですが、インスタンスの起動時間については過去の記事「GAEのインスタンス起動時間」「GAEのインスタンス起動時間 #2」と同様に測定できますので測ってみました。


測定方法・条件

今回はCloud Runなので言語やランタイムのバージョンについては、使う人がDockerコンテナを作る際にいいようにすればいいだけの話です。なので純粋にCloud Runのインスタンス起動時間として、Hello World相当のGoでリクエスト→処理開始までを測ることにします。
  • リージョン : 大阪
  • ネットワーク : NTT Flets光 隼、実効300Mbps程度
  • サービス : 
    • ブラウザからAjaxでリクエストし、JavaScriptでレスポンスが返るまでの時間を測定。
    • スピンアップあり/なしの2パターンで、それぞれ10回測定し、平均値を算出。
    • 前記2パターンの差から、インスタンス起動時間を計算。
  • ジョブ : 
    • GAEからジョブを起動し、GAE/Cloud Runそれぞれに仕込んだログから、インスタンス起動時間を測定。
    • スピンアップは毎回行われるのでサービスのような「スピンアップなし」に該当するパターンは無いはず。念のため検証目的で、前回起動から15分以上時間空けてをスピンアップあり相当、前回起動から時間空けずにをスピンアップなし相当として、2パターンでそれぞれ10回測定し、平均値を算出。

測定結果

単位はすべてmsです。測定結果をtableにするのが面倒だったので、今回もExcelのキャプチャ画像です。

スピンアップあり。

スピンアップなし。

そしてインスタンス起動時間。

第1世代第2世代
serviceservicejob
158.7452.59344.4


まとめ


Cloud Runの世代間

公式ドキュメントの記載どおり、インスタンス起動時間は第1世代のほうが速いという結果です。
ジョブの起動時間はおおむね10秒程度でした。ちなみにジョブ終了→GAEで終了を検知も、おおむね10秒程度でした。

GAEとの比較

GAEのインスタンス起動時間 #2」と比較してみるとCloud Runは全体的に遅く、第2世代のサービスだとGAEの素のJavaよりいいくらいになっています。
本稿の趣旨からちょっと外れますが、スピンアップなしのケースではGoだろうがJavaだろうがほぼ確実に100msを切れるGAEに対して、Cloud Runだと3倍程度のレイテンシが発生しています。Webフロントエンド専用のプロダクトとしてgVisorという独自コンテナ技術まで作っただけあって面目躍如といったところで、汎用のDockerコンテナを利用するCloud Runは一歩及びません。

GoのコードはそのままでGAE/Goに実装したRESTful APIを、Cloud Runサービス/Goに変えたところ、ちょっともっさりするようになった気がした経験があります。これは気のせいではなかったと改めて認識しました。
Firebase Hostingには動的コンテンツをCloud Runで配信する機能「Cloud Run を使用した動的コンテンツの配信とマイクロサービスのホスティング」があるのですが、今回の結果を見ると、App Engineにも対応してほしいと思います。

2024/02/09

GAEのインスタンス起動時間 #2

動機

Java17 + レガシーバンドルサービスを実装したところ、GAEインスタンスのスピンアップが若干遅いと感じました。いつの間にかJava21もプレビューになっていましたので、あわせてインスタンス起動時間を再測定してみました。

また前回(GAEのインスタンス起動時間)はログで確認していたのですが、その方法の精度にも疑問を感じたので、測定方法も対象も変更しています。


今回はJava8がすでにサポート終了しているので、対象外としました。


測定条件・方法

  • リージョン : 大阪
  • ネットワーク : NTT Flets光 隼、実効300Mbps程度
  • インスタンスクラスはF1。
  • ServletのアプリはGoogleのサンプルに入っていたHelloWorld。
  • SpringBootのアプリはGoogleのサンプルに入っていたHelloWorldそのままなので、バージョンは2.7.18。
  • レガシーバンドルサービスは、Googleのサンプルは余計なコードが入っていたのでpom.xmlのみ参考にし、Hello World相当を自作。
  • GoもHello World相当を自作、今回はフレームワークなしのパターンのみ。
  • ブラウザからAjaxでリクエストし、JavaScriptでレスポンスが返るまでの時間を測定。
  • スピンアップあり/なしの2パターンで、それぞれ10回測定し、平均値を算出。
  • 前記2パターンの差から、スピンアップ+VM起動+フレームワーク起動の時間を計算。

測定結果

単位はすべてmsです。測定結果をtableにすると横長になってレイアウトが崩れたので、画像です。
まずはスピンアップあり。

続いてスピンアップなし。

そして、スピンアップ+VM起動+フレームワーク起動の時間。
Servletspringレガシー
Java11625.24728.62098.6
Java17599.84769.21655.0
Java21630.24622.92451.5
Go1.20176.9--


所感

起動時間は 17<11≒21。わずかとはいえ、最新の21で悪化しているという意外な結果に。まだプレビューなのも影響しているのでしょうか。

SpringBoot

前回と比べてバージョンが2.6.6→2.7.18と変化していますが、結果はほぼ同じ。他はJava21で遅いのに、なぜかspringだけはJava21が最速と、一致しません。なぜ...

レガシーバンドルサービス

遅い気がしたのは気のせいではありませんでした。Java8のスピンアップが遅かったのはもしかして、Googleのカスタマイズのせいだったのではという疑惑が生じます。そしてJava21での遅延が目立って大きくなっています。残念過ぎる...

2024/01/14

ビルドエラー "ERROR: failed to initialize analyzer: getting previous image" の対処 [GAE]

事象

久しぶりにGAEのプロジェクトをビルドすると、mavenで以下のエラーに遭遇しました。
このプロジェクトはappengine-maven-pluginを利用してビルドしています。

[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  01:27 min
[INFO] Finished at: 2024-01-xxTxx:xx:xx+09:00
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal com.google.cloud.tools:appengine-maven-plugin:2.5.0:deploy (default-cli) on project プロジェクトID.サービスID: App Engine application deployment failed: com.google.cloud.tools.appengine.operations.cloudsdk.process.ProcessHandlerException: com.google.cloud.tools.appengine.AppEngineException: Non zero exit: 1 -> [Help 1]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoExecutionException


Cloud Buildを確認すると以下のエラーが出てしました。

ERROR: failed to initialize analyzer: getting previous image: getting config file for image "asia.gcr.io/プロジェクトID/app-engine-tmp/app/サービスID/xxxxxx:latest": GET https://storage.googleapis.com/asia.artifacts.プロジェクトID.appspot.com/containers/images/sha256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx?access_token=REDACTED: unexpected status code 404 Not Found: <error><code>NoSuchKey</code><message>The specified key does not exist.</message><details>No such object: asia.artifacts.プロジェクトID.appspot.com/containers/images/sha256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx</details></error>


調査

「差分ビルドしているけど前回のビルドイメージが見つからない」と言っているようです。だいぶ前ですが、Cloud Storageのビルドイメージを保存しているバケットにライフサイクルを適用して、生成から14日を過ぎたものは削除するように設定しています。その当時は14日以上空けてビルドしても問題なく再ビルド可能でした。また手作業でCloud Storageのビルドイメージを消してからのビルドも可能でした。それに差分ビルドによるビルド時間短縮の効果もわずかでした。

その後ビルド方法が変更されて差分ビルドしかしてくれなくなったようです。

ネットで探してみると「GAEのデプロイが失敗する: ERROR: (gcloud.app.deploy) Error Response: [9] Cloud build xxxxx status: FAILURE」が見つかりました。まったく同じ状況です。


解決方法

gcloud app deploy のケース

Goで作成しているサービスは上記の記事と同じくgcloud app deploy --no-cacheで回避できました。


maven appengine:deploy のケース

Javaで作成しているサービスはデプロイまでmvn appengine:deployで行っています。appengine-maven-pluginに--no-cacheと同様のパラメータがないか探してみたのですが、見当たりません。serviceAccountKeyFileという怪しいタグがありますがweb上に全く説明が見つからず、またタグ名から推測される動作も異なります。

なぜかJavaで書いたサービスはgcloud app deployだとビルド・デプロイは成功するものの、生成されるwarに依存ライブラリが含まれないので使っていませんでした。

しかし一旦gcloud app deploy --no-cacheでデプロイすればビルドイメージができるので、その後はmvn appengine:deployでデプロイできるようになりました。


2023/07/03

サーバレス アーキテクチャ

サーバレス アーキテクチャ って何

ちょっとネットで調べてみればわかるのですが、サーバレス アーキテクチャについて書かれた記事はそこそこ数はあります。しかしどれを読んでも結局サーバレスがどういうものなのか理解できない記事ばかりが目立ちます。よく見るのは以下のような例です。

  • こんなサービスですといくつか具体的に紹介されているものの、肝心のサーバレス アーキテクチャそのものについての記述がない。
  • あれではない、これでもないと該当しないサービスの例が挙げてあるものの、では何がサーバレスなのかについては記述がない。
  • 「サーバレス アーキテクチャとはサーバレスな構成です」としか書いてない。←いや説明を説明してw

このような状況なので、調べてもなかなか良い記事にたどり着きません。記事を書いてる人たちも理解していないのではないかと疑いたくなります。


いきなり結論

サーバレス アーキテクチャとは、常時起動しているサーバが存在しない構成

のことです。文字どおりに「サーバがない」と解釈してしまうと「ではどうやって処理するのか」と疑問が湧くだけですが、常時かどうかがポイントです。

以下、従来と比べるとより分かりやすいと思います。


非サーバレス

サーバレスではない従来の構成では、サーバは起動しておいてリクエストが来るのを待っています。リクエストが来なければ、基本的には何もしないでずっと待ち続けています。

GCPでWebサーバを構成する例だと、GCE/GKEに従来のオンプレミスでの構成と同様にWebフレームワーク等をインストールして構成します。

クラウドでは従量課金が基本ですが、アプリが何もしないで待っている間でもプラットフォームから見れば稼働中なので課金されます。またアプリを動かすインフラがスケーリングに対応していない場合、想定される最大の負荷にあわせて構成することになります。その分ランニングコストが高くなります。


サーバレス アーキテクチャ

アプリが必要になった時点で、プラットフォームがアプリを自動的に立ち上げてくれます。アプリが立ち上がったら非サーバレスと同様に、アプリによってリクエストが処理されます。アプリが稼働を停止していると判断されたら、フラットフォームが自動的にアプリを停止してくれます。

クラウドではこのような動作を可能にするプラットフォームが用意されています。当然アプリもそれにふさわしい作り・設定が必要です。アプリが停止した後は、それまでアプリが動作していたインフラ(CPU、メモリ、ネットワーク、etc.)は、別のアプリのために使用されます。そのためインフラ全体では稼働効率をよくすることができます。

また、アプリ起動がインスタンスの増加と同じになるので、水平スケーリングにも対応が容易です。

GCPでWebサーバを構成するなら、GAEを利用します(ただし最小インスタンス数=0設定)。小規模ならCloud Run/Cloud Functionsも利用できます。GKEでも最小Pod数を0にしてネットワークからリクエストで起動するように設定することも可能(なはず)です。負荷の増減に対しても、その瞬間の負荷をさばくのに必要な数だけアプリを立ち上げればいいだけです。

従量課金だとアプリが稼働している間だけ、立ち上がっている数だけ課金されるので、ランニングコストを抑えることができます。


2023/06/29

BigQueryの価格変更とEditions

Googleのブログ記事「データクラウドに柔軟性と予測可能性をもたらす、新たな BigQuery Editions を発表」に載っているように、2023/07/05からBigQueryの料金体系が変わります。すでにネット上では多くの記事が書かれているので、ご存じの方も多いと思います。ここでは主な変更点について説明します。概要の理解を重視し、細かい部分については説明を省いています。またリリースもまだなので、変更される可能性もあります。


ストレージ料金の計算方法の変更

従来からBigQueryに保存されるデータは圧縮されていましたが、ストレージ料金が以下のように変わります。圧縮は平均1/12になるようですので、かなり料金が下がるはずです。

  • 従来 : 圧縮前の論理バイト数に応じて課金される。
  • 変更後 : 圧縮後の物理バイト数に応じて課金される。

ただし注意が必要なのは、計算方法の変更が自動的に行われるかどうかは未定のようです。圧縮後で計算されるようにするには、7/5以降に何か操作が必要になる可能性があります。  


(2023/09/14追記)
ストレージ料金を論理バイト数から物理バイト数に変更するには、データセット単位で設定の変更が必要です。そのためのUIもすでにGCPコンソールに追加されています。手順は以下のとおり。
  1. サイドバーから「BigQuery」→「SQLワークスペース」でエクスプローラを表示。
  2. 左ペインでデータセットを選択。
  3. データセット情報の右端の「詳細を編集」。
  4. 「詳細オプション」をクリックして展開し、下にスクロール。
  5. 「物理ストレージの課金モデルを有効にする」をチェックして「SAVE」。
    物理ストレージの課金モデル

ただし物理バイト数での課金では約2倍の料金になるようです。BigQueryの概要ページには米国での価格が載っています。「ストレージの料金」にはまだ物理バイト数での価格は見当たりませんが、修正が間に合っていないのでしょう。日本での価格は「「IDOLY PRIDE」のBigQuery料金を70%カットしました」に記載がありました。具体的な数値が載っているのでGoogleに直接確認した可能性があります。

オンデマンド検索の料金

従来の定額料金制ではなくオンデマンドでの検索も、7/5以降も引き続き可能です。クエリに使用したデータ量に応じで課金される仕組みも同じです。しかし料金は25%の上昇となります。これが一番 & たぶん唯一の痛い変更ですね。


従来の定額制とEditionsの違い

従来あった月間定額/年間定額とFlex Slotsが廃止され、新たにEditionsが開始されます。Editionsにはサービス内容と料金の違いで3つの選択肢がありますが、それらの違いの詳細は上記Googleのブログ記事や他のネット上の記事をご覧ください。Googleの公式ドキュメントにもすでに「BigQuery エディションの概要」が用意されています。

Editionsはオンデマンドと異なり、クエリに使用する仮想CPUである「スロット」単位での課金になります。スロットを使用した数と時間で課金されます。

つまりEditionsは新しい定額料金制ではありません。設定次第で、定額制の様にも従量制の様にも出来ますし、それらを組み合わせることもできます。

スロットの自動スケーリングの概要」に説明されていますが、Editionsではプロジェクトごとに2段階の設定でスロットの利用の仕方を定義します。

  • ベースライン スロット

    Standardでは使用できないようです。
    常時確保するスロットです。従来の月間定額/年間定額で予約するスロットと同じと考えていいと思います。次の自動スケーリングを使用せずベースラインのみ使用するように設定すると、従来の月間定額/年間定額と同等になります。
    従来の月間定額を使用していたプロジェクトは、7/5に自動的に同量のベースラインに変換されるようです。年間定額の場合はその期限が切れたときにベースラインに変換されるようです。

  • 自動スケーリング スロット

    基本的にはベースラインスロットを使い切った場合に、追加で使用されるスロットです。自動スケーリングなので不要になれば解放されます。前のベースラインを使用せず自動スケーリングのみ使用するように設定すると、完全にオンデマンドに設定することも可能なようです。


現時点ではまだ自動スケーリングの時間間隔や課金の時間軸方向の単位について明確な記載がないのですが、短い時間でスケーリングされ課金の時間単位も短いなら、オンデマンドに近い感覚で使用することができると思います。従来の月間定額/年間定額を利用してきたユーザにとっては、高価な利用料を下げられるかもしれません。


Flex Slotsの置き換え

従来のFlex Slotsは廃止されるので、今まで利用していたならその代用が必要になります。これは「既存のFlexコミットメント」に説明されています。
Flex Slotsを利用するケースで代表的なのは、特定の期間だけBigQueryの負荷を急増させる様なケースです。その期間の直前にFlex Slotsを購入し、期間が終われば削除することで、従来は短期的かつ計画的な負荷の急増に対応することがてきました。

Editionsでは自動スケーリングを設定すれば、このような負荷の急増に対応できます。


BI Engineについて

今回の仕様変更では、BI Engineについてはほとんど言及されていません。「エディションの機能」の表ではStandardプランでは利用できない様です。またこの表の「エディション以外のサービス」はオンデマンドのことを指していると思われるのですが、記述されている内容は従来の内容の様に見えます。そのため単体で利用できるのかどうかも曖昧です。従来は年間定額で一定以上のスロットを予約すればオマケについてくるケースもありましたが、それもどうなるのか不明です。

2023/05/07

GCSからHTMLテンプレートを読み込んでHTTPレスポンスで返す [Go]

 ほぼ自分用の忘備録です。


背景

 Go+Gin構成のwebアプリでは、ファイルとして用意したテンプレートをHTTPレスポンスで返すのは簡単です。以下のようなスニペットになります。


func main() {
    router := gin.Default()
    router.LoadHTMLFiles("HTMLテンプレートファイルのパス")
    router.GET("リクエストエンドポイントのuri", handler)
}

func handler(ginctx *gin.Context) {
    ginctx.HTML(http.StatusOK, "HTMLテンプレートファイル", gin.H{
        キー: バリュー
    })
}


課題

 HTMLテンプレートをGoogle Cloud Storage(以下GCSと略)に置いておき、そこから読み込んで返したい場合、直接GCSから取得してくれる関数はGinにはありません。


解決

 以下のスニペットで可能になりました。エラー処理のコードは省略しています。これでいいのかちょっと不安な箇所もありますが、とりあえず期待動作はしています。


func main() {
    router := gin.Default()
    router.GET("リクエストエンドポイントのuri", handler)
}

func handler(ginctx *gin.Context) {
    // GCSからHTMLテンプレートをロード
    ctx := context.Background()
    storageclient, _ := storage.NewClient(ctx)
    reader, _ := storageclient.Bucket("バケット名")
    	.Object("HTMLテンプレートのオブジェクト名").NewReader(ctx)
    content_bin, _ := io.ReadAll(reader)
    reader.Close()
    
    // GCSから読み出した結果はバイナリなので文字列に変換
    content_str, _ := *(*string)(unsafe.Pointer(&content_bin)
    
    // テンプレートとして登録
    router := gin.Default()
    template, _ := template.New("").Delims("{{", "}}").Funcs(router.FuncMap).Parse(content_str)
    router.SetHTMLTemplate(template)

    // レスポンスで返す
    html := router.HTMLRender.Instance("", gin.H{
        キー: バリュー
    })
    ginctx.Render(http.StatusOK, html)
}

 バイナリ→文字列の変換には「忘れがちなGoでbyteをstringに変換する方法をベンチマークしてみた。」を参考にしました。

2023/04/12

Cassandraって何者?

現在参画しているプロジェクトで、はじめてApache Cassandraを使っています。Cassandraはオープンソースのデータベースで、クラウド上に実装して使う例も多数あります。今回参画しているプロジェクトもそうです。

本記事では、これを分類してみましょう。


Let's 分類


分散型

複数のインフラに分散実装し、全体として1つのデータベースとして機能する分散型データベースであるのが、たぶん最大の特徴です。この点はシステム屋さんには重要でも、純粋にコードを書くだけのソフト屋さんにとっては意識することはありません。

リレーショナル or ドキュメント指向 or キーバリュー型

リレーショナルデータベースと同様にあらかじめスキーマの定義が必要なスキーマ・オン・ライトですが、JOINはありません。あらかじめスキーマの定義が必要ということは半構造化データを前提としているわけではないので(JSON型が使えるのは別の話)、ドキュメント指向でもキーバリュー型でもありません。
Cassandraは、どれでもないですね。
(20230/04/17修正 : キーバリュー型に分類されるそうです。しかしキーバリュー型の代表とも言えるredisとも似てない。)

SQL or NoSQL

Cassandraでのデータ操作はSQLではなく、SQLに似たCQLで行います。SQL互換ではないのでNoSQLに分類されるようです。しかしNoSQLというと「SQLライクな言語」さえもなくてAPIで操作するものも珍しくない(Firestoreとか)と思うので、NoSQLに分類するのも違うと感じます。
Cassandraは、どちらに分類してもしっくりきません。

まとめ?

他に似ていない独自ですね。「俺か、俺以外か」という分類の仕方がいいかもしれません。

2023/01/31

オンプレミスへの回帰について

前回の記事「クラウドへの移行について」と真逆のタイトルです。


ここ3年ほどで何度か『クラウド移行したけど運用コストが予想より高かったのでオンプレに回帰した』という趣旨の記事をネット上で見かけることがありました。しかしそれらの記事にはどれも、具体的にどんな問題があったかについて記述されていません。具体的でないので、記事そのものが疑わしいと感じていました。

クラウドへの移行について」の最後に紹介した「NTTぷらら、映像配信のストレージをAzureからオンプレミスに移行、ストレージ利用コストを削減」のような一部だけ回帰のケースは時々見ます。これらの記事には具体的な記述があり著者も明確なので、事実であろうことを疑う必要もないでしょう。クラウドだっていつでもどんな状況でも優れているわけではない事例です。


最初に記したような完全回帰の記事にはなぜ具体的な記述がないのかですが、こういった記事は、世の中のクラウドへの移行について行けないあるいはクラウド移行したくない事情を持っている一部の人達のプロパガンダだと予想しています。だから具体的な記述がないのではないでしょうか。


また、前回の記事「クラウドへの移行について」にも記したように、マイグレーションしただけではランニングコストが高くつきます。最低限のマイグレーションだけしてクラウドへの移行が終わったと勘違いしてそのまま使い続けて、「高い」と間違った評価をしている可能性も高いと思います。

ランニングコストも低く抑えたいなら、クラウドに特化するモダナイゼーションも必須です。これはコンテナ技術を使ってスケーリングに対応することとは異なります。

2023/01/22

クラウドへの移行について

今更な気もするのですが、クラウドでの開発にかかわっていながら認識のない技術者も意外と多いようなので、記事にしてみます。


クラウドへの移行方法

オンプレミスやレンタルサーバからクラウドへの移行については、大きく2つの方法があります。
  1. マイグレーション
  2. モダナイゼーション

マイグレーション

既存のシステムをできるだけそのまま移行あるいは、既存の製品や技術をできるだけ利用して移行する方法です。新しく習得しなければならない技術・知識が最低限で済み、既存の資産もある程度再利用できるので、移行にかかる期間・コストが小さくなります。
一度クラウドへの移行を果たした後も、別のクラウドへの移行が行いやすい利点もあります。GCPで言えばGCE/GKEなどのIaaSと、オープンソースソフト(OSS)への依存度が高くなります。

その反面、クラウドの持つ特徴・能力を限定的にしか利用できず、運用が始まってからのコストが高くなり、自由度も限定的になります。構成によっても違いますが、運用においてはメンテナンス要員が必要となることも珍しくないと思います。
初期コストを削減する代わりに、運用コストに目をつむるような方法です。

コンテナ技術が利用できるようになってからクラウドの利用が多くなったのは、コンテナにより既存の製品・技術を使いながらもクラウドによるスケールアウトのメリットを享受できるようになったからという面があります。コンテナの中身がレガシーの塊になっているのは、『マイグレーションあるある』じゃないでしょうか。マイグレーションしか経験がないのに「クラウド使えます」とか言ってるようでは、技術者としては中途半端です。

モダナイゼーション

クラウドでの実行を前提としたクラウドネイティブな構成にすることにより、クラウドの特徴・能力を大きく享受できるようになります。さらに特定のクラウドに限定するなら、享受できる利益は最大限にもできます。最適なサービスを選択できれば運用コストは少なく、メンテナンスフリーにできる範囲も広くなります。新しく一から開発する際には、魅力的な選択肢になります。
近年リリースされたサービスではクラウド内のサービスの連携を実現するものも少なくないので、OSSとかは蚊帳の外ということもあります。そういった悲劇に遭わなくて済む利点もあります。IaaSへの依存が少なくなり、PaaS/SaaSの利用が多くなります。

反面、クラウドについて新しく知識・技術を習得する必要がでてきます。既存の資産・技術も置き換えが必要になるものも珍しくありません。「GAEインスタンスの起動時間」でSpringが使い物にならいことに触れているのは、その一例です。また特定のクラウドに限定した作りを選ぶと、他のクラウドへの乗り換えが困難になるクラウドロックインが発生します。
初期コストの増加を覚悟する代わりに、運用コストは最適化できるようになります。

GKEの様なコンテナ技術ももちろん利用できますし、GAEのように特に気にせずそれを利用できるサービスもあります。クラウドネイティブな開発が出来るようになれば、「クラウドを使える」技術者と言えると思います。

移行の実際

完全な新規開発ではモダナイゼーションで始めることもありますが、既存システムの移行となるとマイグレーションが多いと思います。マイグレーションで最短期間で移行後に、運用しながら少しずつクラウドネイティブにモダナイズしていくのもお勧めです。
また完全移行ではなく一部のみを移行する場合もあります。クラウドの利用料金は「使った分だけ」ですが、単価が高め設定の機能の利用が多かったり、単価が段階的に安くなる料金体系でない場合は、クラウドにすべてを移行しても運用コストが下がらないケースがあります。「NTTぷらら、映像配信のストレージをAzureからオンプレミスに移行、ストレージ利用コストを削減」のようなケースです。
マイグレーションで移行してそのままというのはお勧めしません。ランニングコストが高く、クラウドに移行した効果が得られにくいからです。「将来また別のクラウドに移行するかもしれないから、クラウドロックインの発生しにくい方法を選択したい」という考えもあるかもしれませんが、「別のクラウドに移行」する未来が来る可能性なんてどれほどあるんでしょう。

2023/01/13

データストアの選択

世の中には データを保存しておく製品にも、いろいろと選択肢が用意されています。GCPでも目的に応じて特徴の異なる複数のサービスが用意されています。その中でもBigQueryって特徴的で判りやすいと(個人的には)思っていたのですが、最近になってGCPにかかわりながらもBigQueryがどういうものなのかを未だに知らない人に遭遇しました。

ここでGCPのデータの保存目的に使えるサービスを比較しておきたいと思います。


一覧

GCPに用意されたサービスの一覧です。
名称略称概要スケーラブルマネージド
Firestore(特になし)NoSQL、ドキュメント指向データベース。
Cloud SQL(特になし)SQLのリレーショナルデータベース。
Cloud SpannerSpannerSQLのリレーショナルデータベース。
Cloud BigtableBigtableNoSQL・低レイテンシのデータベース。
MemorystoreMemstoreオンメモリで高速動作。
BigQuery(特になし)検索に特化したデータウェアハウス。
Cloud StorageGCS非構造化データ用のストレージ。

「略称」はGoogleが決めた正式なものではないですが、開発者の間でよく使われている呼称です。
「マネージド」の〇はフルマネージド、△はフルではないマネージドを表します。

これら以外にもFirebaseのサービスもあるのですが、それらに関しては割愛します。

各サービスの特徴

サービスを比較・選択するうえで参考としていただけるように、ざっくりと説明します。詳細はGoogleの各サービスをご覧ください。

Firestore

スケーラブルなので負荷が増えても重くなる心配がありません。むしろアクセスが少ないと優先順位が下げられて待ちが長くなる感じさえあります。フルマネージドなのでメンテナンスフリーです。
スキーマレスでドキュメント指向です。自分で各ドキュメントの構造を同じにして使えば、テーブルとして使えます。検索用のインデックスが付いた連想配列の様な感じですが、インデックスによる検索機能は貧弱です。柔軟な検索が必要なら、ElasticSearchやGAEのSearch APIと組み合わせる必要があります。
強整合性でトランザクションにも対応しています。
費用は主に保存容量に対して発生し、無料枠もあるので、ランニングコストは低めです。
SQLしか経験のない方には理解しにくいかもしれませんが、「Not only SQL」なデータベースです。
過去には似たようなDatastoreというサービスがありましたが、それが廃止されてFirestoreのDatastore互換モードとなりました。Firestoreにはネイティブモードもあり、こちらはドキュメントを階層構造にすることもできます。

Cloud SQL

MySQL/PostgreSQL/SQL Serverのいずれかから選んで使用します。これらを自分でGCEに乗せなくても、予めGoogleが用意してくれていると理解すればいいと思います。
なので利点も欠点もMySQL/PostgreSQL/SQL Serverを引き継ぎます。スケーリングに関しては設定した範囲内に限定され、無限ではありません。幸いフルマネージドでメンテフリーで使えます。
費用は主に保存容量とCPUに対して発生します。CPUはGCEと同等なので、ランニングコストはそれなりに発生します。

Cloud Spanner

スケーラブルなので負荷が増えても重くなる心配なし、フルマネージドでメンテナンスフリー、強整合性と、ここまではFirestoreと同じです。こちらはドキュメント指向でありません。SQLでリレーショナルデータベースを使いたい人にとっては、Cloud SQLとFirestore/Bigtableの「良いとこ取り」の様な感じになります。GCPではコスト以外はほぼ最強ではないでしょうか。
費用は主に保存容量とコンピューティングインスタンスに対して発生します。「Cloud Spanner vs Cloud SQL」も参考になると思います。

Cloud Bigtable

スケーラブル、フルマネージド、NoSQLと、ここまではFirestoreと同じです。低レイテンシが最大の売りでしょうか。
GCPに昔からあるサービスで、他のデータベース/ストレージの基盤となるサービスだと聞いたことがあります。(今GoogleのWeb上のドキュメントを探してもそのような説明は見当たらないのですが、当時詳しい人だったかGoogle日本法人の人から聞いた話なので、嘘ではないはず)
費用は主に保存容量とコンピューティングに使われるノードに対して発生します。ノードは事実上GCEと同じなので、たぶんそこそこかかるはず。

Memorystore

Redis/Memcachedのいずれかから選んで使用します。たぶんそれらをGCPに乗せただけと思います。オンメモリなので速いはず。しかもスケーラブル。
速さを活かしてキャッシュや書き換えの集中するデータの保存に向きます。

2022/10/18

円安の影響

2022年に入ったあたりから円安による物価高が、いろいろなところに影響していると思います。わかりやすいのはガソリン・軽油、灯油、電気代、ガス代、食品あたりでしょうか。そのほかにもいろいろな商品がいつの間にか値上げされていてびっくりしたり。


GCPの利用料金は、使用しているリージョンに関係なく、米ドルで計算されます。そのため円相場は運用費用に大きく影響します。LotteryServの価格を決めた2021年は1ドル=110円くらいでした。多少レートが変動してもすぐには価格に影響させなくていいように考えて価格を決めたつもりなのですが、とうとう145円を超えてしまいました。それどころか150円にも届きそうな勢いです。わずか1年ちょっとでこんなに変わるなんて。


LotteryServの応募ページのレスポンスを向上では、ランニングコストも下がったはずなので、それを反映させたいと思っています。特に応募数が多い場面で効果が大きいはずです。

しかし今の円安は、この効果を見事に打ち消してしまっています。そのため今は価格改定の時期を検討しています。


2022/08/31

BigQueryの料金について

BigQueryの基本的な料金

GCPの中でも BigQueryは特徴あって魅力的なサービスだと思いますが、実際に使ってみると「利用料金がいつの間にかとんでもない金額になっていた」ことで有名です。料金がかるのは、基本は以下の2つです。(実際にはストリーミングとかの別料金もありますが、あまり使われないはずなので割愛します)

価格は東京/大阪リージョン、$1=¥135換算です。


ストレージ:

$0.023=¥3.105/月/GB
$0.016=¥2.16/月/GB(長期保存:90日以上連続して変更されなかったテーブル/パーティション)
1TBまでは無料。

検索:

$6.00=¥810/TB (検索対象になったデータに対して課金)
1TBまでは無料。


BigQueryの検索が速い仕組み=料金が高騰する仕組み?

この章は私の主観や予想を含みます。

検索が速いのがBigQueryの最大の魅力と思いますが、実は裏では結構な大規模なインスタンスが動いているのではないかと思います。その根拠は「クエリジョブ」の以下の記載です。

オンデマンド料金では、プロジェクトで最大 2,000 個の同時実行スロットを設定できます。

ここで「スロット」が何なのか問題になりますが、「コミットメント」に以下の様に説明されています。

スロットは、BigQuery で使用される仮想 CPU を表しています。

つまりGCEのvCPUやGKEのノードと同等のもの? でしょうか。

GCEでvCPUを2000予約する費用を試算してみます。どのようなタイプのvCPUを選ぶかにもよりますが、n2-standard-80(vCPU=80、メモリ320GB、$4.9835/時間)だと、以下のとおり。

$4.9835*2000/80=$124.5875≒¥16,819/時間

ちょっと微妙だったような... GCEのvCPUと同じという予想はハズレかもしれません。


検索の料金を安くする仕組み

がっつりBigQueryを使いながらも検索の料金をそれなりに抑えられるかもしれない仕組みが用意されています。一覧にすると以下のとおり。ちょこっと使いたい方は蚊帳の外です。

名称概要料金
月定額契約購入した瞬間から、30日間の定額料金です。$2,400=¥324,000/月/100スロット
年定額契約購入した瞬間から、1年間の定額料金です。$2,040=¥275,400/月/100スロット
Flex Slots指定した短期間だけ、秒単位で定額にします。$4.80=¥648/時間/100スロット
BI Engineオンメモリ検索で高速化。$0.0499/GB/時間


定額料金

文字どおりの定額料金です。最初の1か月または1年は、途中で止められないので注意が必要です。また上記の料金は100スロットであることにも注意が必要です。通常のオンデマンド検索は2000スロットなので、スロットが少ないと「契約してみたけど検索遅い」ということになりそうです。オンデマンドでは2000スロット以上を使用する手段が用意されていませんが、定額契約では2000スロットを超えて予約できるそうです。東京リージョンでは最大1000スロットまでしか予約できないようです。

年定額契約をある程度のスロット以上購入すると、おまけとして下記のBI Engineの容量もついてきます。年定額契約するなら、これも使わない手はないです。


Flex Slots

瞬間だけ定額という、変則的な料金プランです。使用する都度開始/終了を指定するようなので、定期的に決まった検索を行うなどの用途に向いています。購入するスロット数が多すぎると、購入に失敗することがあります。購入に成功すると解除するまで、秒単位で使っていても使わなくても課金されます。

上手く使えば費用と速度の両面で効果があるはずです。他プランとの組み合わせも可能です。


BI Engine

料金体系というより機能なのですが、別の料金体系を持っています。

Business Intelligence(以下BIと略)ツールのデータソースとしてBigQueryのテーブルを使用する場合に、レスポンスの改善や利用料金の低減の目的で使用します。BIツールとBigQueryを接続すると、ちょっとBIツールを操作しただけで何度も検索が発生していて、いつも間にかとんでもない金額を使ってしまうという悲劇を防げます。なんかのBIツールと組み合わせなければならないわけではないので、常時使うことも可能です。

検索をオンメモリで行うので非常に速いそうです。その検索用のメモリの予約した容量に応じて料金が決まります。素のBigQueryよりいろいろと制限が厳しく、BI Engineで検索できない場合、通常のBigQueryで検索され、料金もそちらのものとなります。


2022/07/06

GAEのインスタンス起動時間

動機

GAEのスタンダード環境では、言語によってインスタンスの起動時間に差があるのは、Java8を含む第1世代VMを利用している人には、良く知られた事実です。既に起動しているインスタンスがあっても、リクエストが増えて新たにインスタンスが起動される場合にも、この起動時間がかかります。そのためインスタンス起動が遅いと、時々レスポンスが悪く感じることになります。

昨年公式に公開されたJava17がいつの間にかGAEでもプレビュー版として使えるようになっていましたので、バージョンアップで起動時間がどう変化したか確かめてみたくなりました。それにSpringの起動時間も気になります。で、言語とそのバージョン + Webフレームワークの違いで、実際のインスタンス起動時間を計ってみました。


最初に断っておきますが、私が使用する(可能性のある)組み合わせのみの測定です。


測定条件・方法

  • 基本的にアプリはGoogleのサンプルに入っていたHelloWorld、それに最低限のログを加えたもの。
  • ただしJava8/Goは手元にあった単純なアプリ。Slim3やObjectifyなどは入っていないので、結果には影響しないはず。
  • SpringBootはGoogleのサンプルに入っていたHelloWorldそのままなので、バージョンは2.6.6。
  • GoのフレームワークはGin。
  • インスタンスクラスはF1。
  • インスタンスが立ち上がっていないことをGCPコンソールで確認してから、ブラウザからリクエスト開始。
  • Cloud Loggingに記録されたログにより、リクエストの到着から、リクエストハンドラの先頭に入れたログが出るまでの時間を計算。
  • それぞれ5回測定し、平均値を算出。
  • 1回測定ごとにGCPコンソールから手動でインスタンスを停止。次の実行まで数分間放置。

測定結果

フレームワーク
なしあり
言語Java82.063-
Java111.0553.737
Java170.9393.551
Go 1.160.1760.205

単位はms。


所感

Java8

過去の経験から3秒くらいと思っていたのですが、なんか早いという結果になりました。意外でしたが、ちょっと嬉しい。

Java11

VMの起動が早いのでしょうか。フレームワークなしならVM起動してても、ほとんど待たされている感覚はありません。PHP/Pythonと同じくらいの起動時間だと思われます。


Java17

まだプレビュー版であることはご理解ください。しかしJava11より少し早くなっているのはいいですね。


SpringBoot

JavaVMに1秒+Springに3秒弱という結果でした。ブラウザを触っていても「あれ、遅いかな」と感じたころに応答が描画される感じです。これだけで十分「使えない」と判断できます。
起動時間の4秒というのは、GAEのJavaというと8しかなかった時代に、起動が遅いといわれていた状況よりも悪くなっています。Springはサーバレスという考えのなかった時代の、「起動しておいて口開けて待っている」ことが前提の時代の産物だけに、時代遅れの遺物になってしまった感があります。
サーバレスでやりたいなら、潔くSpringは捨てましょう。


Go

インタプリタもVMも存在しない、ネイティブ実行されるだけあって、爆速です。ブラウザを触っていても、インスタンス起動が起きていることを全く感じることがありません。


Gin

「フレームワークというよりライブラリ」と言われるだけあって、遅延は30ms程度と、ほとんど影響がありません。SpringBootと対照的な結果ですが、時代が違うのでサーバレスも考慮された結果なのかもしれません。


(2022/07/14追記)

現在、GAEのJava17はプレビューから一般提供に格上げされています。

2022/06/25

残念なレガシーバンドルサービス

GAE/JのJava17対応


(2024/02/26追記)
本記事の内容はすでに古くなっています。また最初の公開時に記載内容に間違いがあった可能性があります。


 久しぶりにApp Engineのドキュメントを見たら、まだプレビュー版ですが、Java17の対応が載っていて驚きました。Java17って去年秋にリリースされたばかりなので、GCPの対応はまだ先かなと想像していたのですが、予想よりずっと早いようです。Java17はJava11以来のLTSなので、注目に値します。


ランタイムの世代

GAEのランタイムには第1世代と第2世代の2種類があります。これらは実行されるランタイムのバージョンが異なるだけでなく、第1世代のみGoogleが実装した拡張機能があります。
これらの拡張機能のうち、よく使われるのは以下ではないでしょうか。
  • Memcache : オンメモリのキャッシュ
  • Search API : Googleのサーチエンジンと同じ全文検索
  • Mail API : メールの送受信
  • Users API : Googleアカウントによるユーザ認証

ランタイムを選択するうえで、これらの機能が必要かどうかも分かれ目になります。

個人的にはまだ第1世代ランタイムであるJava8を使用していて、第2世代ランタイムに移行への移行はまだです。その理由がこれらの機能を使用したいからです。


代替サービス/ライブラリ

以前は第2世代ランタイムでは、前記の第1世代ランタイムでしか提供されない機能の代わりに、他のサービス/ライブラリを使用するように、Googleは説明していました。これらの代替手段は現在でも有効で、本来はこれらの代替手段で対応するものと思います。主なものは以下のとおり。 

Memcache → Memorystore (無料枠なし)
Search API → Elasticsearch
Mail API → SendGrid/Mailgun/Mailgrid など
Users API → Identity Platform/Firebase Authentication/OpenID Connect など


Memcacheは、有料のMemorystoreが嫌なら、ちょっと極端ですが諦めてもいいでしょう。

Search APIは、代わりとして気軽に使えるものが事実上ありません。

Mail APIは、SendGridのみ無料枠が用意されています。SendGridは使いやすくて性能も十分と思います。

Users APIは優秀な代替手段が用意されており、無料枠もあるので、積極的に置き換えるべきと思います。


これらのGAEの機能と同じような状況にあったのがDatastoreですが、Datastoreはすでにサービス終了しています。代わって登場したFirestoreには「Datastore互換モード」があるので、Datastoreとほぼ同じように使えます。DatastoreはJavaライブラリが使いにくいですが、Firestoreはいくらかライブラリが使いやすくなっています。個人的にはFirestoreネイティブモードに乗り換えました。


レガシーバンドルサービスの提供

Java17対応と同時に、第2世代ランタイムに「レガシーバンドルサービス」が提供されるようになっていることに気付いて2度びっくりしました。

レガシーバンドルサービス」は第2世代ランタイムから、第1世代ランタイムのこれらの拡張機能を使用するためのライブラリのようです。これを使用することで、第2世代ランタイムも第1世代ランタイムと同様に使用することが出来るようになるようです。


しかし残念なことに、一番使いたいSearch APIは提供されないようです。

Elasticsearchがあるとはいえ、ちょっと興味本位で調査したい場合や、商用であってもサービス立ち上げ直後で利益が出せるのがいつになるか不安な状態では、安くない月額固定料金のサービスへの乗り換えは厳しいです。


Googleの本音を予想

GAEの第1世代ランタイムに用意されていた拡張機能は、Googleがランタイム(Javaの場合はJavaVM)を独自拡張して実装したのではないかと想像しています (だからランタイムの存在しないGoにはこれらの拡張機能が提供されない)。

App Engineスタンダード環境のランタイム」を読んでいるとGoogleは、第1世代ランタイムを停止して第2世代ランタイムに移行させたいのではないかと思えてきます (実際すぐに第1世代ランタイムが止まることはないことは、「レガシーランタイムの長期サポート」に説明されています)。古くなったランタイムのメンテナンスが行われなくなるのは当然なのでずっと第1世代ランタイムがサポートされることはないのですが、第2世代ランタイムへの移行を促したいのは、ランタイムの独自拡張のメンテナンスも止めたいからではないでしょうか。

なかなか移行が進まない原因を探ったら第1世代ランタイムの拡張機能が原因だったから、第2世代でも利用できるように「レガシーバンドルサービス」という形で対処したものと思われます。


Search APIの代替が用意されない理由はちょっと想像できません。Googleにとっては自社製の検索エンジンの応用らしいので、技術的には用意できなくはないはずです。政治的な理由しか考えられません。Elasticsearchに誘導したい理由は何なのでしょう。

2022/06/01

Cloud Loggingにサブスレッドで出力したログが出ない件

Cloud Logginについて

GCPでログを扱うなら、Cloud Loggingは避けて通れない道です。適当にログを吐くコードでを埋め込んでおいて、ブラウザを立ち上げてGCPコンソールのログビューアで表示。しかも30日前くらいまで遡って表示可能。

Cloud Loggingのライブラリの使い方に慣れれば、出力するログのフォーマットもかなり自由に弄べます。ほとんどのGCPサービス・言語で利用できますし、使い方も同じです。

こんなに便利なツールがあるなら、避ける理由などありません。それくらいに便利です。


症状

しかしCloud Loggingには、ログがまともに出ない問題があります。具体的には、

  • サブスレッドから出力したログが、ログビューアに表示されない。主にGCEで発生。
  • サブスレッドから出力したログが、ログビューア上では実際より後の時刻のログに交じって出力される。主にGAEで発生。

メインスレッドから出力すると問題ありません。しかしFirestoreのトランザクションなど、仕様書に明記されていないもののこっそりワーカスレッドを生成して処理しているケースでも上記の問題が発生します。時にはCloud BuildのビルドログがGAEアプリの実行ログに混じることも。


現状

これについてネットで調べても特に情報がないなあと思っていたのですが、ついに公式サイトに載っているのを見つけました。

「Java8 ランタイム環境」の「スレッド」の注釈に説明されています。

注: Java スレッド作成 API を使用する場合、Cloud Trace はリクエスト ID を正しく表示しません。Google は現在この問題の解決に取り組んでいます。

なんと、まだ対応中でした。何年も前から発生しているので早く直してほしいですね。

2022/05/21

GoでGCPプロジェクト管理者を識別する

動機

GCPで動作するアプリケーションを作っていると、アプリケーションの管理者=プロジェクトの管理者 であることが多いと思います。開発フェーズか運用フェーズかでそこに割り当てられている人は変わったとしても、この関係は変わりないのが理想と思います。

であれば、プログラムでプロジェクト管理者のアカウントを判断できれば、「アプリケーション管理者のアカウント」をアプリケーションで保持する必要ながなくなって便利です。


プロジェクト管理者のアカウントをコードで取得する手段を探してみたところ、Goで発見しましたので紹介します。


コード

サービスの名称と機能からIdentity and Access Managementあたりかと あたりをつけて探し始めて、たどり着いたのはResource Managerでした。プロジェクト管理者のアカウントを取得するには、以下の様に複数のAPIを経由します。

import ( "context" "os" "strings" "google.golang.org/api/cloudresourcemanager/v1" ) ... // context.Contextを取得 context := context.Background() // Cloud Resource Managerサービスを取得 crmservice, err := cloudresourcemanager.NewService(context) // (1)Resource Managerサービスを取得 if err != nil { // エラー処理 return } // プロジェクトのIAMポリシーを取得 projid := os.Getenv("GOOGLE_CLOUD_PROJECT") // (2)プロジェクトIDを取得 request := new(cloudresourcemanager.GetIamPolicyRequest) iampolicy, err := crmservice.Projects.GetIamPolicy(projid, request).Do() // (3)IAMポリシーを取得 if err != nil { // エラー処理 return } // このプロジェクトの管理者に含まれているか確認 adminuser := false check_end: for _, binding := range iampolicy.Bindings { // (4)各ポリシー for _, member := range binding.Members { // (5)各メンバ if strings.Contains(member, email) { // (6)Eメールアドレスで判断 adminuser = true break check_end } } } // adminuser == tureならプロジェクト管理者

  1.  Cloud Resource Managerサービスを取得します。
  2.  環境変数GOOGLE_CLOUD_PROJECTからプロジェクトIDを取得しています。Cloud Run/Goではこれで取得できましたが、その他のサービスでは同様に取得できるか分かりませんので、確認してください。
  3. プロジェクトのIdentity and Access Managerポリシーを取得します。メソッドチェインの最後のDo()メソッドが実行結果としてPolicyを返してくれます。
  4. Bindingsプロパティがポリシーにバインドされているプリンシパルの配列です。
  5. 各プリンシパルは複数のメンバーを含んでいます。このメンバーには個人アカウントだけではなく、サービスアカウントなども含まれます。
  6. Eメールアドレスでプロジェクト管理者かどうかを判断しています。個人アカウントの場合は先頭に"user:"が付いているので、Eメールアドレスを含んでいるか否かで判断しています。


ライブラリ

今回使ったライブラリのパッケージは以下。

一方でGoogle公式サイトには以下も載っています。

cloud.google.com/go/resourcemanager/apiv2

後者のライブラリは実装されている機能が少なすぎて、今回のように実装できません。というかAPI仕様を見る限り、ほとんど使い物にならないのではないかと思います。

GoのResource Managerに限らず、どのライブラリを使えばいいのか、こんなの見つけたけど使って大丈夫なのかと、迷うことがよくありますが、今回は以上の実装で目的を果たせました。また前者のライブラリは機能も十分そろっていそうなので、その他の用途にも使えると思います。

2022/05/17

vsReversiアップデート

GCPのデモとして用意していた vsReversiをアップデートしました。


アップデート内容は以下のとおりです。

  • ユーザのログインと新規登録を別に
  • SPA対応
  • デザイン変更

ユーザのログインと新規登録を別に

ユーザ認証にはGCPのIdentity Platform(Firebase Authentication)を使用していますが、これには出来合いのUIとして提供されるもの(Firebase UI)も、単体のAPIも、ログインと新規登録の区別がありません。それらが一緒になったサインインがあるのみです。

しかし日本では、ユーザ登録と登録済みユーザのログインが別になっているのをよく見るので、それらしく分けてみました。やってみて出来なくはないのですが、かえって実装は面倒になってしまった印象です。

またEメールで登録する場合は、OAuth(Google/Facebook/Twitter/etc.)に比べて、追加で実装しなければならないのものが多いので、余計面倒です。具体的には、

  • 入力されたEメールアドレスを確認する
  • 入力されたEメールアドレスが間違っていた場合にユーザ登録を解除
  • ユーザがパスワードを忘れた場合のパスワードリセット
  • ユーザがEメールアドレスの変更を希望した場合の対処 (vsReversiでは割愛)
  • さらにこれらのUIのカスタマイズ (vsReversiでは割愛)

Firebase UIではこれらの一部が対応済みなので、簡単に実装したいならそれを使うのが楽です。ただしUIのデザインは簡素なものなので、カスタマイズしたくなります。


SPA対応

Angularを使って一部のページをSingle Page Applicationに対応させました。ログイン/新規登録後のページがそれです。マテリアルデザインは採用していないので それっぽい見た目にはなっていないのですが、ページ遷移が速くなりました。

今までGAE/Java8メインで作っていたのですが、「GAE/Java8でSPA対応に苦戦」に記載したように、これは複数のURIに1つのページを割り当てることができないので、実装の変更が大掛かりになりました。具体的には...

  • HTMLなどスタティックコンテンツをFirebase Hostingに移行してURI割り当てを解決。
  • ajaxリクエストエンドポイントは過去のGAEの実装を流用。ただしCORS(Cross Origin Resource Sharing)に対応。
  • 管理者用のページはCloud Runで管理者の認証を実装。


GCPの初期からあるサービスだけに、GAEは単体でいろいろ必要なものが用意されていることを実感できました。今回は対応できないものが見つかったので、他にいろいろと物色することになりました。それぞれが別のサービスとして名前を持っているので、利用したサービスの数が増えています。


デザイン変更

SPA対応のついでですが、見た目だけの変更です。


CPUの思考ルーチンなどその他は以前のままで、変りありません。


2022/04/23

GAE/Java8でSPA対応に苦戦

GAEの第1世代VMであるJava8とGoで動かしているアプリを、Single Page Application(以下 略称のSPAを使用します)に一部作り直そうと検討しているのですが、意外と苦戦しています。


問題

SPAでは、複数のパスを実際には1つのHTML/JavaScriptで表示します。例として、以下のパスの3ページを用意したとします。

/         ←3つとも/index/htmlが対応

/aaa    ←〃

/bbb    ←

見た目には3ページでも、実際にはJavaScriptでリクエストされたパスを見て表示内容を切り替えているだけで、コンテンツとしてはこれら3ページを1つのHTML/JavaScriptが表示します。


で、これらのページをいつものようにGAEの静的ファイルとしてホストしようと思っていました。しかしGAEの静的ファイルは基本的に1パス=1ファイルに対応します。つまり"/"に対応するHTML/JavaScriptで3ページすべてを表示するつくりにすると、残り2ページをリクエストしてもGAEの静的ファイルサーバが「404 Not found」を返してしまい、HTML/JavaScriptが得られません。

Java8以外だとapp.yamlの記述(参照:ハンドラ要素)で、"/aaa", "/bbb"が指定された場合でも"/"と同じコンテンツを返すことが可能です。しかしJava8では、そもそもapp.yamlが使用できません。

サーバサイドのアプリはすでに作成済みのものを使いまわすつもりなので、今更ほかの言語で書き換える気などありません。このアプリではMemcacheやSearch APIなど第1世代VMでしか提供されない機能を利用していますので、使えるのは第1世代VMに縛られます。


解決案

いろいろ調査・検討してみました。ここから先はアイデアだけで試したわけではありません。


A案. Firebase Hostingへの一部移行

静的ファイルのホストをFirebase Hostingに変えます。複数パスが1つのHTML/JavaScriptに対応する問題は、firebase.jsonのリライト設定(参照:リライトを構成する)で解決できそうです。

Firebase Hostingは独自ドメインにも対応しています。GAEで実装したAPIはFirebase Hostingとは別ドメインになりますので、Cross Origin Resource Sharing(以下CORS)ポリシーに触れますが、どちらも自前で実装するので適切にレスポンスを返すだけです。


しかし実際のアプリには前記の3つのパス以外に、SPAに入らない全く独立したページも存在します。以下のような感じです。

/         ←3つとも/index/htmlが対応

/aaa    ←

/bbb    ←

/ccc/xxx    ←独立した動的ページ

この独立ページ"/ccc/xxx"は静的ファイルではありませんので、Firebase Hostingで対応できません。現状は静的ファイルと一緒にGAEで対応していますが、Firebase HostingとGAEでドメインが異なるのは問題です。アドレスはエンドユーザにも見えますので、同じアプリの一部であること、あやしいページに遷移したわけではないことがエンドユーザにもわかるように同じドメインへの配置が必要です。


B案. 動的ページは空コンテンツを用意

前記の独立した動的ページ"/ccc"にはhtml/body要素のみの空ページを用意しておき、"/xxx"の部分は前記のリライトで"/ccc"に置き換えます。中身はajaxで"/xxx"を判断して埋めます。

これならエンドユーザに見えるドメインはすべて同じにできます。ajaxのリスエストエンドポイントはGAEなので別ドメインですが、エンドユーザには見えないので問題にならないはずです。

ただしこのページは他のページとデザインなども異なるので、「空ページを埋める」でどこまで対応できるのか不安が残ります。


C案. API Gatewayで振り分け

一般提供になったばかり(だったと思う)API Gatewayで同じドメインの別パスとして、Firebase HostingとGAEの振り分けを追加する案です。B案の動的ページの不安も解決できる可能性があります。

しかしドキュメントを読んだだけでも以下の懸念が...

API GatewayがFirebase HostingやCloud Storageの静的コンテンツも対象に出来れば面白いと思うのですが。1つのWebアプリとして単一のドメインの中で静的コンテンツやajaxのAPIだけでなく、公開APIも対応できます。

API Gatewayのドキュメントには静的コンテンツに関する記述はなさそうです。名前のとおりWeb APIのみを対象としているのかもしれません。


(2022/4/25追記)

Firebase Hostingの動的コンテンツ配信の機能(参照:Firebase Hosting を使用した動的コンテンツの配信とマイクロサービスのホスティング)がGAEにも対応していれば問題ないのですが、この機能はCloud Functions for FirebaseとCloud Runにしか対応していません。

仮にアプリを書き直すにしてもCloud Functions for FirebaseはJavaScript/TypeScriptにしか対応していないようなのでパフォーマンスとランニングコストに疑問がありますし、Cloud Runでもランニングコストが課題になります。

なかなか、かゆいところに手が届いてくれないですね。