Slide 1

Slide 1 text

GoとGraphQLを使用したサービス開発
 Yuki Suwa (@flum_) 
 Go Conference 2021 Autumn


Slide 2

Slide 2 text

Agenda
 1. 自己紹介
 2. 新規事業何が大変か
 3. GoやGraphQLの採用理由
 4. 実装の課題
 5. 工夫していること
 6. まとめ


Slide 3

Slide 3 text

自己紹介
 ● 諏訪 侑希 ● Github: flum1025 ● Twitter: @flum_ ● Retty株式会社 ○ RettyOrder開発リーダー ○ 普段はFrontend, Backendの 設計から実装までやってます ● Go歴: 3年

Slide 4

Slide 4 text

食を通じて 世界中の人々を Happyに。 世界に誇る日本の文化であり、世界中の人々の 暮らしの中心でもある、「食」という分野で、お店を探 す人とお店の人の双方がHappyに なれる、そんな世界を実現したい。その為に、 お店をオススメするというポジティブな感情で 人をつなぐ事がRettyの目標です。 コーポレートビジョン

Slide 5

Slide 5 text

どういうサービスなのか


Slide 6

Slide 6 text

どういうサービスなのか
 Retty Order ユーザー 店員 ①注文 ④提供 ②通知 ③調理

Slide 7

Slide 7 text

新規事業何が大変か


Slide 8

Slide 8 text

新規事業何が大変か
 ● 刻々と変化する状況 ● 限られたリソースと時間 ● ヒアリングだけだと表に出てこない細かい課題や実際のオペレーション

Slide 9

Slide 9 text

新規事業何が大変か
 ● 刻々と変化する状況 ○ => 方向転換に耐えうる設計にしたい ● 限られたリソースと時間 ○ => 複雑な設計にすると運用コストが大きくなる可能性が高い ● ヒアリングだけだと表に出てこない細かい課題や実際のオペレーション ○ => 大きく作ると無駄になる可能性が高い

Slide 10

Slide 10 text

新規事業としての要件
 ● スピードが重要なのでなるべくシンプルに早く実装したい ○ かといって機能開発に振り切ると負債の解消ができず後々死ぬ ● 将来的に開発体制をスケールできる設計を目指したい ● 金銭を扱うサービスになるのでなるべく安全に実装したい ● お店の根幹を担うサービスになるのでEntityの種類が増えそう

Slide 11

Slide 11 text

実装方針


Slide 12

Slide 12 text

実装方針
 ● スピードが重要なのでなるべくシンプルに早く実装したい ○ => GraphQLを採用してスキーマ駆動開発 ■ 実装前にスキーマさえ決めればフロントエンド、バックエンド、アプリを平行して実 装できる

Slide 13

Slide 13 text

実装方針
 ● 将来的に開発体制をスケールできる設計を目指したい ○ => Clean Architectureを採用しドメイン駆動設計 ■ ドメインモデリングしていれば、モノリスで作ったとしてもスケールしたタイミング で切り出しやすい

Slide 14

Slide 14 text

実装方針
 ● 金銭を扱うサービスになるのでなるべく安全に実装したい ○ => Goを採用し静的に型付けする ○ => Clean Architectureならレイヤーごとにテストしやすい

Slide 15

Slide 15 text

実装方針
 ● お店の根幹を担うサービスになるのでEntityの種類が増えそう ○ => GraphQLなら欲しいデータをクライアントで定義できる ■ Resolverさえ実装すればusecaseごとにエンドポイントを用意しなくてよい

Slide 16

Slide 16 text

RettyにおけるGoとGraphQL


Slide 17

Slide 17 text

RettyにおけるGoとGraphQL
 ● Rettyでは一部のマイクロサービスとしてGoとGraphQLが採用 されている ● 社内での運用実績、知見が少しだが溜まっていた https://engineer.retty.me/entry/2021/06/04/110000

Slide 18

Slide 18 text

実装上の課題


Slide 19

Slide 19 text

実装上の課題
 
 
 Retty Order ユーザー 店員 ①注文 ④提供 ②通知 ③調理

Slide 20

Slide 20 text

通知の課題
 ● リアルタイム性が求められる ● 注文ごとにイベントが発生するため、メッセージ量が多い ● 元々はポーリングで実装していた ○ ポーリングの間隔によってはリアルタイムとは言えない ■ 間隔下げれば下げるほど負荷が高い ○ データ数が多く全データ取得するには負荷が高い ■ 一部だけポーリングで取得するような実装もつくれるには作れるが、、

Slide 21

Slide 21 text

そこでGraphQL Subscription
 ● GraphQL上のPub/Subの仕組み ● Mutation等で発生したイベントなどをリアルタイムに購読できる ● GraphQL自体はトランスポート層に依存しないので実装は好きな物を選べる ○ 以下の実装はgqlgenに標準で備わっているため簡単に利用できる ■ WebSocket ■ Long Polling(一回のイベント通知のみ) ○ Transportの実装をカスタムできるので、やろうと思えばServer-Send Event(SSE) やHTTP Streamingでも実装できそう

Slide 22

Slide 22 text

GraphQL Subscriptionを実装するには
 ● 基本的に世のWebアプリケーションはスケールが前提 ● アプリケーションサーバー間でもイベントを共有できる仕組みが必要 ○ どのサーバーに繋がったとしても同じイベントを購読したい ● アプリケーションサーバー間のPub/SubとしてRedisを置くことでどの サーバーに繋がったとしても同じイベントが購読できる ● Subscriptionでは並行処理を多用するのでGoと相性がいい

Slide 23

Slide 23 text

運用中に起きたこと


Slide 24

Slide 24 text

Redisのバッファーが詰まる
 ● ある日、注文画面が更新されないという報告が来た ● 本番環境にアクセスしたところ、 WebSocketのコネク ションは繋がっており、Authentication, Initializationは 正常にできていた ● しかし、イベントを発生させてみてもメッセージが流れて くる様子がない ● Redisのメトリクスをみても負荷が特段高いわけではな く、コネクション数等もかなり余裕があった conneciton_initとSubscriptionクエリが発行されている様子

Slide 25

Slide 25 text

Redisのバッファーが詰まる


Slide 26

Slide 26 text

Redisのバッファーが詰まる
 ● GraphQLサーバーのログをみたところ、go-redisの channel is fullというログが発生していた ● リソース節約のため同イベントの購読は Redisの Subscribeを使いまわす実装になっていた ● 調査したところネットワークが遅いクライアントがいた場 合、メッセージの送信に時間がかかり bufferを食い潰すこ とがわかった timerを利用してメッセージの送信に時間が かかり過ぎる場合は購読を切る仕組みを作 成

Slide 27

Slide 27 text

パフォーマンス監視


Slide 28

Slide 28 text

パフォーマンス監視
 ● 本番で運用するにはサービスの監視だけではなく、 アプリケーション内部を監視しボトルネック を特定させる必要がある ● gqlgenを採用する場合パフォーマンス監視の仕組みを自前で作る必要がある ○ Apollo Tracingには一応対応している ■ が、レスポンス(or ログ)を監視して送る仕組みは自分でつくらないといけない ■ GraphQL以外の部分のTraceとの紐付けができない ○ 過去にはOpenCensus, OpenTracing等に対応していたっぽいが、メンテしておらず最新のバー ジョンでは利用できない ○ gqlgenにはプラグインの仕組みがある ■ 自前でプラグインを作って APMに送る仕組みを作成

Slide 29

Slide 29 text

gqlgenのパフォーマンス監視
 ResponseIntereceptor FieldInterceptor ● InterceptResponseを使用すると各 オペレーションの実行時に呼び出せ る ● InterceptFieldで各Fieldの実行時に 呼び出せる ● Datadog等を使う場合はContextを 利用してOperationとFieldに親子関 係を持たせられる [Point] InterceptFieldは構造体のFieldからデータを取得する際にも 実行されてしまうので、 Resolver関数が定義されてるものに絞る 構造体のFIeldから取得する場合は親の Field解決時にデータ取得済 みなのでここでトレースする必要はない

Slide 30

Slide 30 text

gqlgenのパフォーマンス監視
 


Slide 31

Slide 31 text

開発で工夫していること


Slide 32

Slide 32 text

レプリカラグを意識した開発
 ● Amazon Aurora MySQLを使用している ● Aurora MySQLは一般のMySQLよりは低遅延だが、遅延はある ○ 公式では100ms未満と記載されている ● レプリカラグを意識した開発を強制している ○ Readerインスタンスに `MASTER_DELAY=1` つけるだけ https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/AuroraUserGuide/A urora.Replication.html

Slide 33

Slide 33 text

まとめ


Slide 34

Slide 34 text

こういう実装方針でした
 ● スピードが重要なのでなるべくシンプルに早く実装したい ○ => GraphQLで解決 ● 将来的に開発体制をスケールできる設計を目指したい ○ => Clean Architectureで解決 ● 金銭を扱うサービスになるのでなるべく安全に実装したい ○ => GoとClean Architectureで解決 ● お店の根幹を担うサービスになるのでEntityの種類が増えそう ○ => GraphQLで解決

Slide 35

Slide 35 text

採用してどうだったか
 ● Go ○ 良くも悪くもシンプルなので、ロジックに対するレビューに集中できる ○ コードが冗長になるのはちょっと辛い ● CleanArchitecture ○ ドメインモデリングのために議論が盛んに ■ レビュー前に議論すると大きな手戻りが減らせるし今後の拡張もしやすくなる ■ ドメインについてちゃんと考える必要があるのでスコープも小さくなる ○ テストがしやすいのでテストコードが増えて行きやすい ■ カバレッジが全てではないが、開発時の安心感は絶大 ■ テストがあると思い切ったリファクタがしやすいので開発効率を保てる ● GraphQL ○ 新しいデータに依存しない部分はフロントで完結できるので、フロントの開発スピードは早 い カバレッジは90%ある