cloud console から datastore に任意の int の key を指定した entity を作成できない

datastore 上に 特定の int の ID を key とするマスターデータを作りたいと思った。
例えば、以下のように key に既存の商品IDをセットするようなケース。

--------------------------
key  | name | createdAt
-----|------|-------------
1001 | 醤油 | 2017-01-01
1002 | 砂糖 | 2017-01-01
--------------------------



cloud console 上から kind & entity を新規作成できるみたいなので、
設定しようとしたが、
key に任意の int を設定できない。

key に設定できる値の選択肢としては、
以下の画像のように「数値 ID(自動生成)」or「カスタム名」になっている。


f:id:pospome:20170821180306p:plain



一見「カスタム名」で数値を入力すると、
いけそうだが、文字列の数値として認識されてしまう。

現状はわざわざデータ登録用のスクリプトを作って、
任意の int の key を指定した entity を作っているが、
cloud console 上から作成できないのかな・・・???

誰か知っていたら教えて欲しい・・・。

golang の iota の使い所

自分は iota の使い所が分からなかった。
なんか事故りそうだし、明示的に値を宣言した方が分かりやすいような気がする。

で、こんなツイートをしたら・・・

tenntenn さんと kaneshin0120 さんに返信をいただいた。

ということで、golang の標準ライブラリを grep してみた。

以下は皆大好き net/http package の iota。

const (
	// StateNew represents a new connection that is expected to
	// send a request immediately. Connections begin at this
	// state and then transition to either StateActive or
	// StateClosed.
	StateNew ConnState = iota

	// StateActive represents a connection that has read 1 or more
	// bytes of a request. The Server.ConnState hook for
	// StateActive fires before the request has entered a handler
	// and doesn't fire again until the request has been
	// handled. After the request is handled, the state
	// transitions to StateClosed, StateHijacked, or StateIdle.
	// For HTTP/2, StateActive fires on the transition from zero
	// to one active request, and only transitions away once all
	// active requests are complete. That means that ConnState
	// can not be used to do per-request work; ConnState only notes
	// the overall state of the connection.
	StateActive

	// StateIdle represents a connection that has finished
	// handling a request and is in the keep-alive state, waiting
	// for a new request. Connections transition from StateIdle
	// to either StateActive or StateClosed.
	StateIdle

	// StateHijacked represents a hijacked connection.
	// This is a terminal state. It does not transition to StateClosed.
	StateHijacked

	// StateClosed represents a closed connection.
	// This is a terminal state. Hijacked connections do not
	// transition to StateClosed.
	StateClosed
)

以下は皆大好き?? time package の iota

const (
	January Month = 1 + iota
	February
	March
	April
	May
	June
	July
	August
	September
	October
	November
	December
)

golang 内での定数は他の定数と識別できればよく、
その値自体にあまり意味はないので、
ちょこちょこ iota が利用されている。

kaneshin さんが仰っている「DBの値には意味がある」というのは、
その定数が golang のコード上の定義ではなく、
DB側で定義されている値なので、
iota で実装すべきではないってことだと解釈している。

golang に実装する定数はDB側で定義されている値に合わせるべきなので、
明示的に設定しましょうねって感じかな。

iota を使うと定数の定義順を間違えると値が変わって事故るってのは変わらないけど、
iota を使わなくても設定する値を間違えれば事故るから、
そこはあまり考えなくていーのかもしれない。
iota は定義する順番さえ守っていれば事故らない。

golang の 引数、戻り値、レシーバをポインタにすべきか、値にすべきかの判断基準について迷っている

日頃から

引数にポインタを渡した方がいいのか? 値を渡した方がいいのか?
戻り値はどーなの?
メソッドのレシーバは?

なんて迷っているのでアウトプットしてみる。

メソッドのレシーバについては以下に載っていた。
https://github.com/golang/go/wiki/CodeReviewComments#receiver-type

以下は日本語訳。
http://qiita.com/knsh14/items/8b73b31822c109d4c497#receiver-type

レシーバについては、これが基準な気がする。

そして、このドキュメントにはレシーバというよりも、
ポインタと値の特徴が載っているので、
それらの特徴を「引数」「戻り値」でも考えていけば、それっぽい答えになりそう。

ということで、「引数」「戻り値」を対象にザックリとまとめてみた。

1. ポインタじゃないと期待通りの動作をしない場合はポインタにする

これはポインタにせざるを得ないパターン。
例えば、引数を書き換えて、それをクライアントコードに反映させたい場合はポインタである必要がある。

以下は Change() は引数の i を 100 に書き換えており、
i = 100 は呼び出し元の main の i にも反映させる。

func main() {
    i := 0
    Change(&i)
    fmt.Println(i) // 100
}

func Change(i *int) {
    *i = 100
}

以下のように値だと i = 100 は呼び出し元の main の i に反映されない。

func main() {
    i := 0
    Change(i)
    fmt.Println(i) // 0
}

func Change(i int) {
    i = 100
}

このように引数に関わらず戻り値でも、
「ポインタじゃないと期待通りの動作をしない場合」は
当然ながらポインタにする必要がある。


これ以降はポインタでも値でもどっちでも期待通りの動作をする場合の判断基準になる。



2. 値だけどポインタっぽいやつは値にする

slice, map, chan, func などは、
それ自体がポインタのようなものなので、
わざわざポインタにする必要はなく、
値で渡してしまっていい。


3. プリミティブな値は値にする

int, string とかは値で渡してしまっていい。
これは値のコピーコストが気にならないからだと思う。

time.Time のように基本的に値として扱う struct も値として扱う。


4. 大きい struct, array の場合はポインタにする

大きい struct, array は値をコピーするコストが大きくなるから、
ポインタにした方がいいよってことなんだと思う。

で、最初に紹介したレシーバのドキュメントによると、
「大きいかどうか」の判断基準は以下らしい。

もし構造体の全ての値を引数に渡すと仮定してください。多すぎると感じたなら、それはポインタにしても良いくらいの大きさです。

自分が新卒の頃に先輩に読まされた CodeComplete には
引数の数は最大でも7個って書いてあったような気がする。

個人的には 4 個くらいから多い印象を受けるので、
struct がそれ以上のフィールドを持つ場合はポインタで渡した方がいいのかなと思っている。

slice はそれ自体がポインタみたいなものなので、
わざわざポインタにする必要はない。

array の場合はどの程度の長さが目安になるんだろう???



5. GCコストを考慮する???

レシーバが値であるほうが良い場合があります。値のみのレシーバは生成されるゴミの量を減らすことができます。もし値がメソッドに渡されると、ヒープ領域にメモリを確保する代わりに、スタックメモリのコピーが走ります。

ヒープとスタックについては以下を読めば分かると思う。
http://najeira.blogspot.jp/2013/10/go.html

ただ、実際どの程度パフォーマンスが問題になるかっていうのは、
それぞれの実装でベンチ取らないと判断しようがないし、
4番のコピーコストとの兼ね合いになるのかなと思う。

ココらへんは正直全然わからない。
(´・ω・`)


6. 書き換えた値を反映させない場合は値にする

以下のように引数の User を書き換えない場合は値にすることで、
「引数の User を書き換えないですよ」
という関数の挙動を伝えることができる。

func PrintUser(u User) {
    fmt.Println(u.Name)
}

この観点では戻り値の場合は気にしなくていいと思う。

7. (追記)[ ]T vs [ ]*T

「slice の要素が struct の場合はポインタ or 値 どっちなのか」というケースを書いていませんでした。
ご指摘ありがとうございます。


まとめ

紹介したポイントは以下。

  • 1. ポインタじゃないと期待通りの動作をしない場合はポインタにする
  • 2. 値だけどポインタっぽいやつは値にする
  • 3. プリミティブな値は値にする
  • 4. 大きい struct, array の場合はポインタにする
  • 5. GCコストを考慮する???
  • 6. 書き換えた値を反映させない場合は値にする

1,2,3 番のケースでは ポインタ or 値 の選択を迷わないかなと思った。

自分が迷うのは4, 6番の2つを考慮するケースの時が多いかな。

例えば、「大きい struct」 を「値を書き換えない関数の引数に渡す」時に、
関数の引数はポインタにするのかどうか?

4番を優先してポインタにするのか?
6番を優先して値にするのか?

type BigStruct struct {

}

//引数の BigStruct は ポインタ or 値 ???
func PrintBigStruct(b BigStruct) {
    fmt.Println(b.Value)
}


この場合、自分はポインタにしている。
理由は以下。

  • 標準パッケージで struct を値で渡すコードをあまり見たことない気がするから。(実際そうなのかは分からない)
  • 引数を書き換えるというコードをあまり書くケースがないので、アプリケーションコードの引数がほぼ値になってしまい、塵も積もってコピーコストが大きくなってパフォーマンスに影響しそうだから。(測定したわけじゃないので、実際分からない)
  • 引数をちょこちょこポインタ or 値で指定するのが面倒。(個人差ある)
  • 引数を ポインタ→値 or 値→ポインタ に変更する際にクライアンコードを全て書き換える必要がある。(ただ、そもそも変更するケースってあるのか?)

理由を挙げてみたけど、
結局決定打のないままポインタにしてる感がある。

この記事の最初で以下のように言及してるけど、
レシーバも結局上記のケースでは悩むのかなと思う。

メソッドのレシーバについては以下に載っていた。
https://github.com/golang/go/wiki/CodeReviewComments#receiver-type

以下は日本語訳。
http://qiita.com/knsh14/items/8b73b31822c109d4c497#receiver-type

レシーバについては、これが基準な気がする。

ちなみに、5番のヒープとスタックについては知っていたけど、
特に考慮してませんでした・・・。
これも4番と同じで計測しないと分からないので、
基準としては扱っていない。

ということで、
ここら辺の判断基準にアドバイスがあれば、
この記事のコメント or twitter でメンションください。

ちなみに、interface の ポインタ or 値 については以下が分かりやすいと思います。
http://otiai10.hatenablog.com/entry/2014/05/27/223556

Datastore/Go の cannot load field xxxx into a xxx: no such struct field は Goon で回避できる

以下のブログに書いてある通り、
pospome.hatenablog.com

Datastore は kind の property が struct の field に存在しないと
ErrFieldMismatch というエラーが発生する。

「datastore: cannot load field xxxx into a xxx: no such struct field」のエラーと説明した方が分かりやすいかもしれない。
Datastore を使ったことのある人なら1回は見たことあるはず・・・。

エラーは発生するものの、
存在する filed に対応する property の値は struct へマッピングされるので、
datastore.ErrFieldMismatch をハンドリングして握りつぶしてしまえば、
Datastore の property と struct の field のミスマッチを気にせず処理を続けることができる。
でも、わざわざハンドリングするのはちょっと面倒くさい。

と思っていたが、
Goon は以下の PR が merge されたことで datastore.ErrFieldMismatch を回避することができる。
github.com

デフォルトで回避するようになっているので、
バージョンを上げれば面倒なエラーハンドリングからも開放される。
https://godoc.org/github.com/mjibson/goon#pkg-variables

// IgnoreFieldMismatch decides whether *datastore.ErrFieldMismatch errors
// should be silently ignored. This allows you to easily remove fields from structs.
IgnoreFieldMismatch = true

GAE/Go TaskQueue の max_doublings について

なんかググってもあんまり出てこなかったので、自分用メモ。

TaskQueue のリトライは queue.yaml で設定する。

以下のように設定すると、

task_retry_limit: 5
min_backoff_seconds: 10
max_backoff_seconds: 40

リトライ間隔は 10s -> 20s -> 30s -> 40s -> 40s のようになり、合計で5回リトライする。
min_backoff_seconds で設定した値が待機秒数に加算されるが、
最大でも max_backoff_seconds までしか増えない。
なので、5 回目は 50s になることなく、40s でリトライしている。

【追記】
上記の挙動は間違っていました。
max_doublings を指定しない場合は
10s -> 20s -> 40s -> 40s -> 40s
ように常に加算される待機秒数を2倍にして動作します。

待機秒数を大きく設定した場合、
待機秒数が2倍になっていくと値が大きくなりすぎて都合が悪いことがあるので、
max_doublings で制御する感じなんだと思います。
【追記終わり】

max_doublings では「加算される待機秒数を2倍にする回数」を設定することができる。

例えば、以下のように max_doublings = 3 に設定すると、

task_retry_limit: 7
min_backoff_seconds: 10
max_backoff_seconds: 500
max_doublings: 3

10s -> 20s -> 40s -> 80s -> 160s -> 240s -> 320s のように7回リトライする。

max_doublings = 3 によって、
最初の3回だけ待機秒数が2倍で増えるので、
4回目のリトライ間隔は 80s になる。
それ以降は2倍にならず、直近のリトライ間隔である 80s を加算していき、
最後の7回目のリトライでは 320s になる。

これは Exponential backoff というアルゴリズムらしい。
http://yoshidashingo.hatenablog.com/entry/2014/08/17/135017