読者です 読者をやめる 読者になる 読者になる

Datastore/Go の Get(), GetMulti() で指定した key が存在しない場合はエラーになる

GCP

ちょっとまとめておこうかと。

間違っているところがあったらブログのコメント or twitter で教えてください。
(´・ω・`)

Datastore では Get() に指定した key の entity が存在しない場合、
datastore.ErrNoSuchEntity というエラーが発生する。
*データが存在しないとエラーになるのはちょっとビックリしました。

なので、以下のようなエラーハンドリングは正常に動作しない。
entity が存在しない場合は datastore.ErrNoSuchEntity が発生するので、
最初の if err != nil で引っかかってしまう。
if entity == nil には到達しない。

err := datastore.Get(ctx, key, &entity)

if err != nil {
    //Datastoreでエラーが発生した場合
    return
}

if entity == nil {
    //entityが存在しない場合
    return
}

//entityが存在する場合
return



以下のように datastore.ErrNoSuchEntity を利用して捕捉するのもダメ。
最初の err != nil で datastore.ErrNoSuchEntity を捕捉してしまう。

err := datastore.Get(ctx, key, &entity)

if err != nil {
    //Datastoreでエラーが発生した場合
    return
}

if entity == datastore.ErrNoSuchEntity {
    //entityが存在しない場合
    return
}

//entityが存在する場合
return



以下のように先に datastore.ErrNoSuchEntity をチェックする必要がある。
*このコードだけ見ると、最初に if err == nil でチェックしてもいーんだけど。

err := datastore.Get(ctx, key, &entity)

if entity == datastore.ErrNoSuchEntity {
    //entityが存在しない場合
    return
}

if err != nil {
    //Datastoreでエラーが発生した場合
    return
}

//entityが存在する場合
return



ちなみに以下のエラーは if の等号比較で判別可能。
https://github.com/golang/appengine/blob/master/datastore/datastore.go#L20-L28

そして、GetMulti() でも同じように datastore.ErrNoSuchEntity は発生する。

ただ、面倒なことに XxxMulti() 系のAPI
戻り値の error が appengine.MultiError という特殊な error になる。
https://cloud.google.com/appengine/docs/standard/go/datastore/reference#hdr-Basic_Operations

GetMulti, PutMulti and DeleteMulti are batch versions of the Get, Put and Delete functions. They take a []*Key instead of a *Key, and may return an appengine.MultiError when encountering partial failure.



appengine.MultiError は複数の error を扱うためのもので、
実体は error の slice になっている。
https://github.com/golang/appengine/blob/master/errors.go#L25


で、appengine.MultiError の error interface が以下。
https://github.com/golang/appengine/blob/master/errors.go#L27-L46


Multi系APIは発生したエラー件数によって Error() で返す文字列が変わる。
https://github.com/golang/appengine/blob/master/errors.go#L37-L45


エラーが1件だけだと、
その1件の Error() をそのまま返すので、
ローカル環境でテキトーにデバッグしてて、
エラーメッセージだけ確認してると
appengine.MultiError が返ってきてるはずなのに
単に datastore.ErrNoSuchEntity が返ってきてると思ってしまうことがあるかもしれない。
まあ、ないと思うけど。


GetMulti() で datastore.ErrNoSuchEntity が発生した場合、
appengine.MultiError は以下のような構造になる。

appengine.MultiError {
    datastore.ErrNoSuchEntity, 
}

1件でも複数件でも実体が []error であることは変わらない。


ということで、
複数指定した key のうち、
いずれかが存在しなくても正常系だとみなす場合は
以下のようにループで error をチェックして、
datastore.ErrNoSuchEntity をエラーと見なさない実装にする必要がある。

if err := datastore.GetMulti(ctx, keys, &entities); err != nil {
    mErr := err.(appengine.MultiError)
    for _, e := range mErr {
        if e == nil {
            //entity が存在する
            continue
        }
        
        if e == datastore.ErrNoSuchEntity {
            //entityが存在しないけど正常系とみなすのでスルー
            continue
        }
        
        //ここまで来ると datastore.ErrNoSuchEntity 以外のエラー
    }
}



ここでポイントになるのが最初の if e == nil のチェック。
これはなくてもよさそうだが、
appengine.MultiError の実体である error は key の数だけ要素数が作られるみたいなので、
そうもいかない。


例えば GetMulti() で key = 1,2,3 を指定して、
key = 1 だけ datastore.ErrNoSuchEntity が発生すると、
以下のような
error になる。
エラーじゃない場合は nil で埋まる。

[]error {
    appengine.ErrNoSuchEntity, //entityがない
    nil, //entityある
    nil, //entityある
}



これを for で回すので、if err == nil のチェックをしないといけない。

*if条件の書き方によっては err == nil のチェック不要になりますが、
 nilで埋まる的な説明したかったので・・・。

以下のように Get() と同じようにチェックしてはいけないので、
Multi系API は少し面倒ですね。

if err := datastore.GetMulti(ctx, keys, &entities); err != nil {
    if err == datastore.ErrNoSuchEntity {
        //appengine.MultiError なので、datastore.ErrNoSuchEntity で捕捉できない
    }
}



GetAll() で Query を指定した検索では datastore.ErrNoSuchEntity が発生しません。
ハンドリング不要です。

ちなみに、Datastore のアクセス部分を抽象化する場合、
datastore.ErrNoSuchEntity をそのまま返すと Datastore に依存してしまうので、
nil を返すようにするか、
アプリケーション独自のエラーを定義してあげて、
クライアントコードではそれをハンドリングしてあげる方がいいと思います。

Goのシンプルさについて

golang

LT資料です。

golang + mysql の ORM を色々調べた感想とおすすめ

golang

普段は GAE で golang を使っているけど、
golangmysql 使ったことないってのもどうなのかな? と思ったので、
ちょっとしたサンプルアプリを通して mysql を使ってみようと思った。
で、ORM どーしーよーかなと思って、
色々調べた記録です。

標準パッケージ

標準パッケージが良い感じであれば、ORM は不要だと思ったので確認してみた。
標準パッケージでは database/sql を利用する。
https://golang.org/pkg/database/sql/

以下が使用例
https://github.com/golang/go/blob/master/src/database/sql/example_test.go

生のSQLを実行するのは問題ないんだけど、
Scan() で値を取り出していくのが面倒。

以下のように結果が複数レコードの場合に for で回すのも面倒。
https://github.com/golang/go/blob/master/src/database/sql/example_test.go#L77-L86

あと、struct とのマッピングは欲しい。

ということで、要件にもよるけど、
database/sql をそのまま利用するのはちょっと厳しいかな。

ちなみに、database/sql と database/sql を利用している ORM を利用するには
以下のドライバが必要になる。
https://github.com/golang/go/wiki/SQLDrivers

ドライバとdatabase/sqlの関係性は以下を読むとイメージできるかもしれない。
http://pospome.hatenablog.com/entry/2017/01/29/171904


ORMに求める要件

ORMを探すにあたって、個人的に以下の要件は必須。

structにマッピングできる

これは欲しい。
struct の tag とかでカラム指定したりするイメージ。

テーブル名とかstructに変な命名規則を適用させる必要がない

usersテーブルに対する struct は Users にする必要がある的なルールがあってもいーんだけど、
それを struct の tag とか、interface とかで回避できる仕組みは欲しい。

SQLが書ける

複雑なSQLをクエリビルダとかで書くのが面倒だったり、
ORMがSQLの方言に対応してなかった時(mysql5.7のjson系の関数とか)に生SQLで書きたい。
その結果はもちろん struct にマッピングしたい。



以下は必須じゃないけど、気にするところ。

発行されるクエリがイメージできる

「裏側で妙なSQLが発行されてインデックスが効かない」
「意図しないSQLが発行されている」
みたいなORMあるあるは回避したい。
association みたいなテーブルを良い感じに join する機能を明示的に利用しない限りこーゆーことはないと思うから、
そんなに気にする必要もないかもしれない。

パフォーマンスが極端に悪くない

早いに越したことはないが、どこまで速度にこだわるのかって問題になるから、
正直どーでもいいというか、使い勝手とのトレードオフ
どーしてもパフォーマンスが気になるなら、標準パッケージの database/sql 使えばいいと思うし、
どーでもいいなら、使い勝手を重視すればいい。



色々調べたが、これらを満たさないORMは基本なかったと思う。
なので、最終的には好みの問題になるのかもしれない。

ただ、パフォーマンスは実際に測定してないので分からない。
ネットに比較結果が転がっていたりするので、
気になるようであれば調べてみるといいかもしれない。

gorm

最初に調べたのが gorm というORMだった。
https://github.com/jinzhu/gorm

ドキュメントは以下。
http://jinzhu.me/gorm/

他のORMに比べて多機能な印象を持った。
associations とかもある。
http://jinzhu.me/gorm/associations.html

これを使っておけば変に困ることもないと思う。

ちなみに、gorm を fork した ngorm というのがあるが、
現時点で mysql に対応していないので見送り。
https://github.com/ngorm/ngorm

gorp

次は gorp を調べた。
https://github.com/go-gorp/gorp

「SELECTでは生SQLを書く必要がある」という点は独特だなと思った。

以下のようなイメージで、生SQLを書いて、それに struct をバインドする。
https://github.com/go-gorp/gorp/blob/master/gorp_test.go#L859

insert, update, delete は他のORMと同じ印象。
insert
https://github.com/go-gorp/gorp/blob/master/gorp_test.go#L432
update
https://github.com/go-gorp/gorp/blob/master/gorp_test.go#L564
delete
https://github.com/go-gorp/gorp/blob/master/gorp_test.go#L653

もう1点独特だなと思ったのは struct と DBテーブルのヒモ付を struct の tag, interface ではなく、
以下のようにコードで指定するところ。
struct が tag, interface で汚れないのは嬉しい。
https://github.com/go-gorp/gorp/blob/master/gorp_test.go#L1227-L1228

問題は SELECT WHERE IN() が使えない点。

使えないことはないけど、
gorpではSELECTをSQLで書く必要があるので、
以下のようにプレースホルダーを利用したSQL
そのプレースホルダーに対応する 100, 200, 300 という値をバインドしていかないといけない。

err = dbmap.Select(&users, "select * from posts where id in (?, ?, ?)", 100, 200, 300)



そして、以下のように slice で指定できないのがキツイ。
slice を指定するとエラーになる。

err = dbmap.Select(&users, "select * from posts where id in (?, ?, ?)", []int{100,200,300})



これどーやって in で指定するパラメータを可変にするんでしょうね・・・。
issue にもなっているが、解決されていない・・・。
https://github.com/go-gorp/gorp/issues/85
自分はここが気になって gorp の利用を見送った。

dbr

https://github.com/gocraft/dbr

これは以下の記事を見た方が早い。
https://eurie.co.jp/blog/engineering/2015/12/go-lang-ormapper-dbr

自分の要件を満たしていて良い感じだったが、なぜか timezone が UTC から JST に変更できなかった。

dbrというか、database/sql で利用するドライバのレイヤの話になると思うんだけど、
以下のように設定すれば timezone を変更可能なはずが、なぜか反映されない・・・。
https://note.mu/tomyhero/n/nc31c788bc7d8
http://kenzo0107.hatenablog.com/entry/2015/08/19/165310

他の ORM と比較して公式のドキュメントが弱い印象も受けた。
というか、他が頑張ってドキュメント作ってる気がする。

JST問題は頑張って解決してもよかったが、一旦他のORMを調べることにした。

xorm

最後に試したのがこれ。
https://github.com/go-xorm/xorm

ドキュメントは以下。
http://xorm.io/docs/

エウレカさんで採用されているらしい。
https://developers.eure.jp/tech/go_web_application_1/

インメモリキャッシュを備えているのも珍しい。
実用に耐えるかは不明。

面白いと思ったのは
xormを利用すると wizard というシャーディング&レプリケーション用のライブラリも利用できる。
https://github.com/evalphobia/wizard
ちゃんと見てないし、使ってないから分からないんだけど、
生のSQLやクエリビルダは書けないっぽい????
https://github.com/evalphobia/wizard/blob/master/orm/xorm/interface.go#L13-L54

wizard に関しては、実際どの程度実用に耐えるのか不明。


おすすめは?

おすすめとしては gorm かな・・・。
パフォーマンス面や使い勝手(これは個人差あるけど)は他の ORM に劣るかもしれないが、
多機能なので「あれができない」「これができない」で困ることはないし・・・。
gorm が合わなかったら、他の ORM を使ってみればいいと思う。


結局何を使ったのか?

なんとなく最後の xorm を使うことにした。
今のところ、大きな問題もなく使えている。
素直に gorm にしておけばよかったのかもしれない・・・。

そーいえば、PHP の Cake, Fuel, rubyrails のようなフルスタックなWAFは
それ用のORMが付属してるから、
こーやって色々調べる必要なくて楽だったな・・・。

(継続的に追記)Datastore/Go のデータ設計のコツ

golang GCP

Datastoreを使っていて、
ある程度コツとか注意点みたいなものが分かってきたので、
まとめてみました。
継続的に追記していく予定です。

間違っているところがあれば
コメント or twitter で教えてください。

Datastoreの entity, kind などの用語は理解している前提です。

ParentKeyに気をつける

外部キーのノリで利用してはいけない。
詳しくはこちら。
http://pospome.hatenablog.com/entry/20161009/1475990332


GO では Filter による OR, IN 検索ができない

Keyでの GetMulti() は可能だが、
それ以外の property で OR, IN による検索はできない。
https://cloud.google.com/appengine/docs/go/datastore/queries#filters

以下のようにループで回す必要がある。

var users []User
for _, name := range names {
    var tmp []User
    q := datastore.NewQuery("User").Filter("name =", name)
    if err = q.GetAll(ctx, &tmp); err != nil {
        //error
    }
    users = append(users, tmp)
}

ちなみに、Java, Pythonでは利用できる。
https://cloud.google.com/appengine/docs/java/datastore/queries#filters
https://cloud.google.com/appengine/docs/python/datastore/queries#filters

地味に「!=」も利用できない。
これはDatastoreがindexベースの検索を提供しているので、当然といえば当然だけど。


文字列に対する LIKE 検索がない

これもない。


結局どんなクエリが発行できるのか?

Datastore には IN, OR, LIKE がないことを説明したが、
結局何ができて、何ができないのかが気になると思う。

以下を見れば大体分かる。
https://cloud.google.com/appengine/docs/go/datastore/reference#Query

COUNT, DISTINCT, LIMIT, OFFSET, ORDER は利用できる。

あとは Cursor というページングに利用できる機能もある。
https://cloud.google.com/appengine/docs/go/datastore/reference#Cursor

SUM() などの集計系が弱い(というかない)ので、
集計結果が欲しい場合はDatastoreのデータをBigQueryに突っ込むなり、
Datastoreのデータに対して集計処理をしてあげて、サマリ用の kind に突っ込む、
的なことが必要になる。


SearchAPIによる柔軟な検索

RDBに比べると、どうしても検索の柔軟性が低くなってしまうが、
SearchAPIを利用することで柔軟な検索が可能になる。
http://mame0112.hatenablog.com/entry/2015/05/31/114057

ただ、SearchAPI は Datastore 内のデータについて検索をかけることはできないので、
Datastoreと同じデータを SearchAPI にも用意する必要があるっぽい。
http://gcp-memo.nomiso.net/2015/12/datastore-serach-api.html


structのネストによってValueObjectを利用する

Datastore に保存する entity の定義は struct を利用するが、
以下のように struct をネストすることができる。

type User struct {
    Name        string
    Profile     Profile
}

type Profile struct {
    Tel        string
    Address    string
}



entity の property は User.Name, User.Profile.Tel, User.Profile.Address のようになり、
これらの値は entity を取得したときに User, Profile それぞれの struct にバインドされる。

とはいえ、以下のようにフラットにするのとあまり変わりない気がする。

type User struct {
    Name       string
    tel        string
    address    string
}



struct をネストすると何が嬉しいのかというと、
アプリケーション側のコードに関係してくる。

DatastoreはJOINができないので、
1つのentityが持つ情報量が多くなる傾向にある。

全ての property をフラットに並べてしまうと
1つの巨大なstructに振る舞いを持たせることになるので、
その struct が持つ責務が大きくなってしまう。

struct を適切な粒度で管理しておけば(ネストさせておけば)、
こういった責務の肥大化を防ぐことができる。
なので、Datastoreというよりは、
設計ベースで考えてネストさせるかどうかを決めればいい。

ちなみに、以下を利用すると
Datastoreから entity を get するタイミングと
put するタイミングをフックできるので、
Datastore のデータ構造と struct に乖離があっても、
手動でバインドさせることができる。
https://cloud.google.com/appengine/docs/go/datastore/reference#PropertyLoadSaver


primitive type は後からでも利用可能。

以下の string の Name を

type User struct {
    Name        string `datastore:"name"`
}

以下のように primitive type にした Name に変更しても、
エラーになることはない。

type Name string

type User struct {
    Name        Name `datastore:"name"`
}

まあ、これはどっちも string だから当然といえば当然。


map は保存できない

https://cloud.google.com/appengine/docs/go/datastore/reference#hdr-Basic_Operations

ここに載っているように int, string などの基本的な型はもちろん、
struct の slice まで保存できる
ただし、map は保存できない。

以下にも注意。
http://pospome.hatenablog.com/entry/2017/02/05/164525


slice を検索できる

[]int のような slice の property は filter 可能。
struct の slice も struct の property で filter 可能。


制限事項と保存できる文字列の最大長

https://cloud.google.com/datastore/docs/concepts/limits

こちらに制限事項が載っている。
entity のサイズ制限とかあるので、目を通すといい。

個人的に気になったのは
「インデックス付けされた文字列プロパティの UTF-8 エンコードの最大サイズ」

「インデックス付けされていないプロパティの最大サイズ」
のところ。

string の property に index を貼ると max 1500 byte だが、
index を貼らなければ max 1048487 byte まで保存できる。

1500 byte 以上の json などを保存する場合は
noindex を指定する必要がある。

index を貼らないとその property を検索できなくなってしまうが、
Datastore は string に対する LIKE 検索を提供していないので、
1500byte 以上の json を完全一致で検索することがない限り、
noindex でも問題ないと思う


key以外にユニークなフィールドを持つのは面倒

ユニークな property は key にした方がいい。
以下はDatastoreのエラーの記事だが、
例としてユニークな property の実現方法について説明している。
http://pospome.hatenablog.com/entry/20161108/1478611356

とはいえ、ユニークにしたい値が key として相応しいとは限らない。

例えば、email はユニークにしたいことが多いと思うが、
この場合、pospome@email.com のような記号を含んだ文字列が key になってしまう。
@, - などの記号が key に含まれるのを避けたい場合は
email をハッシュ化したものを key にするといい。
文字列にはなるが、key に記号が含まれるよりはいくらかマシかもしれない。

複合ユニーク制約を持たせるのも面倒なので、
ユニークな組み合わせになる property をハッシュ化して key にした方がいい。
(別にハッシュ化せずに property1-property2 とかでもいーんだけど)

ユニーク制約については以下が参考になる。
http://d.hatena.ne.jp/higayasuo/20091111/1257905482


JOINできないので、実データを突っ込むと良い

Datastoreに限らず、KVSは JOIN ができないことが多いので、
kind 同士でリレーションを貼らずにそのまま値を持った方がいい。

例えば、user が複数の task を保つ場合、
user kind と task kind を用意して、
taks kind に user kind の key を持たせて外部キーみたいなリレーションを貼るのではなく、
task の slice を user kind に突っ込む。

ただし、entity のサイズには制限があるので、
user が保持する task の個数多すぎて、そのサイズ制限を超える場合は別 kind にする必要がある。


Datastore の property が struct の field に存在しないとエラー

以下のような struct があって、

type User struct {
    Name string
    Score int
}

Datastore の property が Name, Score, Address で、
struct の field に Address が存在しない場合、
以下のエラーが発生する。
「datastore: cannot load field xxxx into a xxx: no such struct field」

逆に struct の field が Datastore の property よりも多い場合は struct の field が zero value になるだけで、
エラーは発生しない。

Datastoreのスキーマを変更する際は
先に struct に field 定義を追加したコードをデプロイしてから
Datastore を触るコードをデプロイした方がいい。

エラーが発生してもエラー対象以外のフィールドに値をバインドされるので、
エラーを無視することも可能。
ただし、エラーメッセージには struct の field が入ってくるので、
エラーメッセージの文字列比較でエラーを判別する必要がある。
以下のようなエラー比較はできない。

if err != datastore.ErrNoSuchEntity {

}



propertyのno-indexに注意

詳しくはココ。
https://cloud.google.com/datastore/docs/concepts/indexes#unindexed_properties

type User struct {
    Name string `datastore:"name,noindex"`
}

上記のように検索条件にならない property には noindex を指定してもいいが、
後から必要になって noindex を外した場合、
既存の entity に index が貼られることはない。

noindex を外した後に put された entity にのみ index が貼られる。
インデックスについてはしっかりと学んでおく必要がある。
https://cloud.google.com/datastore/docs/concepts/indexes


1秒間に5回までしか entity を update できない?

これは個人的に動作未確認で、2008年の記事なので、真偽は不明だが、気になったので載せておく。

https://cloud.google.com/appengine/articles/sharding_counters

it is important to note that you can only expect to update any single entity or entity group about five times a second.

これによると、1秒間に5回までしか entity を update できないらしい。
なので、Datastoreは1秒間に5回以上、数値をインクリメントしていく「カウンター」のようなものと相性が悪い。

そこで ShardingCounters というテクニックが紹介されている。
仕組みは単純で、1つの entry を1秒間に5回までしか update できないなら、
entity を複数個用意してランダムでインクリメントして、
それらの entry のカウントを合計すればいいというもの。
自分は元々ソーシャルゲームを運用していたが、
MySQLでこういった実装をしたことがある。


トランザクションは楽観ロック

Datastoreにもトランザクションは存在するが、楽観ロックになっている。

仕組みとしては、
各 RootEntity は最終更新時間を持っていて、
その更新時間がトランザクションのコミット時間よりも前であればコミットできる。
後であれば、ロールバックしてリトライするというもの。

RootEntity の最終更新時間がベースになるので、
巨大な entityGroup かつ、更新頻度が高いものは、
トランザクションでリトライが走る可能性が高くなる。

以下を true にすると複数の kind にまたがってトランザクションをかけることができる。
https://cloud.google.com/datastore/docs/concepts/transactions

トランザクションはここが詳しい。
http://www.apps-gcp.com/datastore-transaction/
https://cloud.google.com/datastore/docs/concepts/transactions


トランザクションでは冪等性を担保する必要がある

Datastoreはトランザクションが失敗するとリトライする。
リトライ回数は以下で設定できる。
https://github.com/golang/appengine/blob/master/datastore/transaction.go#L84-L86

ここでポイントになるのが RunInTransaction() 内の操作は冪等性を担保する必要があるということ。

以下のように RunInTransaction() 内で score++ してしまうと、
Put() が失敗して、
RunInTransaction() 内がリトライされるので、
score は 前回の 101 をインクリメントして 102 になってしまう。

score := 100
RunInTransaction(ctx, func(tx *datastore.Transaction) error {
    score++
    _, err := tx.Put( "key", &User{score})
   return err
})



以下のようにする必要がある。
RunInTransaction() の外で値を確定させておいた方が良い。

score := 100
score++
RunInTransaction(ctx, func(tx *datastore.Transaction) error {
    _, err := tx.Put( "key", &User{score})
   return err
})



ちなみに、Goon を利用すると、struct の key を自動的に埋めてくれるので便利ではあるが、
リトライが走った際に key が埋まったままリトライされてしまうので注意した方がいい。


インデックス爆発

https://cloud.google.com/datastore/docs/concepts/indexes#index_limits


目を通すべき記事(随時 update していきます)

このブログに載せようと思った内容なのですが、
すでに記事があるので載せておきます。
目を通しておく必要があります。
https://cloud.google.com/datastore/docs/best-practices
https://sinmetal-slide.appspot.com/2016/gaejanight0607/gaejanight0607.slide#1
http://qiita.com/vvakame/items/f98b5189b302cd729734
https://cloud.google.com/datastore/docs/articles/balancing-strong-and-eventual-consistency-with-google-cloud-datastore/
https://cloud.google.com/datastore/docs/articles/fast-and-reliable-ranking-in-datastore/
http://qiita.com/hogedigo/items/25b4dbefe694dbfc7dbc
http://qiita.com/hnw/items/52addaebe95bb9a16eac





TaskQueue にもトランザクションをかけることができる

https://cloud.google.com/appengine/docs/go/datastore/transactions#transactional_task_enqueuing
GAEのTaskQueueにもDatastoreのトランザクションが有効。

Datastoreの更新とTaskQueueへのエンキューを一緒に実行するケースは多いと思うので、
トランザクションをかけた方がいい。
ロールバックしてくれる。

ただし、1つのトランザクションでは最大でも5つしかエンキューできない。

An application cannot insert more than five transactional tasks into task queues duing a single transaction



Get(), GetMulti() で指定した key が存在しない場合はエラーになる

http://pospome.hatenablog.com/entry/2017/03/15/204132


まとめ

特にないです。(´・ω・`)

間違っているところがあれば
コメント or twitter で教えてください。

Datastore/GO で datastore: flattening nested structs leads to a slice of slices: field xxx

golang GCP

以下のエラーが出た。

datastore: flattening nested structs leads to a slice of slices: field xxx


Datastore は以下のように struct をネストすることができるが、

type A struct {
    B []B
}

type B struct {
    Name string
}


以下のようにネストした struct (今回で言うとB)が slice を持つと、
このエラーになる。

type A struct {
    B []B
}

type B struct {
    Name string
    Score []int //これがダメ
}