Help us understand the problem. What is going on with this article?

datastore使ってみてハマったこと

More than 3 years have passed since last update.

はじめに

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があるのです:cry:

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を使えてないので、どこかでチャレンジしたいです。

mercari
フリマアプリ「メルカリ」を、グローバルで開発しています。
https://tech.mercari.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away