我设计的最强数据分析软件

這是為了Souzou公司內部學習會Go Friday第60次準備的資料。
雖然原本Go Friday不需要做資料準備之類的事情,但我覺得只依靠直覺去說”這樣夠了”可能無法獲得正確的印象,所以我做了這份資料。
這是關於go.mercari.io/datastore的話題。

今天要说的事情 shuō de shì

为什么是最强的?如何成为最强的?未来的最强者。

    • ほしい理由

 

    • 解決方法

 

    • 実装方法(めんどいのでGo Friday中で口頭で説明)

 

    • 設計上の判断と移行の注意点

 

    これから実装する機能

Datastore 是什么?

如果你是appengine的用户,你肯定会用到Google的东西。

我想要一架小号的原因。

我面临着各种困难和烦人的事情,我想要解决它们。
→好的!那就只能自己制作一个rapper了!

不开心的点1

type PropertyLoadSaver interface {
    Load([]Property) error
    Save() ([]Property, error)
}

在Load和Save中无法获取上下文。

    无法记录日志!无法创建http.Client!无法使用datastore.Get等操作!

解决痛点1

type PropertyLoadSaver interface {
    Load(ctx context.Context, ps []Property) error
    Save(ctx context.Context) ([]Property, error)
}

datastore.SaveStruct 和 datastore.LoadStruct 也已经重新实现。

痛点2

// 独自の型はPutできない…!
type Data struct {
  ChildID ChildID
  CreatedAt UnixTime
}

无法在Put中放置具有独特类型元素的Struct。

为了将数据输出为JSON,不得不将其内容转移到另一个结构中进行处理→ 很烦
如果结构体的元素增加,还需要修改转移代码→ 会出bug
我想随便放入喜欢的类型并实现json.Marshaler之类的东西!→ 真是这样的

解决痛苦点2

type PropertyTranslator interface {
    ToPropertyValue(ctx context.Context) (interface{}, error)
    FromPropertyValue(ctx context.Context, p Property) (dst interface{}, err error)
}

如果您实现了PropertyTranslator,那么您可以在Save&Load时编写与Datastore有效类型之间的映射处理。例如,个人认为,使用JSON,可以将其转换为数字,而在Datastore中,可以将其转换为键,这样在Datastore查看器和前端实现中都更易于理解和处理。非常不错! (导入到BigQuery可能会很困难的说法)

顺便提一下,根据用户权限控制输出项目是很方便的,可以使用jwg。

困难点3

数据存储错误字段不匹配很烦人!既然Go的结构体充当了模式,那么希望它能默默地忽略掉缺少的字段。
写明确忽略的代码太麻烦了!

解决痛点3

实现 datastore.SuppressErrFieldMismatch。
默认为true。设置为false将恢复旧有的行为。

痛点4

写关联实体的操作并且写得高效对我来说很难受…

常见的辣味实例

    1. 显示Circle的列表吧!

 

    1. Circle有多个(0个或以上)Product。

 

    Circle也有记录参展哪一个Event。

愚直コードのRPCの回数=Circleリスト取得+Circleの数×(Event1回+Product×数)

请求的次数?

数据存储(Datastore)在App Engine的实例和其他的机器上运行,因此需要进行远程过程调用(RPC)。
如果分散地获取数据,将会花费更多时间。

技術書典のMemcacheを飛ばした後のトレース

对于这个例子来说,如果没有使用Memcache,处理时间大约需要1100毫秒。即使使用了Memcache,处理时间也会需要大约450毫秒。
如果RPC的次数减少,可以明显改善处理时间。

手动去改善

    1. 获取Circle列表

 

    1. 遍历所有Circle并创建想要获取的Product列表

 

    1. 遍历所有Circle并创建想要获取的Event列表

 

    1. 从Datastore使用GetMulti进行获取

 

    将获取到的Product和Event分发给Circle的每个元素…真的很烦人…

这样一来,RPC的次数就会变成两次。
虽然在速度方面有所改善,但编写代码和可读性将会变得非常差。

解决困难点4

我创建了datastore.Client#Batch()。

    1. Circleのリストを取得する

 

    1. 全Circleを舐めてProductを個別にGetする(遅延評価)

 

    1. 全Circleを舐めてEventを個別にGetする(遅延評価)

*Batch#Exec() ドン!

结束,这也是RPC的两个实现示例。

痛点5

AppEngine DatastoreとCloud DatastoreのAPI違う…
Cloud Datastoreに統一するとappengineではSocket API経由になるので遅いし…

解决痛点5

在 go.mercari.io/datastore 中放置了一个共用的接口,实现分别是 go.mercari.op/datastore/aedatastore 和 go.mercari.op/datastore/clouddatastore。
尽管生成 datastore.Client 的方法不同,但除此之外的 API 相同,因此可以在任何时间点进行替换。
对于 AppEngine 和 GKE 的迁移,可能会变得更加顺利。
同时改善了在 AppEngine 上使用 Cloud Datastore 操作其他项目的 Datastore 时的困扰。

在设计上的决策

    • やること

DatastoreのAPIを便利でさいきょうにする

やらないこと

Datastoreのテストや環境を改善する
(でもCloud Datastore Emulatorでテスト流すと早い…)

AEDatastoreとCloudDatastoreのAPIが異なる

Cloud Datastore側のAPIに寄せる よりメンテされてて実装もマトモなため
なので KeyLoader とか datastore:”,flatten” もあるよ

goon使ってるんだけど移行は…?

boom作りました
Memcacheを使ったりするレイヤは今後Datastoreラッパ側に実装予定

Goのバージョンは?

context パッケージを使ってるのでgo1.8が必要です

go1.6なら gofmt -w -r ‘”context” -> “golang.org/x/net/context”‘ . 的な対応で

semverでやります

depはsemver対応!

设计上的建议 de

    • 自分のProject用の FromContext を自分で実装して、それ経由で datastore.Client を取得するようにしておく

今後キャッシュ用オプションの提供は datastore.FromContext(ctx, options.WithAEMemcache()) 的APIにすると思うので全ての箇所にそういう変更後からいれるの大変なので
Cloud Datastoreの場合プロジェクト名を指定したりする必要などがあり、プロジェクトでそれを行う場所が散らばるのはまずい

Batchは個別に作るより1リクエストの間は使いまわしたほうがよい

*Batch.Exec() 中に追加の予約があった場合、それがなくなるまで再帰的にExecし続ける実装です

转型的注意事项

    • Cloud Datastoreに準拠しているのでAEDatastoreとの違いに注意

Entityのfield要素にstructとかを使っている場合、 datastore:”,flatten” が必要
TransactionでPutした時KeyではなくPendingKeyが返ってくる

CommitするまでKeyがわからない…!
KeyのIDが途中の処理で必要ならAllocateIDsに移行する必要あり

Cloud DatastoreのClientとAEDatastoreのClientの作り方に差がある

AEDatastoreはリクエスト間のClient共有はできない
Cloud DatastoreはClient作るたびに新規にgRPCの接続を作る…?

あまり詳しくは追ってないけど

将来的にAEDatastore→Cloud Datastoreへの移行があるかもしれないなら初期化処理はちゃんとやったほうがよい

接下来的谈话

    • キャッシュレイヤーを入れる

プラグイン形式
appengineのMemcacheを使うプラグイン
同一EntityのGetが複数あった場合1つに圧縮するプラグイン
マシンローカルのキャッシュを使うプラグイン
QueryをKeysOnlyに変換してEntityは個別にGetするプラグイン

small op + Memcache = 安い!

その他好きなのあったら自分で書いて

自動移行ツール作る…?

astutil.Apply しゅごい

この後 @tenntenn さんが!

RunInTransactionとかの書き換えが高難易度…

1回あたりに書き換えるコードブロックの範囲が広い

= 見て書き換えないといけないASTのサイズがデカい

↑がだいたいできたらv1.0.0にする

缓存层的讨论

type CacheOperation int
const (
  InvalidCacheOperation CacheOperation = iota
  PutCacheOperation
  GetCacheOperation
  DeleteCacheOperation
)
type CacheFunc func(
    ctx context.Context,
    op CacheOperation,
    keys []datastore.Key,
    next CacheFunc
) ([]datastore.PropertyList, error)

将不能自行处理的key传递给next,并将next返回的propertyList缓存给自己的下一次使用。
返回一个与传递的keys相同长度的[]datastore.PropertyList,保持传递时的顺序即可。
在Put、Get和Delete之间插入此处理,并且其他任何事情都可以随意处理,这就是我的想法(在实施时可能会有变化)。

需要建议或者报告问题

GitHubのIssue

英語で頼んます(Google Translateでいいよ!)

GCPUGのSlackの#g-cloud-datastore_ja

日本語でおk

社内Slackのvvakame個人チャンネル

見たい人→入社してください

这个句子的结构太糟糕了!

起初我是在幻灯片模式下制作的,但是调整内容以适应一个幻灯片实在太麻烦了,所以我就放弃了,请原谅!

广告
将在 10 秒后关闭
bannerAds