Datastore へのキャッシュに Memcache を利用している場合、Memcache が落ちると Datastore のパフォーマンスが劣化することがある

Datastore へのキャッシュに Memcache を利用している場合、
Memcache が落ちると Datastore のパフォーマンスが劣化することがある。

当たり前と言えば、当たり前だが、
こういったケースを想定できていなかったので、
書いておく。

パフォーマンス劣化について

Memcache が落ちた際のインシデントが以下。

The Memcache service has recovered from a disruption between 12:30 US/Pacific and 15:30 US/Pacific.
https://status.cloud.google.com/incident/appengine/17007#5750790484393984

上記のインシデントに以下の記載がある。

Some customers experienced elevated Datastore latency and errors while Memcache was unavailable. At this time, we believe that all the Datastore issues were caused by surges of Datastore activity due to Memcache being unavailable. When Memcache failed, if an application sent a surge of Datastore operations to specific entities or key ranges, then Datastore may have experienced contention or hotspotting, as described in https://cloud.google.com/datastore/docs/best-practices#designing_for_scale. Datastore experienced elevated load on its servers when the outage ended due to a surge in traffic.

Memcache に Datastore のデータをキャシュしている場合、
Memcache が落ちると、
Datastore へのアクセスが急増してしまう。
その結果、エラーになる確率が上がったり、レスポンスが遅くなったりする。

Datastore へのアクセスに goon を利用していると、
このケースに該当する可能性がある。

ちなみに、エラーになる確率が上がったり、レスポンスが遅くなったりするだけで
全てのアクセスがエラーになるわけではない。
必ずしもサービスに影響が出るレベルでエラーが発生するとは限らない。

対策

Datastore の Entity の key 範囲を広くする

以下に記載があるように Datastore の Entity の key 範囲が狭い場合は、
特定のタブレットへのアクセスが集中し、
パフォーマンスが劣化する可能性が高くなる。
https://cloud.google.com/datastore/docs/best-practices#high_readwrite_rates_to_a_narrow_key_range

key 範囲を広くしましょう。
とはいえ、これは Datastore を利用する上で基本となるルールなので、守れていることが多いと思う。

シャーディング & レプリケーション

key 範囲を広くしていても、
ある特定の key に対するアクセスが集中する場合は
パフォーマンスが劣化してしまう。

以下に記載があるようにシャーディング、レプリケーションを検討するといい。
https://cloud.google.com/datastore/docs/best-practices#sharding_and_replication

シャーディングに関してはこちらにもまとめてある。
http://pospome.hatenablog.com/entry/2017/02/05/171635#エンティティグループは1秒間に1回しか更新できない--解決策

レプリケーションについては調べても分かりませんでした・・・。
知っている人は教えてください・・・。

タブレットの分割

key 範囲を広くしている & 特定の key に対するアクセスが集中しない
という場合でも、
アクセスに対してタブレットの数が少ないと(十分に分割されていないと)、
パフォーマンスが劣化する可能性がある。
https://cloud.google.com/datastore/docs/best-practices#ramping_up_traffic

過去 Memcache が落ちている期間に Datastore のエラーが発生しなくなったことがあるので、
アクセスが集中することによってタブレットが分割してくれたのかなと思っている。

事前にタブレットを分割させた方がいいのか? と言われると、
それなりに手間かかりそうなので微妙かもしれない・・・。

アプリケーションコードでのリトライ実装

わざわざリトライさせるのか? という感じではあるが、
Datastore のアクセスがエラーになると困るような場合はリトライを実装するといい。
全てのアクセスがエラーになるわけではないので、
短期間のリトライであっても成功するかもしれない。

まとめ

アクセスがエラーになった場合でも
データの整合性を損なわないように実装されていれば、
利用側のリトライによってケアできる問題なので、
あまり気にする必要はないかもしれない。

ただ、マイクロサービス化によって一部の処理が非同期化されていたりすると、
なかなか整合性を担保するのが難しかったりするので、
このようなケースがあるということを頭の片隅に入れておくといいかもしれない。

datastore のバックアップが失敗する際のエラー

Datastore のバックアップが失敗すると以下のようなエラーが出る。

Transaction collision. Retrying... (/base/data/home/runtimes/python27_experiment/python27_lib/versions/1/google/appengine/api/datastore.py:2688)

Contention on slice xxxxxxxxx execution. Will retry again. (/base/data/home/runtimes/python27_experiment/python27_lib/versions/1/google/appengine/_internal/mapreduce/handlers.py:305)

Datastore のバックアップは MapReduce によって処理されるので、
普段見慣れない MapReduce のエラーが発生する。

ここで疑問に思ったのが、
Datastore のトランザクション競合エラーが発生していること。
トランザクション競合が原因で MapReduce が失敗した感じなのかな?

バックアップだと、Datastore を read することはあっても、
write することはないので、
トランザクション競合が発生するとは思わなかった。

このエラーが発生した時刻には、
複数のバッチ処理が同時に動作していたので、
バックアップする時間帯に同じ EntityGroup に対してアクセスがあると、
トランザクション競合によって、
失敗する可能性が高くなるのかもしれない。

とはいえ、
ログに Will retry again とあるように失敗しても再実行されるので、
最終的にバックアップが失敗する確率は低いと思う。

Goのサーバサイド実装におけるレイヤ設計とレイヤ内実装について考える

devfest 2017 tokyo の発表資料です。

当日は入室できない人もいたらしい & 機材トラブルで10minほど開始が遅れてしまった
ということで申し訳なく思っています。

また、立ち見する価値がある内容を提供できたのだろうか? とも思っています。

スライドは単体でも発表内容が伝わるように文章を多めに載せているので、
是非確認してみてください。
100ページ越えていますが・・・。


発表資料の背景

当初は golang のコードベースでレイヤ実装を紹介する予定だったのですが、
資料を作っている過程で
レイヤ実装の注意点(ルール)を知らなければ、
「なぜそういう実装になるのか?」が伝わりづらいなと感じました。

そこで、「レイヤ実装の注意点(ルール)」をメインコンテンツにしようと決めました。

なので、「レイヤ内実装」というわりには抽象的な話になってしまった気もします。
ここら辺は難しいですね・・・。

機会があれば、さらに実装に踏み込んだ話や
発表ではあまり振れることのできなかったドメインレイヤの実装についても
お話させていだければと思います。

twitter上の反応




これはおっしゃる通り。
完全に抽象化することができないケースもありますね。
ここがなかなか難しいところ。






そーいえば、DIPとして説明してなかったなと・・・。
最初のレイヤ図にちょろっと書いただけで説明するの忘れてしまった・・・。






こちら仰る通りです。
発表後に修正したスライドに差し替えようと思ったのですが、
slideshare では差し替えられない(?) みたいなので、
時間見つけて調べてみます。
以前は差し替えられた気がするのですが・・・。
ありがとうございました。



発表資料上のコードだけだとそー見えてしまいますが、
コマンドではないですね。
ただ、コマンドでもいいとは思します。
ここは好みですかね。




goon で goon: Failed to decode field createdAt - gob: decoding into local type time.Time, received remote type []int のエラー

struct のフィールドを1つ削除してから goon で Get() したら以下のエラーが出た。

goon: Failed to decode field createdAt - gob: decoding into local type time.Time, received remote type []int

craetedAt が decode できなかったらしい。
で、根本原因としては gob で decode ができませんでしたと。

gob ってことは memcache に古いキャッシュが残ってて、
struct のフィールドを1つ削除したことで古いキャッシュからの decode に失敗してるのかなーと思った。

で、memcache を削除してみたらエラーでないようになりました。

深くは調べてないけど、memcache の古いキャッシュが原因だったのかな。

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 上から作成できないのかな・・・???

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