はじめに
GAE/Go 1年生です。APIサーバーのコードを書いている中で、ハマったことを
まとめようと思います。
今回はdatastoreです。
トランザクション機能の使い方も覚えて快適になってきたこの頃です。
※GAE/Go前提の書き方になってしまうこと、ご容赦ください。
stringのプロパティは、デフォルトで1500byteまでしか保存できない
今年、最初にハマったのがこれでした。
datastoreではstructがそのままschemaになるので、なんとなくstringの上限を意識してなかったのがよくなかったです。
以下が、ドキュメントです。(筆者がGAE/Goを使っているので、Goより
Fields (except for []byte) are indexed by default. Strings longer than 1500 bytes cannot be indexed; fields used to store long strings should be tagged with "noindex". Similarly, ByteStrings longer than 1500 bytes cannot be indexed.
引用元: https://cloud.google.com/appengine/docs/go/datastore/reference#hdr-Properties
noindexタグをつけると、保存できるようになりました。
説明文とかブログとか、長文をDBに保持したいときには必須ですね。
GAE/GoだけIN句が使えない
複数Keyを使ってGetするのはできるのですが、特定のプロパティの値で SELECT * FROM TABLE WHERE Property IN (A, B, C)
みたいなQueryを投げることができません。
でも、Javaだとできるようです。
Goのドキュメントだと
Operator Meaning
= Equal to
< Less than
<= Less than or equal to
> Greater than
>= Greater than or equal to
引用元: https://cloud.google.com/appengine/docs/go/datastore/queries#filters
となっているのですが、Javaだと
Operator Meaning
EQUAL Equal to
LESS_THAN Less than
LESS_THAN_OR_EQUAL Less than or equal to
GREATER_THAN Greater than
GREATER_THAN_OR_EQUAL Greater than or equal to
NOT_EQUAL Not equal to
IN Member of (equal to any of the values in a specified list)
引用元: https://cloud.google.com/appengine/docs/java/datastore/queries#filters
となっていて、INがあるのです
Transaction機能を使っているつもりがつかえてなかった
以下のようなコードを書いていました。
type (
SampleRepository struct {
c context.Context
}
Sample struct {
ID int `datastore:"Id"`
Count int `datastore:"Count"`
}
)
func (r *SampleRepository) Save(sample *Sample) error {
key := datastore.NewKey(r.c, "Sample", "", sample.ID, nil)
if _, err := datastore.Put(ctx, key, sample); err != nil {
return err
}
return nil
}
func handle(w http.ResponseWriter, r *http.Request) {
ctx := appengine.NewContext(r)
sr := SampleRepository{c: ctx}
s := Sample{ID: 1}
err := datastore.RunInTransaction(ctx, func(ctx context.Context) error {
s.Count++
return sr.Save(s)
}, nil)
if err != nil {
//エラーレスポンスを返す
return
}
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
fmt.Fprintf(w, "Count=%d", s.Count)
}
func(ctx context.Context) error
の引数には、transactionを埋め込んだcontextが指定されるようになっていて、
transactionを埋め込んだcontextじゃないとトランザクションは当然効かないのでした。
(参考: https://godoc.org/google.golang.org/appengine/internal#RunTransactionOnce)
Repository構造体を作るときにプロパティとしてcontextを投入するようにしていたせいで、
問題に気づくのに時間がかかりました。
おわりに
他にも、schema変更してマイグレーションしたりするのが大変だったり、といった苦労もありますが、MySQLのときに当たり前にやっていたshardingのことを考えなくて良くなったり、
カラム追加は簡単だったりと、よいところもいっぱいあるな、という感想です。
まだAncestor Queryを使えてないので、どこかでチャレンジしたいです。