Slide 1

Slide 1 text

POSレジとGo 2021/08/11 GeekGig

Slide 2

Slide 2 text

2 今回話す内容 チャネルとgoroutineを 実際のビジネスロジックで使われている事例と そこから得た学びを共有します。

Slide 3

Slide 3 text

3 1. 自己紹介 2. O:derとPOSレジ 3. チャネルとgoroutineを用いたPOS連携 4. 学び アジェンダ

Slide 4

Slide 4 text

自己紹介

Slide 5

Slide 5 text

自己紹介
 ● 照井 寛也(てるいひろや) ● 株式会社Showcase Gigに2021年2月よりジョイン ● POS連携サービスにおけるバックエンドエンジニアを担当 ● Goを書いて半年ちょっと ● Twitter: @1019Hiroya

Slide 6

Slide 6 text

O:derとPOSレジ

Slide 7

Slide 7 text

7 POSレジ ● POS ○ Point Of Sales ● 役割 ○ 「何を・いつ・いくらで・何個販売したのか」を記録 ○ 商品のマスタを管理.... ● 種類 ○ パソコン型 ○ ターミナル型 ○ タブレット型...

Slide 8

Slide 8 text

8 O:derとの関係性 POSレジからのマスタを O:derに同期 O:derからの注文・会計をPOS レジに連携 POS連携サービス

Slide 9

Slide 9 text

9 O:derとの関係性 POSレジからのマスタを O:derに同期 O:derからの注文・会計をPOS レジに連携 POS連携サービス

Slide 10

Slide 10 text

Goを用いたPOS連携

Slide 11

Slide 11 text

11 POS連携サービス POSレジからのマスタを O:derに同期 POS連携サービス

Slide 12

Slide 12 text

12 マスタ同期 ● 店舗毎個別でマスタ同期できる機能がもともとあった 同期 同期 同期

Slide 13

Slide 13 text

13 マスタ同期 ● 店舗毎個別に加えて店舗一括でマスタ同期できる機能が必要 同期 同期 同期 同期 同期 同期

Slide 14

Slide 14 text

14 マスタ同期 ● 店舗毎個別に加えて店舗一括でマスタ同期できる機能が必要 同期 同期 同期 同期 同期 同期

Slide 15

Slide 15 text

15 マスタとは ● O:derにはカテゴリー・メニューの概念がある ○ カテゴリーに対してメニューが紐づく カテゴリー (店舗横断) メニュー (店舗毎) 冷菜 本日の鮮魚の カルパッチョ 柚子の香り 生ハム盛り合 わせ

Slide 16

Slide 16 text

16 マスタ同期の仕様 ● マスタ同期のリクエスト毎に排他制御がされている必要がある ○ リクエスト成功:202(accepted)を返し、実行はgoroutineで行う ○ 既に実行中:409(conflicted)を返す ● マスタ同期する内容は以下 2つ ○ カテゴリー ■ 店舗横断 ○ メニュー ■ 店舗毎

Slide 17

Slide 17 text

17 マスタ同期の仕様 O:der POS連携サービス POSレジ カテゴリー同期 店舗A メニュー同期 1.カテゴリー同期 2. メニュー同期 の順で同期する必要がある

Slide 18

Slide 18 text

18 マスタ同期の仕様 POS連携サービス POSレジ 店舗A メニュー同期 店舗B メニュー同期 店舗C メニュー同期 複数店舗同時に同期する必要がある O:der

Slide 19

Slide 19 text

19 マスタ同期の仕様 POS連携サービス POSレジ カテゴリー同期 店舗A メニュー同期 店舗B メニュー同期 店舗C メニュー同期 O:der 成功 リクエスト 202

Slide 20

Slide 20 text

20 マスタ同期の仕様 POS連携サービス POSレジ O:der 既に実行中 リクエスト 409

Slide 21

Slide 21 text

21 ここから ビジネスロジックをどうGoで実装するかを見ていく

Slide 22

Slide 22 text

22 マスタ同期の仕様 POS連携サービス POSレジ カテゴリー同期 店舗A メニュー同期 1.カテゴリー同期 2. メニュー同期 の順で同期する必要がある O:der

Slide 23

Slide 23 text

23 チャネル POS連携サービス POSレジ カテゴリー同期 店舗A メニュー同期 チャネルを用いてカテゴリー同期が終わり次第メニュー同期の実行 O:der

Slide 24

Slide 24 text

24 チャネル func categorySync(ch chan struct{}, .....) (err error) { defer func() { ch <- struct{}{} } /* カテゴリー同期処理 */ } func menuSync(ch struct{}, .....) (err error) { select { case <-ch: /* メニュー同期処理 */ } }

Slide 25

Slide 25 text

25 マスタ同期の仕様 POS連携サービス POSレジ 店舗A メニュー同期 店舗B メニュー同期 店舗C メニュー同期 複数店舗同時に同期する必要がある O:der

Slide 26

Slide 26 text

26 マスタ同期の仕様 POS連携サービス POSレジ 店舗A メニュー同期 店舗B メニュー同期 店舗C メニュー同期 goroutineで並列実行 O:der

Slide 27

Slide 27 text

27 メニュー同期 func Sync(.....) (err error) { for _, shop := range shops { // メニュー同期 go menuSync(ch, ......) } ……. }

Slide 28

Slide 28 text

28 マスタ同期 func Sync(.....) (err error) { // カテゴリー同期 go categorySync(ch) for _, shop := range shops { // メニュー同期 go menuSync(ch, ......) } ……. }

Slide 29

Slide 29 text

学び

Slide 30

Slide 30 text

30 「よし!できた!」 「アトミックではないね」 (アトミック...でない...)

Slide 31

Slide 31 text

31 アトミック(不可分性) コンピュータ上のプログラムの動作で、密接に関連する複数の処理が外部から一つの操作に見 え、途中の状態を観測したり介入できない性質を、操作のアトミック性、不可分性などという。 (https://e-words.jp/w/%E5%8E%9F%E5%AD%90%E6%80%A7.html) リクエストは他のリクエストが何をしているか観測できない

Slide 32

Slide 32 text

32 マスタ同期の仕様 ● マスタ同期のリクエスト毎に排他制御がされている必要がある ○ リクエスト成功:202(accepted)を返し、実行はgoroutineで行う ○ 既に実行中:409(conflicted)を返す ● マスタ同期する内容は以下 2つ ○ カテゴリー ■ 店舗横断 ○ メニュー ■ 店舗毎

Slide 33

Slide 33 text

33 アトミック(不可分性)でない 同期開始 同期終了 ロック の確認 409(conflicted) ロック中 カテゴリー同期 ロックの取得 メニュー同期

Slide 34

Slide 34 text

34 アトミック(不可分性)でない 同期開始 同期終了 ロック の確認 409(conflicted) ロック中 カテゴリー同期 ロックの取得 メニュー同期 ロックの確認と 取得までに 間が空いている

Slide 35

Slide 35 text

35 アトミック(不可分性)でない 同期開始 同期終了 ロック の確認 カテゴリー同期 ロックの取得 メニュー同期 同期開始 同期終了 ロック の確認 カテゴリー同期 ロックの取得 メニュー同期 リクエストA リクエストB ロックがかかっていない と判断される

Slide 36

Slide 36 text

36 不可分性を保つために 同期開始 同期終了 ロック の確認 409(conflicted) ロック中 カテゴリー同期 メニュー同期 ロックの確認と取 得を 確認直後に行う ロック の取得 ロック中

Slide 37

Slide 37 text

37 不可分性を保つために func changeState(db *sql.DB, from to state, .....) (err error) { result, err := db.Exec("UPDATE sync_state SET state = to where state = from and …") affected, err := result.RowsAffected() if affected < 1 { /* 更新行なし = 他のプロセスで変更された */ } } ● UPDATEでも不可分性を保つためのコードを利用

Slide 38

Slide 38 text

38 「よし!今度こそ!」 「同時並列が多すぎると高負荷になるね」 (確かに、、)

Slide 39

Slide 39 text

39 同時並列による負荷分散 O:der POS連携サービス POSレジ カテゴリー同期 店舗A メニュー同期 店舗B メニュー同期 店舗C メニュー同期

Slide 40

Slide 40 text

40 同時並列による負荷分散 O:der POS連携サービス POSレジ カテゴリー同期 店舗A メニュー同期 店舗B メニュー同期 店舗C メニュー同期

Slide 41

Slide 41 text

まとめ

Slide 42

Slide 42 text

42 実装を通しての学び ● チャネルやgoroutineをビジネスロジックに当てはめて実装できたことは初の経験だった ○ struct{}でメモリの削減になることも学んだ ● ロックの確認・取得・解放にはアトミックを意識したいところ ○ stateなどのDB値リクエスト同士で奪い合わないように気をつけたい ● 負荷分散 ○ goroutineで並行処理が簡単にできるが、連携サービスだからこそ、その前後のサー ビスの負荷を意識して双方に優しいサービスを心がけたい