Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
PHPからGoへのマイグレーション for DMMアフィリエイト
Search
yabako kobayashi
December 12, 2024
Technology
1
100
PHPからGoへのマイグレーション for DMMアフィリエイト
DMM.go #9
DMMのアフィリエイト基盤をPHPからGoにマイグレーションした際の取り組みや、PHPerがGo開発で気をつけることについて
yabako kobayashi
December 12, 2024
Tweet
Share
Other Decks in Technology
See All in Technology
【AWS re:Invent 2024】Amazon Bedrock アップデート総まとめ
minorun365
PRO
7
740
統計データで2024年の クラウド・インフラ動向を眺める
ysknsid25
2
650
Oracle Base Database Service:サービス概要のご紹介
oracle4engineer
PRO
0
15k
実務につなげる数理最適化
recruitengineers
PRO
6
530
アジャイルテストの4象限で考える プロダクト開発の品質への向き合い方
nagano
1
1.2k
Postman と API セキュリティ / Postman and API Security
yokawasa
0
140
AWS re:Invent 2024 re:Cap CloudFront編
yoshimi0227
0
270
テーブルが200以上あるSaaSでRSCとGraphQLを併用する理由
msickpaler
2
1.1k
Turing × atmaCup #18 - 1st Place Solution
hakubishin3
0
370
ナレッジベースはどのようにSQLを生成するのか / Knowledge Bases supports structed data retrieval
hayaok3
2
200
マルチプロダクト開発の現場でAWS Security Hubを1年以上運用して得た教訓
muziyoshiz
1
130
リクルートのデータ基盤 Crois 年3倍成長!1日40,000コンテナの実行を支える AWS 活用とプラットフォームエンジニアリング
recruitengineers
PRO
2
280
Featured
See All Featured
[RailsConf 2023 Opening Keynote] The Magic of Rails
eileencodes
28
9.1k
A Tale of Four Properties
chriscoyier
157
23k
How to train your dragon (web standard)
notwaldorf
88
5.7k
Unsuck your backbone
ammeep
669
57k
[Rails World 2023 - Day 1 Closing Keynote] - The Magic of Rails
eileencodes
33
1.9k
4 Signs Your Business is Dying
shpigford
181
21k
Testing 201, or: Great Expectations
jmmastey
40
7.1k
Chrome DevTools: State of the Union 2024 - Debugging React & Beyond
addyosmani
1
150
Fashionably flexible responsive web design (full day workshop)
malarkey
405
65k
A Philosophy of Restraint
colly
203
16k
Code Reviewing Like a Champion
maltzj
520
39k
Visualizing Your Data: Incorporating Mongo into Loggly Infrastructure
mongodb
44
9.3k
Transcript
© DMM.com PHPからGoへの マイグレーション for DMMアフィリエイト 1
© DMM.com 自己紹介 小林 将太 ➢ 2022/11 DMM入社 ➢ マーケティングテクノロジー部所属
➢ DMMアフィリエイトの開発・運用をして ます 2
© DMM.com DMMアフィリエイトを(一部)リプレイスしました! 本日の内容 Before After 3
© DMM.com 目次 1. DMMアフィリエイトの概要 2. リプレイスの概要 3. Goでの開発 4.
Goを採用したメリット 4
© DMM.com DMMアフィリエイトの概要 5
© DMM.com 既存のアーキテクチャ PHPフレームワークを使用したシンプルな構成 • 複数の画面 ◦ アフィリエイターが閲覧する画面 ◦ 管理者が操作する画面
• 定期実行されるバッチ処理 • クリック/購入を登録するインターフェース • etc… これらが同じリポジトリに詰め込まれたモノリシックな構成 6
© DMM.com リプレイスの概要 7
© DMM.com リプレイス後アーキテクチャ 工数や影響を考慮して段階的なリプレイスを採用 • ユーザーの画面を対象 ◦ 全ての画面を一気にリプレイスせず、変更後の 影響範囲を狭める •
フロントとバックエンドは分離 ◦ 既存は密結合している ◦ 規模や報酬を扱うことを鑑みると、分離してス ケーラビリティや変更影響を限定的にしたい • DBはオンプレミスをそのまま使用 ◦ 報酬(金銭)の取引があるため個人情報が保存 されており、クラウド移行に大きな課題がある 8
© DMM.com Go採用のモチベーション ❏ 部署内で言語統一の流れがあった ❏ Go言語への統一プロジェクトで進化を遂げる、マーケティングテクノロジー部の決意 移管されてきたプロダクト 各プロダクトごとに開発言語や環境が異なり、 相互に連携してるので複雑度が高い
部署の課題 古いシステムが多い 属人化が激しく、メンテナンス性も著しく損 なっているので保守工数が増大 Goへの言語統一 • システム全体の一貫性の向上 • エンジニアリソースを流動的に配 置 生産性/メンテ ナンス性UP 課題へのアプローチ 9
© DMM.com アフィリエイトにGoの採用はフィット するのか? 10
© DMM.com アフィリエイトに対するGoの合致度 1. パフォーマンス観点 a. データ量 i. いままで発生した報酬履歴 1.
レポーティングなどで使用 ii. 未来に支払いする報酬の計算 b. 負荷 i. 動画などキャンペーン時にはアフィリエイトを通した大量リクエスト 1. 大体300rpsぐらい 2. スケーラビリティ(並行処理) a. リアルタイムデータの集計 b. レポートの生成 11 Goのイメージ ❏ パフォーマンスが高い ❏ コンパイルされたバイナリが直接実行されるため、レイテンシが格段に少ない ❏ メモリ管理が効率的 ❏ スケーラビリティに優位性がある ❏ 軽量なゴルーチン(並行処理)が簡単に使える ❏ シンプルな構造と明確な依存管理 アフィリエイト特性に当てはめて検討 Goにマッチしてそう
© DMM.com Goでの開発 12
© DMM.com Go開発の取り組み(プロジェクト構成) 13 課題1. 記述量増加 // Item構造体を定義 type Item
struct { ID int `json:"id"` Name string `json:"name"` Price float64 `json:"price"` } // 指定IDのアイテムを取得する関数 func getItem(w http.ResponseWriter, r *http.Request) { id := r.URL.Query().Get("id") var item Item err := db.QueryRow("SELECT id, name, price FROM items WHERE id = $1", id).Scan(&item.ID, &item.Name, &item.Price) if err != nil { http.Error(w, "Item not found", http.StatusNotFound) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(item) } // 指定IDのアイテムを取得する関数 function getItem($id) { global $pdo; $stmt = $pdo->prepare("SELECT id, name, price FROM items WHERE id = ?"); $stmt->execute([$id]); return $stmt->fetch(PDO::FETCH_ASSOC); } // APIの呼び出し処理 header('Content-Type: application/json'); echo json_encode(getItem($_GET['id'])); Go PHP
© DMM.com なるべく考えることを減らして コアなビジネスロジックに注力したい 14
© DMM.com Go開発の取り組み(プロジェクト構成) 15 1. フロントとバックエンドのインターフェース共通化 ❏ OpenAPI定義を元にコードを生成 ❏ 使用ツール
❏ Backend: oapi-codegen ❏ Frontend: swagger-typescript-api ❏ OpenAPI定義とRes/Req構造体を見比べる必 要がなくなった ❏ レビュー負荷軽減 各リポジトリにサブモジュールとして配置
© DMM.com Go開発の取り組み(プロジェクト構成) 16 2. DB操作はSQLによったものを選定 ❏ SQLから生成できるsqlc ❏ SQLクエリの書き方さえ知ってればよいので
ORMのメソッドを調べたり、メソッドで表現できない複雑 なクエリとで書き分ける必要がない ❏ SQLはメンバー全員慣れていたので好都合 →しかしテーブル依存で断念。。。 ❏ 主な敗因 ❏ さまざまなシナリオをカバーするために複数の類似クエリを作成することが多い ❏ 構造体がNULLになりえるのにNOT NULL型で生成される(#1208) CREATE TABLE account ( id INTEGER PRIMARY KEY, name VARCHAR(128) NOT NULL, email TEXT ); SELECT email, (SELECT a.id FROM account a WHERE a.name = 'foo' LIMIT 1) AS "id" FROM account; type TestRow struct { Email sql.NullString ID int32 // 本当は sql.NullInt32 になってほし い! } 例:
© DMM.com Go開発の取り組み(プロジェクト構成) 17 2. DB操作はSQLによったものを選定 (2回目) ❏ SQLファーストなORMを採用 ❏
bun ❏ クエリビルダーの感覚で使用できる ❏ 機能が少ないので、なにを使うかで迷うことがない デメリット ❏ インデックスヒントやオプティマイザヒントは提供されてないので、必要なプロダクトの場合相性が 良くない ❏ 有名なJSランタイムの名前と被っているのでググラビリティがすこぶる悪い
© DMM.com Go開発の取り組み(プロジェクト構成) 18 自動生成するなら goaという選択肢もあるのでは? ❏ まずgoaを採用していても大きな問題になるような構成ではなかった ❏ 今回に関しては既存の知識で解決できたので、追加の学習の観点で採用しなかった
❏ goaのDSLについて知る必要がある ❏ OpenAPI定義とSQLは広く知られている ❏ goa genにカスタマイズするみたいなパターンが出てきた時や、トラブルシュートに不安があった ❏ DMMではgoaは広く採用されているが、部署内では採用事例がなかった
© DMM.com Go開発の取り組み(プロジェクト構成) 19 課題2. レイヤー分割 ❏ クリーンアーキテクチャを採用 ❏ Goのフレームワークには
LaravelやCakePHP のようなフルスタックフレームワークのお決まり が無いことが度々ある ❏ 今後は規模が大きくなる想定だったので、責務 はある程度分割したかった ❏ ベースとなるルールを用意したかった ├── app/ │ ├── domain/ │ │ ├── model/ │ │ │ └── account.go │ │ ├── repository/ │ │ │ └── account.go │ │ └── service/ │ │ └── account.go │ ├── infra/ │ │ ├── dao/ │ │ │ └── account.go │ │ └── external/ │ │ └── db │ ├── interface/ │ │ └── account.go │ └── usecase/ │ └── account.go └── cmd/ └── main.go
© DMM.com Go開発の取り組み(プロジェクト構成) 20 type Account struct { ID uint
Email string Name string Created time.Time Modified time.Time } 最初はdomain/modelとテーブルが1対1で作成していた ❏ 開発初期は使用テーブルも少な かったので、不都合が発生しな かった ❏ 既存フレームワークもmodelが テーブル同等だったので、なんら 問題がないように思えた 課題2. レイヤー分割
© DMM.com 開発中期あたりから目に見えて破綻の足音が ・・・ ❏ 複数のモデルにまたがるロジックが出 来上がる ❏ アプリケーションレイヤにビジネスロ ジックが露出しだす
❏ そもそも既存テーブルを使い回す関 係上、テーブル設計からできないので 負債が表出してしまっている 21
© DMM.com Go開発の取り組み(プロジェクト構成) 22 課題2. テーブル構造の負債 ❏ Domain Modelとテーブル操作用の structを分割
❏ Domain Modelとテーブル操作後の変換層を追加 ├── domain/ │ ├── model/ │ │ ├── account.go │ │ └── reward.go │ └── repository/ │ ├── account.go │ └── reward.go └── infra/ ├── adapter/ │ ├── account.go │ └── reward.go └── persistence/ └── entity/ ├── account.go ├── reward.go ├── price.go └── conversion.go repository adapter entity model dao use implement
© DMM.com Go開発の取り組み(プロジェクト構成) 23 課題2. テーブル構造の負債 ❏ Model側はアプリケーションレイヤ が欲しいフィールドとビジネスロ ジックに集中できる。
❏ 逆にadapter分、記述量は増えた ので、その点は嬉しくはない・・・
© DMM.com PHP開発者がハマるGoの開発 24 静的型付け ❏ PHPだと配列やオブジェクトに様々な値を、動的に詰め込んで後で取り出すといったことが可能 なので最初は使いづらく感じる ❏ 慣れているmapを使いがちになるので注意する
❏ フィールドの型や意味が固定されている場合は構造体を作る Go PHP
© DMM.com PHP開発者がハマるGoの開発 25 レシーバー /メソッド ❏ DIと合わせて「どうなってるか分かりづらい」と言われることあり ❏ 「クラスを定義する」という発想を捨て、「構造体に機能を持たせる」という考え方に切り替える方が理
解してもらいやすい コンストラクタが独立しているかつ関数 名が決まってない PHP構文との違いだがclass(struct)の 中で宣言されていないので、メソッドな のが直感的でない
© DMM.com PHP開発者がハマるGoの開発 26 グローバル変数 ❏ PHPではグローバル変数をリクエスト毎の共有として使ってしまったりする ❏ 例えば、セッション情報をグローバル変数に格納して、どの処理でもアクセス可能にする //
PHPでのグローバル変数使用例 $_SESSION['user'] = $user; ❏ Goでグローバル変数を使うと... ❏ スレッドセーフではないため、データが競合し、アプリケーションが不安定になることがある ❏ 場合によってはOOMが発生する ❏ Goではcontextでリクエスト毎の情報伝搬を行なう
© DMM.com Goを採用したメリット 27
© DMM.com コンテナサービスとの親和性 28 イメージサイズが小さい PHP(laravel) 578MB Go(echo) 15.3MB ❏
イメージサイズが小さいのでpullが早くなる ❏ レジストリ管理コストが微量だけど削減 ❏ ストレージ料金
© DMM.com コンテナサービスとの親和性 29 スピンアップが早い ❏ Goはシングルバイナリで動くので、単純に起動からレスポンス受付が早い ❏ パフォーマンスがPHPが低いというわけではない PHP
GO 0.000766s 0.000831s ある設定をしたレスポンス速度比較 単純なAPIで計測した結果
© DMM.com コンテナサービスとの親和性 30 スピンアップが早い ❏ PHPにも動的コンパイルをキャッシュする仕組みがあり、これを利用することで実行速度を向上 できる 1. PHPスクリプトを読み込む
2. パースして中間コードに変換 3. 中間コードを実行 4. 中間コードをキャッシュ保存 5. 次回以降、キャッシュした中間コード を再利用 ; OPcacheを有効化 opcache.enable=1 ; CLIスクリプトでも有効化(任意) opcache.enable_cli=1 ; キャッシュサイズを設定(単位: MB) opcache.memory_consumption=128 ; キャッシュするスクリプトの最大数 opcache.max_accelerated_files=10000 ; キャッシュが更新されるタイミング opcache.revalidate_freq=2 PHP実行の流れとopcache opcacheの設定
© DMM.com コンテナサービスとの親和性 31 スピンアップが早い ❏ opcacheで速度改善できるが初回リクエストがくるまでは動的につくるのでスケール前提のコン テナサービスでは上手く作用しづらい ❏ 初回前にキャッシュを作ることもできるが設定が煩雑になったり別途スクリプトが必要に
なったりする ❏ Goは多くの設定をしなくても一定以上のパフォーマンスが出るので、スピンアップという点では優 位があった
© DMM.com まとめ 32 ❏ 開発中は紆余曲折あったけどなんとかリリースまで出来た ❏ ここをコストと思うか投資と思うかで、辛みが違う ❏ PHPからGoへの移行の注意点は、開発初期に集約されているように思える
❏ 開発チームへのGo言語の浸透 ❏ プロダクトアーキテクチャ設計 ❏ 逆に初期を乗り越えればメリットも多い ❏ 負荷試験でもほぼ調整なしで希望しているパフォーマンスがでていた