GAE/Goのblobstoreを使ったCSVアップロードとCSV読み込み

経緯

別のソースコードでCSVアップロードを試していたら、コードの書きかたが正しくないか、github.com/gorilla/muxの使い方に問題があって、CSVデータをテキストでブラウザーに表示するはずがダウンロードになってしまい問題が解決できませんでした。

対応

github.com/gorilla/muxなどや他のソースコードを含めず、単純なCSVファイルをアップロードし、それをブラウザーでテキストとして表示するサンプルを書きました。

これはyomusu/csvblob.goをフォークしました。 context型やappengineのインポートを変更していますが、基本処理はyomusuさんのコード其のままです。

このソースコードでdev_appserver.py上で問題なく動作しました。

gcloudのアップデートに失敗し動かなくなる

Google App EngineはPython2でインストーラーが正常に動作いないし、SDKアップデートができずに壊れた。

 

gcloud feedbakでレポート送れると書かれているけれど、gcloudコマンド壊れて送信できない。

 

GAE/Goは、$GOPATHにあるアプリで参照されているファイルをアップロードする

現在、GAE/Goで、Cloud Translate APIとDatastoreを使った中国語から日本語の単語翻訳、単語帳アプリを作っています。ひとまずデータ登録などをGAE/Goできて以下のサイトの「goapp は $GOPATH 以下もアプリケーションのソースとしてアップロード/コンパイルする」というのが気になったので調べてみました。

記事でちゃんと書いてありますが、自分が作ったGoアプリのimportで使っているファイルすべてがアップロード対象になります。つまり$GOPATHにあるアプリで参照されているファイルです。当たり前ですが$GOPATH/src全てではありません。(私は、何を読み間違えたのか、$GOPATH/srcが対象と勘違いしました。)

実際に確認してみる

どのファイルがアップロードされるかはデプロイ時に–verbosity=infoを付けるだけです。appdata環境変数にもログが出力されます。

通常gcloud app deployコマンドを実行すると[Do you want to  continue(Y/n)?]と表示されますが、–verbosity=infoにしていると、この入力待ちの時にログから、これからアップロードするファイルを確認でき、実際にエクスプローラーでも確認できます。

 

GAE/Go本番環境では、インデックスを再構築しないと動作しない。

開発環境で動作していたのですが、本番環境で動作しなくなりました。Cloud Consoleからログを確認したところ、一致しないインデックスを見つけたというログがありました。

どうやらindex.yamlがある場合は手動で反映させる必要があるようです。

インデックスの再作成と古いインデックスの削除

上記を実行後にすぐにブラウザーでアプリを試してみたら、エラーが変わり以下になっていました。

API error 4 (datastore_v3: NEED_INDEX): The index for this query is not ready to serve. See the Datastore Indexes page in the Admin Console.

「インデックスがまだ出されてません。AdminConsoleのインデックスページを見てください。」とのことなのでしばらくしてからアプリを試したら問題なく動作できました。

Cloud Datastoreのクエリーを変更したりする場合は注意する必要があります。

開発環境でのインデックスのクリア方法

dev_appserver.pyを起動するときのオプションに–clear_search_indexes=trueを付けるとインデックスをクリアしてくれます。

 

index.yamlが自動生成されて動かなくなったdev_appserver.pyを元通りにする

kindやAncestorを試しているうちに動かなくなった

開発途中でいきなりdev_appserver.pyが正常に動作しなくなり以下のログを出力しました。状況としては、

  • dev_appserver.pyは起動問題なし
  • localhost:8000で、datastoreをブラウザから見ようとすると、以下と同様なエラーがブラウザーに表示
  • アプリの登録処理ができない

原因は、index.yamlの自動生成

自動でindex.yamlが生成されていた事でした。このファイルは、独自にインデックスを用意する場合に使うのと、開発サーバーが自動生成したインデックスをここに書き込んでくれるようです。

https://cloud.google.com/appengine/docs/standard/go/config/indexconfig

DatastoreのkindやAncestorを試しているうちにこのindex.yamlが作られたようです。

対応方法は、不要なインデックスをindex.yamlから削除

j上記のコメントの後に、自動生成されたインデックスが書かれていますがコメントアウト、または削除しdev_appserve.pyを起動し直せばよいです。inde.yamlファイル自体を削除すると、インデックスが必要というエラーが別に発生しました。

 

その他

この問題を調べているうちに、datastoreの保存先やクリアの仕方が分かりましたのでメモを残しておきます。

clear_datastore=yesでdatastore削除

clear_datastore=yesとclear_search_indexes起動オプションでデータ削除できることが分かりましたが解決できませんでした。

物理的に削除

上記オプションでdev_appserver.pyを起動すると、以下のようappengine.Noneがあり、これを削除すればよいと分かりました。

https://stackoverflow.com/questions/25831141/google-app-engine-datastore-storage-default-path

削除したけれど解決できませんでした。

Ctrl+Cで終了してみると以下のようにpending transactionsと読み取れるのでこの失敗したデータ登録がどこかで保持されているので、データベースを削除しても実行されているようです。

 

 

 

html/templateでrangeでindexを使い別のスライスを参照する方法

Go言語のhtml/templateは、スライスやMapオブジェクトに対して{{range $index, $element := xxxxx}}のような記法でインデックスが使えます。

例えば、[]Userと[]Keyという対になったスライスが2つある場合、UserをRangeアクションでループ処理している間に、Key[i]のようにして別のスライスにアクセスしたい場合があります。(本当に汎用的な解決策はおそらくzip関数。)

このような場合は、以下の記述で[]Userと[]Keyのそれぞれの要素にアクセスできます。html/templateでは、{{range $i, $user := users}}でインデックスと要素にアクセスできるため{{$i}} {{$user}}で表示しています。またrangeスコープないなので、ドット{{.}}は、現在のuser要素を表示します。最後に、通常のプログラミング言語でKeys[i]やUser[i]のように要素取得するのを、{{index $.Keys $i}}で行っています。index関数の第一引数が、Keysスライス、第二引数がインデックスという意味です。

また、$.Keysがポイントで、rangeスコープ内で、ドットを指定するとuser要素になりますが、$.とすることでグローバルを参照します。

 

 

結果

 

 

GAE/Go開発メモ

GOOGLE_APPLICATION_CREDENTIALS

AppEngineで各種クライアントライブラリーを使う場合はこれを求められる。日本語情報も豊富にあるので、設定方法は簡単で、ファイルをダウンロードしてpowershellで設定。これで、ローカル開発環境で各種ライブラリーが使えます。

本番環境は自動化されている

本番環境にこのファイルを持っていけばいいのかというと、その必要はなくて サーバー間での本番環境アプリケーションの認証の設定 で、この指定がなければ自動で、デフォルトのサービスアカウント情報を使います。

そのため、ソースコード内には、クライアントライブラリーを使うための認証ソースコードなどがなく(もちろんその方法もあります。)github.comなどでも管理しやすいです。

pyコマンドで、dev_appserver.pyを動かす方法

Windowsでは、Python2と3をインストールしている場合、py -2コマンドでPython2.7を実行します。またSDKのインストール先が、sdk.staging/binになる場合があるので、エクスプローラーで確認する必要あります。(現状再インストールしたら、sdk.stagingはなくなり、sdk/binになりました。)

dev_appserver.pyでサーバーを実行した場合、ファイル監視されているので、Goファイルを修正したら、ブラウザーリロードで変更が確認できます。

  • –clear_datastore=trueでdatastoreをクリア
  • –clear_search_indexesでインデックス削除。データストアのクエリーを変更してインデックスが不正な場合などに使う

本番環境デプロイはgcloudコマンド

少し前までは、Google Cloud SDKではなく、AppEngine SDKのためgoappコマンドがありましたが、今は存在しないので、glcoud app deployで本番環境にデプロイします。

Windows環境でGAE/GoとPythonについて

Google Cloud SDKがPython2.7に依存しているためPythonが必要になります。Windowsの場合は、Python2.7とPython3系を両方インストールすればよいです。現在のPythonはpy.exeで”py -2″でPython2を実行し、”py -3″でPython3を実行できます。

Google Cloud SDKの場所

コマンドプロンプトの場合は、%localappdata%でアクセスします。

“%localappdata%\Google\Cloud SDK\google-cloud-sdk”

PowerShellの場合は、$env:localappdataでアクセスします。

“$env:localappdata\Google\Cloud SDK\google-cloud-sdk”

ちなみにLocalAppDAtaは、C:\Users\<username>\AppData\Local

dev_appserver.pyの場所

Google Cloud SDKの場合は、”$env:localappdata\Google\Cloud SDK\google-cloud-sdk\bin\dev_appserver.py”フルパスで指定するとよいです。

ひとまずフルパスで指定するとよい

数年前はAppEngine SDKがあったので、それを環境変数Pathに設定して、dev_appserver.pyなどを実行していましたが、今は、Google Cloud SDK付属のdev_appserver.pyを使います。

2018年06時点では、Google Cloud SDKで本番にデプロイできますが、単体テストをするためには、AppEngine SDKを使う必要があります。

両方のSDKを環境変数Pathに通すと、「ある日突然、コマンドが動かないトラブルが発生しコードを長時間調べても問題なし、結局Pathの順番が落ちだった。」みたいになりかねないので、理解が深まるまでは、フルパスで実行するのもよさそうです。

Google Cloud SDKインストーラーが正常に動作しない

Google Cloud SDKは、Python依存のためインストーラーが正常に動作しない場合があるようです。エンコードエラーが発生したと表示される場合があります。このような場合は、バージョニングされたアーカイブ で少し前のバージョンをインストールするとうまくいくかもしれません。

私の場合は、更新後問題が発生したため、すべて削除して解決しました。

 

2018/06/29 インストーラー実行時にバンドルのPythonを有効にし、CLOUDSDK_PYTHON=C:\Python27\python.exeをシステム環境変数に設定したら正常にインストールできました。矛盾してますが…

Google Cloud SDKと App Engine SDKの違い

現在はGoogle Cloud SDKをインストールして、gcloudコマンドで本番環境にデプロイしますが、以前はApp Engine SDKがありappcfgやgoappコマンドがありました。

Unitテストを実行する場合はgoapp testコマンドを実行するので、Google Cloud SDKとApp Engine SDKが必要になります。Google Cloud SDKに統合されていないので、非常に面倒です。

Local Unit Testing for Go

goappコマンドを使えるように一時的にPowershellで環境変数を設定する

Unitテストをするために、App Engine SDKのzipファイルをc:\devに展開しました。powershellでは、環境変数のダイアログではなく、起動しているPowershellのコンソールのみに以下のように適用できます。

 

GAE/Goの単体テストが重すぎる

Corei5 3GhzのPCで以下のdatastoreにデータを1つ登録、取得し確認するのに、30秒ぐらいかかります。

goapp testコマンドを実行すると以下のようにログが出力されて、go-app 11.997sと最終行に出ていますが、ログが表示されるまでに10秒以上かかる場合があります。

 

Cloud Datastoreの親子関係

以下のデータ構造はよくあります。

  • ユーザー毎のブログデータを作る
  • カテゴリ分けした記事を作る
  • ディレクトリーに分けて、ファイルを保存する

このような親子関係で分類したデータをCloud Datastoreで作る場合は、Cloud Database – データの整合性 を読むと解決できそうです。祖先クエリーや、エンティティグループという概念があるので、これで作れます。またカテゴリー分けしたTodoListのサンプルコードもあるので、これをベースにするとすぐに実装できそうです。

チュートリアル