TECH PLAY

株式会社ZOZO

株式会社ZOZO の技術ブログ

974

こんにちは。MA部の田島です。 弊社では開発ガイドラインというものを用いて、システムの品質を担保しています。今回私がテックリードを務めているということもあり、バッチアプリケーションを開発するためのガイドラインを作成しました。本記事では「開発ガイドライン」と「バッチ開発ガイドライン」を紹介します。 バッチアプリケーション開発に限定したTipsはまとまっているものが多くないため参考にしていただければと思います。 開発ガイドラインについての紹介 冒頭でも紹介した通り弊社では、開発ガイドラインというものを用いてシステムの品質を担保しています。バッチ開発ガイドラインを紹介する前に、まず開発ガイドラインを紹介します。 開発ガイドラインの種類 開発ガイドラインは現在、以下の種類が存在します。 共通 Android iOS Frontend Backend Infra API Batch DB(Database) ML(Machine Learning) 各チームはこの開発ガイドラインに沿うように、システムを構築・改修しています。また、新規システムの構築や大きめのシステムを改修するときは、リリースフローが定められており、その過程で開発ガイドラインに沿っているかのチェックが行われます。 ガイドラインの遵守ルール ガイドラインには項目ごとに以下のようなタグが付いており、タグによってどの程度ガイドラインを遵守すべきかが変わります。 タグ 遵守の必要性 MUST / MUST NOT 必須 RECOMMENDED / NOT RECOMMENDED 推奨 新規ルールが策定された際、既存システムがガイドラインに追従するのは多大な工数が生じることもあります。そのため、完全な遵守は新規システムや新規改修時にのみ適用するようにしています。ただし、既存システムにおいてもガイドラインのルールを守ることで品質の向上を図れるため、遵守することを推奨しています。 バッチ開発ガイドラインの紹介 続いては、本ブログのメインテーマであるバッチアプリケーション開発に特化したバッチ開発ガイドラインについて紹介します。 バッチ開発ガイドライン作成の背景 今回、新たにバッチアプリケーション開発のためのガイドラインを作成しました。こちらはもともと私が所属する部署である、MA部がバッチアプリケーションを大量に開発・メンテナンスしていたため部署向けに作ったものでした。しかし、内容はMA部に限定せず汎用的なものを作成したため、それを社内全体のガイドラインとすることになりました。 バッチ開発ガイドライン 以下がバッチ開発ガイドラインです。実際のガイドラインの内容をほぼそのまま掲載しました。ただし、社内向けの補足情報などが含まれているのでそれらは省略しています。その代わりに、今回各項目において「補足」という項目を追加し具体例や補足情報を追加していますので参考にしていただければと思います。クラウドサービスの利用が前提となっている項目もあるのでその点はご了承ください。 コードベース バッチの設定をアプリケーションと同じリポジトリで管理する(RECOMMENDED) バッチアプリケーションでは以下を同じリポジトリで管理するようにしてください。 項目 具体例 バッチアプリケーション バッチ処理を具体的に行うアプリケーション(Shell/Python/Java/SQLなど) バッチ設定 各バッチのスケジューリングや依存関係など 補足 このガイドラインの前提として、Backendガイドラインにおいて以下が定められています。 アプリケーションの動作に必要な「全て」のコードをGitHubで管理する(MUST) また、このガイドラインの意図は、バッチの設定と動作するアプリケーションを近くに置くことで、システム把握を容易にすることです。RECOMMENDEDにしている理由は、例えばRailsアプリケーションでバッチ専用のAPIを用意し、バッチアプリケーションはそのAPIを叩くだけといった構成にしたい需要もあると考えたからです。 基盤 依存関係があるバッチにはワークフローエンジンを利用する(RECOMMENDED) バッチ同士の依存関係が複雑な処理にはワークフローエンジンを利用してください。 補足 以下にワークフローエンジンの例を掲載します。いずれも弊社で利用実績のあるワークフローエンジンです。 ツール Apache Airflow ( Cloud Composer ) Digdag Argo Workflows Kubeflow Pipelines ( Vertex AI Pipelines ) AWS Step Functions GCP Workflows 処理ごとにコンピュートリソース(CPU/メモリ/ストレージ)を選択できる(RECOMMENDED) バッチ処理毎にコンピュートリソースを選べるようなアーキテクチャを利用してください。以下のようなワークフローエンジンでは、コンテナを利用することでコンピュートリソースを処理ごとに選択できる機構が存在します。 ツール 機構 Apache Airflow Kubernetes Executor / airflow-aws-executors Digdag ECS Command Executor / Kubernetes Command Executor Argo Workflows Core Concepts 参照 AZの障害発生時に別のAZにてバッチを実行出来る(MUST) AZの障害時に、別のAZにてバッチを実行出来るような構成にしてください。 ただし、バッチサーバーが多重起動し、重複してバッチが実行されないように注意してください(参照: 「バッチの2重起動を防ぐ」)。 例えば、ジョブキュー型のワークフローエンジンを利用すると、別AZにジョブワーカーを起動することでバッチ処理の途中から再開が可能となります(※キューもMultiAZ構成になっている必要あり)。 補足 ここでは例としてAZの単位での障害を想定していますが、要件によって「Region」「AZ」等の分離レベルを検討してください。 処理ワーカーが自動スケールされる(RECOMMENDED) バッチ処理を行うのに必要なコンピュートインスタンスが、必要に応じて自動スケールするようなアーキテクチャを利用してください。 ただし、過剰にワーカーがスケールされすぎていないか、ワーカー数に上限を設けるようにしてください。 永続化ファイルは外部ストレージに配置する(RECOMMENDED) ログなどの永続化するファイルはバッチサーバーではなく、外部ストレージに永続化してください。 ツールによってはデフォルトで外部ストレージを利用する設定があるのでそれを利用することをおすすめします。 以下がストレージの例です。 ストレージ 対応ツール S3 Apache Airflow / Digdag / Argo Workflows GCS Apache Airflow / Digdag / Argo Workflows 補足 本ガイドラインの意図としては、バッチアプリケーションが稼働するノードがリタイアしたとしてもログ自体は永続化したいということです。そのため、ストレージでなくともAWSの CloudWatch やGCPの CloudLogging などでのログの永続化も選択肢の1つとなります。 ただし、それらLoggingサービスはストレージサービスよりも比較的高価なため、Storageサービスにログを保存し、それらを容易に参照できる状態になっているのが良いと考えています。 アプリケーション設計 / SRE 自動リトライをする(MUST) 特定の処理がネットワークの一時的な問題などで失敗することがあるため、処理ごとに自動リトライを行ってください。 リトライはツール・ライブラリに任せる(RECOMMENDED) バッチごとにリトライを実装することはバグの原因になるため、ツールのリトライ機構に任せるようにしてください。 ツールのリトライ機構では足りない場合は、ライブラリを利用してください。 また前提として、バッチ処理で利用しているクライアントライブラリなどにリトライ機構が含まれている場合は適切に設定してください。 以下がツールやライブラリの例です。 ツール ツール 機構 Apache Airflow retry parameter Digdag _retry parameter Argo Workflows Retries Step Functions Retry GCP Workflows Retry steps ライブラリ 言語 ライブラリ Java failsafe など Python tenacity など Go retry-go など バッチの2重起動を防ぐ(MUST) 重複起動されてはいけないバッチ処理が多重起動されないようにしてください。 例えば、2重起動を防ぐ仕組みがあるワークフローエンジンを使う、またはバッチの先頭でLockを取ることなどで実現可能です。 処理を冪等にする(MUST) 処理はリトライを何度行っても問題ないようにしてください。 補足 冪等性を考慮する場面として、データの操作があげられます。以前のテックブログで「BigQueryでのデータ追記処理における冪等化の取り組み」を紹介しているので、そちらもご参照ください。 techblog.zozo.com 現在日時に依存する処理を入れない(NOT RECOMMENDED) 上記「処理を冪等にする」を達成するために、現在日時に依存する処理を避けてください。 代わりに処理開始時刻等で代替できないか検討してください。 またリトライ時にも冪等な処理となるように、リトライ時にリトライ前の処理開始時間を利用したり、特定の時間を外部から注入したり出来るようにしてください。 補足 実装の具体例として、ワークフローエンジンを利用している場合には、特定のワークフローの開始時間を取得できます。例えばDigdagでは以下のような「SessionTime」というものが利用できます。これを利用することで、リトライが発生した場合もリトライ前、リトライ後で同様の時間を利用した処理がされます。 docs.digdag.io ひとつひとつの処理を小さくする(RECOMMENDED) 処理が失敗した時のリトライ時の復旧時間を短くするため、ひとつひとつの処理を責任毎に分割してください。 また、各処理を責任毎に小さくすることで全体の見通しが良くなります。 補足 例えば以下のようなクエリでSELECT文に時間がかかる場合、INSERTの処理で失敗するとまた時間のかかるSELECT文からやり直す必要が出てきます。 INSERT INTO `project_id.dataset_id.destination_table` (count_result, timestamp ) SELECT COUNT (*) AS count_result, CURRENT_TIMESTAMP () AS timestamp FROM `project_id.dataset_id.source_table` WHERE last_purchase_date BETWEEN DATE_SUB( CURRENT_DATE (), INTERVAL 1 DAY) AND DATE_SUB( CURRENT_DATE (), INTERVAL 100 DAY); そこで処理を以下のように別々にすることで、2つ目の処理だけをリトライできます。ただし、この場合1つ目のSELECT結果を2つ目のクエリに渡してあげる必要があります。 SELECT COUNT (*) AS count_result, CURRENT_TIMESTAMP () AS timestamp FROM `project_id.dataset_id.source_table` WHERE last_purchase_date BETWEEN DATE_SUB( CURRENT_DATE (), INTERVAL 1 DAY) AND DATE_SUB( CURRENT_DATE (), INTERVAL 100 DAY); INSERT INTO `project_id.dataset_id.destination_table` VALUES (${count_result}, ${ timestamp }) ワーカーを増やせば処理性能が線形に伸びるように実装する(RECOMMENDED) 大量のデータ処理など、データ量が増えた場合でも処理ワーカーを増やすことで処理性能が線形に伸びるように実装してください。 例えばデータの処理を分割し、並列化することで実現ができます。その時のチャンクサイズは後から変えられるようにしてください。 補足 例えば、以下のような毎秒1000件で合計1000件を配信するようなバッチを考えます。 その後配信量が倍に増え、2000件の配信が必要になった場合以下のようにワーカーを倍にすることで処理スピードは落とさずに倍の配信ができます。ただし、ここではリクエストされる側の負荷は気にしないものとします。 早い段階でValidationを行う(RECOMMENDED) 早い段階でデータのValidationをおこない、不正なデータが確認された場合はバッチを落としてください。 長時間のデータ処理が完了した後に不正データにより処理が失敗することによる、処理遅延を防ぐことに繋がります。 処理失敗時に通知する(MUST) バッチ処理が失敗した場合SlackやPagerDutyで気付けるようにしてください。 補足 弊社ではSlackやPagerDutyを利用していますが、それぞれの利用ツールに合わせて通知先は変更してください。以下のようなツールでは通知の仕組みやプラグインが用意されています。 ツール 機構 Apache Airflow slack / pagerduty Digdag slack / その他参考 処理時間のSLAを設ける(MUST) バッチ処理時間にSLAを設け、SLAを超えた場合はSlackやPagerDutyなどで気付けるようにしてください。 SLAの設定は、「処理時間」または「特定の日時」のどちらでも問題ありません。 補足 以下のようなツールではSLAの仕組みが用意されています。 ツール 機構 Apache Airflow Timeouts Digdag sla Argo Workflows timeouts Step Functions TimeoutSeconds バッチ処理が適切に動作を開始しているかを監視する(MUST) 定期実行バッチが適切に開始されているかを監視し、開始されていない場合は気付けるようにしてください。 処理ワーカー数やワーカープロセスの監視を行ったり、プロセスやログが定期的に動いているかを監視したりすることで実現可能です。 例えば、正常にバッチ処理が開始しないケースとしては以下が考えられます。 アプリケーション自体の設定は正しいが、バッチ処理を実行するためのインスタンスが0台になっている アプリケーション自体の設定は正しく、バッチ処理を実行するためのインスタンスも起動しているが、スケジューラなどのプロセスが起動していない 依存関係を把握する(MUST) 各バッチが他の「どのような処理に依存しているのか」・「どのような処理に依存されているのか」を把握してください。 また、特定のバッチに異常があった場合に、依存関係のあるバッチを止めるなどの対応方針を事前に検討してください。 依存処理がある場合は待ち合わせ処理を実装する(MUST) バッチが他の処理に依存している場合は、時間で待ち合わせをするのではなく確実に依存処理が完了したことを確認したうえでバッチ処理を実行するようにしてください。 実行タイミングがいつでもいいものは平日の日中に実施する(RECOMMENDED) 実行タイミングがいつでもいい処理に関しては、対応者が対応しやすい時間帯である平日の日中に行ってください。 月1回など実行頻度の少ないバッチは極力避け、原則デイリー実行にする(RECOMMENDED) 月1回だけ実行されるような実行頻度の短いバッチは、デイリー実行でも問題無いようにして原則1日1回以上動かすようにしてください。 バッチを修正等してから初回起動までに時間が空かないため、問題の発見・修正を素早くできます。 ただし、毎日実行だと料金的なコストが大幅に上がるなどあればその限りではありません。 データの更新や配信等の副作用のあるバッチはDRY RUNを行う(RECOMMENDED) データの更新や配信等の副作用のあるバッチはDRY RUNを行い事前に動作が問題ないこと、処理内容が問題ないことを確認してください。 新規開発・修正をした直後のバッチ実行時には立ち会って実行ログや動作結果を確認する(MUST) バッチの修正を行った直後の初回起動時は、実行時間に立ち会ってログや動作結果を確認してください。 ガイドライン導入の効果と改善点 以上バッチ開発ガイドラインを紹介しました。今回バッチ開発ガイドラインを新規に作成したと紹介しましたが、実際にはガイドラインを作成してから2年ほどが経ちました。ガイドライン作成の結果MA部では、ガイドラインに書いてある項目について気を付けて実装ができるようになったと感じています。 特に冪等性の担保という部分においては、ガイドライン作成前に比べて注意して実装ができるようになったと感じています。そのお陰でシステムのリトライを気軽かつ安全にできるようになりました。 ただし、油断しているとガイドラインに沿っていない実装がまだまだ意図せずに入ってしまっていることがあります。ガイドラインの項目も多いので全PRですべてのチェックを1個1個行うのには時間がかかります。そのため、メンバーそれぞれがガイドラインの内容を当たり前にできるようになることが大事だなと感じています。 まとめ 本ブログでは、開発ガイドライン並びにバッチ開発ガイドラインを紹介しました。バッチ開発ガイドラインについては私達が利用しているガイドラインをほぼそのまま紹介しました。ぜひ利用して頂いて、バッチアプリケーションの品質向上に役立てていただければ幸いです。 終わりに ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com
アバター
はじめに 技術評論社様より発刊されている Software Design の2024年5月号より「レガシーシステム攻略のプロセス」と題した全8回の連載が始まりました。 本連載では、ZOZOTOWNリプレイスプロジェクトについて紹介します。2017年に始まったリプレイスプロジェクトにおいて、ZOZO がどのような意図で、どのように取り組んできたのか、読者のみなさんに有益な情報をお伝えしていければと思いますので、ご期待ください。第1回目のテーマは、「ZOZOTOWNリプレイスプロジェクトの全体アーキテクチャと組織設計」です。 目次 はじめに 目次 ZOZOTOWNリプレイスの背景、目的 背景 目的 柔軟なシステム 開発生産性 技術のモダン化 採用強化 ZOZOTOWNリプレイスの歴史とアーキテクチャの変遷 アーキテクチャの変遷 2004年〜2017年:オンプレミス(リプレイス前) 2017年〜2020年:モノリスリプレイス 2020年〜:マイクロサービス化 技術スタックの選定 組織設計 プロジェクトチームの構成と変遷 マイクロサービス分割と組織 「開発」の側面 「運用」の側面 「組織」の側面 コミュニケーションと意思決定 理解を得る リリースフロー ADR(Architecture Decision Record) ロードマップ おわりに ZOZOTOWNリプレイスの背景、目的 背景 ZOZOTOWNは、2004年12月にサービスを開始して以降、基本的なアーキテクチャは変えずに成長してきました。数年に一度のペースでサイトリニューアルも行ってきましたが、UI/UXを中心としたリニューアルでした。 ZOZOTOWNのサイトデザイン変遷 右肩上がりの成長を続けていたものの、2017年当時、中長期目標として設定していた商品取扱高5,000億円にシステム全体が耐えられないことが予見され、リプレイスプロジェクトの検討が始まりました。 目的 リプレイスプロジェクトは、「ZOZOTOWNを使っていただいているお客様にいつでも快適に買い物をしていただけるサービスを提供するため」、そして、「ZOZOTOWNの成長のため」に行います。この目的を達成するために、リプレイスを通して、改善していきたいことを整理していき、目的を達成するためのポイントを大きく4つ定義しました。 柔軟なシステム リプレイス以前、オンプレミスのみで稼働していたZOZOTOWNは、新春セールなどの高負荷イベント時におけるサーバ増強には苦労してきました。イベントに合わせて事前にサーバの必要台数を見積もり、購入し、セットアップする必要がありました。また、処理コストが重いコンテンツをカットするなど、インフラだけではなくアプリケーションとしても作業が発生していました。 そこで、インフラ基盤をオンプレミスからクラウドへ移行することで、必要なときに柔軟に増強できるシステムにしたいと考えました。 開発生産性 事業展開における機能開発に加え、開発者視点でのUI/UX改善や、ソースコードのリファクタリングなどやりたいことがたくさんあります。しかし、モノリスアーキテクチャで開発されてきたZOZOTOWNでは、開発者数が増加するにつれてデプロイ作業が衝突して順番待ちが発生するなど、開発生産性に悪影響が発生し始めていました。 そこで、マイクロサービス化することで開発に関わるチームが並行で開発できるようにし、開発生産性を高めたいと考えました。併せて、IaC(Infrastructure as Code)や CI/CD(Continuous Integration / Continuous Delivery)を導入することで、デプロイ自動化による開発生産性の向上もねらいました。 技術のモダン化 ZOZOTOWNはVBScriptで動いています。開発元であるMicrosoft社もVBScriptの積極的な機能開発は現在行っておらず、このままでは稼働させる環境もなくなり、事業が停止するリスクがあります。経営リスクを回避するためにも、技術のモダン化は必須で、リプレイスプロジェクトは避けられないと考えました。 また、レガシー言語であるがゆえに、AWS、Google Cloud、DatadogといったクラウドベンダーがSDKを提供しておらず、利用を諦めるか、ライブラリを独自実装しなければならない状況でした。さらに、VBScript向けに協力会社内製のWebフレームワークを使用していて、そのフレームワークの動きを新メンバーは理解しなければならない状況でした。JavaやGo言語のようなクラウドベンダーが標準的にサポートしているプログラミング言語や、現代的なフレームワークに置き換えていくことで、そのような部分の実装コスト、学習コストを減らしたいと考えました。 採用強化 リプレイスプロジェクト、ひいては事業開発を加速するために採用強化が必要でした。リプレイスプロジェクトは、エンジニアとしての成長につながる経験ができるものだと思っています。知見をテックブログやイベント登壇で社外に向けて発信することにより、ZOZOに興味を持っていただき、採用を強化したいと考えました。 ZOZOTOWNリプレイスの歴史とアーキテクチャの変遷 アーキテクチャの変遷 2004年〜2017年:オンプレミス(リプレイス前) Webサーバ、DBサーバなどはすべてオンプレミスで運用していました。基本的な技術スタックはWindowsのIIS(Internet InformationServices)で、Classic ASP(VBScript)を動かしていました。データベースにはSQL Serverを利用していました。主なビジネスロジックはSQL Serverのストアドプロシージャで記述しており、WebサーバはHTML/JSONを生成するビューレイヤーとして利用していました。 リプレイス前のアーキテクチャ このアーキテクチャは2004年当時、Microsoft製品でシステムを構築する際のベストプラクティスとされていたものでした。ネットワークの細い当時はデータの近くで処理するストアドプロシージャのほうが高速で、ロジックを DB サーバに寄せることで DRY(Don't Repeat Yourself)に書ける利点がありました。このアーキテクチャでZOZOTOWNは売り上げを伸ばしてきたことからも、当時の技術選定として成功したと言えるでしょう。 一方で、さまざまな困りごとも出てきていました。主なビジネスロジックはストアドプロシージャとしてDBサーバで実行されるため、DBのCPUを消費してしまいます。CPUを増やすにはDBを増やす必要があり、負荷分散させづらい状況にありました。DBサーバのスケールアップでは限界が見え、DBを垂直分割し対処してきましたが、それでも限界が見えてきました。また、ストアドプロシージャのユニットテストフレームワークが なく、TDD(Test Driven Development)など昨今では当たり前とされている開発プラクティスを導入できないことも困りごとでした。 2017年〜2020年:モノリスリプレイス そこでストアドプロシージャからの脱却を目標の中心に据えて、モノリスtoモノリスアーキテクチャでのリプレイスプロジェクトが始動しました。フェーズを「参照系(商品、検索)」「準更新系(お気に入りログなど)」「更新系(カート、注文、会員)」の3つに分解し、フェーズ1として参照系機能のリプレイスプロジェクトが進行しました。 1 ビジネスロジックを処理するのに、DBサーバのCPUではなくWebサーバのCPUを使うアーキテクチャに変更できれば、Webサーバの台数を増やすことで負荷に対処できます。そこでSQL Serverの参照系ストアドを Java API化することにとにかく注力し、DBサーバの負荷を下げることが目標とされました。商品データは、オンプレSQL Serverからレプリケーションを貼って、クラウドSQL Serverに持ってきました。 リプレイスプロジェクト初期のアーキテクチャ この参照系リプレイスプロジェクトによって、ECサイトでトラフィックの大多数を占める参照系ワークロードのスケーラビリティを手に入れることができました。一方で、次のような課題も見えてきました。 モノリスアーキテクチャであることで生まれるデプロイの順番待ちが開発生産性に支障を与えていること クラウドSQL Serverのコスト(とくにライセンス費用)が高いこと 更新系ワークロードリプレイスの目処が立っていないこと ユーザートラフィックの入り口がオンプレIISのままであり、VBScript脱却の目処が立っていないこと 2020年〜:マイクロサービス化 リプレイス後の目指す姿(右)とストラングラーパターン(段階的な機能の置き換え)による移行 これを受けて新たなリーダーのもと、ZOZOTOWNリプレイスプロジェクトは再始動しました。マイクロサービス化しながらリプレイスプロジェクトを行う方針に大きく変更となり、メインで利用するDBもMySQLとするなど、技術スタックに関する変更も行われました。リプレイス後のアーキテクチャイメージと切り替え方法も示され、VBScriptからの脱却と、更新系ワークロードのリプレイス計画も立てられました。 技術スタックの選定 リプレイスプロジェクト再始動のタイミングで、技術スタックについてもあらためて選定し直しました。選定のポイントとしては「大規模開発に向いている」「人材を採用しやすい」「社内ノウハウがある」「廃れるリスクが少ない」技術を選定しました。とくに、最後の点については、リプレイスプロジェクトが長く続く可能性を考慮し、リプレイスのリプレイスが走ることを避けるために最重視したポイントでした。技術者個人としては新しいものを使いたくなりますが、邪念を排除し、組織にとって最も良い選択を行うことが肝要だと考えています。結果として、次のような技術選定になりました。 フロントエンド:JavaScript / TypeScript バックエンド:Java、Go、Python(ML向け) インフラ基盤:AWS、Google Cloud(ML向け)、Kubernetes RDBMS:MySQL(RDBMSが不得意な分野は別途検討、DynamoDB、Elasticsearchなど) バックエンドのプログラミング言語は、Javaをメインで利用しています。すでにJavaでリプレイスプロジェクトが始まっていたというのもありますが、大規模開発に向いていること、業務委託も含め人材を採用しやすいことを評価しました。Javaは一時期、機能開発が停滞していた印象を持っている方もいると思いますが、当時すでに機能開発も活発化しており、モダンな機能も取り込まれていることから、廃れるリスクも少ないと評価できたのが幸いでした。Go言語も使って良いと定めており、高速に動作し、アプリケーションの立ち上がりが早いことから、とくにトラフィック量の多いアプリケーションや、AWS Lambdaのようなサーバーレス環境で利用すると恩恵があります。機械学習の文脈では、ディープラーニングフレームワークなどライブラリの都合でPythonでAPIサーバを構築することもあります。 クラウドサービスは、AWSをメインで利用しています。人材採用しやすく、廃れるリスクが小さいのもそうですが、高いサービス品質を持ち、主要クラウドサービスの中で「最も痒かゆいところに手が届く」クラウドサービスであると評価しています。機械学習の文脈では、Google Cloudを使っても良いと定めています。データウェアハウス製品であるBigQueryや、Google独自の機械学習ワークロード向け集積回路TPU(Tensor Processing Unit)の提供を始めとして、データおよび機械学習の文脈で独自の強みを持つクラウドサービスであると評価しています。 コンテナ基盤は、Kubernetesを利用しています。2020年当時、Kubernetes以外にも活躍しているコンテナ基盤はありました。しかし、Google CloudやAzure、AWSがKubernetesのマネージドサービスを提供するようになり、Kubernetesが廃れるリスクはかなり小さいと判断できた時期でした。AWS EKS(Elastic Kubernetes Service)の提供は2018年であるため、2017年に技術選定をしていたら、違った判断になったかもしれません。 組織設計 プロジェクトチームの構成と変遷 2017年開始当初のリプレイスプロジェクトでは、フロントエンド/バックエンドはそれぞれでカートや商品詳細、検索などといった各機能を担当しやすいような複数チームで構成され、SREはすべてのインフラを管理する1つのチームで構成されていました。そして、各チームが事業案件(ZOZOTOWNの新機能追加や機能改修など)を担当しながら、リプレイスも進めるという二足のわらじ体制をとっていました。 ZOZOにはファッションやZOZOTOWNが好きなスタッフが多く、さまざまな部署からZOZOTOWNをさらに良くするためのアイデアが次々と寄せられます。それらに応えるため、どうしてもリプレイスの優先度を落とさざるを得ず、開発リソースの確保といった部分で、うまくリプレイスプロジェクトが進行していきませんでした。そこでバックエンドエンジニアの数名でリプレイス専属のチームを作り、リプレイスを進めていきました。 まずは、その専属チームが1つのリプレイス事例を作りました。その後、マイクロサービス化を拡大していく過程で、ID基盤や会員基盤・検索基盤・カート決済基盤など各マイクロサービスのチームを、構築するタイミングで作っていきました。そして、各マイクロサービスを担当するチームと、SREチームから構成される技術開発本部(現:技術本部)が設立されました。 リプレイスを行う初期段階では、事業開発を行ってきたチームが自分たちでリプレイスをやりたい、と思うのは当然だと思います。筆者たちも何度かトライしましたが、そのやり方でうまくいったことはありませんでした。リプレイス専属チームを作り、事例を作り、徐々に既存チームも巻き込み、最終的に融合するか、既存チームにシステムを移管する、という流れを取るのが有効です。 マイクロサービス分割と組織 マイクロサービスは組織論でもあります。マイクロサービス分割に関しては、ZOZOTOWNが持つ機能を大別し、それぞれの機能(マイクロサービス)を担当する専属のチームを作る想定で次のように分割設計を行いました。 認証・会員 商品(ブランド、ショップ)・お気に入り 検索 推薦 カート・決済 むやみやたらにマイクロサービスの数は増やさず、基本的には1チーム1マイクロサービスを担当するくらいが大まかな方針です。逆に言うと、マイクロサービスの規模感は1チームが専属で必要な作業量を見込めるくらいとなるように分割設計しました。将来的に1チームの担当サービスが増えて2、3個になってしまうことは想定していますが、1チームが数十個マイクロサービスを担当するような設計は避けています。 このようなマイクロサービスの分割設計は、「開発」「運用」「組織」の側面から検討を進めました。 「開発」の側面 開発の側面からは、各サービスに自律性を持たせ、開発生産性を高められる設計を意識しました。 まずは、ZOZOTOWNにおける機能とデータ、データとデータの関連性を把握することから始めました。これらの関連性を把握することで、たとえば、AとBの似たような機能をサービス分割するか一緒にするか、の意思決定がしやすくなります。ポイントとして、データの更新時に、複数のマイクロサービス間で分散トランザクションが必要にならないような境界線を見つけ、サービス分割を行うことを意識しました。うまくサービス分割できると、データに何かしらの仕様変更が入った場合でもサービス間の調整が不要になり、効率的な開発フローを回すことにつながります。マイクロサービスのメリットの1つである自律性を高めることにつながります。 「運用」の側面 システム運用の側面からは、リソース増強が必要な機能をきちんと把握し、効果的に実施できるようにする設計を意識しました。 モノリスの場合、特定のサブ機能のみをスケールさせたくても、モノリスサーバ全体をスケールさせなければなりません。マイクロサービスの場合、特定のサブ機能(≒マイクロサービス)のみをスケールさせることが可能なので、費用対効果を高めることができます。そのために、事業特性や過去実施イベント、障害記録を分析し、高負荷になる機能を整理しました。 ZOZOTOWNの場合、セールやZOZOWEEK、福袋といったイベント実施のタイミングで、とくにカート投入、購入、認証、商品検索などの機能が高負荷になる傾向があります。しかし、そういったイベント時でもそれほど負荷がかからないような機能もあります。そのような傾向を整理し、どういったマイクロサービス分割が、リソース増強などの運用全般で効率が良いかを考慮しました。 「組織」の側面 組織の側面からは、業務の専門性と、現在の開発体制からマイクロサービス化後の開発体制とのギャップを意識しました。 ZOZOTOWN の場合、カート投入・購入、商品検索、推薦といった機能が業務の専門性が高い機能に該当しますが、それらのチームで利用する技術スタック・業務知識は、ほかのチームに比べて特殊です。そのようなことを考慮し、組織分割の検討材料として取り入れました。 また、事業展開における業務量の把握も行いました。JIRAやGitHub、今後どんな開発案件があるかを示した開発ロードマップなどから、過去、現在、未来の開発案件がどのような機能の改修を必要とするものなのか、その傾向を分析しました。リプレイスが終わった後は事業開発をしていくことになります。各チームの業務量のバランスが良くなるように、ドメイン知識の関連性も意識したうえで担当マイクロサービスをアサインし、人員計画を考えました。 急にマイクロサービスと同数のチームを作るような大幅な組織変更をしてしまうと、新任リーダーの教育やチームビルディング、チーム間の連携に齟齬が生まれるなど、多くの懸念がありました。技術スタック・業務知識のキャッチアップにも時間がかかります。理想的なマイクロサービスの形、それに合わせた開発体制を目指して、技術的な練度を高めながら、現実的な組織変更を少しずつ重ねることで、組織づくりをしていきました。 コミュニケーションと意思決定 理解を得る リプレイスプロジェクトを進めるうえで、ビジネス部門、エンジニア部門(対社内/対社外)、経営層に対して、リプレイスの重要性を理解してもらうことは重要だと考えています。 ビジネス部門に対しては、社内向けの成果発表会などで、リプレイスによる事業効果(システム障害の減少、高速化、コスト削減など)を伝えることで、協力を得られやすい関係性を構築することを意識しました。 エンジニア部門に対しては、同じく社内向けの成果発表会・テックブログ・登壇での発信や、開発体験の向上、アラート件数の減少、などの成功体験を実際に感じてもらうことによって、リプレイスプロジェクトへの協力を得られやすくなりました。社外に対しては、テックブログや登壇を通じてZOZOの抱える課題や技術力を発信することで、採用強化につなげていきました。 そして経営層に対しては、リプレイスの目的、かかる費用、計画、考えられるリスクを整理して説明し、プロジェクトの承認を得ました。定期的なプロジェクト進捗報告を実施し、計画に変更があれば再度承認を得ながら進めています。進捗報告では、得られた事業効果も伝えることで、プロジェクトの優先度低下や廃止といった事態を避けることを意識していました。 リプレイスプロジェクトを計画どおり進めることは大事ですが、あくまでも事業が成功しているうえで成り立っているプロジェクトだと考えています。開発者としては、機能開発をフリーズして、リプレイスに専念するのが最もやりやすいでしょう。しかし、その間に競合他社に置いていかれ、リプレイスを実施するプロダクトとしての価値そのものを失ってしまっては元も子もありません。そのリスクを避けるために「事業を止めない」をリプレイスプロジェクトのポリシーとしました。サイト無停止でユーザーに気づかれないようにリプレイスしたり、リリース前に負荷試験を実施しボトルネックを事前に潰しておいたりと、事業に悪影響を出さないことを大前提として、技術的難易度が高くても工夫して成し遂げるように進めています。 リリースフロー ZOZOでは社内標準のリリースフローを定義しています。ZOZOにおけるリリースフローとは、案件の進捗工程(企画・設計・開発・テスト・リリース)ごとに必要な確認事項を定めたものです。リリースフローに従う必要があるかどうかはアーキテクチャの重要な変更、重要な情報の取り扱いなどの一定の条件が存在しますが、ZOZOTOWNリプレイスプロジェクトの各プロジェクトは必ずリリースフローに従うルールとしています。 リリースフローは、CTO、CISO部やテックリードを含めて企画、設計、テストの3段階でレビューMTGを行い、技術的、セキュリティ的に問題がないかを確認します。レビューMTG以外にも、全社で定められている開発ガイドラインに準拠しているかのチェックリスト確認もリリースフローには含まれています。 ADR(Architecture Decision Record) リプレイスプロジェクトでは、アーキテクチャや設計において、さまざまな意思決定が生まれます。それぞれがどのような検証、理由により方針を決定したのかADRとしてまとめています。ADRは、次の内容で記述しています。 タイトル(Title) コンテキスト(Context) 決定(Decision) ステータス(Status) 結果(Consequences) 最終的なアーキテクチャがまとめられているドキュメントはあるものの、その技術選定の理由や設計意図については残されていない、または議事録やコメントの一部として埋もれてしまっていることがよくあると思います。プロジェクトの初期から参画しているメンバーのみが理解している状態では、途中から参画するメンバーは新たな変更などの意思決定において、判断が難しくなってしまいます。また、時間経過とともに記憶もあいまいになり、初期から参画しているメンバーですらその意図を忘れてしまうこともあるでしょう。このようなことが積み重なってくると、変更に対して多くの時間がかかり、開発生産性の低下を招く可能性もあります。そのような状態を避けるためにADRを残すようにしています。 ロードマップ リプレイスプロジェクトにはいくつものサブプロジェクトがあり、各サブプロジェクトをまとめたロードマップを定義しています。しかし、当初からロードマップを定義できていたわけではありません。クラウドやマイクロサービスに対してノウハウや経験がない状態からの挑戦でしたので、まずは取りかかりやすい部分を対象にリプレイスしてみることにしました。しかし、障害を発生させてしまうなど、うまくいかないことも数多くありました。ですがそこで多くのことを学び、それらの経験をもとに次のように年度ごとにテーマを設け、少しずつ範囲を広げるようにロードマップを作成していきました。 2020年度:マイクロサービスプラットフォームの土台を作り、マイクロサービス化の事例を1、2個出していく(達成) 2021年度:さらにマイクロサービス化し、体制も強化し並列度を上げていく。うまくいくことを実証する(達成) 2022年度:終わりまでの全体設計・ロードマップをあらためて引き、さらに並列度を上げていく ロードマップを作成する際には、技術的な基盤を先に作りつつ、リプレイスの効果が大きいものを優先的に着手することを意識しました。また、事業効果(システム障害の減少、高速化、コスト削減など)も得られるように、サブプロジェクトのスコープを調整し、着実に事業成果を報告できる計画にしました。リプレイスプロジェクトは、過去に一度会社としての優先順位が下がったことがあります。それを避けるために、経営層にリプレイスプロジェクトの価値を定期的に理解してもらえるような計画を意識しました。 リプレイスロードマップは、進捗や状況次第で、四半期に一度のペースで適宜見直し、取り組んでいます。 おわりに 今回は、ZOZOTOWNリプレイスの全体感について、プロジェクト推進において意識していることやうまくいったこと、うまくいかずに軌道修正したことを盛り込んで紹介しました。今後、大規模システムのリプレイスプロジェクトに関わる方々の参考になれば幸いです。 第2回以降は、ZOZOTOWNリプレイスプロジェクトがこれまで取り組んできたサブプロジェクトを具体的にいくつかピックアップして、より詳細に紹介していく予定になっていますので、ご期待いただければと思います。 本記事は、技術本部 ECプラットフォーム部 ディレクターの高橋 智也と執行役員 兼 CTOの瀬尾 直利によって執筆されました。 本記事の初出は、 Software Design 2024年5月号 連載「レガシーシステム攻略のプロセス」の第1回「ZOZOTOWNリプレイスプロジェクトの全体アーキテクチャと組織設計」です。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com フェーズ1は2017年度中に終わらせるのが当初の目標でしたが、会社としての優先順位が下がるなどもあり、結果として3年ほど続くこととなってしまいました。 ↩
アバター
はじめに こんにちは、計測プラットフォーム開発本部iOSブロックの 中岡 です。普段はZOZOMAT/ZOZOGLASSの運用・保守や計測技術を使った新規事業の開発をしています。 目次 はじめに 目次 計測フレームワークとは Swift Package Managerへの移行の経緯 Swift Package Managerへの移行 移行作業でハマったこと まとめ 計測フレームワークとは 私たちのチームは、ZOZOMAT/ZOZOGLASSの機能を開発し、それらをライブラリとしてZOZOTOWN iOSチームに提供しています。このライブラリのことを私たちは計測フレームワークと呼んでいます。そしてこのライブラリの提供方法として今まではCocoaPodsを利用していました。元々はCarthageを利用していたのですが、Apple silicon導入に伴いCocoaPodsへ移行しています。そちらの経緯は以下の記事とMeetupのアーカイブをご参照ください。 techblog.zozo.com youtu.be speakerdeck.com Swift Package Managerへの移行の経緯 基本的にCocoaPodsに移行してからは開発体験などに問題はありませんでした。しかし、Appleから発表されたPrivacy Manifestに対応するため、依存ライブラリのいくつかを最新に更新する必要があり、そこでいくつかの問題に直面しました。以下はZOZOTOWN iOSの依存関係の一部です。 この中で以下のライブラリがPrivacy Manifestの対象 1 でした。 Lottie BoringSSL grpc-swift の0系の依存ライブラリ 1系にすることで swift-nio ベースとなり依存が消える nanopb ARCore の依存ライブラリ 2 Lottieに関しては特に問題なくCocoaPodsで最新版に更新できました。しかし、BoringSSLとnanopbはCocoaPodsで最新版にするには一筋縄ではいきませんでした。まずBoringSSLに関しては、依存しているgrpc-swiftの最新版がCocoaPodsでのサポートはされておらず、Swift Package Manager(以下SPM)のみとなっていました。 github.com nanopbに関しては、依存しているARCoreはCocoaPodsをサポートしています。しかし、CocoaPodsを使用してARCoreの最新版を導入すると、問題が発生しました。本来インストールしているOpenCVではなく、ARCoreが内部で使用しているOpenCVとZOZOGLASSがリンクし、クラッシュすることがわかりました。この問題は、SPMを利用してARCoreを導入することで解消されたことが確認されており、現在はその詳しい原因を調査中です。 これらの問題から、計測フレームワークをSPMへ移行することにしました。また合わせて、ZOZOTOWN iOSは共有の依存としてLottieやnanopb(Firebaseの依存)を持っているので、これらも同時にSPMへ移行しました。 Swift Package Managerへの移行 基本的には、podspecに記載されているdependencyやsource_fileをPackage.swiftに移行するだけです。以下の公式ドキュメントを参考に作業を進めました。 github.com developer.apple.com 移行前のpodspecの一部 Pod :: Spec .new do |spec| spec.name = " ZOZOMAT " # 省略 spec.source_files = " Sources/ZOZOMAT/**/*.swift " spec.resource_bundles = { " ZozomatResources " => [ " Sources/ZOZOMAT/Resources/**/*.xib " , # 省略 ] } spec.dependency " SwiftGRPC " , " 0.11.0 " spec.dependency " lottie-ios " , " 3.2.3 " # 省略 } end 移行後のPackage.swiftの一部 import PackageDescription let package = Package( name : "ZOZOMAT" , defaultLocalization : "ja" , platforms : [ .iOS ( "15.5.0" ) ] , products : [ .library ( name: "ZOZOMAT" , targets: [ "ZOZOMAT" ] ) , ] , dependencies : [ .package ( url: "https://github.com/grpc/grpc-swift.git" , .upToNextMajor ( from: "1.21.1" )) , .package ( url: "https://github.com/airbnb/lottie-spm" , .upToNextMajor ( from: "4.4.0" )) , // 省略 ] , targets : [ .target ( name: "ZOZOMAT" , dependencies: [ .product ( name: "Lottie" , package: "lottie-spm" ) , .product ( name: "GRPC" , package: "grpc-swift" ) , // 省略 ] , path: "Sources/ZOZOMAT" , // source_fileに対応するディレクトリを指定 resources: [ .process ( "Resources" ) ] , ) ] ) 移行作業でハマったこと そして作業を進めていく中で以下の2つのハマりポイントがありました。 Objective-Cが含まれているSwift Packageでのバンドルリソースへのアクセス方法 同じソースコードを複数のターゲットで使用できない まず1に関して、ZOZOMATの3D表示の機能はC++とObjective-Cで書かれており 3 、それらは別のライブラリとして管理されています。そしてこのライブラリはシェーダーコードなどのリソースを持っています。Objective-CからSwift Packageのバンドルにアクセスするには SWIFTPM_MODULE_BUNDLE というマクロを使用します 4 。基本的にはSwiftの Bundle.module と同じように動作します。以下のPackage.swiftの場合、リソースバンドルは SampleObjc-Package_SampleObjc-Target.bundle という名前になります。 import PackageDescription let package = Package( name : "SampleObjc-Package" , products : [ .library ( name: "SampleObjc-Target" , targets: [ "SampleObjc-Target" ]) , ] , targets : [ .target ( name: "SampleObjc-Target" , path: "Sources/SampleObjc" , resources: [ .process ( "Resources" ) ] ) , ] ) しかし、Objective-Cから SWIFTPM_MODULE_BUNDLE を使いアクセスしようとすると、以下のエラーのようなエラーが出て、クラッシュします。これは実際のバンドル名が SampleObjc-Package_SampleObjc-Target.bundle なのに、 SampleObjc_Package_SampleObjc_Target.bundle にアクセスしているために発生します。これを避けるためにObjective-CのSwift Packageでは名前に - を含めないようにする必要があります。 *** Terminating app due to uncaught exception 'SwiftPMResourcesAccessor', reason: 'unable to find bundle named SampleObjc_Package_SampleObjc_Target' 次に2の「同じソースコードを複数のターゲットで使用できない」に関してです。ZOZOMATは一部の機能をZOZOTOWN iOSとZOZOMATの計測機能のみを持つデモアプリで処理を分岐させるためにActive Compilation Conditionsを使っています。podspecに以下のようにフラグを設定しZOZOTOWNに提供しており、デモアプリの場合は pod install 時に post_install でそのフラグを書き換えていました。 ZOZOMATのpodspec # ZOZOTOWN用フラグを設定 spec.pod_target_xcconfig = { " SWIFT_ACTIVE_COMPILATION_CONDITIONS " => " ZOZOMAT_RELEASE " , } ZOZOMATのローカルにあるデモアプリ用のPodfile # デモアプリ用のフラグを設定 def set_demo_mode (pi) pi.pods_project.targets.each do |t| t.build_configurations.each do |bc| if t.name == " ZOZOMAT " bc.build_settings[ " SWIFT_ACTIVE_COMPILATION_CONDITIONS " ] = " ZOZOMAT_DEMO " end end end end post_install do |pi| set_demo_mode(pi) end これは元々Carthageを使っていた時に、デモアプリ用とZOZOTOWN用の2つのXcodeターゲットを用意し、フラグで処理を分岐させるという方法をとっていたためです。これと同じことを実現するために以下のように共通のソースコードを持つ2つのターゲットを定義し、それぞれに別のフラグを設定しようと考えましたが、SPMでは同じソースコードを複数のターゲットに含めることができませんでした。以下のようにPackage.swiftを定義しそれぞれのターゲットで swiftSetting を設定しようとするとエラーが出ます。 そのため、現在は一時的な対応としてデモアプリを動かす際は swiftSetting を書き換えて作業を行うという方法をとっています。将来的にはライブラリ内にこのような分岐を持つことは本来望ましくないので、リファクタリングをする予定です。 target 'ZOZOMATDemo' has overlapping sources: /Users/xxxxxx/SamplePackage/Sources/ZOZOMAT/Sample.swift import PackageDescription let package = Package( name : "ZOZOMAT" , products : [ .library ( name: "ZOZOMAT" , targets: [ "ZOZOMAT" ]) , .library ( name: "ZOZOMATDemo" , targets: [ "ZOZOMATDemo" ]) , ] , targets : [ .target ( name: "ZOZOMAT" , path: "Sources/ZOZOMAT" , swiftSettings: [ .define ( "ZOZOMAT_RELEASE" ) ] ) , // 以下のようにすることはできない .target( name : "ZOZOMATDemo" , path : "Sources/ZOZOMAT" , swiftSettings : [ .define ( "ZOZOMAT_DEMO" ) ] ), ] ) まとめ 本記事では、計測フレームワークをSPMへ移行する際の経緯と移行作業でハマったポイントについて紹介しました。SPMへの移行により、依存ライブラリの更新ができPrivacyManifestの対応ができましたが、移行によって新たに発生した問題もあります。 ブランチ切り替えの際に毎回Package resolveが走り時間がかかる ZOZOTOWN iOSはCocoaPodsとSPMの混在状態となっている ZOZOMATのデモアプリを動かす際に手動で swiftSetting を書き換える必要がある 今後はこれらの問題を解決し、より良い開発体験になるように改善していきたいと考えています。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com https://developer.apple.com/support/third-party-SDK-requirements/ ↩ 5/1現在、最新のARCoreが依存しているnanopbはPrivacy Manifest対応されていないが将来的に対応される予定( issue ) ↩ https://techblog.zozo.com/entry/zozomat-cross-platform-3d ↩ https://developer.apple.com/videos/play/wwdc2020/10169/ ↩
アバター
こんにちは、DevRelブロックの ikkou です。2024年5月15日から17日の3日間にわたり沖縄県は那覇市で「RubyKaigi 2024」が開催されました。ZOZOは例年同様プラチナスポンサーとして協賛し、スポンサーブースを出展しました。 technote.zozo.com ZOZOとWEARとRubyKaigi エンジニアによるセッション紹介 Generating a custom SDK for your web service or Rails API Namespace, What and Why YJIT Makes Rails 1.7x Faster Using Ruby in the browser is wonderful. An adventure of Happy Eyeballs Embedding it into Ruby code Unlocking Potential of Property Based Testing with Ractor Let's use LLMs from Ruby 〜 Refine RBS types using LLM 〜 The depths of profiling Ruby rb_profile_thread_frames() TracePoint API Thread Events API It’s about time to pack Ruby and Ruby scripts in one binary スポンサーブースの紹介 RubyKaigi出張版「みんなの失敗」 失敗を “水に流せる” トイレットペーパー リニューアルしたWEARの紹介 箱猫マックスとチーコのステッカー RubyKaigi公式イベントのスタンプラリー After RubyKaigi 2024〜メドピア、ZOZO、Findy〜を開催します! おわりに ZOZOとWEARとRubyKaigi カラッと晴れた沖縄の空と会場の「那覇文化芸術劇場 なはーと」 ZOZOとRubyKaigiの関係は前身にあたるVASILY時代の RubyKaigi 2017 から始まっています。翌年の RubyKaigi 2018 では スタートトゥデイテクノロジーズとして初めて協賛 、その翌年の RubyKaigi 2019 では ZOZOテクノロジーズとしてRubyスポンサーに協賛 、 現CTOの瀬尾がスピーカーとして登壇 しています。このときは ファッションチェックアプリとRuby on Lambdaで開発したランキングサイトを展示していました 。 その後、コロナ禍を経て再開した RubyKaigi 2022 からはファッションコーディネートアプリ「WEAR」のバックエンド開発を担うチームが中心となって協賛とスポンサーブースの出展を続けています。 RubyKaigi2017参加レポート(全日分)とスライドまとめ RubyKaigi2018参加レポート RubyKaigi 2019参加レポート〜sonots登壇セッション & エンジニア8名による厳選セッション RubyKaigi 2022参加レポート 〜エンジニアによるセッション紹介〜 RubyKaigi 2023参加レポート 〜エンジニアによるセッション紹介〜 私たちが運営しているファッションコーディネートアプリ「WEAR」のバックエンドはRuby on Railsで開発しています。2013年にVBScriptで作られたシステムですが、2020年頃からVBScriptのシステムをコードフリーズし、Rubyへのリプレイスをはじめました。現在もリプレイスを進めながら、新規の機能もRubyで開発しています。 ZOZOのスポンサーブースに遊びにきてくれたMatzさん また、かねてよりMatzさんを技術顧問としてお迎えし、月次でMatz MTGと題したオンラインミーティングを実施しています。Rubyど真ん中の話から広く技術的に興味がそそられる話まで色々話せる時間として人気です。 昨年10月には10周年を迎えたWEAR ですが、RubyKaigi 2024の会期直前となる 5月9日に「WEAR by ZOZO」としてリニューアルしました 。リニューアル直後となるスポンサーブースの出展だったこともあり、会期中に不測の事態が発生しないか心配していましたが、何事もなく、ブースを訪れた方に実機で新機能を試してもらえました。 そんなZOZOとWEARとRubyKaigiの関係をお伝えしたところで、RubyKaigi 2024に参加したエンジニアがピックアップしたセッションの紹介と、スポンサーブース紹介の2軸でお送りします。ボリュームたっぷりの記事となっています! エンジニアによるセッション紹介 Generating a custom SDK for your web service or Rails API @tsuwatch です! @mullermp さんの「 Generating a custom SDK for your web service or Rails API 」が個人的にかなり良かったのでご紹介します。 本セッションはAWSで実際に使用している Smithy というIDLと実際に smithy-ruby を使いながら何ができるのかを紹介するものでした。 aws-sdk-ruby に実際に使われているそうです。 Smithyとは、プロトコルに依存しないIDLで、クライアント、サーバ、ドキュメントなどを生成するためのツールセットです。AWSが提供している数万のサービスに対してSDKを生成してきた叡智が詰まっています。 一部抜粋ですが、以下のようにサービスや、リソース、リソースに対する操作についての定義を記述すると、それを実行できるSDKが自動生成されるというものです。 $version : " 1.0 " namespace example.railsjson use smithy.ruby.protocols #railsJson use smithy.ruby.protocols #UnprocessableEntityError // / Rails High Score example from their generator docs @railsJson @title ( " High Score Sample Rails Service " ) service HighScoreService { version : " 2021-02-15 " , resources : [ HighScore ], } // / Rails default scaffold operations resource HighScore { identifiers : { id : String }, read : GetHighScore } // / Modeled attributes for a High Score structure HighScoreAttributes { // / The high score id id : String , // / The game for the high score game : String , // / The high score for the game score : Integer , // The time the high score was created at createdAt : Timestamp , // The time the high score was updated at updatedAt : Timestamp } // / Permitted params for a High Score structure HighScoreParams { // / The game for the high score @length ( min : 2 ) game : String , // / The high score for the game score : Integer } // / Get a high score @http ( method : " GET " , uri : " /high_scores/{id} " ) @readonly operation GetHighScore { input : GetHighScoreInput , output : GetHighScoreOutput } // / Input structure for GetHighScore structure GetHighScoreInput { // / The high score id @required @httpLabel id : String } // / Output structure for GetHighScore structure GetHighScoreOutput { // / The high score attributes @httpPayload highScore : HighScoreAttributes } そして以下のような呼び出しが可能になります。 require ' high_score_service ' client = HighScoreService :: Client .new( endpoint : ' http://127.0.0.1:3000 ' ) c.get_high_score( id : ‘ 1 ’) OpenAPIではダメなのかという意見もあるかと思いますが、Smithyはプロトコルに依存しないというところが明確に差分としてあります。そのため、RESTful APIはもちろん、RPC、pub/subなどを定義できます。しかも、なんとSmithyはOpenAPI、JSON Schema、Protobufに変換できます。 https://github.com/disneystreaming/smithy-translate その他にもかなりの拡張性があり、 Paginators , Waiters , Interceptors などさまざまな拡張性があります。 AWSほど巨大なサービスを社内もしくは社外に提供している事例はそれほどないでしょうが、OpenAPIよりもプログラマブルで拡張性があり、柔軟に使用できるので検討の余地が大いにあるのではないでしょうか。場合によっては自分たち開発者側だけの利用だけでも役に立つかもしれません。 セッションを聞いていると、自動生成されるデモを実演するたびにcoolと気持ちよさそうにしていたので、その気持ちがすごくわかりますと思いながら聞いていました。 その他の参考資料 https://github.com/smithy-lang/awesome-smithy https://aws.amazon.com/jp/blogs/news/developer-preview-smithy-code-generated-ruby-sdk/ Namespace, What and Why 伊藤です。RubyKaigiには今年で2回目の参加でしたが、今年も内容が幅広く、興味深いセッションばかりでした! 私からは @tagomoris さんの「 Namespace, What and Why 」をご紹介します。 speakerdeck.com このセッションでは、Namespaceの概要や目的、デモ、そして将来の展望までをお話しされており、非常に興味深い内容でした。 Rubyにはグローバルな名前空間しか存在しません。Namespaceは、Rubyに仮想的なトップレベル名前空間を導入し、ライブラリなどをグローバル名前空間から独立した形でrequire/loadできるようにする仕組みです。 Namespaceの導入の目的は、ライブラリなどの名前、定義、バージョンの衝突を回避することです。 名前の衝突 通常、Rubyでは階層化された名前を使う( Foo::Bar::Baz ) 既に他の誰かにFooを使われていたら、新たにFooを複製できない 定義の衝突 Module/Classの定義を変えると、全アプリケーションに影響が出る(定数やグローバル変数などをアプリケーション毎に変えられない) バージョンの衝突 アプリケーションによってライブラリなどの依存バージョンが異なる(ライブラリをアップデートすると、意図しない箇所に影響を及ぼす可能性がある) Namespace on readでは、Namespace毎にrequire/loadするライブラリなどを明示的に指定し、それぞれ独立した形で読み込むことを実現しています。裏側でNamespace毎にライブラリなどを複製し、トップレベル名前空間を仮想的に分けることで実現しているようです。 Rubyの Playground でNamespace on readを試せたので、デモを紹介します! 例えば、以下の2つのモジュールがあったとします。 #--- ./module1.rb module Hoge def self . foo puts ' module1 ' end end #--- ./module2.rb module Hoge def self . foo puts ' module2 ' end end 上記2つのモジュールは、モジュール名が衝突しているため、両方のモジュールを使おうとすると、以下のようにどちらか一方に上書きされてしまい、同時には使用できません。 require ( ' ./module1.rb ' ) require ( ' ./module2.rb ' ) Hoge .foo #=> module2 ところが2つのNamespaceを用意し、それぞれにどのモジュールを読み込むかを明示的に指定することにより、トップレベル名前空間を仮想的に分けられ、両方のモジュールを使用できるようになります。 namespace1 = Namespace .new namespace1.require( ' ./module1.rb ' ) namespace2 = Namespace .new namespace2.require( ' ./module2.rb ' ) namespace1:: Hoge .foo #=> module1 namespace2:: Hoge .foo #=> module2 Namespaceを用いると、namespace1とnamespace2の2つにトップレベル名前空間を分離させ、見事に名前空間の衝突を回避できていますね! 将来的に、例えばBundlerにNamespaceが組み込まれたら、gemのバージョンの衝突が自動的に回避されるようになるかもしれません。つまり、ライブラリの依存関係の地獄から抜け出せるということです! Namespaceはまだ絶賛開発中とのことなので、リリースを心待ちにしております! YJIT Makes Rails 1.7x Faster 小山 です。私からは @k0kubun さんの「 YJIT Makes Rails 1.7x faster 」をご紹介します。 speakerdeck.com Ruby 3.3 YJITの高速化に寄与した対応の詳細とRuby 3.4 YJITによってRailsアプリケーションが1.8倍高速化されたということが主題のセッションでした。タイトルは1.7xとなっていますが、発表当日までの間に1.8倍に高速化されたとのことで、冒頭で訂正されていました。 YJITはCRubyのJITコンパイラで、30回以上呼び出されるメソッドの高速化が行われます。 Shopifyで最もトラフィックが多いShopify StoreFrontで、Ruby 3.3 YJITとインタプリタを比較した場合、Ruby 3.3では平均17%高速化されたとのことです。 YJITはデフォルトではオフになっているので有効化するには以下のいずれかの設定が必要です。 コマンドライン引数 --yjit で有効化 環境変数 RUBY_YJIT_ENABLE=1 で有効化 Rubyコード内の RubyVM::YJIT.enable で有効化 Rails 7.2でRuby 3.3以降を使用している場合、デフォルトのイニシャライザ内で RubyVM::YJIT.enable が呼び出され有効化されるようになるとのことで、YJITが標準となります。 YJITのメモリ使用率を最小化するには以下の方法があります。 --yjit-exec-mem-size で生成するコード量をコントロールする Ruby 3.31でのデフォルトは48MiB --yjit-exec-mem-size の3〜4倍のメモリを使用する 2〜3倍分はメタデータに対して使われる Ruby 3.3のYJIT最適化で最もインパクトのあった以下の対応によって、Railsbenchが7%高速化されたとのことです。 サポートされていない呼び出し方や分岐の数の多い呼出しでのインタプリタへのフォールバックが行なわれなくした 例外ハンドラのコンパイルを行った スタック操作でレジスタが使われるようにした Ruby 3.4ではスタック値のようなローカル変数が最適化されて、 Kernel#binding が呼び出されないと推測している 数値, true, false, nil., :symbolなど単純な値を返すメソッドのインライン化 Ruby 3.4ではself, local variablesを返すメソッドにも対応 多くのCメソッドのインライン化 Ruby 3.4では jit_prepare_lazy_frame_call によって遅延フレーム呼び出しが可能になる YJITではプロダクションのアプリケーションで10〜20%ほど高速化されるためYJITを有効化しましょう。より多くのコードがJITコンパイルされ、レジスタ割り当てがされ、メソッドがインライン化されるためRuby 3.3にアップグレードしましょう。と締められていました。 RubyバージョンのアップグレードおよびYJIT有効化による恩恵がとても大きいことを話されており、私たちのアプリケーションにも早く導入したいと思いました! Using Ruby in the browser is wonderful. chika です。私からは @ledsun さんの「 Using Ruby in the browser is wonderful. 」をご紹介します。 speakerdeck.com このセッションは、 ruby.wasm を使用する上で不足している機能を実装し、 ruby.wasm に追加したということと、 ruby.wasm を使用したRuby製のフロントエンドフレームワークの紹介でした。 JavaScriptではできるがRuby( ruby.wasm )ではできないこととして、以下があります。 new演算子 new演算子は、JavaScriptでは new.Foo() と記述する演算子ですが、Rubyでは Foo.new というメソッド呼び出しとなる プロパティ呼び出し JavaScriptにおけるプロパティ呼び出しも同様に、Rubyではメソッド呼び出しとなる 外部リソースにアクセスする JavaScriptではローカルファイルへのアクセスや、OSの機能を呼び出せるが、Rubyではできない ruby.wasm がCRubyとJavaScriptの架け橋となり、 JS.global や JS.eval 、 JS::Object によってJavaScriptのAPIを呼び出すことで可能となっているが、JSとRubyのコードが入り混じってしまい、分かりにくくなってしまう 現状のRubyや ruby.wasm できない部分や分かりにくいところを改善するために、以下2つの改善を ruby.wasm 本体に向けてPRを作成したとのことでした。 1つ目は、JavaScriptのオブジェクトを、newメソッドから作れる修正になります。 元の ruby.wasm ではnew演算子が存在しないため、 JS.eval によってJavaScriptのnew演算子を呼び出す必要がありました。 JS .eval ' return new URLSearchParams(location.search) ' それが、今回のPRによって、以下のようにnewメソッドにて記述することが可能となります。 JS .global[ :URLSearchParams ].new( JS .global[ :location ][ :search ]) 実際のPRはこちら: https://github.com/ruby/ruby.wasm/pull/246 2つ目は、JavaScriptの JS::RequireRemote を require_relative#load に互換する修正です。 機能としては、Rubyの require_relative と互換性があり、 拡張子なしの機能名で指定可能 相対パスでの解決 二重ローディングの防止 があります。 言語本体のファイルに何も手を入れる必要がなく、ブラウザとターミナルでcompartibleになっているそうです。 この修正によって、JavaScriptでの以下の構文が、 < script type = "text/ruby" src = "lib_a" ></ script > < script type = "text/ruby" src = "lib_b" ></ script > < script type = "text/ruby" src = "main" ></ script > ruby.wasm にて以下のように記述することが可能となります。 require_relative ' lib_a ' require_relative ' lib_b ' 実際のPRはこちら: https://github.com/ruby/ruby.wasm/pull/292 次に作成しているものとして、Ruby製のフロントエンドフレームワークを紹介していました。 Ruby製のフロントエンドフレームワークはおそらく現在1つもなく、史上初のフレームワークかもしれないとのことで、とても面白い取り組みでした。 現在、 ruby.wasm を使ったRuby製のフロントエンドフレームワーク「Orbital Ring」というものを作成していて、現在の機能は以下になります。 AutoLoader Railsのclassic autoloaderを参考に、require_relativeが必要なくなるよう実装 Rendering ブラウザで動作するので、HTMLをレンダリングして書き換えたいため、画面を描写するメソッドを追加し、erbに渡すよう実装 Event Binding Railsのroutingのようなものを実装 現在120行ほどのコード量とのことでした(少ない!) また、実際にフレームワークを使用して作成したアプリケーションを公開していて、実際に以下のリンクからアクセスして動かすことが可能となっていました。 https://ledsun.github.io/kakikata/ セッションの紹介は以上になります。 ruby.wasm が登場してから2年ほど経ちましたが、毎年 ruby.wasm に関するセッションがいくつもあり、どんどん進化していっているなという印象で、追うのが楽しくなっています! 来年もまたどう進化するのか楽しみです。 An adventure of Happy Eyeballs 春日 です。私からは @coe401_ さんの「 An adventure of Happy Eyeballs 」をご紹介します。 speakerdeck.com このセッションでは、Happy Eyeballs Version 2という RFC 8305 で定義された接続アルゴリズムを用いてsocketライブラリを改修した際の考慮事項や実装方法の説明、実際にデモを行い従来の課題を解決できたことをお話しされていました。 従来のRubyのsocketライブラリの実装としては、次のようになっています。 DNSサーバに対し、接続したいドメイン名を問い合わせ、対象のIPアドレスを取得 解決されたIPアドレスに対し、接続を試行 接続が確立したらsocketオブジェクトを返す しかし、従来の実装方法では次のような課題がありました。 DNSサーバへの対象サーバのIPv4とIPv6の問い合わせは順番に行われるため、先に問い合わせた方の名前解決に時間がかかる場合、後に問い合わせる方がすぐにIPアドレスを返せるとしても、先に問い合わせた方の回答を待機する必要がある IPv4, IPv6の名前解決が両方とも成功した際、それぞれへの接続も順番に行われるため、先に接続試行した方が長時間接続の確立ができない場合、次に試行するアドレスがすぐに接続可能だとしても、先に接続試行した方が失敗するまで次の接続を待機する必要がある このような課題に対処するため、RFC 8305でHappy Eyeballs Version 2(HEv2)というアルゴリズムが定義されています。 HEv2のアルゴリズムは次の通りです。 DNSサーバへのクエリを非同期で行う 解決されたIPアドレスをソートする 接続試行を250msごとに非同期で行う どれか1つの接続が確立したら他の接続はキャンセルする 解決されたIPアドレスリストやソケットの状態によって次にすべき処理が異なることから、これらの処理をステートマシンで表せることに着目しました。この状態遷移図は2020年のRubyアソシエーション開発助成金ですでに作成されていたようです。( https://www.ruby.or.jp/grant/2020/matsushita_mentor_report.pdf ) この状態遷移図を参考に、コードを実装します。この実装では、 Kernel#loop とcase文が用いられています。今の状態をstateオブジェクトにもち、その値によって異なる処理を行います。 接続中のソケットは書き込み待ちのIOオブジェクトとして IO.select の引数に渡されます。そのいずれかで接続が確立されるか失敗すると IO.select は待機を終了し、そのソケットの配列を書き込み可能なIOオブジェクトとして返却します。 また、名前解決の待機のためには IO.pipe が用いられています。スレッド内で名前解決が完了すると書き込み用のIOオブジェクトに書き込まれ、 IO.select の引数に渡されている読み込み用のIOオブジェクトで読み込み可能になります。その結果、 IO.select は待機を終了し、読み込み用IOオブジェクトを返します。 IO.select の引数にこれらのオブジェクトを同時に渡すことで、1つの式で接続の完了と名前解決の両方を待機可能にしています。 各状態の詳細な処理内容は、セッションスライドをご覧ください。ここでは紹介しなかった、実装中に発生したRFC 8305では定義されていない部分の解決方法なども説明されています。 デモでは、IPv6の接続に長時間かかる場合、従来の接続方法だと接続ができていないところを、HEv2を用いた Socket.tcp で接続した場合は即座に接続ができたことを見せていただきました。 HEv2を用いた Socket.tcp はRuby 3.4に含まれるようです。また、Cで実装された TCPSocket.new のHEv2対応も現在取り組み中とのことです。 セッションの紹介は以上になります。 RFCで定義された接続方法を実装に落とし込むところはかなり興味深く、非常に勉強になりました。このアップデートにより、接続時のタイムアウトがかなり減るのではないかと期待できますね。リリースが楽しみです。 Embedding it into Ruby code 高久です。私からは @soutaro さんの「 Embedding it into Ruby code 」をご紹介します! speakerdeck.com 自社プロダクトへのRBS導入が現実的に考えられそうな内容で印象に残っているため選びました。 このセッションではRBSの型宣言を直接Rubyファイル書けるようにする rbs-inline を紹介されていました。 従来のRBSは型宣言したいRubyコードとは別に、RBSファイルに型情報を定義する必要があります。しかし1つの処理に対して2つのファイルに記述することは、開発やメンテ、プルリクレビューのしづらさが課題としてありました。 そこでRubyのコードに直接型情報を書ける rbs-inline の開発を現在進めているとのことでした。 rbs-inlineでは具体的に以下のようにコメント形式で型情報を記述します。 # rbs_inline: enabled class Person attr_reader :name #:: String attr_reader :addresses #:: Array[String] # @rbs name: String # @rbs addresses: Array[String] # @rbs returns void def initialize ( name :, addresses :) @name = name @addresses = addresses end def to_s #:: String " Person(name = #{ name } , addresses = #{ addresses.join( " , " ) } ) " end end YARDに少し似ていますね。YARDの構造に似せて作っているとのことです。しかしYARDタグは構文や型の構文がRBSとは異なるため、別タグとして記載する必要があります。 rbs-inlineはまだ検証段階で最適な構文等を模索中とのことでした。 私自身もWEARにRBS導入を検討したことがあるのですが、まさに@soutaroさんがおっしゃっていたような課題感からまだ導入に踏み切れていない状態でした。特にRubyコード修正した時に、RBSコードも一緒にメンテする必要があるのは気持ち的に少し大変で、メンテが漏れ始めてくると段々誰もメンテしないファイルになる可能性が高いと思っていました。レビューで毎回RBSファイルの修正有無をレビュアーが確認するのも大変です。 それが現状YARDのメンテやレビューが個人的にはなんの苦でもないことを考えると、同じコードに直接書くことで正しくメンテされた状態を維持できる可能性がかなり高くなると感じました。 YARDとどのように互換性を持たせるのかも気になるところで、今後の開発進捗が楽しみです! Unlocking Potential of Property Based Testing with Ractor 三浦 です。私からは @ohbarye さんの「 Unlocking Potential of Property Based Testing with Ractor 」をご紹介します! speakerdeck.com このセッションはohbaryeさん自身が作成したProperty Based TestingをRubyで実施するためのGemの話と、Property Based Testingのデメリットである実行時間を短縮するために更にRactorを掛け合わせて実行を試みた話の2段階構成となっていました。 Property Based Testingは名前だけは聞いたことがあり、実際に使ったことはなかったので、勉強したいと思いこのセッションに参加しました。 私たちがよく利用しているユニットテストは、開発者が入力値と期待する出力を明示し、コードがその通りに動作することを確認するテストです。この手法は「事例ベーステスト(Example Based Testing)」と呼ばれています。 メリット 入力値と期待値が決まっているのでどのようなテストをしたいのかが理解しやすい Property Based Testingに比べると実行時間が早い デメリット コードが冗長になる テストケースの粒度が開発者依存となるため、想定していない入力値によるバグが起こり得る 一方、今回のセッションで取り上げられている「プロパティベーステスト(Property Based Testing)」は、開発者はコードが満たすべき特性や条件を明示した「プロパティ」と呼ばれるものを定義し、ランダムに生成された入力値に対してそれが一貫して成り立つかを確認するテスト手法です。 メリット コード量が少なくて済む 開発者が想定していない入力値によるバグもカバーできる デメリット パターン数やShrinkの実行回数によっては処理時間が長くなる 上記のメリットとデメリットを踏まえて、対象コードの品質をどこまで担保したいかを考え、それに応じて使い分けるのが良さそうだと思いました。 Property Based Testingは、 PBT というGemで実装が可能です。 下記はセッションでも紹介されていた具体例となります。 context ' verify biggest method using property based testing ' do it ' return biggest number in array ' do # Runnerメソッド Pbt .assert do # テストに使用したい入力値の条件を記載(ex.ランダムな整数が入った配列) Pbt .property( Pbt .array( Pbt .integer)) do |numbers|   # テスト対象のメソッドを実行 result = biggest(numbers) # 実行結果が仕様通りになっているか検証( ex.配列で一番大きな数値が返ってくる) expect(result).to eq numbers.sort.last end end end end 具体的な入力値は明示せず、 Pbt.property の引数にテストで利用したい値の条件を指定します。実行すると、条件に合わせた入力値がランダムに生成され、テストが実行されます。 デフォルトでは100パターンのテストが実行されます。失敗するケースが検出されると、同じ現象が再現する最小の入力値を探すためのshrinkが行われます。shrinkの実行履歴は Pbt.assert の引数に verbose: true を設定することで確認できます。 ケース漏れの防止や原因の特定は容易になるものの、テストの総実行回数が多いため、時間がかかってしまいます。 その対策として、Ractorでテストを並列実行させ処理実行が早くなるかを検証されていました。いくつかのテストに対してRactor、Process、Thread、シーケンシャルそれぞれ実行した際のパフォーマンスを比較するというものでした。CPUバウンドなテストに対してRactorは有効でしたが、基本的にはシーケンシャルに実行する方が早いという結果でした。 Property Based Testingとは何かから丁寧に説明されており非常に分かりやすいセッションでした。WEARでも入力パターンの多いコードがいくつか存在するので試してみたいと思いました。 Let's use LLMs from Ruby 〜 Refine RBS types using LLM 〜 小島です。私からは @kokuyouwind さんの「 Let's use LLMs from Ruby 〜 Refine RBS types using LLM 〜 」をご紹介します! speakerdeck.com このセッションでは、プロジェクト全体のRBSをLLMによって生成しようという試みが話されていました。RubyコードからRBSを自動生成する手法は既存でも存在するのですが、それぞれ一長一短があるため完璧なRBSを生成するのは現状では難しく、RBSを生成してから手動で修正することが必要になることが多くありました。そのため、既存手法とLLMを合わせて利用することで手動での修正を必要としないで理想的なRBSを出力させようという取り組みです。既存の手法では型を定義しきれず手動で修正しなくてはならないところ、LLMを利用して修正しているのですね! 今回の発表ではこれらを実現するRBS Gooseというツールを開発したと発表されていました。このRBS Gooseはまだ開発段階であり、実用できるレベルには至っていないとおっしゃっていました。 現段階でのRBS GooseからのRBS生成精度も発表で話されており、RubyコードからRBSを生成する既存の手法に関してはどの手法を使用してもRBS Gooseの出力精度に大差はないが、LLMのモデルの違いによっては大きく精度が変わるようでした。一番理想的なRBSを生成したモデルはGPT-4 Omniだったそうです。そのため、 rbs prototype rb + GPT-4 Omniの組み合わせが良さそうと発表では話されていました。RBS生成の精度は、今回検証に利用したコードであればGPT-4 Omniを利用した場合でPerfect(意図した型定義がされている)とのことでした。すごいですね! また、RBS Gooseから生成されたRBSでuntypedのままになっているところに関して、LLMがなぜuntypedのまま残したかをコメントしているものがあったそうです。これはとても興味深い結果だと感じました。 しかし不十分な点もあり、特殊ケースのRBSを扱えなかったり rbs_rails や typeprof などはトップレベルにRBSを生成するので対応が取れないといった課題点を話されていました。 LLMは現在とても注目されていると共に急速に進歩している分野です。RBS GooseはLLMが進歩すればするほど精度が良くなっていくと思われますのでこれからが楽しみですね! The depths of profiling Ruby 笹沢( @sasamuku )です。私からはスマートバンク社の @osyoyu さんの「 The depthes of profiling Ruby 」をご紹介します。 speakerdeck.com 本セッションでは、osyoyuさんが開発されたプロファイラ「 Pf2 」の解説がなされました。 Pf2の特徴として強調されていた点は下記になります。 マルチスレッドプログラムの解析 C拡張の呼び出しもトレース可能 複数の可視化モードが利用可能 個人的に興味深かったのはプロファイリングを実現する機能群です。それぞれ掘り下げて見てみましょう。 rb_profile_thread_frames() Pf2でスタックトレースを取得する際に使用されるAPIです。このAPIはosyoyuさん自身によって実装され、Ruby 3.3でリリースされました。 https://github.com/ruby/ruby/pull/7784 https://product.st.inc/entry/2023/12/25/160504 従来から実装されている rb_profile_frames() はカレントスレッド(GVLをロックしているスレッド)の情報しか取得できないという課題がありました。このAPIの追加により、マルチスレッドプログラムで任意のスレッドの情報を取得できるようになりました。これによりWebアプリケーションのようなIOの多いソフトウェアにおいても正しく全体をプロファイルできます。 TracePoint API Pf2でGCイベントの受信に使用されるAPIです。実行されるイベント種別を指定し、イベント発生時に任意のコードを実行できます。イベント種別には、クラス定義、メソッド呼び出し、C拡張のメソッド呼び出しなどがあります。 https://docs.ruby-lang.org/ja/latest/class/TracePoint.html TracePoint APIは任意のイベントを取得できるため、コードリーディングやバグを調査する際に使えそうです。簡単な例を下記に示します。発表でも登場した Hash#[] はC拡張として実装されていますが c_call イベントを指定することで呼び出しを検知できます。 trace = TracePoint .new( :c_call ) do |tp| p tp end trace.enable hoge = { a : 1 , b : 2 } hoge[ :a ] #=> #<TracePoint:c_call `[]' sample.rb:8> Thread Events API Pf2でGVLの状態取得に使用されるAPIです。同じ名前のAPIのドキュメントは見つけられませんでしたが、調べてみると rb_internal_thread_add_event_hook というAPIが近い機能を提供していました。 プロと読み解く Ruby 3.2 NEWS で次のように紹介されています。 スレッドが停止したり実行可能になったり実際に実行再開したりするときに内部的なイベントを発行して、それをトラップすることでスレッドの挙動を計測できるようになりました。 RubyKaigi 2023の「 Understanding the ruby global vm lock by observing it 」ではこのAPIを使ったツールの発表がDatadog社のivo anjoさんからありました。 本セッションに参加したことでPf2の特徴を知るだけでなくプロファイラで使用されているAPIに関心を持つことができました。まだ概要レベルしか理解できていないので、今後は実装を読んだり(Cワカラナイ)、プロファイラの動向にも目を光らせたりしていこうと思います! お昼ご飯にosyoyuさん激推しの「ポーたまおにぎり」を食べました! osyoyuさんが激推ししていたポーたまおにぎりも美味しかったです! 海ぶどうを挟むとさらに最高でした。 It’s about time to pack Ruby and Ruby scripts in one binary 山岡( @ymktmk )です。私からはSTORES社の @ahogappa さんの「It’s about time to pack Ruby and Ruby scripts in one binary」をご紹介します! speakerdeck.com 本セッションでは、ahogappaさんが開発されたRubyスクリプトとGemからシングルバイナリにコンパイルできる「 Kompo 」というツールが紹介されました。 開発者のahogappaさんは、Rubyでゲームエンジンを開発しているため、ゲームを配布する際にはバイナリの生成が必要でした。インタプリタ言語であるRubyは、Rubyがインストールされていない環境においても、バイナリを配布することで各環境に左右されることなくスクリプトを実行できるようになります。 これまでにも、Rubyをバイナリにコンパイルできるツールは存在していましたが、様々な問題がありました。 Ruby本体にパッチを当てている Ruby 3.0以上をサポートしていない Windows OS上でしか動作しない 同等の既存ツールには、これらの問題があり、それらを解決するために Kompo というツールが作成されました。ちなみにcompose、component、compositeなどの単語から由来しているそうです。 kompoの特徴としては、以下の通りです。 モンキーパッチのみで、Ruby本体にパッチを当てない 一時ファイルへの書き込みがない Gemfileをサポートしている 個人的に、 Kernel#require 、 require_relative 、 load などの内部実装にモンキーパッチを当て、Rubyスクリプトの解釈やC拡張の読み込みこんで、シングルバイナリを生成する手法は非常に興味深いと感じました。将来的にはクロスコンパイルやバイナリファイルの圧縮などにも対応していく予定だそうです。Rubyistにとって、ランタイムの依存なしにスクリプトを実行できることは、CLIなどのワンライナーなプログラムを運用する際に非常に有用です。さまざまな課題があるかもしれませんが、今後の発展に期待です! スポンサーブースの紹介 RubyKaigi 2024の会場となった「那覇文化芸術劇場 なはーと」のエントランスを進むと「ハイサイ」と迎えてもらえました! 前述の通りRubyKaigiの会期直前にWEARのリニューアルを控えていたため、スポンサーブースはDevRelブロックが中心となって準備しました。今回は陸路での移動ではなく空路での移動だったため、スポンサーブースの設営に必要な物品のヌケモレがないよう、いつも以上に慎重に準備を進めました。 また、事前に荷物の遅延が発表されていたため、印刷所から会場直送のパネルや事前に送った備品が届かなかった場合に備えて、会場近隣の印刷所と100円ショップを調べておきました。幸いなことに遅延はありませんでしたが、結果的に100円ショップを調べておいたことは役立ちました。 ZOZOのスポンサーブース全景 ZOZOのスポンサーブースでは社内企画「みんなの失敗」のRubyKaigi出張版として、日替わりでRubyKaigi参加者の “失敗” を集め、それを集合知の教訓として、ノベルティのトイレットペーパーと一緒に “水に流して” もらいました。また、リニューアルしたばかりのWEARを紹介し、iPhone実機で「ファッションジャンル診断」や「WEARお試しメイク」を触ってもらいました。 RubyKaigi出張版「みんなの失敗」 RubyKaigi出張版「みんなの失敗」は「付箋に自身の失敗を手書きしてもらう」というやや参加コストの高い企画でした。しかし、実際にはとても多くの「失敗」が集まり「あるある」や「わかる」といった声をたくさん聞きました。 Day 1では、水に流したい “開発の失敗” を挙げてもらいました。 Day 1で集めていた水に流したい “開発の失敗” の結果 RubyKaigiは国際会議という位置づけのため、日本語話者だけでなく英語話者もたくさん参加しています。掲示していたパネル類はあらかじめ日本語と英語を併記していましたが、その場で書いてもらう付箋はそういうわけにはいきません。そこで都度ChatGPTで翻訳し、ピンクの付箋にtranslateとして英語訳した “失敗” を記載しました。途中から追いつかなくなってしまいましたが、翻訳の効果もあってか英語話者からの “失敗” を集められたのは目論見通りでした。 Day 2では、水に流したい “技術的負債” を挙げてもらいました。 Day 2で集めていた水に流したい “技術的負債” の結果 このDay 2はスポンサーブースを巡るスタンプラリーが始まったこともあってか、非常に多くの “技術的負債” が集まりました。そのため、急きょ近隣の100円ショップに走り、部材を購入してパネルを拡張しました。 “技術的負債” として「スロークエリ」を挙げた方が一定数いたのは印象的でした。その特性上、“技術的負債” はなかなか “水に流しにくい” かもしれませんが、いつかは解消したいですね、といった会話もありました。 Day 3では、国際会議らしく水に流したい “i18n対応” を挙げてもらいました。 Day 3で集めていた水に流したい “i18n対応” の結果 Day 1、Day 2に比べると挙げにくいテーマかと思いましたが、それでも多くの “i18n対応” が集まりました。 失敗を “水に流せる” トイレットペーパー “ZOZO” の文字列とZOZOのコーポレートロゴが隠されている「失敗を “水に流せる” トイレットペーパー」 “失敗” を投稿していただいた方には、その失敗を “水に流せるように” トイレットペーパーをお配りしました。これは社内企画版の「みんなの失敗」と同様ですが、社内企画版は包み紙がありません。ノベルティとしてお渡ししているトイレットペーパーはZOZOスタッフも持っていない特別版です。その場でご説明した方もいますが、実は “ZOZO” の文字列とZOZOのコーポレートロゴが隠されています。 RubyKaigi 2022の “最後まで身につけているファッションアイテム” としての温泉タオル、RubyKaigi 2023の “一合一会” 米に続き、遊び心を忘れないデザイナーチームによる特別なアイテムでした。 リニューアルしたWEARの紹介 デスク右側に設置したモニターでリニューアルしたWEARを紹介 RubyKaigi出張版「みんなの失敗」のパネルがテーブルの2/3ほどを専有してしまいましたが、残ったスペースにモニターを設置し、リニューアルしたWEARの紹介をループ再生していました。 LEDテープでモニターを “デコってある” 姿は技術カンファレンスに似つかわしくないかもしれません。しかし、これは “映え” 目的のものではなく、想定よりも割り当てられたスポンサーブースが暗かったため、少しでも明るくするため準備日のDay 0に急きょ購入して設置したものです。 AIを活用してファッションの「好みのジャンル傾向」がわかるファッションジャンル診断 ユーザーが投稿したフルメイクデータをARで試せる「WEARお試しメイク」 また、自由に触れるiPhoneを設置し、AIを活用してファッションの「好みのジャンル傾向」がわかるファッションジャンル診断や、ユーザーが投稿したフルメイクデータをARで試せる「WEARお試しメイク」などを体験してもらいました。 ZOZOTOWNのZOZOCOSMEに導入されているARメイク機能は、リップやアイブロウなど、部位ごとのパーツを試せますが「WEARお試しメイク」はフルメイクを試せるのが大きな特長です。メイク関連アイテムとして、手鏡も配っていました。 箱猫マックスとチーコのステッカー 箱猫マックスとチーコのステッカー Take Freeのアイテムとして箱猫マックス(Box-Cat Max)とチーコのステッカーを配布していました。箱猫マックスはZOZOTOWNのキャラクターで、チーコはWEARのキャラクターです。 チーコのステッカーは「かわいい」と言ってくれる方が多かったのはとても嬉しいことです。そしてLINEスタンプとして販売している、エンジニアの生態をリアルに再現した「 箱猫マックス Vol.6 」の中から選出したステッカーはLGTMやDONEが人気でした! RubyKaigi公式イベントのスタンプラリー RubyKaigi公式イベントのスタンプラリー RubyKaigiでは例年、公式イベントとしてスポンサーブースを巡るスタンプラリーが開催されています。このスタンプラリーは参加者とスポンサーブースのZOZOスタッフが会話する良いきっかけにもなっています。参加した皆さんは最後まで集まりましたか? After RubyKaigi 2024〜メドピア、ZOZO、Findy〜を開催します! 5月28日にAfter RubyKaigi 2024〜メドピア、ZOZO、Findy〜を開催します! RubyKaigi 2024の興奮さめやらぬ5月28日に、メドピア株式会社、株式会社ZOZO、ファインディ株式会社の3社で非公式アフターイベントのAfter RubyKaigi 2024を開催します! RubyKaigi 2024に参加した方も、参加できなかった方も、ぜひお気軽にご参加ください! findy.connpass.com おわりに 技術カンファレンスでは恒例のスポンサーパネルへのサイン! ZOZOでは、各種エンジニアを採用中です。ご興味のある方は以下のリンクからご応募ください。 corp.zozo.com 恒例となっている次回の開催日と開催地の発表 改めてRubyKaigi運営の皆さん、参加者の皆さん、おつかれさまでした。また来年は松山でお会いしましょう! 現場からは以上です!
アバター
はじめに こんにちは、ZOZOTOWN開発本部アプリバックエンドブロックの髙井です。 私達のチームでは、レガシーとなっているZOZOTOWNアプリ用API(以下、レガシーAPIと呼ぶ)のリプレイスに2023年から着手しています。リプレイス対象となるレガシーAPIは規模が大きいので、フェーズで区切り、段階的にリプレイスを進めています。区切られた各フェーズは、フェーズ1、フェーズ2といった形で呼び分けており、フェーズごとにリプレイス対象とするエンドポイントを設定しています。一方で、事業案件や他マイクロサービスのリプレイスが並行して行われるため、フェーズごとにリプレイス計画を柔軟に調整してきました。 本記事ではレガシーAPIのリプレイスについて、フェーズ3までを担当者が背景と課題を踏まえつつ紹介していきます。 目次 はじめに 目次 背景 フェーズ1 課題 1. リプレイス先APIの開発が初めて 2. リプレイスの土台が整っていない 課題1への取り組み 1. 毎日集まって開発状況を確認 2. 事前に既存実装のシーケンス図を作成 3. 積極的に有識者とのモブプロを行う 課題2への取り組み 1. 開発チームメンバー全員での既存コードの読み合わせ 2. クライアントに影響がない実装方法を採用 3. 本番環境へのリリース手法としてカナリアリリースを採用 結果 課題が解決できたか? 新たな課題 フェーズ2 課題 フェーズ2で取り組んだこと 結果 課題が解決できたか? 新たな課題 フェーズ3 課題 フェーズ3で取り組んだこと 結果 課題が解決できたか? 新たな課題 まとめ 背景 こんにちは。湯川と申します。まず私からは、本リプレイスプロジェクトの始まった背景と導入部分を説明します。 レガシーAPIは、十数年前にZOZOTOWNアプリがローンチされた際から存在しており、当初からVBScriptで実装されていました。しかし、年月が経ち、コードは複雑化し、メンテナンスコストが増大していました。さらに、VBScriptは歴史のある言語であり、新しいエンジニアの採用が難しい状況でした。 そこで、マイクロサービス化を進め、Webサイトをモダン化するリプレイスプロジェクトが社内で発足し、レガシーAPIもこのプロジェクトの一環としてリプレイスすることとなりました。リプレイスの目的は、VBScriptからの脱却を図り、現代的な言語とフレームワークを使用することです。 既にZOZOTOWNアプリの一部の画面で本番稼働しているBFF API(JavaのSpringで実装)をリプレイス先APIとして、レガシーAPIをリプレイスしていくこととなりました。 BFF APIについてはこちらの記事で紹介しているので、合わせてご覧ください。 techblog.zozo.com まず、私たちが取り組んだのは、計画を立てるために各APIが直接DBへ問い合わせしている箇所やマイクロサービスを参照している箇所など、外部との接続がどこでどのくらいあるかを調査することでした。 この調査により、以下のポイントが明らかになりました。 ロジックの複雑度の算出:どのくらいの接続があるかを把握することで、ロジックの複雑度を算出し、APIの移行難易度を把握することに役立ちました。 接続経路の特定:各APIがどこと接続しているかを把握することで、既存で存在しない接続経路があればシステムアーキテクチャに関わる新規構築が必要だと判断できました。 このポイントを押さえながら段階的なアプローチを取り、チーム内で完結する小規模なリプレイスから進めていく計画を立てました。 フェーズ1 こんにちは、アプリバックエンドブロックの伊藤です。ZOZOには2022年1月から中途入社し、2023年2月から5月にかけて実施されたレガシーAPIのリプレイスフェーズ1のプロジェクトに、開発者の1人としてアサインされていました。 課題 フェーズ1のプロジェクトには以下のような課題がありました。 1. リプレイス先APIの開発が初めて リーダーはリプレイス先のAPI開発経験がありましたが、開発者としてアサインされたメンバーはJavaの開発経験が浅く、リプレイス先APIの開発が初めてでした。また、社歴も比較的浅いため、現状のキャッチアップが必要な状態からのスタートでした。 2. リプレイスの土台が整っていない 今後長期にわたるリプレイスの初フェーズということで、そもそも案件全体でリプレイスについて十分な知見がない状態でした。 そのため、以下のような観点から対象のエンドポイントを選定し、リプレイスを開始しました。 今後のフェーズの土台を築く上で、まずはリプレイスの実績を作る 未経験者でも置き換えしやすい、スコープが小さく複雑度の低いシンプルなものから取り組む 課題1への取り組み 課題1つ目の「リプレイス先APIの開発が初めて」に対しては以下のようなことに取り組み、課題の解決に役立ちました。 1. 毎日集まって開発状況を確認 Javaの開発経験の浅いメンバーにとって、困っていることや詳細なタスク状況を共有しやすい環境を作ることは、開発をスムーズに進める上でとても重要でした。 2. 事前に既存実装のシーケンス図を作成 処理の流れやエラーパターンが事前に視覚化されていたことで、実装面で役に立ったことはもちろん、他チームとのコミュニケーションもスムーズに進みました。 3. 積極的に有識者とのモブプロを行う チームメンバーがどのようにコーディングやデバッグをしているかを学び、経験不足を補うことができました。 課題2への取り組み 課題2つ目の「リプレイスの土台が整っていない」に対しての取り組みは以下の通りです。 1. 開発チームメンバー全員での既存コードの読み合わせ 実装前に作成したシーケンス図へは起こしきれないような細かいロジックがありましたが、メンバー間で理解度を合わせることで、コードレビューの段階でバグを見つけることができました。 2. クライアントに影響がない実装方法を採用 レガシーAPIのインタフェースとURLは、あえて既存の形式を維持する方針でリプレイスを行いました。理由はクライアントであるアプリ側への影響なく、バックエンドとインフラのみの修正でリプレイスを進められるようにしたかったからです。そうすることで関わるチームは少なくなり、結果的にスピーディーな開発につながりました。 具体的に行った対応は以下の通りです。 リプレイス先APIでは、レガシーAPIのインタフェース設計をそのまま引き継いだ アプリからのリクエストを受け付けるAPI Gatewayに対象エンドポイントのルーティング設定を追加した 3. 本番環境へのリリース手法としてカナリアリリースを採用 既存実装がかなり複雑な部分でバグを生んでしまい、リリース後に意図しないエラーが発生しましたが、段階的にN%リリースをしたため影響を最小限に抑えることができました。 カナリアリリースの詳細については以下のテックブログで紹介しておりますので、気になった方はご参照ください。 techblog.zozo.com 結果 課題が解決できたか? 対象エンドポイントのスコープは小さかったものの、上記のような取り組みのおかげで経験不足を補いながら無事にリリースができました。これらの取り組みは後に紹介されるフェーズ2以降でも活かされています。 また事前にわかっていた課題以外にも、以下のような実装以外のタスクが発生しましたが、プロジェクト内で都度対応しながら解決していきました。 今後続くリプレイス案件の土台を整えるために、最適なリプレイス方法を議論する必要があった リプレイス先APIのコーディング規約やレビュー規約が整っていない状態であった メンバー間の役割分担が上手く行かず、1人が案件リーダーと開発リーダーの2つの役割を担ったため作業過多になった 新しくリプレイスプロジェクトを始める際には、このような点を事前に考慮できると良さそうです。他にも上記の例に限らず、想定外のタスクが発生するかもしれないため、スケジュールにバッファを設けておくと良いかもしれません。 新たな課題 フェーズ1の中でわかったことは、レガシーAPIの実装は想像よりも難解であったということです。コードレビュー時にはチームメンバー全員でコードの読み合わせをしたものの、実装時には担当者が個人でレガシーAPIの処理を調査したために、もっと深いところで理解度に個人差が生まれていました。その結果リリース後のバグ発生につながってしまいましたが、バグを正しく修正するために再度チームメンバー全員でコードを読み合わせたことが役に立ちました。 フェーズ2 ZOZOTOWNアプリバックエンドのエレーナです。レガシーAPIリプレイスフェーズ2プロジェクトの開発者の1人として参加したので、フェーズ2について説明いたします。 課題 リプレイス先APIでは、データベース(以降DB)へ直接接続しない設計となっています。原則的に該当するマイクロサービスからしかDBにアクセスしません。一方でレガシーAPIは直接DBにリクエストしているところが多い状態で、置き換え方法が決まっていないことがリプレイスのブロッカーとなっていました。そのため、そのブロッカーを解除することがフェーズ2のメインの目的となりました。 その上で、同時並行で開発していた新規機能においても、リプレイス先APIからの直接DBリクエストが必要だと判明しました。新規機能のリリーススケジュールに影響を与えないように、DBアクセスの課題の優先度がさらに高まったため、早急に解決する必要がありました。 フェーズ2で取り組んだこと モノリスなレガシーAPIをマイクロサービスで設計されたシステムへリプレイスする場合、理想的な置き換え先は以下になります。 BFF層で管理すべきコードをリプレイス先APIへ置き換え マイクロサービスで管理すべきコードをマイクロサービスへ置き換え しかし、一部のマイクロサービスがまだ設置できていない現状で、リプレイスを必ず新規サービスの設置から始めると、多大な工数がかかります。リソースが限られている状態でさらに柔軟な方法が必要なので、マイクロサービスができていない場合は該当するロジックを過渡期APIへ置き換えるようにしました。 過渡期APIとは、レガシーAPIのシステムを再利用した名前の通り仮のAPIとなっています。レガシーのインフラストラクチャー上にできているので、DBへ接続経路が存在しています。 過渡期APIに含む処理は重要なポイントが2つあります。 その処理はリプレイス先APIでできないこと(例:DB直アクセス) 将来的にはマイクロサービスへ置き換える前提 サービス自体がまだ存在しなくても今のタイミングでBFF層とマイクロサービス層のロジックが切り離し可能で、過渡期APIへ置き換える余分なステップが無駄になりません。 そして最後に、事業案件の事情でフェーズ2をなるべく早めにリリースできるように、対象エンドポイントは1本で複雑度が低いものを選定しました。 結果 課題が解決できたか? 小さくてシンプルなエンドポイントを選んだおかげで不具合なしに短い期間で実装が完了して無事にリリースできました。リリーススコープが小さかったですが大きなリプレイスブロッカーを解除できました。 新たな課題 実は、リプレイスの大きなブロッカーがもう1つ残っています。レガシーAPIは端末やユーザー情報をセッションに保存および取得しているところがあるので、該当処理の置き換え方法を検討する必要があります。最初はその課題を含めてフェーズ2で全リプレイスブロッカーを解除する予定でしたが、フェーズ2のリリースを早める方が優先だったのでセッション課題の解決をフェーズ3へ移動しました。 フェーズ3 アプリバックエンドブロックのカイルです。2023年10月からレガシーAPIのリプレイスプロジェクトに参加しています。 課題 マイクロサービスを経由していないDBアクセスの経路ができたので、次は セッションに入っている情報へのアクセス経路を作る という課題の解決に取り組みました。レガシーAPIでは、ユーザーがログイン中でも未ログインでもシステム全体でユーザーの情報にアクセスできるようにセッションに保存しています。 セッションと言っても、過去のマイグレーションプロジェクトでサーバー上に保存していたデータをRedisに移行することがすでに完了していました。ただ、アクセスする方法はレガシーAPIのフレームワークで使っているプラグインのみで、違う言語で実装されているリプレイス先APIからアクセスする経路は存在しませんでした。 セッションデータが保存されているRedisに直接アクセスしてもよかったのですが、特定のデータ形式や保存先に依存したくないと考えていました。また、セッション情報は複数のシステムからアクセスする必要があるのでアクセス方法を統一する要望もありました。 レガシーAPIではセッションから取得した情報を使っているエンドポイントが多く、アクセス方法の課題が今後のリプレイスのブロッカーとなっていました。 フェーズ3で取り組んだこと セッション情報のアクセスを統一するため、マイグレーションプロジェクトを担当しているチームでまずセッション基盤を開発することにしました。フェーズ3ではリプレイス先APIからセッション基盤への経路を追加してセッション情報を取得できるようにしました。 また、フェーズ3へ入る前にレガシーAPIが動いていたオンプレサーバーの縮退スケジュールが決まりました。縮退スケジュールに間に合うようにサーバー負荷が高いエンドポイントからリプレイスを行うことを決めました。 そこで、まずはレガシーAPIの全エンドポイントで、レガシーAPI全体のリクエスト数に対する各エンドポイントのリクエスト数の割合を算出しました。そして、この割合が高いものをサーバー負荷が高い(リプレイスの優先度が高い)エンドポイントとして優先順位を付けました。 そのため、セッションの取得が必要となり、かつサーバー負荷が高いエンドポイントとして、商品詳細画面の下部に表示されるレコメンド商品を返すAPIをフェーズ3の対象としました。 フェーズ3の進め方についても新しく実施してみたことがあります。フェーズ1の課題だったレガシーAPIの理解を改善するために、案件のキックオフ後に開発メンバー全員でコードを読みながら既存のレガシーAPIの仕様を理解したうえで開発に入りました。 結果 課題が解決できたか? 対象エンドポイントで必要となるセッション情報を新たにセッション基盤経由で取得できるようになり、今までなかった経路をひとつ実現できました。そのおかげで今後リプレイスするエンドポイントでもセッション情報の参照が必要な時にこの経路を使って実装できるようになりました。 そして事前に既存処理をすり合わせて設計を決めることによって、よりスムーズに開発を進めることができました。 新たな課題 今回できたのはセッション情報の参照のみですが、他のエンドポイントでセッション生成や更新、セッションクリアなどの操作が必要になってきます。セッション基盤ではそのようなケースをどう扱うかはまだ検討中の段階で、これから確定して実装する必要があります。 そして開発プロセスにおいても、全体の流れが安定してきた一方で新たな課題も見えてきました。特にリリースサイクルをもっと柔軟にしないといけないと感じました。今まではひとつのフェーズの対象となっているエンドポイントを全て開発して、テストとリリースをまとめて行っていましたが、フェーズが大きくなるとコードの量が膨大になって開発しづらくなります。エンドポイントごとのリリースサイクルを導入できたらコード管理やリリーススケジュールが調整しやすくなる想定です。 フェーズ3でも実際に開発の途中でスコープが変更されて、同じブランチで管理していた複数エンドポイントのコードを分ける作業が必要となりました。今後のフェーズではリリースサイクルをもっと細かくすることで開発を楽にしたいと考えています。 まとめ 本記事では、これまでのレガシーAPIのリプレイスで取り組んだ課題とその解決方法をフェーズごとに紹介しました。これまでのリプレイスによって、チーム内のJava開発経験を蓄え、各マイクロサービスへの接続経路を作る事ができました。まだリプレイスは道半ばです。今後はこれまでのリプレイスで蓄えた開発経験や整えた接続経路を基に、残りのエンドポイントのリプレイスも進めていきます。本リプレイスでのリプレイス計画の立て方、進め方が、今後APIのリプレイスを検討している方の参考になれば幸いです。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com
アバター
はじめに こんにちは、DevRelブロックの ikkou です。5月13日に「Google Cloud Next ‘24 Recap in ZOZO」と題した、Google Cloud Next ‘24の振り返りイベントをオンラインで開催しました。 zozotech-inc.connpass.com 本振り返りイベントの前提となる、Google Cloud Next ‘24に参加したメンバーによるレポート記事もあわせてご覧ください。 techblog.zozo.com 目次 はじめに 目次 当日の登壇内容 Google Cloud Next ‘24 Recap AIにより変わる開発・運用について AIに対応したBigQueryと今後のデータ分析について Datastreamを使用したリアルタイムデータストリーミングの紹介 最後に 当日の登壇内容 4月のGoogle Cloud Next ‘24に参加したMA部のエンジニア3名に加え、特別ゲストとしてグーグル・クラウド・ジャパン合同会社の小野様にご登壇いただきました。 タイトル 登壇者 Google Cloud Next ‘24 Recap グーグル・クラウド・ジャパン合同会社 小野 友也 様 AIにより変わる開発・運用について 平宮 瑛宜 AIに対応したBigQueryと今後のデータ分析について 杉田 駿介 Datastreamを使用したリアルタイムデータストリーミングの紹介 佐久間 貴人 今回のイベントではオフライン会場は設けず、YouTubeのライブ配信のみで実施しました。当日の発表はYouTubeのアーカイブ動画をご覧ください。 なお、グーグル・クラウド・ジャパン合同会社の小野様による発表は都合によりアーカイブに含まれておりません。あらかじめご了承ください。 www.youtube.com Google Cloud Next ‘24 Recap グーグル・クラウド・ジャパン合同会社の小野様には、Google Cloud Next ‘24で発表された各機能をご紹介いただきました。20分という短い時間で、新たに発表された機能をギュッと濃縮した、振り返りイベントに相応しい発表でした。ありがとうございました! AIにより変わる開発・運用について speakerdeck.com 平宮は、前提事項としてZOZOでMA部が何をやっているのか紹介した後、Gemini in ◯◯シリーズとして、Google CloudにおいてGeminiをどのように活用できるかを紹介しました。 AIに対応したBigQueryと今後のデータ分析について speakerdeck.com 杉田はZOZOの業務とも関わりの深いBigQueryに関するAI系の新機能について、具体的な例を挙げながら紹介しました。できることが多くなる分、より緻密な権限管理が必要となることについても触れました。 Datastreamを使用したリアルタイムデータストリーミングの紹介 speakerdeck.com 佐久間はDatastreamのユースケースとZOZOでの利用例にあわせて、今後試したいことを紹介しました。 最後に イベント当日にリアルタイムでご視聴いただいた皆様ご視聴ありがとうございました。見逃した方はぜひアーカイブ動画をご覧ください。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com
アバター
はじめに こんにちは。計測システム部、研究開発ブロックの皆川です。普段はコンピュータービジョンに関わる研究開発を担当しています。 2024年の3月に3次元コンピュータービジョンの国際学会である 3DV 2024 がスイスのダボスで開催され、幸運にも参加できたので、発表の内容や参加した感想をご紹介いたします。 目次 はじめに 目次 3DV 2024とは なぜ参加したのか 開催地のダボスと、会場のダボスコングレスセンターについて 学会のスケジュール 印象に残った発表 全体的な感想 3D Computer Vision for Dynamic Scene Understanding by Daniel Cremers ドライバーアシスト ドローンを使った研究 バンドル調整 初期のSLAM 直接的なSLAM ニューラルネットワークとSLAM さいごに おまけ 3DV 2024とは 先述の通り、3DVは3次元のコンピュータービジョンに関する国際学会です。3DVは、他のメジャーなコンピュータービジョンの学会であるICCVやCVPR、SIGGRAPH等の学会と比べて、かなり小規模な学会と言えます。実際、近年のそういった巨大学会の参加者は5000人程度のものから多いものだと1万人以上に上ります。一方で3DV 2024の参加者数は、著者が実際に関係者から聞いたところによると350人と、巨大学会と比べて10分の1以下のスケールでした。 なぜ参加したのか 近年、学問分野として大きな盛り上がりを見せているコンピュータービジョンやAIですが、その研究環境に関する特徴として以下のようなことが言えると思います。 arxiv というpeer reviewを必要としない高速かつオープンアクセスな論文プラットフォームがある。 実際の論文だけでなく、論文のアイデアを視覚的に説明する動画だったり、実際に自分で試せるコードを内包したプロジェクトページがある( その一例 )。 特定の技術領域を整理して、理解を助けるようなサーベイ論文( その一例 )やGitHubリポジトリ( その一例 )がある。 X(旧Twitter)で、スペシャリスト達が論文の間を埋めるようなアウトプットをしてくれている。 これはつまり研究者にとって自走しやすい環境が整っているということです。実際、今回発表された研究は、ほぼ例外なくarxivに既に掲載されており、プロジェクトページで動きを確認できるものが多かったです。さらに事前に該当するサーベイを読み込んでいたら、学会会場で得られる情報はあまり多くないと思います。それでは一体、飛行機で14時間もかかる物価の高い国に苦労して行くことの対価はどこにあるのでしょうか。それは以下にあると思います。 社外の研究者の持っている経験則や常識、ナラティブを吸収できる 参加者から刺激をもらえる 結果から言うと、どちらも大きな収穫がありました。具体的に言うと、カメラ位置の推定技術など、雑多な技術が混在していてなんとなく俯瞰できてないような分野をよりクリアな目で見ることができるようになりました。またNeural Implicit RepresentationやDiffusion Modelといった、流行の技術と人体計測の関係もよりクリアになりました。 また他社の研究者達との雑談からたくさん刺激を受けました。ドイツ語圏(スイス、ドイツオーストリア)の研究機関の多くは普段から英語で研究活動をやっていること。研究者同士の機関を超えたコラボレーションが多いこと。そしてGAFAMの研究開発のスケールの大きさなど、驚かされることが多かったです。 開催地のダボスと、会場のダボスコングレスセンターについて 開催地となったダボスは、スイスの玄関口チューリッヒ空港から電車で3時間くらいにある、人口1万人ほどの小さな観光地です。 Davos Dorf駅を中心とした居住エリアが、ダボスの90%以上を占める山岳エリアに囲まれており、居住エリアの端から端まで約1時間ほどで歩ける小さな都市です。 筆者が行ったのは3月の下旬でしたが、気温はおよそ摂氏0度で、雪がまだかなり残っており、スキー客の姿もちらほらと見られました。 現地の人の話では毎年クリスマスシーズンに行われるアイスホッケーのトーナメントの時期と、1月に開かれる世界経済フォーラム(通称ダボス会議)の時期は、町に溢れかえるほどの人が来るそうです。 学会会場の ダボスコングレスセンター は、先述の世界経済フォーラムの会場として有名です。画像は正面の入口なのですが、地図を頼りに行くと裏口に辿り着いてしまい、日本だとよくみられるような立て看板などなかったため、参加者と思われる人達が迷っていました。2日目以降も裏口で迷う参加者がおり、他の参加者がこっちだよ、と道案内をしてあげるような場面も見かけました。 下記画像は宿泊先のホテルから見た景色です。正面に見える茶色の建物がダボスコングレスセンターの裏口に当たる部分です(画像に写っているのは建物全体の5分の1程度)。実際はこの裏口の脇の坂を下って、5分ほど歩き、この大きな建物の正面に回る必要がありました。 坂を下るとコングレスセンターの正面玄関に着きます。 学会のスケジュール 学会のスケジュールは以下のような構成でした。 初日 チュートリアル 2日目から4日目 オーラル発表 ポスター発表 キーノート チュートリアルは カメラ幾何学 と、 3D Gaussian Splatting に関するものでした。執筆時点(2024/05/11)では、チュートリアルと キーノート は一般公開されています。 ポスター発表は全発表者に義務付けられていました(チュートリアルとキーノートを除く)。ポスター発表とオーラル発表は交互にあるので、オーラル発表で気になったことは後のポスター発表で直接発表者に質問できる仕組みでした。オーラル発表はすべてメイン会場(下記の画像参照)で行われるので、学会に特有の「どの発表を見るかの下調べにとても時間がかかる」という現象から完全に自由でした。 また、参加者同士のネットワークがすでにある程度出来上がっており、学会全体を通してかなりアットホームな雰囲気があったように感じます。逆にいうと、ポスターセッションや自由時間(下記の画像参照)のときに、自分のような新規参加者は少し居心地の悪さを感じるかも、と思いました。 印象に残った発表 全体的な感想 全体としては、カメラの位置推定や3D再構成に関する基礎研究が多かったという印象でした(合わせて体感で4割くらい)。対照的にVedaldi氏の キーノート やNeRFの主著者であるMildenhall氏の キーノート では、3Dのパラメトリックモデルや生成モデルの応用など、新規性の高いトピックが触れられていました。 また同じトピックでも、理論的な発表と実践的な発表のバランスが取れているように感じました。例えばカメラの位置推定で言うと、初日に 理論的なチュートリアル 、2日目以降には実際にドローンや自動運転の会社の創業者でもあるプレゼンターのキーノート( ドローン 、 自動運転 )がありました。 発表の内容としては、著者と同一の課題(画像を元にした身体計測)に取り組んでいる発表がなかったのは残念でした(過去の3DVにはありました)。ただし既存のパイプラインに組み込めそうな技術はいくつか見つかったので、収穫はあったと言えます。 3D Computer Vision for Dynamic Scene Understanding by Daniel Cremers このキーノートは、Cremers氏の研究グループの約20年間の自動運転に関する研究を総括するような発表でした。 www.youtube.com ドライバーアシスト 約20年前のドライバーアシストの研究成果について(動画の 7:53 頃)。Cremers氏の研究グループは、画像から深度と物体の動きを色付きで可視化するような仕組みについて研究をしていたそうです。現在は自動運転の研究が盛んですが、当時はドライバーをアシストするような方向の研究分野も盛んだったとのこと(現在もこの分野はあるそうです)。 ドローンを使った研究 ドローンを使った研究も長年続けてきた分野とのこと( Engel et al., IROS 2012 。動画の 10:28 頃)。PTAMという方法でSLAMを行い、一応の自律飛行はできるようになったが、求めていた精度までは達しなかったそうです。例えば屋外に出てから屋内に戻るといったような飛行は実現できなかったとのことでした。ただし2017年に提唱したLSD SLAMという方式ではそれが実現できたとのこと( Von Stumberg et al., ECMR 2017 )。 バンドル調整 オーストリアの数学者Kruppaが1913年にした証明が今日バンドル調整(Bundle Adjustment)と呼ばれる技術の先駆けになったとのことでした。バンドル調整とは複数のカメラ画像から対象の物体の再構成とカメラ位置の推定精度を上げるような技法のことで、とても歴史が長いことから解かれた問題と理解している人が多いそうですが、実際は違うとのことです。実際、最近の研究( Demmel et al., CVPR 2021 , Weber et al., CVPR 2023 )では演算スピードやメモリ効率の大幅な向上が達成されているとのこと。 初期のSLAM 上記のようにリアルタイムでない、3D再構成の精度を最重要視したバンドル調整についての研究のほか、SLAM(Simultaneous location and mapping)の研究も多いとのこと。また、SLAMが初めてリアルタイムで実現できたのは2002年頃(動画の 20:50 頃)だそうです。ただし当時のSLAMの方式はKruppaの流れを汲むもので、画像から特徴点を抽出、マッチングする方法でした。 「3DV 2024 Keynote - Daniel Cremers - 20.03.2024」の20:37よりスライド部分を強調して引用 直接的なSLAM その方法的な限界を突破するため、直接的な方法であるLSD SLAM( Engel et al., ECCV 2014 )を提唱したのが2014年だそうです。特徴点の抽出に頼らず、画像1から画像2へ再投影した際の色の差が最小になるようなカメラの移動と3Dモデルを見つける、という問題設定です。当時世界初の大規模SLAMシステムであるにもかかわらず、単眼カメラと市販のラップトップのCPUでリアルタイム処理ができるとのこと(動画の 22:48 頃)。後継のDirect Sparse Odometry( Engel et al., PAMI 2018 )やDMVIO( Stumberg et al., ICRA 2022 )で性能は更に上がったとの事です。 ニューラルネットワークとSLAM ニューラルネットワークがSLAMの分野で応用され出したのは意外に遅く、2017年頃だそう( Zhou et al., CVPR 2017 等)。ただし、当時それらはまだSOTA(state-of-the-art、特定タスクで最高スコアの方法のこと)ではなかったとのことです。D3VO( Yang et al., CVPR 2020 )では、連続する2画像を用いカメラ位置や深度などをニューラルネットワークに学習させることで複眼のVIOと同等の精度を達成できたとのこと。つまりこの方法は深度センサーや感性センサーを代替する方法として有効であることが示唆されるそうです。 以上はいずれも静的なシーンの理解に分類されるタスクとのことです。例えば、連続する二画像間で動いているものは、普通フィルタリングやマスキングで推論や演算に影響のないような処理がされるとのこと。その他にもダイナミックなシーンの理解というテーマで最近の研究結果が紹介されていましたが、時間の都合上割愛いたします。Cremers氏は発表全体を通して、厳密さとわかりやすさ、そしてユーモアに注力されているのが感じられました。また自動運転やSLAMの技術にあまり詳しくない著者でも、発表を何度も見返すことで分野への理解がどんどん深まるように感じました。 さいごに コンピュータービジョンの小規模な国際学会である3DV 2024に参加した感想や内容の一片をお伝えしました。生身の人間が集まる学会に物理的に参加することの利点として、そこでしか得られない情報を得られたり、他の研究者達から刺激を貰えることがあると感じました。また小規模学会の良さとして、聞く発表を選ぶ必要がない良さは感じましたが、分野に明るい人は見る発表の選択肢が少ないと感じるかもしれないとも思いました。 また個人的な感想ですが、毎日朝から晩まで屋内にこもってひたすら新しい技術を勉強し、夜はホテルに帰ってひたすら寝るという生活は、受験生の夏休みのような少し懐かしい感じもしました。今回こういう特殊な経験ができたことに対して、とても感謝しています。 おまけ 会場ではBoston Dynamicsの Spot が歩き回ったり、タンスの中のものを探したりするデモが見られました。スイスの ETH Zurich ではこのSpotを使って研究ができる学部生向けの授業があるそうです。 次の指示を待っているSpotの様子 以上になります。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com 最後までご覧いただきありがとうございました!
アバター
はじめに こんにちは、 ZOZOMO店舗在庫取り置き というサービスの開発を担当している、ZOZOMO部OMOブロックの木目沢です。 2024年4月17日から19日の3日間にかけて東京ビッグサイトで 「ファッション ワールド 東京 2024 春」 が開催され、このイベントにZOZOが出展しました。 ZOZOの出展ブースでは、私が開発を担当している 店舗在庫取り置き や FAANS 、 Fulfillment by ZOZO の3サービスが展示されました。 展示にあたっては各サービスのビジネスチームのメンバーが中心となり、準備から出展までを行いました。私たち開発チームからも数名が参加し、ビジネスチームと共に出展ブースにて多くの来場者の方々とお話しさせていただきました。 この記事では、前半で 「ファッションワールド東京 2024春」 の概要とZOZOの展示ブースの様子を紹介し、後半では開発チームがビジネスチームとともに活動する理由について述べます。 目次 はじめに 目次 ファッションワールド東京とは ZOZOが出展しました 店舗在庫取り置き Fulfillment by ZOZO FAANS 開発メンバーも参加しました 開発チームがビジネスチームと共に自事(※)をする意味 おわりに ファッションワールド東京とは ファッションワールド東京 は、毎年春と秋にRX Japanが主催するアパレル向けの展示会です。最新のサステナブルファッション、アパレル、生地、素材、ファッションDXを扱う企業が出展しています。今年の春は2024年4月17日から19日の3日間で、世界25カ国、800社が出展し、22910名が来場しました。当日の様子は 公式サイト でも公開されていましたので、ご参考までに紹介しておきます。 ファッションワールド東京 2024春 ZOZOが出展しました ZOZOの出展ブースでは、 店舗在庫取り置き や FAANS 、 Fulfillment by ZOZO のZOZOMO関連のサービスが展示されました。展示ブースには各サービスをご利用いただいているブランド様、サービスに興味を持たれているブランド様にもご来場いただき、サービスについて案内いたしました。 ZOZOブースの様子 ブース内では定期的に各サービスをより良くご利用いただくためのミニセミナーを開催しました。各回とも大変好評で、多くの方々にご来場いただき、サービスのアピールをさせていただく機会となりました。 FAANS/店舗在庫取り置き セミナーの様子 以下、ブース内に設置されていた各サービスの概要を説明するパネルと、各サービスに関連するテックブログの参考記事をご案内します。 店舗在庫取り置き AWSで実践するカオスエンジニアリング 〜ZOZOMOでの取り組み〜 ZOZOMO開発チームのユニットテスト戦略とテスト駆動開発 DynamoDBによるOutboxパターンとCDCを用いたCQRSアーキテクチャの実装〜ZOZOMOでの取り組み Chatworkさんと合同でCQRS Meetup【Chatwork × ZOZO】を開催しました #cqrsmeetup Fulfillment by ZOZO AWS CDKで構築するイベント駆動型アーキテクチャの実装戦略 FBZにおけるサーバーレス監視で実施したアラート通知の最適化 マルチAZ化から学んだ無停止でインフラを変更するために考慮すべき3点 物流支援サービスを支えるAWSサーバーレスアーキテクチャ戦略 FAANS 新規サービス「FAANS」における、立ち上げからReact+TypeScriptのSPA開発を2年間運用した際に取り組んだ組織的・技術的な課題 Kubernetesネイティブなワークフローエンジンとは!FAANSでArgo Workflowsを導入した話 FAANSにおけるCloud RunからGKE Autopilotへのリプレイス事例 Storybook × MSW × Chromaticを使ったUIの影響範囲を自動検知するための取り組み 開発メンバーも参加しました 今回の展示はビジネスチームが中心となり、出展準備を進めていきました。開発チームも運営に参加し、ご来場いただいた方々にサービスの内容や必要に応じて技術的な背景なども説明いたしました。 ファッションワールド東京のような展示会に出展し、多くの企業の方々にサービスをアピールすることは良いビジネス機会となります。また、既にサービスをご利用いただいているブランド様やサービスに興味のある企業の方々に直接お話しする機会の少ない開発チームのメンバーにとっては、ご意見やご感想を直接お伺いできるチャンスでもあります。 また、今回のようにビジネスチームと開発チームと共に活動することは、プロダクトにとっても必要なことです。以下はこの点についてご紹介したいと思います。 開発チームがビジネスチームと共に自事(※)をする意味 私たち開発チームはただサービスを作って終わりではなく、常に価値のあるソフトウェアを継続的に提供し、事業を成長させていくために活動しています。そのためには、ビジネスチームと常に一緒に自事(※)をし、状況の変化に合わせて開発を進めていく必要があります。 私たちは状況の変化に合わせて開発を続けていくために「アジャイル」であることを重要視しています。この考え方をまとめた アジャイルソフトウェア開発宣言 では、以下のように紹介されています。 私たちは、ソフトウェア開発の実践 あるいは実践を手助けをする活動を通じて、 よりよい開発方法を見つけだそうとしている。 この活動を通して、私たちは以下の価値に至った。 プロセスやツールよりも個人と対話を、 包括的なドキュメントよりも動くソフトウェアを、 契約交渉よりも顧客との協調を、 計画に従うことよりも変化への対応を、 価値とする。すなわち、左記のことがらに価値があることを 認めながらも、私たちは右記のことがらにより価値をおく。 (この宣言は、この注意書きも含めた形で全文を含めることを条件に自由にコピーしてよい。) まさに今回の展示会への参加は「顧客との協調」「個人と対話」を重視した結果とも言えます。さらに、このソフトウェア開発宣言には「 アジャイルソフトウェアの12の原則 」というページがあることはご存知でしょうか。そこには、アジャイルソフトウェア開発宣言にある「価値」に対応する「12の原則」が解説されています。 そのうちの1つに以下のような原則が示されています。 ビジネス側の人と開発者は、プロジェクトを通して 日々一緒に働かなければなりません。 顧客との協調していくことや状況によって変化していくことに対応するため、常にビジネス側と共に仕事をしていく必要があるということです。私たちもこの点について日々意識して活動しており、今回の展示への参加もその一環となります。 開発メンバーも参加しました ※ ZOZOでは仕事のことを自事と呼んでいます。「スタッフ一人ひとりが他人のことも自分のこととして考える」という意味と、「仕事=仕えること」ではなく「自事=自然なこと」だという意味が込められています。 おわりに 本記事では、 「ファッションワールド東京 2024春」 への出展の紹介と、開発チームがビジネスチームと共に自事をする意味を紹介しました。 今回の展示では私自身も開発メンバーとして3日間参加し、ご来場いただいた多くの方々にサービスを案内いたしました。また、サービスをご利用いただいているブランド様ともお話させていただく機会もありました。サービスについて高い評価をいただくこともあり、立ちっぱなしの3日間は大変疲れましたが、このようなお話をいただき疲れも吹き飛びました。 今後もビジネスチームと共に価値を提供し続けていきたいと考えています。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com 最後までご覧いただきありがとうございました!
アバター
はじめに こんにちは、計測プラットフォーム開発本部SREブロックの近藤です。普段はZOZOMATやZOZOGLASS、ZOZOFITなどの計測技術に関わるシステムの開発、運用に携わっています。 計測プラットフォーム開発本部では、複数のプロダクトを運用していますが並行して新しいプロダクトも開発しています。SREチームでは増え続けるプロダクトの運用負荷に対して改善は行っていますが、さらなるプロダクトの拡張に備えてZOZOFITの開発運用を別チームへ移管することになりました。移管作業の中でAWSリソースを別チームが管理するAWSアカウントへ移行する作業が発生することになりました。本記事では移行時に遭遇した課題と、その課題の解決に至るまでの取り組みをご紹介します。 目次 はじめに 目次 背景・課題 調査 ユーザ移行Lambdaの作成 簡易ダイアグラム フローチャート ユーザ移行Lambdaの処理 IAMの設定 移行元で用意するRole 移行先のユーザ移行Lambdaの実行Role 移行後 移行後に顕在化した問題 ユーザ移行Lambdaを経由した場合、ユーザ認証が大文字と小文字を区別するcase-sensitiveな判定になってしまう ユーザ移行Lambdaを経由したサインイン時にSMSが2通届いてしまう まとめ 背景・課題 まず、ZOZOFITを移管する上でAWSのリソースを別アカウントへ移行する事を検討しました。別チームの管理となるため管理上は別アカウントへ移行するのが適切な形です。ただし、クロスアカウントでのリソース移行は制約も多いため、慎重に検討する必要があります。このためまずはクロスアカウントでのデータ移行方法と影響範囲について調査しました。 ZOZOFITのシステム構成は以下の記事で詳細を記載していますが、データの移行対象となるのは、S3、RDS、Cognitoのユーザープール、の3つでした。前提として今回の移行ではサービス停止(ダウンタイムが発生する)が許容されていました。この中で、S3はレプリケーションを事前に設定し、RDSはスナップショットを利用してそれぞれデータを移行するため、クロスアカウント固有の事情で影響が大きくなることはないと判断しました。一方で、Cognitoのユーザプール移行に関しては未知の部分だったので調査から始まりました。 調査 Cognitoのユーザープールの移行方法について調査した結果、 AWS公式ドキュメント から、以下の2つの方法があるとわかりました。どちらの方法もクロスアカウント固有の制約はなく、この2つの移行方法についてそれぞれのPros/Consを整理して比較しました。なお、どちらの方法でもセッション情報が引き継がれず、ユーザーがサインアウト状態になる影響も判明しました。この点に関しては共通事項のため比較要素としては記載していません。 1 CSVファイルからユーザプールへのインポート 2 ユーザ移行Lambdaを利用した、イベントドリブンのユーザ移行 移行方法 Pros Cons CSVファイルからユーザプールへのインポート すべてのユーザの移行が一括で行える パスワードリセットが全ユーザに強制される ユーザ移行Lambdaを利用した、イベントドリブンのユーザ移行 サインインまたはパスワードをリセットすることで、データの移行が完了する ユーザがサインインまたはパスワードリセットを行った際にしか移行が行われないため、移行に長期間を要する Pros/Consを比較した結果、ユーザ影響の少ないユーザ移行Lambdaによるデータ移行を移行方法として選択する方針となりました。しかし、ここで1つ課題が見つかりました。移行対象のユーザープールではMFAの設定を必須にしており、移行時にMFAの有効化ができるか懸念があったためです。他社の事例を見ると回避手段がないように見えましたがAWSのテクニカルアカウントマネージャーに相談したところ、ユーザ移行Lambdaを利用してMFAの設定を有効化する方法を教えていただきました。懸念事項の回避手段が見つかり、実際にユーザ移行Lambdaを作成することになりました。 ユーザ移行Lambdaの作成 まずはユーザ移行Lambdaの挙動を整理するために簡易的なダイアグラムとフローチャートを用意しました。 簡易ダイアグラム フローチャート サインイン パスワードリセット 移行先アカウントのユーザープールに紐づけられたユーザ移行Lambdaが移行元のユーザープールのデータを取得する形となっています。ユーザ移行Lambdaは取得したユーザのデータの取得をレスポンスとして返すだけで、実際の登録処理は行いません。実際の移行先のユーザープールへのデータ登録はAWS側が行います。ここまででユーザ移行Lambdaの動きを簡単に説明しましたが、ユーザ移行Lambdaの実処理に関して実装時に注意したポイントを記載します。 ユーザ移行Lambdaの処理 ここからユーザ移行Lambdaのコードと実装時に注意したポイントを解説します。AWSで提供されているドキュメントを参考にしながらPythonで実装しました。処理の流れとしては、最初にユーザの存在を確認し、次にイベント情報をみて処理を分岐させます。サインインの場合は認証を処理した上でレスポンスを返し、パスワードリセットの場合は何もせずにレスポンスを返す形になっています。ここで注意したポイントは以下の2つです。 データ移行の観点から、レスポンスに含めるユーザ情報は移行元ユーザープールの情報を利用する レスポンスを受け取るのはAWS側であるため、 AWS公式ドキュメント に記載されているコードに沿って実装することを優先し、例外などもそのまま返す import boto3 from boto3.session import Session import json import os def lambda_handler (event, context): # setting src resource info SRC_ROLE_ARN = os.environ[ 'SRC_ROLE_ARN' ] SRC_USER_POOL_ID = os.environ[ 'SRC_USER_POOL_ID' ] SRC_USER_POOL_CLIENT_ID = os.environ[ 'SRC_USER_POOL_CLIENT_ID' ] SRC_AWS_REGION = os.environ[ 'SRC_AWS_REGION' ] # switch to src aws account sts_cli = boto3.client( 'sts' ) response = sts_cli.assume_role( RoleArn=SRC_ROLE_ARN, RoleSessionName= "switch_role_session" ) session = Session( aws_access_key_id=response[ 'Credentials' ][ 'AccessKeyId' ], aws_secret_access_key=response[ 'Credentials' ][ 'SecretAccessKey' ], aws_session_token=response[ 'Credentials' ][ 'SessionToken' ], region_name=SRC_AWS_REGION ) src_client = session.client( 'cognito-idp' ) # get user info from src cognito by input user info from event username = event[ 'userName' ] try : user = src_client.admin_get_user(UserPoolId=SRC_USER_POOL_ID, Username=username) except Exception as e: print (f "Unexpected {e=}, {type(e)=}" ) raise e # initiate auth from src cognito, if admin_initiate_auth is failed then is raised error if event[ 'triggerSource' ] == 'UserMigration_Authentication' : print ( 'UserMigration_Authentication' ) password = event[ 'request' ][ 'password' ] try : response = src_client.admin_initiate_auth( UserPoolId=SRC_USER_POOL_ID, ClientId=SRC_USER_POOL_CLIENT_ID, AuthFlow= "ADMIN_NO_SRP_AUTH" , AuthParameters={ 'USERNAME' : username, 'PASSWORD' : password } ) except Exception as e: print (f "Unexpected {e=}, {type(e)=}" ) raise e event[ 'response' ][ 'finalUserStatus' ] = 'CONFIRMED' event[ 'response' ][ 'enableSMSMFA' ] = True elif event[ 'triggerSource' ] == 'UserMigration_ForgotPassword' : print ( 'UserMigration_ForgotPassword' ) # common response sign-in/password reset # create new user on dst cognito by response data # just now, new user is active and verified for userattribute in user[ 'UserAttributes' ]: if userattribute[ 'Name' ] == 'phone_number' : phone_number = userattribute[ 'Value' ] if userattribute[ 'Name' ] == 'email' : email = userattribute[ 'Value' ] if userattribute[ 'Name' ] == 'email_verified' : email_verified = userattribute[ 'Value' ] if userattribute[ 'Name' ] == 'phone_number_verified' : phone_number_verified = userattribute[ 'Value' ] if userattribute[ 'Name' ] == 'custom:user_id' : user_id = userattribute[ 'Value' ] event[ 'response' ][ 'userAttributes' ] = { 'username' : username, 'email' : email, 'custom:user_id' : user_id, 'email_verified' : email_verified, 'phone_number' : phone_number, 'phone_number_verified' : phone_number_verified } event[ 'response' ][ 'messageAction' ] = 'SUPPRESS' # output migration user_id print (f "Migration: {user_id=}" ) return event IAMの設定 今回はクロスアカウントでの移行となるため、移行元と移行先でそれぞれRoleを作成しました。移行元では移行先のユーザ移行Lambdaが実行するRoleに権限を委譲する目的でRoleを用意しています。 移行元で用意するRole IAMRoleLambdaFunctionImportCognitoUserPool : Type : 'AWS::IAM::Role' Properties : RoleName : 'import-cognito-user-pool-role' AssumeRolePolicyDocument : Version : '2008-10-17' Statement : - Effect : 'Allow' Principal : AWS : !Sub 'arn:aws:iam::${DestinationAWSAccountID}:role/${DestinationAWSIamRole}' Action : 'sts:AssumeRole' Policies : - PolicyDocument : Version : 2012-10-17 Statement : - Effect : 'Allow' Action : - 'cognito-idp:AdminGetUser' - 'cognito-idp:AdminInitiateAuth' Resource : - !Ref CognitoUserPoolSrcArn PolicyName : 'import-cognito-user-pool-policy' 移行先のユーザ移行Lambdaの実行Role IAMRoleLambdaFunctionImportCognitoUserPool : Type : 'AWS::IAM::Role' Properties : RoleName : 'import-cognito-user-pool-lambda-function-role' AssumeRolePolicyDocument : Version : '2012-10-17' Statement : - Effect : 'Allow' Principal : Service : - 'lambda.amazonaws.com' Action : 'sts:AssumeRole' Policies : - PolicyDocument : Statement : - Effect : 'Allow' Action : - 'logs:CreateLogGroup' - 'logs:CreateLogStream' - 'logs:DescribeLogStreams' - 'logs:PutLogEvents' Resource : !Sub 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/*' PolicyName : 'import-cognito-user-pool-lambda-function-policy' - PolicyDocument : Version : 2012-10-17 Statement : - Effect : 'Allow' Action : - 'sts:AssumeRole' Resource : !Sub 'arn:aws:iam::${SourceAWSAccountID}:role/${SourceAWSIamRole}' PolicyName : 'import-cognito-user-pool-assume-role-policy' 移行後 ユーザ移行Lambdaで発生した例外は準正常系のみであり、想定内の挙動に収まりました。また、移行後に全ユーザがサインアウト状態となるため、想定以上のサインインが行われた場合にCognitoのAPIのRateLimitに抵触することを懸念しましたが、無事想定内に収まりました。結果、大きな問題は起きませんでしたが、いくつか想定外の問題が発生しました。 移行後に顕在化した問題 ユーザ移行Lambdaを経由した場合、ユーザ認証が大文字と小文字を区別するcase-sensitiveな判定になってしまう 今回は移行処理のため、移行元のユーザのemailをレスポンスとして返却していましたが、この点で問題が発生しました。具体的にはLambdaが受け取ったusernameとLambdaから返されるレスポンスのemailが完全に一致していない場合はデータ移行がされないとわかりました。通常の処理では大文字と小文字を区別しないcase-insensitiveな判定をしているため、挙動が違う形となってしまいました。通常ケースとユーザ移行Lambdaを経由した場合の挙動の違いは以下の通りです。 ケース ユーザの入力値 DBに保存されている値 結果 通常のサインイン USER@example.com user@example.com 成功 ユーザ移行Lambda経由のサインイン USER@example.com user@example.com 失敗 通常のパスワードリセット USER@example.com user@example.com 成功 ユーザ移行Lambda経由のパスワードリセット USER@example.com user@example.com 失敗 テスト時に検証できていなかったケースで、移行後にAPIサーバ側でDBに保存されているユーザのemailを取得し、CognitoにはDBから取得したemailを受け渡す形に修正し、問題を解消しました。 ユーザ移行Lambdaを経由したサインイン時にSMSが2通届いてしまう サインイン時にユーザ移行Lambdaを経由した場合、移行先と移行元のCognitoからそれぞれSMSが届いてしまう問題が発生しました。こちらはテスト時にも発生していた問題でしたが、見落としてしまいました。切り替え後に移行元のCognitoでMFAを無効化することで対応しました。単純なテスト時の見落としですが、切り替え後に移行元のCognitoでMFAを無効化するべきだったこと、一時的にであれこの事象を許容できない場合はダウンタイムが不可避なこともわかりました。 まとめ いくつか移行後に問題が見つかりましたが、大きなトラブルなくユーザ負担も最小限に抑えて移行が完了しました。また、今回の移行作業は経験したことのない作業だったので多くの知見が得られました。 特に認証機構として利用しているCognitoについてデータ移行を想定していなかったこともあり、実際に調査してみないとわからない部分が多くありました。いざ移行するとなった時に初めて方法を検討する形となったのも反省点です。どのようなサービスであれ移行を考慮した上での技術選定は大切だなと改めて学びました。 計測プラットフォーム開発本部では、今回紹介したように、新規サービスの開発を活発に行いながら運用負荷を削減することでバランスを取って働いています。このような環境を楽しみ、サービスを一緒に盛り上げていける方を募集しています。少しでもご興味のある方は以下のリンクからぜひご応募ください。 corp.zozo.com CSVファイルのインポートについては、 AWS公式ドキュメント に詳細な仕様が記載されています。 ↩ ユーザ移行Lambdaについては、 AWS公式ドキュメント に詳細な仕様が記載されています。 ↩
アバター
こんにちは、MA部MA開発ブロックの @gachi-muchi-engineer です。 4/9-4/11に開催された Google Cloud Next '24 へ参加してきました。去年に続きオフライン開催で、今年はアメリカ・ラスベガスで開催されました。弊社からはMA部の @gachi-muchi-engineer ・ @da-sugi ・佐久間の3名が参加しました。去年参加した際の様子は以下のテックブログで紹介しています。 techblog.zozo.com 今年はどのようにAIを利用しているのか、利用していくのかを紹介したセッションが多かったように感じられました。本記事では、現地での様子と特に興味深かったセッションをピックアップして紹介します。 また、今回のテックブログで紹介できなかった内容などを含めてRecapのオンラインイベントを2024/5/13に開催予定です。このイベントでは、Google Cloud Japan合同会社の方にも登壇していただき今回のGoogle Cloud Next '24について詳しくお話いただきます。ぜひご参加ください。 zozotech-inc.connpass.com 現地での様子 Google Cloud Next '24の会場であるマンダレイ・ベイのコンベンションセンター 今回のイベントは、ラスベガスのマンダレイ・ベイで開催されました。会場はホテルに併設されているコンベンションセンターで、東京ドーム4個分ほどの広さだそうです。フルリモートで運動不足だった私達の足腰は、会場内を歩き回るだけでかなり鍛えられたと思います。 セッション会場の様子 各セッションは、センター内の大きな会議室で行われました。たくさんの会議室があり、それぞれ広々とした会場でした。 エキスポ会場内の地図 企業ブースは、とても大きな展示スペースで大変な数が出展されていました。お話した企業ブースの方が「この雰囲気はクレイジーだぜ」と言っていたのがとても印象深かったです。 それ以外にも企業ブースでは、Passport Program(いわゆるスタンプラリー)が行われていました。特定のブースを回ってQRコードを読み込むとCloud Nextのグッズがもらえるというものもありました。 エキスポ会場内の様子 以降で現地参加したメンバーが気になったセッションについて紹介します。 セッション紹介 @gachi-muchi-engineer です。 私は主にバックエンドの開発・運用に携わっています。今回参加したGoogle Cloud Next' 24の発表の中から今後の開発・運用が大きく変わっていくと感じた内容を紹介します。 Gemini for Google Cloud 今回のGoogle Cloud Next '24では、Google CloudのサービスにGeminiを導入した機能の紹介が多かったです。 まずGeminiについて簡単に紹介します。GeminiはGoogleが提供する最新のAIモデルです。テキストや画像、動画、音声などの複数の異なるデータを一度に理解できるマルチモーダルモデルの生成AIです。 詳しくは、以下のGoogleブログの記事をご覧ください。 blog.google GeminiをGoogle Cloudのサービスに導入することで、エンジニアをサポートする機能が追加されました。私のパートでは、Google CloudのサービスにGeminiが導入された機能を中心に紹介していきます。 What's next for Google Cloud databases in the gen AI era データベースに関して、"What's next for Google Cloud databases in the gen AI era"のセッションからいくつか紹介します。 Gemini in Databases データベースの開発・管理・運用を支援する3つの機能の紹介がありました。 1.Database Studio 「 What's next for Google Cloud databases in the gen AI era 」の27:32より引用 Cloud SQLにDatabase Studioが追加されました。BigQueryのWebコンソールと同じような操作性で、Webコンソールからクエリを実行し結果を確認できるようになります。 それに加えて、Geminiのサポートによりコンソールで自然言語を利用してクエリを生成できるようになります。他にも既存クエリを最適化してもらえるのはとても便利だと感じました。また、 Query Insights と合わせて利用することによりクエリの実行計画の可視化やクエリの実行時間の比較なども行えるので、開発者にとって非常に使いやすいと感じました。Database Studioを実際に利用してみましたが、とても操作性がよく利用しやすかったです。 今までは運用時に、 Cloud SQL Auth Proxy を利用したり、Cloud Shellからデータベースに接続したりしていました。ただ以下の課題を感じていました。 Cloud SQL Auth Proxyでは意図しないデータベースに接続してしまうなどのリスクがある Cloud Shellから接続する場合は、接続するのに時間がかかってしまう Database Studioを利用することで、これらの課題を解消し、より簡単に運用ができると感じました。 これだけでもとても便利ですが、これに加えてGeminiによるクエリのサポートを得られるようになります。Geminiに「こういうデータがほしい」と問いかけるだけでクエリを生成してくれるので、調査などの運用コストが下がると感じました。 2.Database Center 「 What's next for Google Cloud databases in the gen AI era 」の27:56より引用 Google Cloudのプロジェクトごとに運用しているデータベースを一元管理できるサービスです。自然言語で「セキュリティリスクを含んでいるデータベース」や「特定の状態になっているデータベース」などの検索が可能です。もちろん検索しただけではなく、そのデータベースに対してセキュリティアップデートの適応やスケールアップなどの操作も可能です。 3.Database Migration Service 「 What's next for Google Cloud databases in the gen AI era 」の35:22より引用 データベース移行を支援するサービスです。データベース移行は、データの整合性や移行後のパフォーマンスなど様々な課題がありますが、プロシージャーなどの移行や最適化もサポートされているので、簡単に行えると感じました。 紹介したサービスのAIによる支援は開発に関して大幅に生産性を向上させるだけでなく、セキュリティの課題やパフォーマンス向上に関するレコメンドなどは運用コストの大幅な削減に繋がると感じました。MA部では、月1回程度の頻度でアップデートの確認や対応をしています。地味に運用コストが結構掛かってしまったり対応漏れがあったりするので、Database Centerの活用でこういった課題の解決ができそうだと感じました。Database Migration Serviceは、データベース移行における課題やタスクをほとんど解消してくれるサービスなので、もし機会があればぜひ利用したいと思いました。 ベクトル検索に対応したデータベースプロダクト 「 What's next for Google Cloud databases in the gen AI era 」の5:04より引用 まずベクトル検索に関して簡単に紹介します。一般的なキーワード検索は、ある単語が文字列に含まれているものを検索する方法です。文章内に検索対象のキーワードが含まれていないと検索されません。一方でベクトル検索は、キーワードをベクトル(数値)に変換して、そのベクトルの類似性を利用した検索する方法になります。検索対象のキーワードが含まれていなくても検索されます。 これまでAlloyDB, Cloud SQL for PostgreSQLがベクトル検索に対応していましたが、これに加えて下記のデータベースプロダクトでもベクトル検索に対応予定です。 Cloud SQL for MySQL Spanner Firestore Bigtable Memorystore for Redis これにより、Google Cloudが提供するあらゆるデータベースプロダクトで最新データを利用してベクトル検索が可能になります。これによりリアルタイム性が求められるプロダクトでデータベースの選択肢が増えたと感じました。 MA部ではリアルタイムマーケティングシステムを運用しています。詳しくは以下のテックブログで紹介しています。 techblog.zozo.com 今回のベクトル検索の対応により、上記のテックブログで紹介したイベント検知とユーザー抽出の領域や最適化の領域において、将来的にベクトル検索を利用するアプローチが考えられ非常に興味深かったです。 このセッションでは、デモやユースケースの紹介もあり、どのように開発や運用に活用されるかを具体的な例を用いて非常にわかりやすく紹介されていました。運用に関しては、よくある社内でのケースを元にDatabase Centerがどのように活用できるのかが紹介されていました。特に自然言語からSQLを生成し実行するアプリケーションのデモは非常に興味深かったです。ぜひセッションのアーカイブ動画をご覧ください。 cloud.withgoogle.com Cloud Run: What's new "Cloud Run: What's new"のセッションからいくつか紹介します。 Volume Mounts 公開資料「 Cloud Run: What's new 」のP.9より引用 Cloud RunにおいてNFSとCloud StorageのVolume Mountsがサポートされます。これにより、Cloud Run上でのファイルの読み書きが可能になります。 これまでのCloud Runではファイルの読み書きが インメモリのファイルシステム だけでしたが、この機能が追加されることでメモリを気にせずファイルの読み書きが可能になります。この機能により、Cloud Run上で構築できるアプリケーションの幅が広がると考えられます。 Automatic Security Updates 公開資料「 Cloud Run: What's new 」のP.10より引用 デプロイされたimageのbase imageのセキュリティアップデートが自動で適用されるようになりました。 ダウンタイムはなく、リビルドの必要もありません。セキュリティインシデントに対して48時間以内にアップデートが自動で適用されるので、セキュリティのリスクを最小限に抑えられます。App EngineとCloud Functionsで運用されてきた機能のようで、Cloud Runでも利用できるようになります。 Gemini in Cloud Run Recommendations 公開資料「 Cloud Run: What's new 」のP.12より引用 Cloud Runのサービス一覧のページでGeminiがサポートされます。今までもレコメンドは表示されていましたがGeminiを利用することで、チャットを通してより積極的に利用してほしいと考えているようです。例えば、コストについての推奨事項があった場合に「より効果的なコストパフォーマンスを提供するための構成」などGeminiに質問ができるようになります。 Application canvas 公開資料「 Cloud Run: What's new 」のP.19より引用 Application canvasを使うことで、システムアーキテクチャをWebコンソール上で簡単に設計できるようになります。設計された各サービスの起動や接続に必要なロールの設定などを画面上で全て設定できるようになるサービスです。特にシステム構築の初期段階でアーキテクチャ図を作成する際にこちらを利用することで、そのままプロトタイプも開発できるようになるので非常に便利なサービスだと感じました。 これだけでも非常に便利なのですが、Geminiに自然言語で「こんなことができるアプリ」のように問いかけると適切なアーキテクチャを構築してくれます。セッションのデモで実際に自然言語で問いかけを行って、アーキテクチャの生成からアプリケーションのデプロイやアーキテクチャを修正する一連の流れが紹介されていました。 MA部では、現在運用しているマーケティングに関するシステムのリプレイスを行うZMPというプロジェクトを進めています。詳しくは以下のテックブログで詳しく紹介しています。 techblog.zozo.com その中で、Cloud RunはAPIや管理画面を構成する際に利用しています。Gemini in Cloud Run Recommendationsが利用できるようになると運用や改善点を発見するのに非常に役立ちそうだと感じました。私たちのチームでは、プロジェクトが進行していく中でリプレイスするシステムが増えていきます。Application canvasは、そこでシステム構築を検討する初期段階でアーキテクチャ図を作成する際に非常に便利だと感じました。自然言語で問いかけるだけでよいのも非常に便利です。 What's new with BigQuery 続いて"What's new with BigQuery"のセッションからいくつか紹介します。まず、このセッションでは様々な機能がBigQueryに統合され「AIに対応した単一のデータ分析プラットフォーム」であることが強調されていました。統合(unified)というキーワードがとても多く使われていたのが印象的で、新機能も様々なサービスとの連携強化やプラットフォームとしてより使いやすくなるような機能が多く紹介されていました。 公開資料「 What's new with BigQuery 」のP.12より引用 以下では、BigQueryの新機能の中から特に興味深かったものを紹介します。 Continuous real-time analytics in SQL 公開資料「 What's new with BigQuery 」のP.19より引用 この機能は、ストリーミングデータに対して継続的にSQLを実行できるようになるといったものです。 今まではDataflowを利用することが多かったですが、この機能によって選択肢が増えると思いました。BigQuery Studioなどから簡単に利用できるのであれば、Dataflowを構築する手間が省けるので非常に便利だと感じました。 BigQuery data canvas 公開資料「 What's new with BigQuery 」のP.27より引用 BigQuery data canvasは、自然言語を利用してデータ分析とビジュアライゼーションの作成が可能になります。 特に非エンジニアのデータ分析のハードルが大きく下がると感じました。 BigQuery data preparation 公開資料「 What's new with BigQuery 」のP.28より引用 BigQuery data preparationは、AIの支援を受けながらデータのクレンジングや変換が行えます。 AIがデータ変換に対してレコメンドしてくれる点が非常に便利そうだと感じました。大量にデータがあった場合に、意図しないデータや想定外のデータが含まれているときの警告を出したり変換を提案したりしてくれるようになります。以前データ分析をした際に、意図しないデータが入っていることで予想と違う結果になってしまい分析結果を検証しなければならないケースがありました。この機能を利用することで、そういった課題を解消できると考えられました。 BigQuery Workflows 公開資料「 What's new with BigQuery 」のP.25より引用 BigQuery Workflowsは、BigQuery Studioから簡単にワークフローを作成できる機能です。 Webコンソールからノーコーディングでワークフローを構築できます。構築されたワークフローはスケジュール実行ができるだけでなく、Cloud DataformやCloud Composer用にエクスポートできるようです。これはデータマートの作成や集計などで利用できそうなイメージが湧きました。BigQuery data preparationやBigQuery data canvasと組み合わせることで、データの前処理から分析、ビジュアライゼーションまでを一貫して行うことができると感じました。 What's next data analytics in the AI era こんにちは、MA部MA施策推進ブロックの @da-sugi です。私のパートでは、BigQueryで新たに発表があった機能を活用したデータ分析の進化について紹介します。 こちらのセッションでは、他セッションでも紹介されたBigQueryの新機能を前半で紹介しつつ、後半では、Geminiを活用したデータ分析のデモが行われました。AIに対応したBigQueryによって、スピーディーな分析と意思決定がいかに実現可能か、デモを交えて発表していました。会場の雰囲気としては1つ1つのプレビューの発表でも拍手が出るくらい、盛り上がっていました。 この章では、主にデモについて紹介します。新機能はデモに関連するものについてだけ紹介します。 BigQuery Studio (GA) 「 What’s next for data analytics in the AI era 」の24:09より引用 BigQuery Studioはデータの探索、分析、可視化、および共同作業を行うためのツールで、SQL、Python、自然言語などを使用してデータにアクセスできるデータ分析プラットフォームです。 わずか7 ~ 8か月前のGoogle Cloud Next '23でプレビューとして発表されたばかりですが、今回のGoogle Cloud Next '24で一般提供となりました。 BigQuery integration with Vertex AI for multimodal AI (GA) 「 What’s next for data analytics in the AI era 」の29:17よりスライド部分を強調して引用 BigQueryとVertex AIの統合によってマルチモーダルAIを実現できることが発表されました。 この統合により、BigQueryに保存されているデータを使用して、マルチモーダルデータを処理し、Vertex AIで機械学習モデルをトレーニングすることが可能になります。 Vector search in BigQuery (preview) 「 What’s next for data analytics in the AI era 」の31:59より引用 BigQueryにおける「ベクトル検索(Vector Search)」のプレビューが発表されました。 特定のカラムにベクトルデータが保存されており、そのデータをクエリして類似性の高いベクトルを持つレコードを見つけ、これにより画像検索など様々な分野で類似性検索を効率的に行うことが可能となります。 Gemini in BigQuery(preview) 直感的なインタフェースを使用してデータを探索し、インサイトを抽出できるGemini in BigQueryのプレビューが発表されました。Geminiを使用すると、SQLクエリを記述することなく、データセットの傾向やパターンを視覚的に理解できるようになります。またBigQuery Data Canvasを使うことで、インタラクティブな操作や自然言語でのデータ検索を共同で作業でき、その結果をビジュアル化して分析・共有できます。 デモ GeminiとLookerを使用して、オンラインファッションECサイトのデータを分析するデモが行われました。画像は、カスタムLookerアプリケーションで作成したもので、このリッチなレポートはGeminiとLookerで、なんとわずか数分で構築されたものだと言っていたのには驚きました。 「 What’s next for data analytics in the AI era 」の36:08より引用 実際にデモを通して、ビジュアライズに必要なデータをどのように構築されたのかを見ることができました。 まずデータ分析までの重要な3つのステップについて説明がされました。 「 What’s next for data analytics in the AI era 」の37:12より引用 ソーシャルメディアデータなどの複数のソースから取り込まれたデータを、全てまとめて分析できるようにGeminiとBigQueryを使用して変換 BigQuery Data CanvasとBigQueryのツールを使用して、トップトレンドの商品を発見・分析し、チャートを作成 BigQuery Vector Searchを使用して、トップトレンドの商品に似ている商品を見つける 1. GeminiとBigQueryを使用したデータのクリーニング クリーニング前のデータ 「 What’s next for data analytics in the AI era 」の38:14より引用 クリーニング作業 「 What’s next for data analytics in the AI era 」の38:25と38:49よりスライド部分を強調して引用 右側に表示されたAI生成コードの適用をクリックするだけで、瞬時に投稿日のフォーマットを整え、商品名のみが抽出されました。 クリーニング後のデータ 「 What’s next for data analytics in the AI era 」の38:54より引用 2. BigQuery Data Canvasを使用したトップトレンドの可視化 自然言語でのデータ検索と分析 Data Canvasを使用することで、自然言語でデータの検索ができ、即座にテーブルを作成できていました。 「 What’s next for data analytics in the AI era 」の39:43より引用 そのデータを元に、GeminiとBigQueryが自然言語からSQLを作成してデータ分析を可能にし、またVISUALISEから、わずか数秒でチャートだけでなくテキストのインサイトを作成していました。 「 What’s next for data analytics in the AI era 」の40:03より引用 「 What’s next for data analytics in the AI era 」の40:31より引用 3. BigQuery Vector Searchを使用した商品の類似性検索 Vector SearchはBigQuery内で、テキストから画像、動画からテキストなど、様々なデータ形式の類似性検索を行えます。これを使用して、デモではトップトレンドの商品に似た商品を検索していました。 「 What’s next for data analytics in the AI era 」の41:30より引用 4. MLの活用 また、BigQueryの機械学習を使用することで、トップ5の商品の今後1年間の販売予測もすぐに可能でした。これもGeminiとの対話によって実現されています。 「 What’s next for data analytics in the AI era 」の42:12より引用 セッション内のほとんどの作業が、単一のアプリケーション上で自然言語での対話によって実現可能であるのは非常に便利だと感じました。AIを活用したデータ分析がどれほど進んでいるのかを再認識できました。 ZOZOでは、BigQueryの新機能(GeminiとVector Search、Vertex AI)を組み合わせて、デモにあったように簡単な手順で様々な指標でのデータ分析が可能になり、次にどんな施策・配信をするのかなどの意思決定をよりスピーディーに行えるようになると思いました。 またMA部としては、前章(What's new with BigQuery)でも紹介があったSQLの継続的な実行(Continuous real-time analytics in SQL)と組み合わせ、最新のトレンド・販売予想などのデータを利用した配信も可能になるので、開発・運用しているマーケティング関連のシステムの改善や新規機能にも活用していけそうだと考えています。 What's new with IAM こんにちは、MA部MA開発ブロックの佐久間です。私からは "What's new with IAM - from least privilege to organization policies and AI-powered assistance"のセッション内容について紹介します。 私は普段バックエンドエンジニアとして開発・運用業務を行っていますが、IAMというセキュリティに関係する部分において、AI/MLがどのように機能していくのか興味がありました。管理者だけではなく、私のようなアプリケーション開発者の目線においても理解が深まる内容でした。 以降、IAMについての最新情報とともに、気になったトピックを紹介していきます。 Identity Provider こちらはGoogle CloudのIAM全体像です。 公開資料「 What’s new with IAM 」のP.6より引用 Identity Platformはその根底に位置し、多様なIdentityで構成されます。Identity Providerにはユーザーのものを使用できますが、以下の3通りの使い方があります。 【Cloud Identity】IdPをGoogleに同期して使用 【Identity Federation】Workforce Identity Federationを使用してGoogleに同期せず使用 【Mixed Mode】上記2つを合わせ、従業員の拡大や買収などで同期しきれない分にIdentity Federationを使用 今回、120以上の製品がIdentity Federation対象としてGAとなりました。一例ですが、Microsoft Power BIのEntra IDでBigQueryを利用できるようになっています。 Access Boundary 多層防御のアクセス管理として、IAMのGrant、Denyに加え、Access Boundaryが紹介されました。 公開資料「 What’s new with IAM 」のP.14より引用 こちらはアクセス可能なリソースの範囲、境界を制限するポリシーです。この境界には組織、フォルダー、プロジェクトといったレベルで定義できます。これらのポリシーはGrantを意味するのではなく、アクセス可能な最大の範囲を定義します。こちらは間もなくプレビュー版が公開されるそうです。 たとえ誤操作や誤認識で許可されてしまってもセーフティネットとして機能してくれそうです。 Privileged Access Manager Privileged Access Managerでは権限を資格として定義し、申請や承認の仕組みが利用できます。更新や承認が監査ログとして残る他、任意のEメールアドレスへ経過を送信できます。この度プレビューになりました。 公開資料「 What’s new with IAM 」のP.20より引用 Compute Adminなどの特権をそのまま付与するのではなく、資格として付与する権限や期間を定義できるため、一時的なトラブルシューティングのために1時間だけ払い出す、などの使い方ができます。そのため、作業後には意図しない権限が残り続けることはなく、不要な棚卸し作業から解放されそうです。 CIEM (Cloud Infrastructure Entitlement management) Google Cloud-AWS間でCIEMがプレビューになりました。年内にはMicrosoft Azureとの連携が予定されています。 公開資料「 What’s new with IAM 」のP.25より引用 CIEMではマルチクラウド環境でのアクセス権限管理を可能にし、IAMロールの最適化についてもレコメンドを受けることができます。また、Chronicle SOARというセキュリティプラットフォームに統合することで、発覚した過剰な権限がどのような脅威になるのかやその修復方法がわかり、JIRAチケットの自動発行なども可能になります。Chronicle SOARの詳細については以下のドキュメントをご覧ください。 cloud.google.com セッションでは過剰な権限付与がどれほど一般的に行われているかが説明され、最小権限の原則の重要性を強調されているようでした。 Resource Configuration 既に110以上のビルトインポリシーが、ガードレールのようにデフォルトで有効になっていますが、さらに組織レベルでのカスタムポリシー作成がGAとなりました。 公開資料「 What’s new with IAM 」のP.29より引用 例えば、GKEクラスターの作成にはバイナリ認証を有効にする必要がある、などを組織のポリシーとして定義できるようになります。 バイナリ認証が有効になっていると、検証環境で合格したイメージのみが本番環境にデプロイされることを担保できるなど、開発者にとってもうれしいポイントです。 Gemini Cloud Assist 最後に、Geminiによるアシストについての紹介です。こちらは間もなくプレビューになるそうです。 公開資料「 What’s new with IAM 」のP.37より引用 IAMやRoleに10,000もの推奨事項があるとして、必ずしも全てに対応する必要はないはずです。そこでGeminiが何から取りかかれば良いか、優先すべき事項を提案してくれます。また、あるサービスアカウントが最後に使われたのはいつだったのか、複雑なアクセスポリシーからどんな権限で拒否されているのかなどについても、自然言語で質問できるようになります。 やはり自然言語でどんな相談にも乗ってくれるのは、初学者や管理者などあらゆる立場の人にとって頼もしい存在に感じられました。ただセキュリティに関する部分なので、AI/MLが修復を推奨しなかったものはどのようなものなのか、なぜ推奨に至らなかったのかなどにも個人的には注意しようと思います。 まとめ 今年のGoogle Cloud Next '24は、去年同様にAIに関するセッションがとても多かったです。 去年はAIの可能性や今後についての観点でのセッションがメインだったと感じましたが、今年は進化したAIと実際の利用事例の紹介や今後Google CloudにどのようにAIが組み込まれ、進化していくかという内容が多かったと思います。 セッションだけでなく、世界中の企業が集まる企業ブースでAIの活用事例や現場のエンジニアとコミュニケーションを通して、本当にいろいろなところでAIが活用されているとを知ることができました。 この一年でAIが進化する速度の凄まじさを感じるとともに、次の一年でどこまで進化するのかが楽しみになりました。 紹介したセッション以外にもたくさんの興味深い発表がありました。全てのセッションは参加登録すれば公式サイトの Session Library から視聴できます。ぜひご覧ください。 最後に カンファレンス参加に伴う渡航費や宿泊費は 福利厚生 のひとつであるセミナー・カンファレンス参加支援制度によって全て会社負担です。 ZOZOでは一緒にプロダクトを開発してくれるエンジニアを募集しています。ご興味のある方は下記リンクからぜひご応募ください! corp.zozo.com
アバター
はじめに こんにちは。検索基盤部 検索技術ブロックの今井です。 検索基盤部では検索機能や検索精度を改善する中で検索クエリの意図解釈にも取り組んでいます。ZOZOTOWNで検索窓にクエリを入力して検索ボタンを押すと、クエリに応じて検索の絞り込み条件に変換するクエリ解釈機能の処理が動作します。 例えば、「ワンピース 白色」と検索した時、「ワンピース」を洋服のカテゴリー、「白色」を色のカテゴリーと解釈し、「白色のワンピース」を検索する絞り込み条件に変換します。 2024年5月現在ではスマートフォン向けWebサイト( https://zozo.jp/sp/xxx )とアプリのみ、クエリ解釈機能の処理が適用されています。クエリ解釈機能では意図解釈や検索の絞り込み条件に変換しています。 現在はシンプルな辞書ベースの手法を用いていますが、カバーしきれない課題も出てきており、改善のモチベーションが少しずつ上がってきています。本記事ではこれまでのクエリ解釈の取り組みについてシステム面も含めて紹介します。 目次 はじめに 目次 クエリ解釈について 従来のZOZOTOWNのクエリ解釈機能 導入背景 アーキテクチャ クエリ解釈機能のロジック 課題 クエリ解釈APIへのリプレイス 辞書生成バッチについて おわりに クエリ解釈について クエリ解釈は、検索の精度改善を目的として、検索者が入力したクエリの意図を解釈し、検索条件に変換します。クエリ解釈機能は以下のような処理フローで実現され、検索者の意図に沿った検索結果を返すことを目指しています。 ZOZOTOWNでは一部のみを導入し、全ての処理の導入までは至っていませんが、いずれは全て導入してクエリ解釈を強化したいと考えています。 以前弊社のテックブログ「 ZOZOTOWN検索の精度改善の取り組み紹介 」でも紹介している下記の外部記事で詳しく解説されていますのであわせてご参照ください。 Daniel Tunkelang: Query Understanding 検索体験を向上するQuery Understandingとは ※クエリ解釈は英語で query understanding (wikipedia) と呼ばれています。 従来のZOZOTOWNのクエリ解釈機能 ZOZOTOWNには10年ほど前からクエリ解釈機能が導入されていました。 導入背景 導入に至った背景としては以下2つの観点がありました。 検索の精度改善の観点 検索エンジンに対してキーワードマッチで検索するよりも、特定カテゴリのID等で検索結果を絞った方が検索精度の向上が期待できるため SEO観点 キーワード検索の検索結果ページに遷移するよりも、ブランドページやカテゴリページに遷移した方がSEO観点で良いとされるビジネス的な要件のため これらの観点が元となって導入された機能は以下です。 クエリ文字列から特定の絞り込み条件に変換する仕組み 特定ページへのリダイレクトURL構築(Web用) アーキテクチャ クエリ解釈機能が導入されたシステムアーキテクチャは以下です。この機能はClassic ASPで実装されています。 クエリ解釈機能のロジック 先述の通り、クエリ解釈機能で用いる手法はシンプルな辞書ベースの変換手法を用いています。以下は辞書のイメージです。 ターム 意図エンティティタイプ 意図エンティティ名 意図エンティティID ジャケット category "jacket" 1 パンツ category "pants" 2 zozo brand brand "ZOZO BRAND" 11 zozo shop shop "ZOZO SHOP" 21 zozo brandshop brand "ZOZO BRANDSHOP" 12 shop "ZOZO BRANDSHOP" 22 ... 検索条件の構築までの流れは以下です。タームによっては複数意図を持つタームも存在します。(e.g. 上記の表の"zozo brandshop") クエリ ジャケット を入力する 辞書内のターム一覧から ジャケット に完全一致でマッチした辞書エントリーを取得する 取得した辞書エントリーから カテゴリエンティティ(カテゴリ意図) であること、そのidが 1 であることを認識する 辞書エントリーによっては複数エンティティに紐づいていることがある 検索エンジンにリクエストする絞り込み条件として category_id に 1 をセットする 複数エンティティが存在する場合、優先意図の考慮や絞り込み条件同士の整合性が取れているかなどをチェックして一意に絞り込む 導入しているクエリ解釈機能の中には、以下3つの処理が含まれています。 処理 説明 クエリ分割(Query Segmentation) スペース区切りの文字列を意味のあるまとまりごとに扱うようにするために判定・分割する。 e.g. ・クエリ「ジャケット」→「ジャケット」としてセグメント化 ・クエリ「zozo brandshop 春服」→「zozo brandshop / 春服」として分割しセグメント化 クエリの属性の引当(Entity Recognition) セグメント化された各文字列がどの属性のタームなのかをエンティティとして識別する。 e.g. ・「ジャケット」→「カテゴリ:jacket(ID:1)」を属性として識別 ・「zozo brandshop 春服」→「ブランド:ZOZO BRANDSHOP(ID:12)」「ショップ:ZOZO BRANDSHOP(ID:22)」「キーワード:春服」を属性として識別 絞り込み検索条件の構築(Query Scoping) 識別されたタームを検索条件に変換する。検索エンジンに応じてクエリ要素へのマッピング内容が変わる。 e.g. ・「カテゴリ:jacket(ID:1)」→「カテゴリID:1」の絞り込み条件に変換 ・「ブランド:ZOZO BRANDSHOP(ID:12)」「ショップ:ZOZO BRANDSHOP(ID:22)」「キーワード:春服」→「ブランドID:12」AND「キーワード:春服」の絞り込み条件に変換("zozo brandshop"をショップではなくブランド条件として検索する場合) 課題 現行の辞書ベースの手法には、クエリ解釈精度面の課題があることが分かりました。特に、辞書にマッチしたタームが以下のケースに該当する場合は、変換を控える必要があります。 誤変換されるケース 複数の変換候補があるにもかかわらず、特定の候補に変換することがある この誤変換によって、他の変換候補の検索結果が表示されなくなる問題も発生 同音異義語が存在するケース ワンピース(カテゴリ)、ワンピース(漫画)は、カテゴリに変換するとワンピース(漫画)関連の商品が表示されなくなる 文字列長が短いケース 1文字、2文字など短いタームを変換してしまうため、検索者が意識せずたまたま入力したタームに対して誤ったまま変換してしまう可能性がある これらの問題が発生した場合は1件ずつアドホックに対応してきました。ただし、このままだとアドホック対応をいつまでも続ける必要があるため、根本解決する必要があります。 根本解決に向けて進めるために、まずはレガシーな実装の問題を解決するところから進めようと考えました。現行のクエリ解釈機能を保守運用する中で以下のアーキテクチャ面の課題が出てきました。 APIの管轄部署が検索基盤チーム以外であったため、改修や新機能の検証を気軽に出来なかった 実装がレガシー Classic ASPで実装されていたため、新規参画者に開発・保守運用の経験が少なく、学習コストが高かった モノリシックなレガシーAPIの1機能となっているため、改修による他影響を考える必要があった テストコードがないため、リグレッションに対して注視する必要があった アドホック対応 現状上記のような問題が発生したときに都度対応している ビジネス要件での変換依頼に対応することが時々ある 改善施策を施しABテストを実施するサイクルが回せていない これらのアーキテクチャ面の課題を解決すべく、まずはクエリ解釈APIのリプレイスを実施し、変換の課題を解決するための環境を整えました。次項では、そのリプレイス対応について説明します。 クエリ解釈APIへのリプレイス これらの課題解決の第一歩として、ZOZOTOWNのモノリシックなレガシーAPIの中からクエリ解釈の主機能を切り出し、クエリ解釈APIとしてリプレイスしました。このリプレイスではまず既存仕様を踏襲することとし、今後改善しやすいアーキテクチャを構築することを最優先としました。 このAPIは弊社技術スタックの推奨言語の1つであるGo言語で開発しました。採用理由としては、高速に動作することやGo言語開発の経験者による開発スピードの向上が期待できることなどが挙げられます。 リプレイス前後のシステムイメージは以下です。 ※ 辞書ファイルのレコード数は大規模ではないためKubernetesのPodに内包する形式を採用。そのため外部通信が発生せず安定かつ高速に動作。 クエリ解釈APIとして切り出すことで以下の恩恵が得られました。 機能追加、保守運用のし易さ リプレイス前は必要最低限の変更しか行われていなかったが、リプレイス後は問題の特定や影響範囲が分かりやすくなったこともあり定期的に改善が行われるようになった これにより「実装がレガシー」と「APIの管轄部署が検索基盤チーム以外であったため、改修や新機能の検証を気軽に出来なかった」の課題が解消された パフォーマンス向上 リプレイス前のAPIが高速でなかったこともあったが、リプレイス前後で比較すると10倍以上高速に動作 辞書登録する内容を一部見直し、リプレイス前は検索時に逐次DB問い合わせして取得していた情報をリプレイス後では予め辞書登録しておくようにしたことも効果的であった 今回のリプレイスにより以下の課題を解消できました。 実装がレガシー APIの管轄部署が検索基盤チーム以外であったため、改修や新機能の検証を気軽に出来なかった ただし、「アドホック対応」と「改善施策を施しABテストを実施するサイクルが回せていない」の課題の解消にはまだ至っていないため、引き続き解消に向けて取り組んでいきたいと思います。 辞書生成バッチについて 辞書ファイルは辞書生成バッチで生成していましたが、このバッチもClassic ASPで実装されていました。クエリ解釈APIのリプレイスに伴いバッチ側も同様にリプレイスを行いました。 具体的には、ワークフローエンジンに Vertex AI Pipelines を採用しPythonでバッチ処理を実装しました。 Classic ASPからVertex AI Pipelinesに乗り換えたことでワークフローエンジンとしての基本的な機能を得ることができ、保守運用を行いやすくなりました。以下は基本的な機能の例です。 バッチ処理内のタスクの依存関係を定義できる 処理途中で失敗したタスクからリトライできる backfillが容易になる データソースにはGoogle BigQueryとカスタマイズCSVファイルを利用します。Google BigQueryからはブランドやショップ、カテゴリなどの情報を取得します。これらのデータを用いて辞書ファイルを作成しGoogle Cloud Storageにアップロードします。 Google Cloud Storage上の辞書ファイルは、クエリ解釈APIのDockerコンテナイメージを作成するタイミングでダウンロードし内包しています。このDockerコンテナイメージからKubernetesのPodを作成・起動することで、外部通信が発生することなく安定かつ高速に動作します。 おわりに 本記事では、ZOZOTOWNでのこれまでのクエリ解釈の取り組みについて紹介しました。 クエリ解釈機能を従来のClassic ASPの実装からAPIとして切り出してGo言語でリプレイスしました。リプレイスではこれまでの仕様を踏襲したため依然として残っている課題も多々ありますが、切り出したことで改善しやすい状態に整えられました。 現在、クエリ解釈の次ステップの試みとして誤変換に対する課題を解決する手段などを検討・分析しています。この取り組みについても紹介できるようになり次第共有したいと思います。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com
アバター
はじめに こんにちは。SRE部フロントSREブロックの三品です。 3月19日から3月22日にかけてKubeCon + CloudNativeCon Europe 2024(以下、KubeCon EUと呼びます)が行われました。今回弊社からはZOZOTOWNのマイクロサービスや基盤に関わるエンジニア、推薦システムに関わるエンジニアの合わせて4人で参加しました。 本記事では現地の様子や弊社エンジニアが気になったセッションや現地の様子について紹介していきます。 目次 KubeConEU2024の概要 セッションの紹介 現地の様子 ブースについて 参加に向けてのTips 最後に KubeCon EU 2024の概要 昨年4月にオランダ アムステルダムで行われたKubeCon EUの様子については昨年の参加レポートをご覧ください。 techblog.zozo.com 今年のKubeCon EUはフランスのパリで開催されました。昨年まではコロナ禍の影響もありオフラインとオンラインのハイブリッドで開催されていました。 しかし今年はオフラインに統一され12,000人以上が現地で参加しており、今年もKubeCon EU史上最大の参加人数を更新しました。 キーノート会場の様子 KubeCon EUではキーノートやセッション、LTなどを通してKubernetesに関する最新のアップデートの紹介や実際にKubernetesを採用した企業の幅広い運用ノウハウを聞くことができます。 以降では参加してきた社員がそれぞれ気になったセッションや現地の様子について取り上げてご紹介します。 セッションの紹介 セッションタイトル Tutorial: Cloud Native WebAssembly and How to Use It - Brooks Townsend & Michael Yuan Cloud-Native LLM Deployments Made Easy Using LangChain Strategies for Efficient LLM Deployments in Any Cluster Why Is This so HARD? Conveying the Business Value of Open Source Kubernetes Maintainers Read Mean Comments To Infinity and Beyond: Seamless Autoscaling with in-Place Resource Resize for Kubernetes Pods Comparing Sidecar-Less Service Mesh from Cilium and Istio Future of Intelligent Cluster Ops: LLM-Azing Kubernetes Controllers Is Your Image Really Distroless? - Laurent Goderre, Docker Building Confidence in Kubernetes Controllers: Lessons Learned from Using E2e-Framework - Matteo Ruina, Datadog & Philippe Scorsolini, Upbound Tutorial: Cloud Native WebAssembly and How to Use It - Brooks Townsend & Michael Yuan ML・データ部MLOpsブロックの松岡です。 私の所属するMLOpsブロックではMLを使用した様々なサービスのプラットフォームを運用しています。MLにおいて近年話題になっているのがLLM(大規模言語モデル)とLLMを使った生成AIです。KubeCon EUでもLLMについてのセッションが多くありました。 その中でも特に興味深かったのがWasmを紹介した次のセッションです。 Wasmについての概要 LLMを実用化するにあたって重要な懸念点の1つがサービスがスケールする際のコストの問題です。 それを解決するためにユーザーのCPUやGPUで高速にプログラムを動かすことができる WebAssembly(Wasm) が注目されています。 このセッションでは、Wasmの概要から入り、実際にWasmを使ってLLMを動かしChatbotを構築する方法を紹介していました。説明が幅広く網羅的でかつ具体的でわかりやすかったです。 Wasmはブラウザ上で高速にプログラムを動かすために登場したバイナリコード形式のプログラミング言語です。 アセンブリと言う名前ですが、コーディングにあたってアセンブラ言語を記載する必要はなく、 C言語 や Rust など様々な高級言語からWasmバイナリにコンパイルできます。 コンパイル済みのコードは純粋な機械語ではなく抽象化された機械語となっており、Wasm Runtimeが各環境に応じた形へ変換し実行します。この仕組みによりJavaのように単一のコードで異なるOSやCPUアーキテクチャをサポートします。 当初この技術は3D処理などをブラウザ上で実行する用途として注目されていましたが、最近ではMLの推論をエッジで実行する用途にも注目されています。そしてWasmが小さなVMのようなものであることを応用しサーバーサイドにおけるDockerの置き換えとしても注目されているようです。 Wasm RuntimeによりCPUのアーキテクチャが抽象化される 「 Cloud Native Wasm And How To Use It 」より引用 WASIでWasmの利用用途が広がる このセッションではWASI(WebAssembly System Interface)0.2.0についても説明されていました。 WASIは、Wasmが直接サポートしていない標準入出力への読み書きや、ファイルシステム、ソケットへのアクセスなどアプリケーションに必要な機能をWasmから使用するのに使われています。これによりWasmはブラウザだけでなくサーバーサイドでの利用も可能になりました。 これらのインタフェースを標準化し、各Wasm Runtimeにてサポートすることで、実行環境が抽象化されコンテナ標準のように使用できるようになることがアナウンスされていました。 Wasmをコンポーネントとして扱い、コンポーネントを統合して新たなコンポーネントを作るアイデアが紹介されていました。それぞれのコンポーネントはGoやJavaなど様々な言語で記載可能です。コンパイラーがWasmのバイナリに変換します。コンポーネント間の入出力はWASIによるインタフェースで定義されます。 WASIは WIT(Wasm Interface Types) に基づく高度な型システムを利用できます。セッションではWITの型を一瞥できる wit-cheat-sheet が紹介されていました。 各コンポーネントはメモリー空間が独立しておりWASI以外には依存しないことで高い独立性を保ちます。そして、これらのコンポーネント間の呼び出しはナノ秒で行うことができるとのことです。WASIにより入出力が定義されているため、コンポーネント間は言語を揃える必要すらありません。Rustで書いたコンポーネントとGoで書いたコンポーネントを組み合わせて新しいコンポーネントを作るといったことが可能なようです。これはコードの再利用性を高め、システム構築の言語選定時に大きな自由度を与えてくれることになります。 異なる言語のコンポーネントがWASIでつながる 「 Cloud Native Wasm And How To Use It 」より引用 Wasm Edge Runtimeにより多くの環境でバイナリを高速に動かすことができる Wasm Edge Runtimeでの実行速度についての説明では、Wasmがネイティブの速度を超える事例について紹介していました。 sometimes faster than nativeについて 「 Cloud Native Wasm And How To Use It 」より引用 これは、特定の環境に最適化されていないネイティブバイナリーコードよりは、実行環境ごとに最適化されたWasmのほうが高速になりえるという話です。特定の環境に最適化されたネイティブバイナリーコードよりWasmのほうが速いというわけではないので注意してください。WasmはWasm Runtimeにより実行環境に応じて最適化されて実行できる利点がここでは主張されています。 また、プラグインにより機能を提供することで最小の機能を保ちながら必要な機能を追加していく事が可能であること、一例としてガベージコレクションをプラグインで提供したことが紹介されていました。 このガベージコレクションはKotlinからの強い要望があったそうですが、Kotlinの実行環境としてJVMだけでなくWasmという新しい選択肢が出てくることは大変興味深いです。 Cloud NativeにおけるWasiの有用性 WasmをKubernetes上で動かし、 Linuxコンテナーを置き換えるものとするアイディア も紹介されていました。 マイクロサービス化において、オーバーヘッドとなるLinux部分をWasmのモジュールへ置き換えることでコストの削減とスループットの向上につながる可能性があります。Wasmのモジュールはミリ秒で起動します。これはLinuxコンテナを起動するよりはるかに高速です。またWasmのモジュールが必要なメモリー量はLinuxコンテナよりも少ないです。そのため、Linuxコンテナの代わりにWasnのモジュールを用いることでコスト・運用ともに大幅な改善が期待できるとのことです。 また、Wasmはアーキテクチャに依存しないことも魅力的です。例えば私は普段Apple SiliconのMacBook Proで作業していますが、これにはArmをサポートするCPUとMetalをサポートするGPUが採用されています。ところがサーバーサイドでは一般的にx86をサポートするCPUとCUDAをサポートするGPUを使用することが多く、両環境でコンテナイメージを共有できませんでした。 Linuxコンテナイメージに変わって、Wasmコンポーネントを使用することでこの差は抽象化され両環境で一貫したバイナリを実行できるのはとても魅力的に感じます。フロントエンド寄りの技術だと思っていたWasmがCloud Nativeで大きな変革を起こす可能性があることは今回のセッションでの良い発見でした。 Wasmについて深く調べてみたくなりました。 LLMをエッジで動かすチュートリアル セッション後半では実際にWasmを使用して簡単な Hello World! を作成した後、より実践的なデモとして端末のGPUを使ってLLMモデルを動かしChatbotを実行するハンズオンが行われました。 LLMを使った生成AIは大量のコンピュータリソースを使用することがネックとなっています。WasmによりユーザーサイドのGPUを使用することでこの問題を解決できるかもしれません。このハンズオンのユースケースもまたWasmが今後大きく注目される部分だと思います。 Wasmは Cloud Native Wasm Day Hosted by CNCF においても詳しく紹介されているためそちらも合わせて見られることをおすすめします。 この他にもさまざまな魅力的なセッションがあり、全てを紹介できないので、いくつかをピックアップします。 Cloud-Native LLM Deployments Made Easy Using LangChain このセッションではLangChainを使用したクラウドネイティブなLLMのデプロイ方法について段階を追って説明されていました。モデルをデプロイするための手順として使用するモデルの定義、モデルの実行方法についての検討、LLMのパッキング、モデルのコンテナ化、複数モデルの統合の5段階で説明されていました。ML、特にLLMについてはモデルの作成だけでなく生成もコストが大きいこと、その結果に曖昧性が残ることなどから多くの項目を検討する必要があることを考えさせられました。 Strategies for Efficient LLM Deployments in Any Cluster 利用用途に応じたモデル選定について解説したセッションです。コストの大きなLLMsだけでなく必要に応じてSMLs(Small Language Models)=小規模言語モデルを選ぶメリットなどを紹介しています。 モデルの得手不得手を理解して、複数モデルの使い分けを意識したいと思います。 Why Is This so HARD? Conveying the Business Value of Open Source オープンソースプロジェクトの価値をいかに示すか解説したセッションです。 GitHub のIssueやPull Requestへラベルを付けて進捗を可視化する方法など、オープンソースに限らずプロジェクト管理で幅広く活用出来そうなアイデアが紹介されていました。 Kubernetes Maintainers Read Mean Comments KubernetesのIssueに投げられた意地悪なコメントについて紹介されていました。技術的な情報ではありませんが、コミュニティーがどのように機能しているかを理解し、円滑なコミュニケーションを推進する方法について示唆がありました。 To Infinity and Beyond: Seamless Autoscaling with in-Place Resource Resize for Kubernetes Pods KubernetesでPodsを運用するうえで悩みの種であるスケーリングについて、スケーリングがどのように機能するか、新しいin-Place Pod Resizingについて紹介されていました。 スケーリングにおける副作用についての学びが大きかったです。 Comparing Sidecar-Less Service Mesh from Cilium and Istio ML・データ部MLOpsブロックの 岡本 です。 Solo.ioのChristian Posta氏による、 Cilium と Istio Ambient Mesh を使ったサイドカーレスなサービスメッシュ構成の比較についてのセッションをご紹介します。本セッションのメインテーマでは、サイドカーレスなサービスメッシュについて、CiliumとIstio Ambient Meshを使ったそれぞれの構成を次の観点で比較していました。 コントロールプレーン データプレーン 相互認証 / mTLS 可観測性 トラフィック制御 サイドカーとは、Kubernetes Pod内のアプリケーションコンテナに対し補助的な役割を持つコンテナです。サービスメッシュを構成する場合に、従来はKubernetesの各Pod内でアプリケーションコンテナとEnvoyコンテナを稼働するサイドカーモデルが取られていました。 サイドカーモデルで構成するサービスメッシュには、サービスメッシュを透過的に構成できることや他のワークロードにリソースが占有されてしまう(ノイジーネイバー問題)を回避できるなどの利点がありました。 サイドカーコンテナの利点 「 Comparing Sidecar-less Service Mesh from Cilium and Istio 」より引用 一方でサイドカーモデルにはいくつかの課題もありました。例えば、アプリケーションコンテナとサイドカーコンテナの競合によるPodの起動・終了シーケンスの複雑化や、サイドカーのアップグレード時にアプリケーションの再起動が必要になることなどです。 サイドカーコンテナの課題 「 Comparing Sidecar-less Service Mesh from Cilium and Istio 」より引用 サイドカーレスなサービスメッシュはこのようなサイドカーモデルでの課題を解決するために提案されました。 次にセッションのメインテーマであるCiliumとIstio Ambient Meshを使ったサイドカーレスなサービスメッシュ構成の比較のうち、データプレーン部分の違いについて簡単にご紹介します。 サービスメッシュのアーキテクチャは大きくコントロールプレーンとデータプレーンに分かれています。データプレーンでは、主にPod間の通信を制御するプロキシの役割を担っており、コントロールプレーンではデータプレーンを管理しています。 データプレーンにおける構成の主な違いは、CiliumではOSI参照モデルのL4処理をeBPFで実装しているのに対し、Istio Ambient MeshではZtunnelで実装しているという点です。eBPFとはextended Berkeley Packet Filterの略であり、Linuxカーネルのコードを変更することなく、動的にカーネルの機能拡張を行う技術です。CiliumではeBPFを利用することでネットワーク処理を効率化しています。一方でZtunnelとはzero trust tunnelの略であり、Istio Ambient Meshのために実装されたNodeごとのプロキシです。Istio Ambient MeshではZtunnelを利用することでwaypoint proxy(L7処理を行うコンポーネント)へ効率的かつ安全にトラフィックを転送できます。 CiliumサービスメッシュのL4 「 Comparing Sidecar-less Service Mesh from Cilium and Istio 」より引用 Istio Ambient MeshサービスメッシュのL4 「 Comparing Sidecar-less Service Mesh from Cilium and Istio 」より引用 サービスメッシュの機能をどのネットワークレイヤで実現し、どこで実装しているのか、CiliumとIstio Ambient Meshでのそれぞれの構成の比較は次のスライドをご参照ください。 CiliumサービスメッシュでのL4・L7の分離 「 Comparing Sidecar-less Service Mesh from Cilium and Istio 」より引用 Istio Ambient MeshサービスメッシュでのL4・L7の分離 「 Comparing Sidecar-less Service Mesh from Cilium and Istio 」より引用 発表の最後の部分では、次の観点についてそれぞれのサービスメッシュ構成が適しているかそうでないかについて述べられていました。 リソースのオーバーヘッド 機能の分離 セキュリティの粒度 更新時の影響 これらの観点について各構成はそれぞれ次のスライドで評価されています。 Ciliumサービスメッシュの評価 「 Comparing Sidecar-less Service Mesh from Cilium and Istio 」より引用 Istio Ambient Meshサービスメッシュの評価 「 Comparing Sidecar-less Service Mesh from Cilium and Istio 」より引用 Ciliumでは特にリソースオーバーヘッドを小さくしたいケースに適しており、機能の分離という観点ではあまり適していないことがわかります。Istio Ambient Meshでは特に機能の分離という観点で適している一方で、リソースオーバーヘッドについてはCiliumでの構成にやや劣ることがわかります。このようにサイドカーレスなサービスメッシュにおいてはいくつかの構成が考えられます。またそれぞれの構成にはトレードオフがあるとわかりました。 サイドカーレスなサービスメッシュはサイドカーモデルでの課題解決のために提案されているとご紹介しましたが、サイドカーモデル自体についても日々改善されています。Kubernetesのv1.29では Sidecar Containers の機能がbetaで提供され、明示的にアプリケーションコンテナとサイドカーコンテナが区別できるようになります。これによりサイドカーモデルでのサービスメッシュでは、セッション内で挙げられていたいくつかの課題の改善が見込まれています。 今後のサービスメッシュにおいてはサイドカーモデルまたはサイドカーレス、CiliumまたはIstio Ambient Meshなどユースケースに応じてどの構成を取るべきか判断が必要だと感じました。 Future of Intelligent Cluster Ops: LLM-Azing Kubernetes Controllers SRE部ECプラットフォーム基盤SREブロックの織田です。 1日目のKyenoteは、すべてがMLやAI、LLMに関するものでした。今回紹介するセッションもLLMを利用してマニフェストに記述したコマンドや文章を理解し、効率的に実行する LLMNETES の紹介です。 LLMNETESは、OpenAIのGPT-3を含む、複数のLLMをサポートしておりローカルのモデルもデータセットとして使用できます。また、独自のインタフェースを実装することで独自のLLMも使用できます。 次にマニフェスト例とそれらをApplyすることでどのようなことができるのか4つほど紹介します。 1つ目は、ポート80を公開したnginx Podを3台作成する例です。 spec.inputに実行したいことを記述するとそれ通りにリソースの作成などを実施できます。複雑でマニフェストの作成に時間がかかりそうな場合やPodをすぐに用意したい場合などに役立ちそうに感じました。 apiVersion : llmnetes.dev/v1alpha1 kind : CommandExec metadata : name : command1 spec : input : Create 3 nginx pods that will serve traffic on port 80. 2つ目にデプロイされているPodとDeploymentのイメージをスキャンする例です。 spec.typeにScanImages、spec.resourcesにスキャンしたいリソースを記述すると対象のリソースのイメージをスキャンできます。 CIからツールを使って定期的にスキャンする場合は、CIでツールのセットアップなどを行う必要があるため、マニフェストをApplyするだけでスキャンできるのは便利そうに感じました。 apiVersion : llmnetes.dev/v1alpha1 kind : ClusterAudit metadata : name : cluster-audit-cves spec : type : ScanImages resources : - Pods - Deployments 3つ目にカオスエンジニアリングのシミュレーションをトリガーする例です。 spec.commandに実行したいことを記述します。以下は、default namespaceのPodを削除する例となります。 apiVersion : llmnetes.dev/v1alpha1 kind : ChaosSimulation metadata : name : chaos-simulation-cr spec : level : 10 command : break my cluster networking layer (or at least try to) 最後は、クラスタ内の非推奨APIを検出する例です。 spec.typeにClusterUpgradeCheckを指定します。現在、非推奨APIを検出する方法は多く存在しますが、マニフェストをApplyするだけで検出できるのはとても魅力的に感じました。 apiVersion : llmnetes.dev/v1alpha1 kind : ClusterUpgradeCheck metadata : name : cluster-upgrade-check spec : type : ClusterUpgradeCheck 1つのツールでイメージのスキャン、カオスエンジニアリング、非推奨APIの検出など様々なことができます。そのため、実施したいことごとにツールをインストールせずシンプルな構成になることで、アップグレードなどの運用コストが下がるというメリットがありそうに思いました。 LLMNETESは、開発中でありまだ本番で利用するまでには至っていませんが、検証などを行いながら導入を検討していきたいと考えています。 Is Your Image Really Distroless? - Laurent Goderre, Docker SRE部フロントSREブロックの三品です。 私が所属するSRE部フロントSREブロックでは、ZOZOTOWNが持つAPIの中でもクライアントに近い、frontendレイヤーのサービスを運用しており、言わばEmbedded SRE的な業務を行っています。 今回KubeCon EUに参加して私が業務の中で活かせそうだなと思ったセッションをいくつか紹介します。 このセッションでは、私たちが使っているDocker ImageはDistributionの影響を受けない状態にできていますか?と言うことを投げかけていました。 セッションの内容を説明すると、最初にDocker Imageを作成するのに必要なLinuxのDistributionやセキュリティとユーザビリティのジレンマについて説明していました。 what is a distro? 「 Is Your Image Really Distroless? - Laurent Goderre, Docker 」より引用 Usability-Security Dilemma 「 Is Your Image Really Distroless? - Laurent Goderre, Docker 」より引用 次にDistrolessなDocker Imageを生成するためDockerfileを作成しbuildするための方法やツールが説明されていました。 Multi-stage builds 「 Is Your Image Really Distroless? - Laurent Goderre, Docker 」より引用 buildKit 「 Is Your Image Really Distroless? - Laurent Goderre, Docker 」より引用 その後、bashなどがメインコンテナに含まれることによっての危険性を危惧し、initコンテナを利用し安全に依存関係のファイルをインストールする方法についてdemoを交えて説明していました。 init containers to the rescue! 「 Is Your Image Really Distroless? - Laurent Goderre, Docker 」より引用 私は今までDistrolessについてあまり意識を向けたことがありませんでした。 しかし、セッションを通してbashなどがメインコンテナにインストールされていることで実際にコンテナに侵入された場合の危険性について再考できました。 セッション内でinitコンテナの利便性について触れていましたが、initコンテナを利用してmainコンテナに何かを注入する処理を行うと、container起動までに追加の時間を必要とする可能性があります。セキュリティとのトレードオフになるため、initコンテナを利用する際にはこの点を考慮する必要があると感じました。 コンテナをセキュアに扱うと言う意味ですごく意味のあるものだと考えられますので、今回取り上げました。 Building Confidence in Kubernetes Controllers: Lessons Learned from Using E2e-Framework - Matteo Ruina, Datadog & Philippe Scorsolini, Upbound このセッションでは、Kubernetesコントローラーの信頼性を高める手法としてエンドツーエンドテスト(以降、E2Eテストと呼びます。)を取り上げ、E2Eテストの基礎理念から始まって特に他社2社の実践例について取り上げていました。 本記事ではその中でも自分が個人的に気になった部分を抜粋して紹介します。 本セッションではKubernetesのE2Eテスト( e2e-framework )をベースに説明されていました。 ※ e2e-frameworkとは、Kubernetesクラスター内で実行されるコンポーネントのエンドツーエンドテストを行うためのGo製フレームワークです。Kubernetesリポジトリ上にも多くのE2Eテストの例が公開されています。 その後E2Eテストのツールの特徴について述べられていました。 Goals 「 Building Confidence in Kubernetes Controllers: Lessons Learned from Using E2e-Framework 」より引用 日本語で翻訳すると以下のようになります。 採用に役立つ文書化されたフレームワーク 組み込みGoテストパッケージを使用する テストを構成するためのプログラム可能なAPIコンポーネントを提供する クライアントの機能を抽象化するヘルパー関数を提供する Kubernetesへの依存を避ける Go E2E Test Framework for Kubernetes でも同じようなことが書かれているのでこちらもご覧ください。 次にプログラムの動きについて説明をしていました。基本的なプログラムの動きについて説明します。 Example code 「 Building Confidence in Kubernetes Controllers: Lessons Learned from Using E2e-Framework 」より引用 環境ごとの設定情報を持つ 環境ごとの設定情報を元に実際のテストを紐づける テストの実行 DatadogやCrossplaneのE2Eテストの事例紹介ではE2Eテストフレームワークの比較や実装方法、問題点が紹介されていました。本記事ではその中でもフレームワークの比較について取り上げたいと思います。 Alternatives we considered 「 Building Confidence in Kubernetes Controllers: Lessons Learned from Using E2e-Framework 」より引用 セッションの中では、フレームワークがGoで書かれていること、そしてテストの時に環境のセットアップができると言う意味でe2e-frameworkが有効であると結論付けられていました。 セッション内では、実際にe2e-frameworkを実際に使ってみての課題感についても取り上げられていましたが、本記事ではその点については触れないので興味のある方は動画をご確認ください。 私は、Kubernetesのカスタムコントローラーを運用する業務は直接的には実施していませんが、Kubernetesの挙動を簡素化させたい時や機能をカスタムしたい時があります。そういった時にKubernetesのカスタムコントローラーは有効なので、引き続きKubernetesのE2Eテストの分野についてもウォッチしていきたいなと感じました。 現地の様子 今年のKubeCon EUはパリで開催され、日本とは全く違う景色を楽しむことができました。また、オリンピックを控えたこの時期には、オリンピックグッズを扱う店舗も見られ、オリンピックの雰囲気を肌で感じることができました。 パリの風景 会場のParis Expo Porte de Versaillesです。敷地内にはマクドナルドやコンビニも隣接されており、非常に行動しやすい会場でした。 会場入り口の様子 バッジピックアップの会場 会場の案内図 会場周囲にはトラムが走っており、交通の便がよく治安も良い場所のように感じました。地元のグループによる早朝パリ観光マラソンも開催されていました。 会場近くの風景 続いては、KubeCon EUのランチです。KubeCon EUのランチでは、肉、魚、ビーガン向けの食事などが提供され、参加者の多様な食事ニーズに対して細やかな配慮が施されていました。 会場で提供されたランチ(1日目) 会場で提供されたランチ(2日目) また、今回もCloud Native Community Japanさん主催のもと 日本人交流会 が実施され、各社の参加されているエンジニアの方々と意見交換ができとても楽しい時間を過ごすことができました。 日本人交流会の様子 ブースについて KubeCon EUはCloudNativeConと同時に開催されKubernetesをはじめとする様々なOSSプロジェクトのブースや企業ブースがあり、それらのプロジェクトに参加するメンテナーと身近に話せる機会でもあります。 実際に我々も社内で利用するOSSプロジェクトのブースに出向き機能要望や意見交換を実施できました。 KubeCon EUはOSSプロジェクトのメンテナーとの距離が近く議論できる機会になるなと感じました。 Fluxブースの様子 Istioブースの様子 参加に向けてのTips できるだけ早めに準備する 参加の可否をできるだけ早く決めて、一刻も早く動くことで自由度が広がります。 KubeCon EUのチケットは早い時期に買うほど大幅に割引が効くため、参加コストを大きく抑えられます。 例えば、2023年11月28日までにチケットを購入していた場合$1149で参加できました。しかし、2024年3月17日以降にチケットを購入すると$2229となっておりほぼ倍の金額となっていました。 とくに宿泊する宿については、時間が経つにつれて条件の良い宿が埋まっていくため早めに準備することで現地での移動を便利にできます。 時差ボケに備える フランスは日本と比べて8時間遅く時間が進みます。フランスの朝8時は日本では深夜のため、朝起きることは簡単でもセッションが進むにつれて眠たくなる状態でした。 そのため意識的に睡眠を取って体内時間をずらす必要がありました。 質問を用意しておく セッション終了後には質疑応答が用意されていることが多く、登壇者との交流の機会でもあるため、セッションの内容を予習しておき疑問点をまとめておくとより有意義な参加になります。 最後に 今年からKubeCon EUはオフライン形式で統一され、会場は活気にあふれていました。 今回はKubeCon経験者(NAも含む)からKubeCon初参加者まで、幅広いメンバーで参加しましたがオープンソースプロジェクトのメンテナーや発表者との距離が近かったため、Kubernetesおよび、その関連エコシステムについての深い学びを得ることができました。 ZOZOでは一緒に働くエンジニアを募集しています。また、KubernetesやCloudNativeが大好きなエンジニアも大歓迎です。ぜひご応募ください。 corp.zozo.com corp.zozo.com
アバター
こんにちは、カート決済SREブロックの飯島と、ECプラットフォーム基盤SREブロックの織田です。 本記事では複数チームで運用する共通のAWSアカウントとKubernetesにおけるコストの可視化についてご紹介します。 背景 コスト可視化に対する課題 課題解決へのアプローチ AWSリソースのコスト可視化 AWSコスト配分タグ タグの定義と運用ルール タグの付け方 AWS Cost Explorer AWSコスト配分タグの活用例 Kubernetesクラスタのコスト可視化 Kubecost 比較検討 カスタムバンドル採用の決め手 アーキテクチャ 可視化の仕組み ダッシュボード 効果 コスト可視化の活用事例 最後に 背景 現在、ZOZOTOWNはモノリスなサービスを機能ごとに分け、マイクロサービスに移行しながらモダンアーキテクチャへのリプレイスを実施しています。マイクロサービスの移行先としてクラウドプラットフォームはAWSを、アプリケーション実行基盤はKubernetesを選定しています。両者とも複数チームで共通アカウントを利用するマルチテナント構成となっています。 AWSアカウントやKubernetesをマルチテナント化することには、メリットとデメリットの両面があります。 マルチテナントのメリットとしては、1つのAWSアカウントや1つのKubernetesクラスタですべてのサービスを管理できるため、運用作業が簡素化されます。Kubernetesの場合、アップグレードの回数が減り、共通の設定を他のクラスタに展開する必要がないため、運用コストを削減できます。 しかしその一方で、マルチテナントにすることで課題も生じます。特に、コスト可視化の面での影響が大きく、1つのアカウントやクラスタを複数のチームやサービスが利用しているため、チームやサービス単位でのコスト分離が困難になり、コストの可視化が難しくなります。 このように、マルチテナントにすることで運用が簡素化される半面、コストの可視化や分離が難しくなるというトレードオフが発生しておりました。 コスト可視化に対する課題 上記で述べた通り、マルチテナントでは共有されたリソースを複数のチームやサービスが利用するため、個別のコスト把握が困難になる傾向があります。1つのAWSアカウントやKubernetesクラスタで請求やリソース使用量が集約されてしまうため、チームやサービス単位でコストを明確に分離・可視化できていませんでした。 そのため、各プロジェクトやチームのコスト負担を正確に見積もることが難しくなり、予算計画の精度が下がります。マルチテナントの利点を生かしつつ、コスト可視化を実現するためには、仕組みを構築する必要がありました。 次に紹介するアプローチを講じることで、マルチテナントでもチームやサービス単位でのコスト可視化を実現し、より適切な予算管理を行えるような取り組みを実施しました。 課題解決へのアプローチ 課題解決のアプローチとして、AWSコスト配分タグの活用と、KubernetesクラスタへのKubecostの導入を実施しました。次のセクションからはそれぞれの詳細について説明します。 AWSリソースのコスト可視化 AWSコスト配分タグ AWSコスト配分タグ とは、AWSリソースに割り当てることができるラベルです。このタグにより、リソースのコストをより詳細に追跡し、分析できます。 タグの定義と運用ルール AWSコスト配分タグとして以下の表に示すタグを新たに作成しました。 Key Value CostEnv AWS環境を表す文字列。「dev」や「prd」など。 CostService どのサービスで使用しているかを表す文字列。Namespace名に統一。 CostTeam どのチームが管理しているかを表す文字列。 各タグのValueの値には、命名規則を設けてフォーマットを統一しています。原則として、AWSリソースには「CostEnv」と「CostService」を付けることを必須としています。CostServiceが付いていれば管理しているチームは把握できるため、「CostTeam」は任意としています。 タグの付け方 基本的にAWSリソースはAWS CloudFormationで管理されています。そのため、スタックテンプレートを使用してタグを付けます。以下にサンプルのコードを示しますが、タグの書き方はリソースによって異なりますので、詳細は公式ドキュメントをご参照ください。 Resources : IAMUserSample : Type : "AWS::IAM::User" Properties : UserName : "IAMUserSample" Tags : - Key : CostEnv Value : "dev" - Key : CostService Value : "zozo-techblog-sample" 上記のサンプルのコードを実行すると、AWSコンソールから以下のようにタグ付けされていることが確認できます。 注意すべきポイントはタグ付けができないAWSリソースも存在することです。また、AWS CLIではタグ付けができてもAWS CloudFormationではできない場合もあります。以下の公式ドキュメントに情報がまとまっているので、合わせてご参照ください。 Resource types you can use with AWS Resource Groups and Tag Editor AWS Resource Groups Tagging API Reference AWS Cost Explorer AWS Cost Explorerとは、AWSのコストおよび利用状況を可視化し、分析するためのツールです。事前にAWS Billing and Cost Managementコンソールから タグの有効化 を実施することで、AWSコスト配分タグを利用した検索やフィルタリングが可能です。 AWSコスト配分タグの活用例 AWS Cost Explorerの操作方法について説明します。以下の図は、検索とフィルターを使用していない状態で、AWSアカウント全体のコストの合計が表示されています。 例えば、ディメンション機能を使用してTag:CostServiceを選択すると、サービスごとのコストの合計が表示されます。 また、ディメンション機能で「サービス」を選択し、フィルター機能を使用してTag:CostServiceから特定のサービスを選択すると、そのサービスのAWSリソースごとのコストが表示されます。 Kubernetesクラスタのコスト可視化 Kubecost Kubecost とは、Kubernetesクラスタ内のリソース利用とコストを追跡・管理するためのツールです。無料版とエンタープライズ版が提供されています。また、AWSが提供している、Amazon EKS上でKubecostを利用するのに最適化された カスタムバンドル もあります。弊社ではAmazon EKSでKubernetes環境を構築しているため、このカスタムバンドルを採用しました。 比較検討 どの種類を採用するか決めるにあたり、主に「データの閲覧可能な期間」と「利用料金」の2つの観点で検討しました。以下の表は各種類の特徴をまとめたものです。 Kubecostの種類 データの閲覧可能な期間 利用料金 カスタムバンドル 15日前までの制限あり 無料 無料版 15日前までの制限あり 無料 エンタープライズ版 制限なし 有料 カスタムバンドルと無料版では、データの閲覧可能な期間が15日前までに制限されており、それ以前のデータは閲覧できません。コストを見積もる際には、月や年単位の過去のデータが必要となるため、コスト可視化の用途には適していませんでした。利用料金についてはどちらも無料ですが、カスタムバンドル以上のメリットがない無料版は選択肢から外しました。 一方、エンタープライズ版ではデータの閲覧に制限がなく、無期限に閲覧できます。ただし有料であり、Amazon EKSクラスタで稼働するNode数に応じて料金が決まります。利用料金を算出した結果、エンタープライズ版の採用が即決できるものではありませんでした。 カスタムバンドル採用の決め手 Kubecostにはデータをエクスポートできる API が提供されています。詳細については後述しますが、このAPIを使用してデータをAmazon S3に保存することで、閲覧可能な期間の制限に縛られなくなります。このようにして、カスタムバンドルのネックが解消できたため、採用することにしました。 余談ですが、カスタムバンドルのメリットの1つに、AWSサポートでの問い合わせが可能ということが挙げられます。問題発生時の問い合わせ先がAWSに統一できることはユーザーとしてもメリットとなります。 アーキテクチャ 弊社で採用しているKubecostのアーキテクチャです。 公式のアーキテクチャ も合わせてご確認いただくと理解しやすいかと思います。 Kubecost内のコンポーネントごとの役割としては以下のようになっています。 Kubecost Cost-Analyzer Frontend: nginxを実行し、Prometheusへのルーティングを行う Cost model: コストの計算とメトリクスを提供する AWS Sig V4 proxy: Amazon Managed service for Prometheusからクエリするためのプロキシ。パスワードレス認証が可能になり、AWS認証情報が公開されるリスクを軽減できる Prometheus Prometheus Pod: Kubecost Cost-Analyzerが生成したデータをAmazon Managed service for Prometheusにリライトする Amazon Managed service for Prometheus: コストとメトリクスを格納する時系列データストアでKubecostが生成したデータを格納する Kubecostは、Kubernetes APIサーバーからNode、Pod、コンテナ、ボリュームなどのメトリクスを収集します。主要なクラウドプロバイダー(AWS、GCP、Azure、etc.)の価格データが組み込まれており、そのデータを使用して、リソース使用量に基づきコストを算出します。 また、収集したメトリクスと価格データを組み合わせて、リソースごとのコストをモデリングします。CPUとメモリの使用量、永続ボリュームの種類とサイズ、ネットワークの送受信データ量などから、それぞれのコストを計算します。 Namespace、Deployment、Service、Podなどのレベルでコストを集計しているため、特定のワークロードのコストを確認できます。Kubecostはリザーブドインスタンスやスポットインスタンスの利用、適切なインスタンスタイプ選択に関する最適化の提案をします。また継続的にメトリクスを収集することで、長期的なコストトレンドの分析や予測も可能になります。 Web UIやGrafanaを使用することで、メトリクスの可視化やアラートを設定できます。しかし弊社では、先述したようにカスタムバンドルでのデータ永続化に課題があったため、独自に可視化の仕組みを構築しています。詳細に関しては、次のセクションで説明します。 可視化の仕組み 先述したように、KubecostのAPIを使用してデータをエクスポートし、Amazon S3に保存しています。その処理と、Amazon S3のデータを可視化する仕組みについて説明します。アーキテクチャは以下の通りです。 まず、毎日定時に起動するGitHub ActionsのWorkflowからAPIを実行し、前日のNamespaceごとのコストをCSVファイルで取得します。その後、取得したCSVファイルをAmazon S3にアップロードします。APIは以下のcurlコマンドで実行しています。 curl " http://kubecost-cost-analyzer.kube-support.svc.cluster.local:9090/model/allocation " \ -d window =yesterday \ -d aggregate =namespace \ -d accumulate =true \ -d format =csv \ -G 次に、Amazon S3のイベント通知機能を使用し、アップロードしたCSVファイルをAmazon SQSのキューに通知します。最後に、それをSplunkの Add-on で取り込み、ダッシュボードで可視化しています。弊社でのSplunkの活用に関する情報はテックブログで公開されていますので、そちらもぜひご覧ください。 techblog.zozo.com techblog.zozo.com ダッシュボード ダッシュボードではデータを表示したい日付の範囲と、対象のAmazon EKSクラスタの環境が選択できます。選択した日付の範囲内の、Amazon EKSクラスタのコストの合計が表示されます。 また、日ごとのAmazon EKSクラスタ全体のコストとNamespaceごとのコストが表示され、選択した日付の範囲内で推移を確認できます。 先述したようにKubecostではNamespaceだけでなくDeploymentやServiceなどのコストも集計しています。またコストの分析や予測、監視といった機能も提供していますが、弊社ではチームやサービス単位でのコストの可視化を目的としたため、上記の仕組みを採用しました。 効果 これまでの課題解決へのアプローチにより、マルチテナントのAWSアカウントとKubernetesのコスト可視化が実現できました。その結果、コストを正確に見積もることができるようになり、適切な予算管理が可能になりました。またチームやサービス単位でコストが明確に分離・可視化できるようになりました。 次に、コスト可視化の活用事例をご紹介します。 コスト可視化の活用事例 ZOZOTOWNのカート投入処理では、Webサーバとデータベースの間にキューイングシステムを挟むことでリクエストのキャパシティコントロールを実現しています。このシステムで使われているAmazon Kinesis Data Streamsのシャード数を変更しました。 techblog.zozo.com 以下の図は、左側がシャード数変更前のコストを示し、右側がシャード変更後のコストを示しています。関連リソースのAmazon DynamoDBを含め、大幅なコストの削減が実現できました。 このようにコスト可視化によって効果が明確に見えるようになったことは、コスト最適化の業務に取り組むうえでのモチベーション向上に繋がりました。 最後に 本記事では、マルチテナントのAWSアカウントとKubernetesにおけるコスト可視化の課題に対し、AWSコスト配分タグの活用とKubecostの導入による解決事例を紹介しました。同様の課題を抱えている方がいれば、ぜひ参考にしてください。 SREにおいてコスト最適化は重要なミッションです。今回ご紹介したコスト可視化の仕組みを利用し、ZOZOTOWNの信頼性向上の一環として取り組んでいきます。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com
アバター
はじめに こんにちは。DevRelブロックの @wiroha です。2024年3月22日〜24日に「 try! Swift Tokyo 2024 」が開催されました。ZOZOはGOLDスポンサー・DIVERSITY & INCLUSIONスポンサーとして協賛し、ブースを出展しましたので現地のレポートをお届けします! 目次 はじめに 目次 会場 スポンサーブース アンケートの回答 印象に残ったセッション Swiftの型推論を学ぼう コード署名を楽しく乗り切る方法 Party、その他 アフターイベント LT1. SwiftPM マルチモジュール構成への第一歩 LT2. SwiftとC++を利用した画像処理プログラミング LT3. Introducing Pkl 感想戦 最後に 会場 会場は前回(2019年)と同じ、ベルサール渋谷ファーストです。try! Swiftのマスコットキャラクター「Riko」ちゃんのパネルやポスターが出迎えてくれます。 セッションルームはとても広く、たくさんの人が入れるようになっていました。朝から満員のようでした! 休憩ができるBreak Areaではかわいらしいお菓子やコーヒーなどが提供されていました。食べるのがもったいなくなります! こちらはスポンサーブースやAsk the Speakerコーナーのあるエリアです。休憩時、昼食時は特に大きな賑わいを見せ、皆さま交流を楽しんでいました。 スポンサーブース 各社さまざまな工夫を凝らしたブースが出展されていました。ZOZOのブースはたくさんのパネル・ノベルティにマネキンで存在感を出していました。 今回のメイン企画は「みんなの失敗」です。ZOZOには「#みんなの失敗」というSlackチャンネルがあり、失敗を共有することで共に学び、時に笑い、「日々進歩」しようという社内企画が行われています。その出張版として、ブースで皆さまの「開発の失敗」や「技術的負債」をお聞きしました。参加してくださった方には、失敗を水に流せるようにオリジナルトイレットペーパーのノベルティを差し上げました! トイレットペーパーの柄はZOZOのロゴをモチーフにしており、「ZOZO」の文字もこっそり隠れているんです! みなさんは気付きましたか? アンケートの回答 1日目のお題、「水に流したい開発の失敗」には最終的にパネルに貼りきれないほどの回答をいただきました! ヒヤリハットで済むものから、「これは深刻そう…」というものまで、さまざまな失敗が集まりました。なぜそうなったか、その後どうしたのかといった会話が弾みました。 2日目のお題は「水に流したい技術的負債」です。一度回答した方も「どんな回答が増えたか見に来ました」とたずねてくださる方が多数いました。これから勉強をはじめる方や新規開発中の方など、失敗や負債がまだない方には最近勉強していることを回答いただきました。 「英語」という回答には納得の声が出ました。try! Swiftは英語話者の参加が多いため、勉強しようというモチベーションが上がりますね。 「Apple Vision Proを買いましたか?」の質問は、悩んでいる方・次世代機を待っている方が多数ではあるものの、既に持っている・もうすぐ手に入るという方もいました! ZOZOでは会社でApple Vision Proを保有しており(技適特例申請済み)、ブースで体験できるようにしたところ一時は行列ができるほど盛況しました。 AirPlayでApple Vision Proで見ているものを共有しながらデモしている様子 印象に残ったセッション ここからは参加したエンジニアより、セッションの感想を紹介します。 Swiftの型推論を学ぼう speakerdeck.com 技術開発部のばんじゅんです。わたしはおもちメタルさんの「Swiftの型推論を学ぼう」をおすすめしたいと思います。コンパイラの理論的な部分はとっつきにくいものも多いですが、興味を持って調べるとおもしろい分野でもあります。このセッションでは、型推論ではまず押さえておきたい単純な例から始めて、まるで手で推論を計算するかのように学ぶことができます。一歩発展して、SwiftらしくOptionalの扱いの一部も計算できます。簡単に説明されていますが二度三度みてもよいくらい内容は詰まっていると感じます。より多くの人がコンパイラをつくる技術に興味をもつ入口のひとつになっていると良いなと思いながらセッションを聞きました。 登壇資料のスクリプト付きバージョン も公開されているので、内容をテキストで確認したいときはそちらが便利です。 コード署名を楽しく乗り切る方法 ZOZOTOWN開発1部iOSブロックの荻野です。このセッションでは、アプリをリリースするときに必ず通るコード署名について聞くことができました。発表の最初、「コード署名でつまずいたことがある方はいますか?」という問いかけに対して、会場にいたほとんどの人が手を挙げていました。自分と同じようにコード署名に苦い思い出のある人がこんなにいるんだと安心しました(笑)。 セッションの中では、コード署名において必要な要素を4つに分解し、それらをパズルのピースのように見立てていました。そうすることで起きているエラーのどこに問題があるのかが視覚的にわかりやすくなり、どう解決したらよいのかがすんなり理解できました。 発表の最後では、多くの参加者が「コード署名が楽しくなったと感じた」と手を挙げており、とても面白くためになる発表でした。発表資料は現時点では未公開のようで、公開されたらぜひ見ていただきたいです。 Party、その他 他にもSpeaker Dinner、スタンプラリー、Party、ワークショップなど企画が盛りだくさんのカンファレンスでした。 アフターイベント 開催翌週の3/27(水)、アフターイベントとして「 try! Swift Tokyo 2024 After Talk 」を開催しました。try! Swift Tokyo 2024のDIVERSITY & INCLUSIONスポンサーとして協賛しているピクシブ株式会社、株式会社ZOZO、STORES 株式会社による共催イベントです。モバイル開発に関するLTと、try! Swift Tokyo 2024の感想戦を行いました。当日の様子はYouTubeのアーカイブで公開しています。 www.youtube.com LT1. SwiftPM マルチモジュール構成への第一歩 speakerdeck.com ピクシブ株式会社 / 山本 小龍 @shoryu927 LT2. SwiftとC++を利用した画像処理プログラミング speakerdeck.com 株式会社ZOZO / 加藤 祥真 @shoma10170806 LT3. Introducing Pkl speakerdeck.com STORES 株式会社 / 榎本 健太 @enomotok_ 感想戦 感想戦はSTORES 株式会社の坂田( @huin )さま、ピクシブ株式会社の @FromAtom さま、STORES 株式会社の榎本さま、株式会社ZOZOの加藤で行いました。全体を振り返って楽しかった点や印象に残っているトーク、逆に難しかったので復習したいトーク、思い出に残った企画などを語り合いました。アフターイベントにご参加くださったみなさま、ありがとうございました! LT・感想戦の登壇者のみなさま 最後に try! Swift Tokyo 2024本編からアフターイベントまで、交流と学びを楽しませていただきました。オフラインカンファレンスは普段会えない方々とお話ができる貴重な機会です。このような場を提供くださったtry! Swift Tokyo 2024の運営の皆さま、本当にありがとうございました! ZOZOではカンファレンスに参加する方には参加費用の補助、登壇する方には登壇支援を行っています。一緒にサービスを作り上げてくれるiOSエンジニアを募集していますので、ご興味のある方は以下のリンクからぜひご応募ください。 hrmos.co カジュアル面談も実施していますので、「直近での選考応募は考えていないけども話を聞いてみたい」という方からもご応募お待ちしています! hrmos.co
アバター
はじめに こんにちは、SRE部 検索基盤SREブロックの花房です。普段は、ZOZOTOWNの検索関連マイクロサービスにおけるQCD改善やインフラ運用を担当しています。 以前まで、検索基盤を支えるチームではElastic Cloudの特権アカウントをメンバーで共用していました。本記事では、2023年4月にリリースされた Elastic CloudのRBAC(Role-Based Access Control)機能 を活用して、特権アカウントの共用から脱却した取り組みについて紹介します。さらに、既存機能と組み合わせることで実現した、効率的な権限管理についても紹介します。 同様の課題を抱えている読者の方には、下記の部分で参考になれば幸いです。 Elastic CloudにおけるSSOの活用 Elastic CloudのRBACによる権限管理の実例 Elastic Cloudアカウント情報を利用した、ElasticsearchのRole Mappingによる権限管理の実例 目次 はじめに 目次 背景・課題 Elastic Cloudの利用方法 何故、特権アカウントを共用していたか 権限管理における課題 解決策 Elastic Cloudの権限管理 Organization RBACの利用 SSOによるアカウント管理の省略 Elastic Cloudアカウントを利用したElasticsearchの権限管理 Cloud Roleに対するRole Mapping SSOアカウントに対するRole Mappingの追加 ElasticsearchのオリジナルRole 共通のRoleと、チームごとのRoleの作成 TerraformによるIaC化 結果 おわりに 背景・課題 Elastic Cloudの利用方法 ZOZOTOWNの検索基盤では、大規模な商品データを扱うためにElastic Cloud上のElasticsearch 1 を利用しています。Elastic Cloudは、ElasticsearchやKibanaなどのElastic製品をクラウド上で利用できるサービスです。検索基盤を支えるチームは、それぞれ担当業務が異なりますが、全チームがElastic Cloudにアクセスします。以下に業務の例を挙げます。 Elasticsearchを利用する検索機能の開発 ノード数やマシンスペックの増強 アラート発生時の調査 一般的に、クラウドサービスへのアクセスはメンバーそれぞれのアカウントを使用すべきです。さらに、そのアカウントには担当業務にあった権限セット(以降、 Role と表します)を付与します。このように管理することで、情報漏洩などのセキュリティ上のリスクを防ぐことができます。しかし、本記事の取り組みを実施するまで、私たちは特権アカウントを共用してElastic Cloudにログインしていました。その状態を下記の図に示します。 何故、特権アカウントを共用していたか Elastic Cloudの利用を開始した当初、登録できるアカウントは1つまでという制限があったため、やむを得ず特権アカウントを共有していました。 特権アカウントを使用すると、Elasticsearchクラスタの管理を担うDeploymentに対して全ての操作が可能です。そのため、特権アカウントの共用は誤操作による重大な事故を引き起こすリスクがありました。例えば、誤って本番環境のクラスタを削除してしまうことが考えられます。 そこで、操作頻度が高くリスクも大きいDeployment管理にはIaC化を施しました。IaC化により、管理画面からDeploymentを変更・削除する機会をなくすことで事故を防いでいました。こちらのIaC化の詳細については、以前の記事を参照ください。 techblog.zozo.com その後、 Elastic Cloudのアップデート により、Organizationへのアカウント追加が可能になりました。しかし、全てのアカウントに管理者権限が付与されてしまうため、細かい権限管理はできませんでした。以上の理由から特権アカウントの共用が続いていました。 権限管理における課題 これまでに説明した利用方法の問題を整理します。 アカウントの共有によるセキュリティ上のリスク 誤操作により重大な事故を引き起こす可能性について、対策を施していたが排除はできていない 上記の問題は、単純にメンバーごとにアカウントを分離すれば解決できそうに思います。しかし、アカウント分離について下記の課題があり、状況は変わりませんでした。 チームに合ったRoleを柔軟に設定できない メンバーごとのアカウントを新たに作成する場合、アカウント管理が大きなトイルになる 以降では、上記の問題・課題の解決方法と、改善後の権限管理について説明します。 解決策 Elastic Cloudの権限管理 2023年4月、 Elastic CloudのRBAC機能がリリース されました。この機能を利用すると、Organizationへ招待したメンバーに対して細かく権限を設定できます。 私たちは、この機能を利用することで特権アカウントの共用から脱却できました。 Organization RBACの利用 Elastic CloudのRBACでは、用意されたRole(以降、 Cloud Role 2 と表します)をメンバーに付与して権限を管理します。Cloud Roleには下記の5つが存在します。 Organization owner Billing admin Admin Editor Viewer 私たちは、Deployment操作の制限のためにAdminとViewerを使用しています。そのため、今回はその2種類のみ紹介します。その他のCloud Roleの説明は、 公式ドキュメント をご確認ください。 Cloud Role 説明 Admin Deploymentの作成や、プロパティ管理、セキュリティ権限の管理が可能 Viewer Deploymentの閲覧が可能 AdminとViewerは、Deployment単位で設定可能です。私たちは、SREチームに全DeploymentのAdmin、バックエンドチームやMLチームには全DeploymentのViewerを設定しています。このように設定している理由は、バックエンドやMLチームの誤操作による事故を防ぐためです。 Deployment管理と同様に、OrganizationのRBACもIaC化したいと考えていました。しかし、Organization機能がTerraformに対応していないため、今回は断念しました。現在は手動で権限を管理していますが、下記の理由から保守は難しくなく問題にはなっていません。 検索基盤を支えるチームは合計20人程度 設定するCloud Roleは、AdminまたはViewerの2種類のみ SSOによるアカウント管理の省略 RBACのためには、利用メンバーごとにアカウントを分ける必要があります。しかし、新たにElastic Cloudアカウントを作成すると、管理者側と利用者側の両方でアカウント管理がトイルになります。そこで、Elastic CloudへのSSOログインに会社管理のGoogleアカウントを用いるようにしました。 GoogleアカウントでのSSOにより、アカウント管理のトイルを削減できました。招待作業は残りますが、退職時の削除作業やパスワード変更は不要になります。退職時は会社側でGoogleアカウントが削除されるため、自動的にElastic Cloudにもログイン不可になります。また、利用者はElastic Cloud用にアカウントを使い分ける手間がなくなりました。会社管理のGoogleアカウントは、Elastic Cloud以外にも様々なサービスで普段利用しているため、統一することで利便性の向上にも繋がりました。 SSOを利用するために特別なことは必要ありません。通常のアカウントと同様に、Organizationに招待する際、Roleを付与した上でGoogleアカウントのメールアドレスに招待メールを送るだけです。利用者は招待メールからログイン画面に進み、GoogleアカウントでSSOログインするとOrganizationに参加できます。 ここまで説明した改善により、Elastic Cloudへのアクセス方法は下記の図のようになりました。 Elastic Cloudアカウントを利用したElasticsearchの権限管理 Elastic Cloudアカウントは、Elasticsearchへのアクセスにも利用可能です。直接アクセスするのではなく、クラスタに紐づくKibanaを通してGUIでアクセスします。Deploymentの管理画面からKibanaにアクセスすると自動的にログインされますが、下記のようなログイン画面が表示されることもあります。その場合は下側の Login in with Elastic Cloud を選択してください。 一方、Elastic CloudとElasticsearchは権限管理の仕組みが分かれているため、Cloud RoleではElasticsearchの権限を定義できません。それらを関連付けるため、 Elastic Cloudのアカウント情報を利用した設定 がElasticsearchのRole Mappingに標準で追加されました。 Role Mapping はElasticsearch内のRBACを実現する機能で、以前から存在しました。 その追加された設定では、Elastic Cloudアカウントでのアクセス時、Cloud Roleに対応するElasticsearchのRoleを付与します(以降、 Default Mapping 3 と表します)。この設定のように、Elastic Cloudのアカウント情報はElasticsearchから参照可能になっています。 以下では、Default Mappingと、私たちが追加したSSOアカウントに対するRole Mappingについて説明します。 Cloud Roleに対するRole Mapping Default Mappingが適用されると、Cloud Roleに対応するElasticsearch側のRole(以降、 Stack Role と表します)が付与されます。 Default Mappingにおいて、Stack Roleとして設定されるものは Elaticsearch標準のRole です。AdminとViewerについて、Cloud RoleとStack Roleのマッピング関係を下記に示します。 Cloud Role Stack Role Admin superuser Viewer viewer つまり、アクセスしたDeploymentについて、AdminのCloud Roleを持っていればStack Roleはsuperuserになります。ViewerのCloud Roleを持っていればStack Roleはviewerになります。 私たちは、バックエンドチームやMLチームにCloud RoleとしてViewerを付与しているため、Stack Roleとしてviewerが付与されます。このviewerでは操作が閲覧のみに制限されるため、そのまま利用するにはElasticsearchに関する権限が足りませんでした。バックエンドチームやMLチームは開発において、Index自体やIndex内のドキュメントを編集する必要があるためです。そこで、オリジナルのRoleを作成して適用することにしました。 先に、オリジナルのRoleをRole Mappingで適用する方法を説明します。その後、オリジナルのRoleについて説明します。 SSOアカウントに対するRole Mappingの追加 Elasticsearchにおいて、Elastic Cloudアカウントを対象としてオリジナルのRoleを付与するには、下記の画面のように設定します。 このUser IDを条件とする方法(以降、 Custom Mapping と表します)は、Elasticのサポートの方から教えていただきました。User IDはプロフィール画面やOrganization画面から確認できます。 Custom Mappingの説明のため、上記では画面をお見せしました。実際には、私たちはGUIではなくTerraformで管理しています。Elastic CloudのOrganizationはIaC化できませんでしたが、ElasticsearchのRole MappingはIaC化可能でした。公式のプロバイダー elastic/elasticstack を使用して、下記のように設定できます。 resource "elasticstack_elasticsearch_security_role_mapping" "zozo_default_role_mapping" { provider = elasticstack.cuslter_1 # Role Mappingを作成するクラスタを指定 name = "dev-zozo-default-role-mapping" enabled = true roles = [ # 付与するRoleを指定 elasticstack_elasticsearch_security_role.zozo_default_role.name ] rules = jsonencode ( { # Elastic CloudアカウントのUser IDが一致すればRoleを付与 any = [ for user_id in local.all_ec_user_ids : { field = { username = user_id } }] } ) } ここで説明したCustom Mappingの追加により、Elaticsearch内においてもアカウントを増やさず、チームごとの詳細な権限管理を実現しました。 ElasticsearchのオリジナルRole 以下では、Custom Mappingで付与するオリジナルのRoleと、そのIaC化について説明します。 共通のRoleと、チームごとのRoleの作成 オリジナルのRoleの権限設計は下記のように進めました。 各チームに、環境・Deployment・Indexごとに必要な操作をヒアリング ヒアリング結果の必要な操作と、 Cluster Privileges・Index Privileges との照らし合わせ 運用のしやすさを考慮したRole設計 権限設計の結果、オリジナルのRoleとして下記の2種類を作成しました。実際は本番・ステージング・開発の環境ごとに、さらに分かれています。 全員が最低限必要となる権限をまとめたRole チームごとに追加で必要となる権限をまとめたRole 1を作成した理由は、各チームが最低限必要となる権限がまとまっていたため、また、チームごとのRoleを冗長にせず管理しやすくするためです。2を作成した理由は、チームごとに権限が欲しいDeploymentやIndexが異なり、共通のRoleにはまとめられない権限が存在したためです。 TerraformによるIaC化 オリジナルのRoleもIaC化が可能であり、Role Mappingと合わせて下記のように設定しています。Indexに対する権限設定では、 dynamic blocks を使用して簡素化しています。 # 共通Role resource "elasticstack_elasticsearch_security_role" "zozo-default-role" { provider = elasticstack.cluster_1 name = "dev-zozo-default-role" cluster = local.zozo_default_role_cluster_privileges dynamic "indices" { for_each = local.zozo_default_role_index_privileges content { names = indices.value.names privileges = indices.value.privileges } } } # 共通RoleのRole Mapping resource "elasticstack_elasticsearch_security_role_mapping" "zozo_default_role_mapping" { provider = elasticstack.cluster_1 name = "dev-zozo-default-role-mapping" enabled = true roles = [ elasticstack_elasticsearch_security_role.zozo_default_role.name ] rules = jsonencode ( { # 全員のElastic CloudアカウントUser IDを条件に指定 any = [ for user_id in local.all_ec_user_ids : { field = { username = user_id } }] } ) } # バックエンドチーム用Role resource "elasticstack_elasticsearch_security_role" "backend_team_role" { provider = elasticstack.cluster_1 name = "dev-backend-team-role" cluster = local.backend_team_role_cluster_privileges dynamic "indices" { for_each = local.backend_team_role_index_privileges content { names = indices.value.names privileges = indices.value.privileges } } } # バックエンドチーム用RoleのRole Mapping resource "elasticstack_elasticsearch_security_role_mapping" "backend_team_role_mapping" { provider = elasticstack.cluster_1 name = "dev-backend-team-role-mapping" enabled = true roles = [ elasticstack_elasticsearch_security_role.backend_team_role.name ] rules = jsonencode ( { # XチームメンバーのElastic CloudアカウントUser IDを条件に指定 any = [ for user_id in local.backend_team_ec_user_ids : { field = { username = user_id } }] } ) } local.tf は下記サンプルのように設定しています。 # zozo_default_roleに関する部分のみ抜粋 locals { all_ec_user_ids = [ "1234567890" , "1234567891" , "1234567892" ] zozo_default_role_cluster_privileges = [ "monitor" , "monitor_snapshot" , "manage_index_templates" , "manage_ilm" , "manage_slm" ] zozo_default_role_index_privileges = [ { names = [ "sample-1-*" ] privileges = [ "all" ] } , { names = [ "sample-2-*" ] privileges = [ "create_snapshot" ] } , { names = [ "sample-3-*" ] privileges = [ "monitor" , "read" , "view_index_metadata" ] } ] } 結果 Elastic Cloudの権限管理は、最終的に下記の図のようになりました。SREチームは、Default MappingによりElasticsearch内でsuperuserが付与されるため、チーム用Roleの設定はありません。 今回の取組みにより、特権アカウント共用による下記の問題が解消されました。 アカウントの共有によるセキュリティ上のリスク 誤操作により重大な事故を引き起こす可能性 さらに、SSO対応・Role管理・IaC化を進めたことで下記の効果も得られました。 アカウント運用管理により発生するトイルの削減 Googleアカウントを用いたSSOログインによる利便性向上 RBAC利用による柔軟かつ統一された権限管理の実現 IaC化による権限管理の保守性の向上とヒューマンエラーの防止 おわりに 本記事では、Elastic CloudのRBAC機能を活用した、安全・効率的・柔軟な権限管理を紹介しました。 今回の取組みにより、特権アカウントの共用から脱却できました。しかし、下記のようにIaC化出来ていない箇所やリスクを排除できていない課題は残っているため、引き続き改善を進めていきます。 Elastic CloudのOrganization機能はTerraform未対応のためIaC化できていないこと マイクロサービスからElasticsearchへはsuperuserでアクセスしており、意図しない動作により関係のないIndexなどを削除してしまう可能性があること ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 hrmos.co Elasticsearchはバージョン8.2.3以降を利用しています。 ↩ Cloud Roleは正式名称ではありません。 公式ドキュメント では、 Elastic Cloud role と表現されています。本記事では、似た名称である Elastic Stack role が登場するため、読みやすさを考慮してそれぞれ Cloud Role 、 Stack Role と表現しています。 ↩ Default Mappingは正式名称ではありません。 公式ドキュメント では、説明の後、 the default mapping と表現されています。本記事では、独自に作成したRole Mappingと区別して読みやすくするため、それぞれ Default Mapping 、 Custom Mapping と表現しています。 ↩
アバター
こんにちは、カート決済部カート決済サービスブロックの林です。普段はZOZOTOWN内のカートや決済の機能開発、保守運用、リプレイスを担当しています。 弊社ではカートや決済機能のリプレイスを進めており、これまでにカート投入のキャパシティコントロールや在庫データのクラウドリフトを実現しています。 techblog.zozo.com techblog.zozo.com 本記事では新たにクレジットカード決済処理を非同期化したリプレイス事例を紹介します。 はじめに 背景・課題 非同期化のシステム構成 パターン1 - 完全非同期化パターン パターン2 - 非同期・同期切り替えパターン パターン3 - ポーリングパターン システム構成の決定 メッセージングサービスの選定 効果 今後の展望 まとめ さいごに はじめに 本章では、非同期化前のZOZOTOWNのクレジットカード決済を用いた注文処理の流れを説明します。 ZOZOTOWNの注文情報はオンプレミス環境で稼働しているSQL ServerのCartDBとFrontDBという2つのDBで管理しています。注文リクエストがくると、CartDBに注文データを作成します。この時に作成される注文データをZOZOTOWNでは仮注文と呼んでいます。仮注文の作成後、クレジットカード与信取得のためのリクエストを行います。与信取得後、仮注文の状態を与信完了に更新します。その後、CartDBからFrontDBへ注文データを移します。FrontDBの注文データを本注文と呼んでいます。 上記の処理の流れを以下の図に示します。 背景・課題 ZOZOTOWNでは、セールの終了時刻となる日曜から月曜にかけての日跨ぎ時に注文のピークを迎えます。この時の注文数が通常時の十倍前後となります。 次のグラフはあるセールの日の日跨ぎ時の注文数の推移です。 注文がスパイクしたタイミングで、レイテンシも上がっています。レイテンシが上がることで、ユーザーの待ち時間が長くなりUXの低下が発生します。以下が注文リクエストのレイテンシの推移です。 最初は、このレイテンシの悪化の原因がDBにあると考え、日跨ぎ時のDBのCPU使用率を調査しました。以下が同じ時間帯のDBのCPU使用率の推移です。 DB自体の負荷は余裕があり安定した状態となっていました。原因を深掘りしたところ、クレジットカードの与信リクエストの遅延でした。社外のAPIになるため、我々が直接レイテンシを改善することは困難でした。さらに、今後の成長を踏まえると、ユーザーの待ち時間がより長くなることで、UXの低下が予想されました。 そこで、UXの改善のための最初の対応としてクレジットカード決済処理の非同期化を進めることとしました。 非同期化のシステム構成 非同期化を行うにあたり以下の3パターンを検討しました。 与信処理から非同期化し、注文成立していない状態でも注文完了の画面を表示する 1のパターンを用意するが、同期処理にも切り替えられるようにする 与信処理から非同期化し、フロント側ではポーリングを行い注文成立したら注文完了の画面を表示する それぞれの詳細とメリット・デメリットを以下に記載します。 パターン1 - 完全非同期化パターン このパターンではユーザーとの注文完了コミュニケーションも非同期になります。処理フローは以下のとおりです。 Webサーバが注文リクエストを受け付ける 仮注文の作成 メッセージングサービスにエンキュー Webサーバは受付完了の画面を返す Workerがデキュー クレジットカードの与信確保のリクエスト CartDBに与信結果を保存 FrontDBに本注文を作成 5以降の処理が非同期になっています。ユーザーから見た場合、エンキューしたタイミングで画面が切り替わるため与信処理のレイテンシに影響されなくなります。 メリットとしては以下が挙げられます。 与信処理のレイテンシに影響されることなく注文リクエストが完了する キューとWorkerによるキャパシティコントロールができる デメリットとしては以下が挙げられます。 ユーザーは受付完了の画面に遷移できた場合でも与信NGで注文成立しない可能性がある 注文成立しない場合は支払い方法の変更通知などをメールで知らせる必要がある 支払い方法変更の猶予を持たせるために、在庫を確保しておく必要がある 注文数が少ない時間帯でも注文NGの場合に、ユーザーコミュニケーションが非同期になってしまう パターン2 - 非同期・同期切り替えパターン このパターンはパターン1の構成と既存の構成どちらも利用します。環境変数やアプリケーションロジックにより、既存パターンとパターン1の非同期化ロジックを切り替えられるようにします。 メリットとしては以下が挙げられます。 ピーク時はパターン1を利用できる 注文数が少ない時は、既存ロジックに切り替えることでピーク時以外はパターン1のデメリットをカバーする デメリットとしては以下が挙げられます。 同期と非同期どちらも必要になるので決済フローの改修コストが増える パターン3 - ポーリングパターン このパターンでは、パターン1と同様に与信処理は非同期化を行います。UXの低下は避けたいので、フロントからポーリングを行い、注文状態を確認するようにします。本注文が作成されたら、注文完了の画面に遷移します。与信等でエラーになった場合はカートトップに戻ります。処理フローは以下のとおりです。 Webサーバが注文リクエストを受け付ける 仮注文の作成 メッセージングサービスにエンキュー Webサーバはポーリング用のIDを返却 フロントはIDを用いてポーリング開始 Workerがデキュー クレジットカードの与信確保のリクエスト CartDBに与信結果を保存 FrontDBに本注文を作成 6以降の処理が非同期になっています。非同期で本注文が作成されると5でポーリングしているところで検知し、注文完了の画面に遷移します。ユーザーから見た場合は既存のUI/UXと変わりませんが、与信確保のリクエストはキャパシティコントロールされています。 メリットとしては以下が挙げられます。 キューとWorkerによるキャパシティコントロールができる 注文リクエスト自体は与信処理のレイテンシに影響されない 注文完了のユーザーコミュニケーションを非同期にする場合でも非同期部分のフローは変更なしで実現できる デメリットとしては以下が挙げられます。 ユーザーから見た時のUI/UXは変わらないので、注文リクエストをしてから注文完了の画面に行くまでに待ち時間がかかる システム構成の決定 3つのパターンの中から弊社ではパターン3を選択しました。理由は以下のとおりです。 現在のUXから低下させる部分を作りたくない パターン1ではピーク時以外のUXが低下してしまう 今後の改修コストの増加を極力避けたい パターン2では同期と非同期の二重開発になり改修コストが増える 注文完了のユーザーコミュニケーションを非同期にした場合、ビジネス要件の調整に時間がかかる 与信失敗した場合にその注文はどうするのか 支払い方法変更を受け付ける場合、どのくらいの期間受け付けるか 今後ピーク時だけ注文完了のユーザーコミュニケーションを非同期にすることも比較的容易に可能である パターン3でキャパシティコントロールを実現しつつ、並行してユーザーコミュニケーションの非同期化のためのビジネス要件の調整を進めることにしました。 メッセージングサービスの選定 データをキューイングするためのメッセージングサービスの検討もおこないました。今回候補に上がったAWSのサービスが以下の2つです。 Amazon Kinesis Data Streams (KDS) Amazon Simple Queue Service (SQS) それぞれの特徴やメリット、デメリットの詳細はカートリプレイスPhase1のテックブログで触れているのでご参照ください。 techblog.zozo.com クレジットカード決済の非同期化では以下4点を主な検討事項としました。 厳密な順序性はいらない スパイクにも耐えられる 1リクエストが詰まっても他に影響がでないようにしたい なるべくマネージドなサービスにしたい この中で採用の決め手となったのが、「1リクエストが詰まっても他に影響がでないようにしたい」でした。 与信処理では一部のリクエストだけが遅くなることがあります。そのため、特定のリクエストが遅延した場合でも他のリクエストへの影響を減らせる構成を検討していました。 KDSの場合シャード単位で読み出すため、特定のリクエストが遅延した場合の影響はシャード全体に渡ります。 SQSの場合は、特定のリクエストが遅延してもその他のWorkerがリクエストを処理できるので影響範囲を少なくできます。 上記の点から、本件ではSQSを採用することにしました。 効果 パターン3を実装した結果、仮注文作成までが注文リクエストの役割となったため、注文リクエスト自体のレイテンシは安定化し高速になりました。非同期化の実装前後での日跨ぎ時のレイテンシの比較は以下のとおりです。 また、SQSとWorkerでキャパシティコントロールができるようになった結果、与信リクエストの遅延がなくなりました。さらには、どれくらい与信キューに溜まっているかを可視化できるようになりました。以下は、日跨ぎ時に滞留しているキュー数の推移です。 滞留しているキュー数が明確になったことで、今後のZOZOTOWNの成長率との組み合わせでどれくらいキューに溜まり、注文完了までどれくらいかかるかの計算が可能になりました。 今後の展望 今回与信のキャパシティコントロールは実現したものの、UXの改善には至りませんでした。現状は注文完了の画面表示までのユーザーの待ち時間はリプレイス前と変わりません。今後ZOZOTOWNが成長していくと、必ず待ち時間は延びていきます。そのため、ユーザーコミュニケーションを非同期にしていく必要性があります。ユーザーコミュニケーションを非同期にした場合、以下の検討事項があります。 与信失敗時の注文データの取り扱い 既存では失敗時はカートに商品を戻している 再度注文を行なってもらうより支払い方法変更できることが望ましい 支払い方法を変更できる場合はどれくらいの期間在庫を確保するか 在庫の確保期間が長ければ売り上げ損失になる可能性がある 在庫の確保期間が短ければUXの低下を招く可能性がある これらの検討事項はシステムだけではなく、ビジネス要件とも大きく関係してきます。現時点ではまだ検討中で、ビジネス側と密に連携し、今後の方針を決めていきたいと考えています。 まとめ 本記事ではクレジットカード決済の非同期化のシステム構成を紹介しました。非同期化したことにより、与信処理のキャパシティコントロールを行うことができました。非同期化を検討している方がいれば、ぜひ参考にしてみてください。今後はユーザーとのコミュケーション部分も非同期で行えるようにし、UXの向上を目指していきたいと考えています。 さいごに ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com
アバター
はじめに こんにちは、MA部の林( @hayash__p )です。 私達のチームでは、メール、LINE、Push通知、サイト内お知らせなどでユーザにZOZOTOWNのセールや新着商品を紹介するといった、マーケティングに関わるシステムを開発しています。これまで、配信チャネルや配信内容ごとに個別最適化したシステムを開発していましたが、それらを一新したマーケティングプラットフォームを作ることになりました。新しいマーケティングプラットフォームであるZOZO Marketing Platform(以下、ZMP)の概要については以下のテックブログをご覧ください。 techblog.zozo.com 本記事では、マーケティングプラットフォームのリプレイスにあたり、フロントエンドエンジニアとして取り組んだことを紹介します。 目次 はじめに 目次 背景 ZMPの管理画面モジュール MPマネージャー 技術選定 MPマネージャーと関連システムの構成について MPマネージャーでのフォルダ構成と役割について OpenAPI用のフォルダ フロントエンドAPI用のフォルダ 共通コンポーネント用のフォルダ 開発中に出てきたApp Router関連の課題 Serverからのfetchでは、リクエストオブジェクトにアクセスできない Server Actionsを後から導入したため、リファクタリングの余地がある エラー画面が描画された場合も、ステータスコードが200で固定になってしまう テストライブラリがServer Componentに未対応 まとめ ZMPの振り返り・今後 MA部フロントエンドとしての振り返り・今後 背景 ZMPでは、マーケターがキャンペーンの管理・運用をより手軽に行うため、新規に管理画面を作ることになりました。ZMPの初期リリース段階(以下、フェーズ1)での管理画面の要件は以下でした。 メール、Push通知による配信の設定ができること 拡張を前提とした画面であること(後々、LINE、サイト内お知らせなど、既存システムで利用している配信チャネル全てを網羅する想定のため) 画面には配信の関係者のみがアクセスできること 今まで、管理画面のないマーケティングシステムのバックエンド開発が中心だったこともあり、MA部にはフロントエンドに特化したチームがありませんでした。しかし、ZMPには複数のマーケティングシステムを集約するような画面が必要でした。そのため、フロントエンド、バックエンドそれぞれに主軸を持った開発者同士のチームで、ZMPの管理画面用Webアプリケーションを構築することになりました。 ZMPの管理画面モジュール MPマネージャー ZMP管理画面のフロントエンドモジュールを、Marketing Platform Managerを一部省略し、MPマネージャーと呼んでいます。ここからは、MPマネージャーで採用した技術、関連システムを含む構成、MPマネージャー自体の構成について紹介します。 技術選定 フロントエンドフレームワークは、ZOZOTOWNのWebホーム画面でも利用されているReact・Next.jsを採用しました 1 。また、Next.jsのルーティングシステムは、技術選定の時期にbetaとして登場したApp RouterをZOZO社内で初めて採用しました。当時はPage Routerがstableでしたが、Page Routerを選んだ場合、App Routerがstableとなった際に切り替えコストがかかってしまうことを考慮したためです。 私自身は当時の選定には携わっていなかったのですが、実際に手を動かして今回開発をしたところ、App Routerに関する課題がいくつか発生しました。具体的な例を挙げて後述します。 MPマネージャーと関連システムの構成について MPマネージャーとその関連システムの構成はこのようになっています。 内製のAPI(以降、バックエンドAPI)は配信設定のロジックはもちろん、DBやストレージ、外部APIとの疎通を担当しています。そんなバックエンドAPIから取得したデータを表示したり、フォームで入力したデータをバックエンドAPIに送信したりするのがMPマネージャーの担当領域となっています。 バックエンドAPIは同時並行で開発しており、バックエンドAPIとフロントエンドのインタフェースを共通化し相互連携を強化するために、OpenAPIで定義を作り共用しました。MPマネージャーではその定義を元に Prism でモックサーバーを立てて開発を進めました。 MPマネージャーでのフォルダ構成と役割について App Routerを使った場合のフォルダ構成の情報が少ないため、App Routerを使って開発する方の参考になればと思い、MPマネージャーでのフォルダ構成を紹介します。 MPマネージャーでは、 bulletproof をベースとしつつ、 Next.jsのルーティングルール も尊重して、フォルダ構成を設計しました。 下記は、MPマネージャーのフォルダ構成です。このうち、bulletproof・App Router・OpenAPIの組み合わせにより、特殊な実装・構成となった部分をピックアップして紹介します。 . ├── src/ │ ├── api/ │ ├── app/ │ │ ├── _api/ │ │ │ └── usersServer.ts │ │ ├── _components/ │ │ │ └── UserIcon.tsx │ │ ├── api/ │ │ │ └── users/ │ │ │ └── route.ts │ │ ├── users/ │ │ │ ├── _components/ │ │ │ │ └── UserForm.tsx │ │ │ └── page.ts │ │ ├── error.tsx │ │ ├── layout.tsx │ │ ├── loading.tsx │ │ └── page.tsx │ └── _components/ │ └── Button.tsx ... OpenAPI用のフォルダ まずは、 api に関連するフォルダについてです。 └── src/ └── api/ こちらは、 OpenAPI Generator を用いてOpenAPIの定義ファイルをTypeScript用に自動生成したファイルの置き場です。ルーティングに含みたくなかったこと、型定義のファイル群でありアプリケーションそのものと切り離したかったことから、 src/app 配下ではなく、 src 直下に専用のディレクトリを設けることにしました。 フロントエンドAPI用のフォルダ MPマネージャーでは、 Route Handlers を用いてフロントエンド側にもAPIを構築しました(以降、フロントエンドAPI)。ここからは、フロントエンドAPI用のフォルダについて紹介します。 └── src/ └── app/ └── api/ └── users/ └── route.ts フロントエンドAPIを用意したのは、Client ComponentとServer Component、両方からのアクセスとバックエンドAPIを繋ぐインタフェースを用意したかったためです。フロントエンドAPI( src/app/api/[モデル名]/route.ts )は、OpenAPIを利用してバックエンドAPIにアクセスする実装となっています。 // src/app/api/users/route.ts import { NextResponse, NextRequest } from 'next/server' ; import { UserApi } from '@/api' ; export const dynamic = 'force-dynamic' ; export async function GET ( request : NextRequest ) { try { const api = new UserApi(); const users = await api.getUsers(); return NextResponse.json(users); } catch (error) { // エラー処理 } } また、そのフロントエンドAPIを叩くためのfunctionをまとめたのが、 src/app/_api フォルダです。 └── src/ └── app/ └── _api/ └── usersServer.ts 'use server' ; import { fetchWithJwtIap } from '@/lib/typescript-fetch-on-server-component' ; export const getUserList = async () => { const res = await fetchWithJwtIap( // MEMO: NEXT_PUBLIC_HOST = フロントエンドのホスト ` ${ process .env.NEXT_PUBLIC_HOST } /api/users` , ); ... } ; page.tsx にて、下記のように呼び出して利用します。 // src/app/users/page.tsx import { getUserList } from '@/app/_api/usersServer' ; async function Page () { const users = await getUserList(); return < UserList users = {users} />; } ; 共通コンポーネント用のフォルダ MPマネージャーでは、以下のルールで共通コンポーネントのフォルダを3通り用意しました。 └── src/ └── _components/ └── Button.tsx bulletproofを参考にしたため、基本的にはこの src/_components に共通コンポーネントを配置しています。 └─ src/ └── app/ └── _components/ └── UserIcon.tsx こちらの src/app/_components は、OpenAPIに依存した共通コンポーネントの置き場です。先述の src/api から何かしらのファイルを読み込み、かつ複数箇所で使われる場合はこちらに配置します。 └── src/ └── app/ └── users/ └── _components/ └── UserForm.tsx 最後に、 src/app/[モデル名]/_components は特定のページでのみ使うコンポーネントの置き場です。使用するページが限定的であれば、近くに配置した方が管理しやすいため、このフォルダも用意しました。 このフォルダ構成にしたことで、OpenAPIひいてはバックエンドAPIに依存するコードかどうかや広範囲で使われているコンポーネントかどうかを意識しやすくなりました。 開発中に出てきたApp Router関連の課題 ここまでは、MPマネージャーを俯瞰的に解説してきました。ここからは、開発中に出てきたApp Router関連の課題とその解決法について説明します。 技術選定時はbetaで始まり、開発中にstableとなったApp Routerでしたが、stableとなった後でも利用事例が少なく、GitHubのコメントだけが頼りになることもしばしばありました。ここからは、開発中に詰まった点を実際の例を用いて紹介していきます。これからApp Routerでの本番アプリケーション開発を検討している方・すでに開発を始めている方の参考になれば幸いです。 Serverからのfetchでは、リクエストオブジェクトにアクセスできない MPマネージャーでは、「画面には配信の関係者のみがアクセスできる」ように、想定外のユーザーがアクセスできないよう IAPによる認証 を実施し、バックエンドAPIに認証情報を送っています。このIAPによる認証情報は、カスタムヘッダーに格納されています。 しかしApp Routerでは、Serverからのfetchではリクエストオブジェクトにアクセスできないという仕様により、カスタムヘッダーが取得できませんでした。 GitHub.com // src/app/users/page.tsx export const dynamic = 'force-dynamic' ; export default async function Page () { const users = await fetch ( ` ${ process .env.NEXT_PUBLIC_HOST } /api/users` , ); ... } // src/app/api/users/route.ts import { NextRequest } from 'next/server' ; export async function GET ( request : NextRequest ) { console .log(request. headers . get ( 'X-Goog-IAP-JWT-Assertion' )); // null } こちらは、 Discussionsのコメント を参考に、fetchの前に next/headers を読み込むことで解決しました。 // https://github.com/vercel/next.js/discussions/44270#discussioncomment-6064242 の対策のため、constで定義 const headers = import( 'next/headers' ); export const fetchWithJwtIap = async ( requestInfo : RequestInfo , init? : RequestInit | undefined , ) => { const headersList = ( await headers).headers(); const jwtIap = headersList. get ( 'X-Goog-IAP-JWT-Assertion' ); return fetch (requestInfo, { ...init, headers : { ...init?. headers , [ 'X-Goog-IAP-JWT-Assertion' ] : jwtIap, } , } ); } ; 今回はカスタムヘッダーを取得する実装になりましたが、Cookieなどの他のリクエストオブジェクトにアクセスしたい場合も、同じように next/headers を読み込むことで実装できそうです。 Server Actionsを後から導入したため、リファクタリングの余地がある MPマネージャーでは、フォームデータの送信はClient Component、初回表示用のデータ受信はServer Componentと、両Componentからfetchを使用しています。ただ、前述の通り、Client ComponentからのfetchとServer Componentからのfetchにて、認証・認可のためのコードに若干の違いが発生してしまいました。そこで開発初期は、ファイルを2つ用意してClient Componentでは [モデル名]Client.ts を、Server Componentでは [モデル名]Server.ts を呼び出す実装にしました。 Client Componentでのfetchの例は次のとおりです。 // src/app/_api/usersClient.ts export const postUser = async (...) => { const res = await fetch ( `/api/users` , { method : 'POST' , headers : { 'Content-Type' : 'application/json' , } , body : ... } , ); ... } ; // src/app/users/new/page.tsx import { UserForm } from '@/app/users/_components/UserForm' ; async function Page () { return < UserForm />; } ; // src/app/users/_components/UserForm.tsx 'use client' ; import { postUser } from '@/app/_api/usersClient' ; async function UserForm () { return ( < form > ... < button type = "submit" onClick = {postUser (...) } /> </ form > ); } ; また、Server Componentでのfetchの例は次のとおりです。 // usersServer.ts 'use server' ; import { fetchWithJwtIap } from '@/lib/typescript-fetch-on-server-component' ; export const getUserList = async () => { const res = await fetchWithJwtIap( // MEMO: NEXT_PUBLIC_HOST = フロントエンドのホスト ` ${ process .env.NEXT_PUBLIC_HOST } /api/users` , ); ... } ; // src/app/users/page.tsx import { getUserList } from '@/app/_api/usersServer' ; async function Page () { const users = await getUserList(); return < UserList users = {users} />; } ; 後々、 Server Actions がリリースされ、Client ComponentでのfetchをServer側の処理として扱うことができるようになりました。その結果、Client Componentからも [モデル名]Server.ts が呼び出されるようになり、現在は [モデル名]Server.ts だけが残っています。 // usersServer.ts 'use server' ; import { fetchWithJwtIap } from '@/lib/typescript-fetch-on-server-component' ; export const getUserList = async () => { ... } export const postUser = async (...) => { const res = await fetchWithJwtIap( ` ${ process .env.NEXT_PUBLIC_HOST } /api/contents` , { method : 'POST' , headers : { 'Content-Type' : 'application/json' , } , body : ..., } ); } ; // src/app/users/new/page.tsx import { UserForm } from '@/app/users/_components/UserForm' ; async function Page () { return < UserForm />; } ; // src/app/users/_components/UserForm.tsx 'use client' ; import { useTransition } from 'react' ; import { postUser } from '@/app/_api/usersServer' ; async function UserForm () { const [ , startTransition ] = useTransition(); const handleClick = (...) => { startTransition( async () => { await postUser(...); } ); } ; return ( < form > ... < button type = "submit" onClick = {handleClick (...) } /> </ form > ); } ; 開発途中で追加された機能を導入したこともあり、試行錯誤を経てこの構成に辿りつきました。そのため、バックエンドAPIが求める形に入力値を加工する処理などがServer ActionsとRoute Handlersに点在してしまっています。バックエンドAPIにより近い位置で加工した方が良いと考えているため、将来的にはRoute Handlersに処理を集約できたらと考えています。 エラー画面が描画された場合も、ステータスコードが200で固定になってしまう Next.jsには本番環境ではServer Componentで起きたエラーはマスキングされてClient Componentに送られるという仕様があります。 nextjs.org そのため、Server Componentで throw Error をするとエラーメッセージの詳細が取得できなくなってしまいました。特にバリデーションエラー時に、何が原因でエラーになったのかをClient Componentで判別できず困りました。 MPマネージャーでは、バリデーションエラーなどの想定したエラーであれば、200系のステータスコードとエラー理由を含んだJSONをレスポンスとして返す実装にして乗り越えました。個人的には、エラー時にエラー系のステータスコードを返す実装が好ましいと思っているため、今後のアップデートを注視していきます。 また、MPマネージャーではSuspenseによるStreamingを利用していたのですが、これがステータスコードに想定外の影響を与えていました。Suspenseを使うと、データのfetch中にローディングマークを表示できます。ですが、画面の描画が始まっている = ルーティングは正常に完了していることから、fetch中にエラーが起こってエラーページが呼び出された場合も、200の正常系のステータスコードが返されていたのです。 nextjs.org この問題には有効な解決策を打てないままでした。しかし、画面の描画が始まった後のエラーはSentryでキャッチできます。また、MPマネージャーそのものが落ちていないかの死活監視は、ロードバランサのステータス監視で代用できます。以上の設定があれば、MPマネージャーが何かしらエラーで落ちてしまった場合も検知できると判断しました。 現在、GitHubの Discussion にて議論されているため、こちらもアップデートを注視していきます。 テストライブラリがServer Componentに未対応 MPマネージャーでは、テストライブラリとしてjestやtesting-libraryを採用しました。最初はClient Componentのユニットテストを中心に実装していたため、特に問題なく利用できました。ですが、Server Componentのテストには未対応でした( testing-libraryのIssue )。そのため、Server Componentのユニットテストが実装できずにいました。 Issueでは 回避策 としてE2Eテストが上げられていました。Next.jsでは playwrightのE2Eテストがサポートされている ことから、Server Componentが絡むコードは、E2Eテストで実装することとなりました。これにより、網羅的にテストコードを書くことができ、品質を担保できるようになりました。 まとめ ZMPの振り返り・今後 2024年1月から、ZMPを利用して設定したデータを元にメール、Push通知の配信ができるようになりました。 このように、画面上でメール、Push通知による配信の設定ができるようになっています。 ここまでで、フェーズ1に要件として上がっていた全ての項目が達成されています。 メール、Push通知による配信の設定ができること 拡張を前提とした画面であること(後々、LINE、サイト内お知らせなど、既存システムで利用している配信チャネル全てを網羅する想定のため) 画面には配信の関係者のみがアクセスできること 今後は、フェーズ2以降に予定されている機能の画面開発を進めていきます。 LINE、サイト内お知らせ用の配信機能 配信設定のレビュー機能 配信システムではない既存のシステムのリプレイス 参考: Digdagのワークフローをマーケターが実行するためのシステム 最終的に、ZMPには多種多様なマーケティングに関わるシステムが集約されることになるため、マーケターにとって操作しやすい管理画面が提供できるよう、開発を続けていきます。 MA部フロントエンドとしての振り返り・今後 MPマネージャーでは、App Routerという新しく利用例が少ない技術を採用しました。実際に開発を始めてみると、日本語ドキュメントが少ないこと、社内初の利用だったため知見が共有できなかったことから、MA部のフロントエンドとしては挑戦的な技術選定になってしまったように思います。具体的には、リクエストヘッダーを操作したり、テストを拡充したりなど、本番利用では欠かせない機能を開発している時に壁を感じることが多かったです。 しかし、今回出てきた問題点は、GitHubのIssueやDiscussionを参考に対応策を編み出せたため、リリースまで辿り着きました。また、認証など、周辺モジュールと連携する土台が完成しています。以上から、App Routerでの開発体制が軌道に乗り始めたところだと考えており、ZMPでは今後もApp Routerを使い続ける予定です。 今後は、既存システムのZMPへの移行が属人化しないよう、MAのフロントエンドのチーム体制を整えていきたいと考えています。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com ZOZOTOWNのWebホーム画面をNext.jsでリプレイスして得た知見 - ZOZO TECH BLOG ↩
アバター
はじめに こんにちは。WEARフロントエンド部Webチームの藤井です。私たちのチームでは、 WEAR のWebサイトのリプレイスと新規機能の開発を並行して進めています。これらの開発を推進する中で、Pull Requestのレビュー負荷を軽減し、開発生産性を向上させるための取り組みを行なってきました。本記事では、その中で効果的だった取り組みについてご紹介します。 目次 はじめに 目次 背景と課題 レビューの体制の薄さ スコープの広さ 仕様把握の負担 対応内容についての説明不足 処理の複雑性 仕様の抜け漏れ 動作確認の手間 課題解決に向けた取り組み レビュー体制の見直し Pull Requestを小さくする Issueを小さくする Pull Requestの粒度について明文化する 機械的なチェックの拡充 ESLintルールの拡充 Visual Regression Testの拡充 Pull Requestテンプレートの拡充 事前の仕様・設計相談 仕様・デザインのレビュー 実装前の設計相談 コーディングルールとしての蓄積 レビューしやすい環境の整備 Preview環境を使った動作確認 Storybookのレビュー活用 定期的な振り返り 取り組みの結果 サイクルタイム比較 Before After 主要スタッツ比較 Before After まとめ おわりに 背景と課題 WEARフロントエンド部では、開発速度の向上を目指して「Pull Requestのオープンからマージまでの平均時間 24時間以内」という目標を掲げています。また、月次のKPTや週次の1on1の中で、Pull Requestのレビューに対する負荷が高いという課題が浮上しました。レビュー負荷が高いと感じる理由や当時の傾向として、以下のような意見がありました。 レビューの体制の薄さ 社員3名、内定者アルバイト2名のメンバー構成で、基本的には社員2名のapproveでマージをするルールで運用しています。このルールで全てのPull Requestを運用するには社員1人あたりのレビュー数量的に負荷が高いという課題がありました。 スコープの広さ 1つのPull Requestに複数の対応内容やついでのリファクタリング、バグ修正などを含めていることがありました。その結果、Pull Requestのスコープが広がり、何の目的でどの部分のコードを修正しているかを把握するのに負荷がかかっていました。 仕様把握の負担 各メンバーそれぞれ違う画面のリプレイスや新規機能の開発を行なっているため、レビュアーが他の担当の画面仕様を把握するのに時間がかかってしまうことがありました。特に画面のリプレイスの場合には仕様書がないため、各レビュアーが既存画面や既存コードから調査するということが発生していました。 対応内容についての説明不足 Pull Requestの対象範囲がレビュアーに伝わらず、レビュイーが意図していない対応箇所を指摘することがありました。 処理の複雑性 コンポーネントや関数の設計が複雑になってしまい、レビュアーが処理を理解するのに時間がかかってしまうことがありました。また、レビュー時点で処理の複雑性に気づき、設計と実装の手戻りが発生することもありました。 仕様の抜け漏れ レビュー時に仕様の考慮漏れに気づいたり、仕様やデザイン面での相談が発生したりすることがあり、レビューが中断したり、実装の手戻りが発生してしまうことがありました。 動作確認の手間 ブランチをチェックアウトし、ローカル開発環境を立ち上げてUIのチェックや動作確認に時間がかかっていました。 課題解決に向けた取り組み 前述した課題に対してチーム内で改善策を話し合い、実践し、その効果を振り返りながら改善を続けました。その中で効果があったと考える取り組みを以下に紹介します。 レビュー体制の見直し レビューするメンバーが少ない中でも品質を担保しつつ効率的に運用していくためにレビュー体制を見直しました。例えば、画像差替えや文言修正など1名によるレビューでも品質が担保できる軽微な修正に関しては1名のapproveでマージするなど例外ルールを設けました。また、内定者アルバイトのメンバーにも少しずつレビューに入ってもらい、後進育成に取り組んでいます。 Pull Requestを小さくする Pull Requestの変更内容が大きいと、レビューに時間がかかります。例えば、変更内容の把握に時間がかかったり、まとまった時間がとれるまでレビュー着手できず遅れたりするということがありました。Pull Requestを小さくすることによって、隙間時間にもレビューができ、レビューに対する心理的な負担も軽減しました。Pull Requestを小さくするためには以下のような取り組みを行いました。 Issueを小さくする 1つのIssueに1つのPull Requestを紐づけることを基本とし、まずはIssueの単位を小さくするように心がけました。毎日の朝会でIssueを共有し、必要に応じて分割することで、Pull Requestを小さくなるように定着させていきました。 Pull Requestの粒度について明文化する IssueやPull Requestを小さくするといっても人それぞれ感覚が異なるので、Pull Requestの粒度について明文化するようにしました。例えば、以下のようなことを明示しました。 新規に作成するコンポーネントや関数は基本的に1つで1つのPull Requestとする ファイルの移動やリファクタリングは機能修正のPull Requestと分けて作成する IssueやPull Requestを小さくすると、仕様の全体像や背景がわかりづらいという問題がありました。その問題を回避するために、 タスクリスト 用のIssueを作成し、そこから細かいIssueを作成しています。よく使う汎用的なタスクリストについては Issueテンプレート を設定しておくことで、テンプレートから簡単にタスクリストを作成できます。 機械的なチェックの拡充 機械的にチェックできる項目はLinterや自動テストに任せることで、レビューの効率の向上を図りました。具体的には以下の取り組みを行いました。 ESLintルールの拡充 元々、ESLintのメインのルールセットに eslint-config-airbnb-typescript を採用していましたが、コードの複雑性もチェックするために以下のルールも追加しました。これにより実装段階で処理が複雑になることを回避でき、その結果、レビューの負担が軽減しました。 complexity max-statements max-statements-per-line max-depth max-lines max-lines-per-function max-params max-nested-callbacks また、最近ではアクセシビリティに関するチェックを行うプラグイン eslint-plugin-jsx-a11y も追加しました。こちらも実装段階で機械的にチェックできるので、レビューの負担軽減につながりました。 Visual Regression Testの拡充 以前から導入していたPlaywrightやChromaticによるVisual Regression Testにおいて、対象画面や細かいデータパターンなどテスト対象を拡充しました。UI面で意図しない変更が生じていないかのチェックを自分たちで比較する必要がなくなり、レビューコスト削減につながりました。また、機械的にチェックすることで人間の目による見落としがなくなり、品質向上にもつながりました。 Pull Requestテンプレートの拡充 仕様把握やUIチェックで時間がかかっていた課題を解決するためにPull Requestテンプレートを拡充しました。例えば、リプレイス前の画面URL、既存コードのGitHub URL、Figmaやリプレイス前の画面との比較スクリーンショットを提示するようにしました。そうすることで、各レビュアーが既存仕様の調査や画面を比較する手間がなくなり、レビュー負荷を軽減できました。現在は以下のようなPull Requestテンプレートで運用しています。 ## 対応内容 <!-- やったことを簡潔に --> ### Issue closes # <!-- このPRをマージすることで解決するIssue --> ref. # <!-- その他このPRに関係するIssue/PR --> ### 仕様書 <!-- Confluence、JIRA、リプレイス前のURLやsourceコード --> ### この PR でやらないこと ## 比較スクショ ## 影響範囲 <!-- この変更によって影響するページなど --> ## レビュー観点 <!-- このPRで特に注視して確認して欲しいこと --> 事前の仕様・設計相談 設計や実装の手戻りを減らすために、実装へ取り掛かる前に仕様や設計に関して相談するようにしました。具体的な取り組みは以下の通りです。 仕様・デザインのレビュー 実装に入る前段階で、チーム内で仕様やデザインをレビューするようにしました。特に画面のリプレイスの場合、既存の仕様が古くなっていたり、他の画面とのUIや仕様の一貫性が欠けていたりすることがありました。これらの問題を解消するために、実装前にPMやデザイナーと相談して仕様の見直しや利用率が極端に低い機能を削除しました。また、UIや仕様の統一を図り、共通コンポーネントを利用できるようにしました。これにより、実装段階やレビュー段階での仕様やデザインの見直しが減り、開発の効率化やレビュー負荷の軽減につながりました。 実装前の設計相談 毎朝行なっているタスク・進捗共有や毎夕行なっているレビュー会において、設計方針や実装中の悩みについても相談するようにしました。これにより、レビュー時に設計から見直すような手戻りが減り、実装やレビュー時間の短縮につながりました。 コーディングルールとしての蓄積 設計相談で決まった設計方針やレビュー中にでた指摘事項などで汎用化できるものについては、コーディングルールや命名規則としてGitHub Discussionsに記載し、ドキュメント化しました。方針が明文化されていることによって実装やレビューにおける迷いが軽減されました。 レビューしやすい環境の整備 以前はローカルにブランチをチェックアウトして動作確認を行なっていたのですが、Preview環境やStorybookを活用することによって、簡単に動作を確認できるようになりました。 Preview環境を使った動作確認 有難いことに、定期的に弊SREチームが各チームで困っていることを吸い上げて開発環境の改善を行なってくれており、そのうちの1つがPreview環境でした。Pull Request毎ごとにPreview環境を起動が可能になり、Preview環境内の対象画面のURLを共有するだけで容易に動作を確認できるようになりました。Preview環境の詳細についてはSREチームの山岡による以下の発表資料をご覧ください。 speakerdeck.com Storybookのレビュー活用 コンポーネント単位でのUI確認はStorybookを活用しています。Storybookを使用することで、ページに組み込む前のコンポーネントや、異なるデータや状態のパターンについてもそれぞれ確認できます。また、StorybookをChromatic上にデプロイしているので、ローカルでStorybookを起動せずともWeb上で閲覧でき、Chromaticにて変更点の差分も容易に確認できます。 定期的な振り返り 週に1回、Findy Team+を見て、オープンからマージまでの時間や平均の変更行数をチェックし、メンバー全員で振り返りをしました。数値が良い時、悪かった時それぞれ要因を考え、次の週へ向けて良かったことの継続や改善をしました。 取り組みの結果 前述した取り組みについては前々から行なっていたものもありますが、課題感をもってチーム全体として積極的に取り組んだのは2023年4月半ばでした。Findy Team+のレビュー分析を見ると2023年4月後半あたりにオープンからマージまでの平均時間が減り、グラフが安定してきている傾向が見られます。お休みや研修期間などもあり、マージまでの時間が少し増えている期間もありますが、2023年4月以前と比較すると時間も短く、安定しています。 サイクルタイム比較 以下のBefore、Afterは取り組みの効果が顕著に現れた2023年5月からの半年間とそれ以前の半年間のサイクルタイム分析の数値です。オープンからレビューまでの平均時間は少し伸びていますが、それ以外の時間と全体の合計値は改善し、合計で12時間も短縮できました。 Before After 主要スタッツ比較 オープンからマージまでの時間もおよそ4時間短縮でき、目標の24時間に近づくことができました。また、平均の変更行数も81行となり、Pull Requestが小さくなっています。 Before After まとめ チーム全体で課題解決に取り組み、さまざまな小さな改善を積み重ねることで、Pull Requestのレビュー負荷の軽減や開発プロセスの効率化につながったと実感しています。一時的な改善に終わらせないためにも、今後も継続的な取り組みが必要であり、長期的に持続可能な開発体制を築いていくことが大切だと思います。 おわりに ZOZOでは、一緒にサービスを開発する仲間を募集しています。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com
アバター
はじめに こんにちは、検索基盤部の倉澤です。検索基盤部では、検索機能に必要なデータを生成するバッチシステムの開発や運用を担当しています。また、ユーザーのニーズやサービスの成長に合わせてリアーキテクチャを行うこともあります。今回は、リアーキテクチャを繰り返し行う中で見えてきたバッチシステムの内部設計の品質を高める・標準化するためのポイントを紹介します。 今回、バッチシステムの内部設計をソフトウェアのアーキテクチャ特性(品質特性とも呼ばれる)に基づいて説明します。 ソフトウェアのアーキテクチャ特性とは、非機能要件や品質特性と同じ意味を指しますが、 「ソフトウェアアーキテクトの基礎 (Fundamentals of Software Architecture)」 ではシステムが成功するために必要な運用と設計の基準として定義されています。アーキテクト特性には拡張性(Scalability)やメンテナンス容易性(Maintainability)、再利用性(Reusability)などがあり、イリティ(ility)とも呼ばれます。 リアーキテクチャによって得たバッチシステムの内部設計のポイント以下3点を紹介します。 ビジネスロジックはなるべく1つのコンポーネントに集約する データの抽出、変換、統合、削除などの各処理をなるべく小さなコンポーネントに分割する データリソースが増えた場合を考えデータ抽出処理と変換処理を独立させる 検索基盤部では、内部設計の品質を高めるためにDesign Docを活用しています。どのような項目に何を意識して記載しているかを最後に紹介したいと思います。 目次 はじめに 目次 背景 リアーキテクチャ前のバッチシステムの課題 良くない点1: ビジネスロジックが複数のコンポーネントに点在している なぜ良くないのか 課題1: メンテナンス容易性(Maintainability) 課題2: 拡張性(Scalability) どう解決したのか 良くない点2: 1つのコンポーネントに複数の責務を持つ処理が混在していた なぜ良くないのか 課題1: 再利用性(Reusability) 課題2: サポート容易性(Supportability) 課題3: テスト容易性(Testability) 課題4: 凝集度 どう解決したのか 良くない点3: データ抽出処理が独立していない なぜ良くないのか 課題1: 拡張性(Scalability) どう解決したのか Design Docの活用 なにを書くか Goal / Non-Goal 用語集 全体のシステム構成図 コンポーネントの定義 効果 まとめ おわりに 背景 ZOZOTOWNの検索機能の1つにユーザーが入力したクエリの意図を解釈する機能があります。この機能は、ユーザーが入力したクエリを解釈し、検索結果の品質を向上させることを目的としています。例えば、ユーザーが「ジャケット」というクエリで検索した場合、音楽に関連した情報ではなく洋服のカテゴリーであることを解釈し、検索結果の品質を向上させます。 この機能を実現させるために、ファッション用語を定義した辞書を作成するバッチシステムを開発しました。検索機能はバッチシステムが作成した辞書を元にルールベースでクエリの意図解釈を行います。この意図解釈を行う辞書は、社内外にある様々なデータソースからタームとその意味を抽出し、検索機能が利用できる形式に変換したり、ZOZOTOWN独自のビジネスロジックを適用したりすることで生成されます。 本文中の「ビジネスロジック」とは、辞書で定義しているタームの意味を恣意的に変更することを指しています。例えば、ZOZOTOWNで扱っているブランド名には一般名詞に該当するものがあり、ZOZOTOWNで扱う辞書を生成する際には該当のブランド名を一般名詞ではなく固有名詞として扱うことがあります。 今回紹介するバッチシステムは上記のようなドメイン特性がありますが、データ抽出・変換・統合・削除など一般的なバッチシステムの特性を持っています。そのため、本記事で紹介する内容は今後のバッチシステムの開発・運用においても参考になると考えています。 以下がリアーキテクチャ後のバッチシステムの全体像です。 上記のバッチシステムは主に2つに分かれています。 ビジネスロジックが介入していない辞書を作成するバッチシステム 各サービスのビジネスロジックが介入している辞書を作成するバッチシステム ビジネスロジックが介入していない共通処理をまとめたバッチシステムが前段にあることで、各サービスのバッチシステムはビジネスロジックの実装に集中できます。また、辞書を活用するサービスが増えた場合でも拡張しやすい構成となっています。 なぜ、上記のようなバッチシステムのアーキテクチャを採用したかを、次章のリアーキテクチャ前に存在していた課題を紹介することで説明します。 リアーキテクチャ前のバッチシステムの課題 リアーキテクチャのきっかけは、バッチシステムが生成する辞書をZOZOTOWN以外のサービスで使用したいという要望が発生したことでした。リアーキテクチャ前は、ZOZOTOWNの検索機能で利用することだけを考えた設計になっており他サービスはZOZOTOWNのビジネスロジックが介入した辞書を使うしかありませんでした。 以下がリアーキテクチャ前のバッチシステムの全体像です。 上記の構成図からわかるようにコンポーネントから出力される成果物は全てビジネスロジックが介入しています。ZOZOTOWNのビジネスロジックが介入している成果物を他のサービスで展開することは適していません。 これから紹介する課題はリアーキテクチャ前のバッチシステムが良くない設計だったというわけではなく、要求に応えるためのリアーキテクチャによって発生した課題です。当時の状況を考えるとリアーキテクチャ前のバッチシステムの構成はシンプルな設計だったと言えます。 良くない点1: ビジネスロジックが複数のコンポーネントに点在している なぜ良くないのか 課題1. ビジネスロジックの変更がしにくい・仕様の把握がしにくい 課題2. 他のサービスに成果物を展開できない 課題1: メンテナンス容易性(Maintainability) 「メンテナンス容易性(Maintainability)」とは、システムの変更や拡張がどれだけ簡単に行えるかを示す特性です。 コンポーネントのビジネスロジックに変更を加える際、他のコンポーネントのビジネスロジックに影響がないかを常に気にしないといけないため変更が難しくなります。また、各コンポーネントにビジネスロジックが点在していることで仕様を把握することが難しくなります。 課題2: 拡張性(Scalability) 「拡張性(Scalability)」とは、システムがどれだけ成長に対応できるかを示す特性です。 各コンポーネントにビジネスロジックが点在していることで、他のサービスに成果物を展開することが難しくなり、拡張性が損なわれることを示しています。 どう解決したのか ビジネスロジックをなるべく1つのコンポーネントに集約することで、仕様の把握から変更をしやすくし、メンテナンス容易性(Maintainability)を高める 各サービスで共通となる成果物を生成するバッチシステムを起点にして、そこから他のサービスに成果物を展開できる設計にすることで、拡張性(Scalability)を高める 良くない点2: 1つのコンポーネントに複数の責務を持つ処理が混在していた なぜ良くないのか 課題1. コンポーネントを再利用することが難しい 課題2. インシデントの原因究明に時間がかかる 課題3. コンポーネントのテストが難しい 課題4. コンポーネントの凝集度が低くなる 課題1: 再利用性(Reusability) 「再利用性(Reusability)」とは、システムの一部を他のシステムや他の部分で再利用できるかを示す特性です。 1つのコンポーネントにデータ抽出から変換処理など様々な責務を持つ処理が混在していると、特定のバッチシステムに特化したコンポーネントとなり、再利用性が損なわれます。 課題2: サポート容易性(Supportability) 「サポート容易性(Supportability)」とは、システムのエラー時に必要となる情報のログを整えられているかを示す特性です。 私たちはコンポーネント内の各処理で都度、成果物の状態を全てログとして残すのは現実的でないと判断したため、コンポーネントの出力結果をデータストレージに保存しています。そのため、コンポーネント内のどの処理でどのようなデータが生成されたのかをトレースすることが難しくなり、インシデントの原因究明に時間を要することがありました。 課題3: テスト容易性(Testability) 「テスト容易性(Testability)」とは、システムのテストがどれだけ簡単に行えるかを示す特性です。 テスト容易性は複数の要素から構成されます。課題3は「単純性(Simplicity)」と「理解容易性(Understandability)」に関わります。複数の責務を持つコンポーネントは、テスト対象となるコードの増加により単純性が損なわれます。さらに、テスト対象が増えることでテストケースの数も増え、テストの目的が理解しにくくなります。 課題4: 凝集度 課題4はコンポーネントの処理がどのくらい関連度のある塊になっているかを示す凝集度に関わる課題です。コンポーネント内に複数の処理があること自体は問題ありませんが、まとまり具合が問題となります。 リアーキテクチャ前の実装では、時間的凝集になっている箇所がありました。時間的凝集とは、同じようなタイミングで実行される処理をまとめ、実行順序を入れ替えても問題ない特徴があります。ここでは、ビジネスロジックと正規化処理を入れ替えても問題ないということがわかっていました。凝集度が低いコンポーネントは、課題3で説明したテスト容易性が損なわれることにも繋がります。 また、コンポーネント内の処理間に新しく追加したい処理が発生した場合、既存のコンポーネントへ実装することになり、さらに凝集度は低くなります。 どう解決したのか データの抽出、変換、統合、削除などの各処理をなるべく小さな単位でコンポーネント化し、再利用性(Reusability)やテスト容易性(Testability)、凝集度を高める 正規化処理の具体的な処理内容は外からコントロールできるようにし、再利用性(Reusability)を高める 小さなコンポーネント毎に成果物を出力することで状態をトレースしやすくし、サポート容易性(Supportability)を高める 削除処理を1つのコンポーネントに集約し、最後に配置することでインシデント対応時の情報源となるデータの欠損を防ぎ、サポート容易性(Supportability)を高める 良くない点3: データ抽出処理が独立していない なぜ良くないのか 課題1. 新しくデータリソースが増えた場合に対応しづらい 課題1: 拡張性(Scalability) バッチシステムのデータリソースは将来増えることが予想されていました。そのため、データ抽出処理とシステムの拡張性は密接に関連しており、データ抽出処理が独立していないとデータリソースが増えた場合に対応しづらく拡張性(Scalability)が損なわれます。 成果物のデータ形式にJSON Linesを採用しているため、各コンポーネントにはJSON Lines形式に変換する処理が含まれていました。そのため、データリソースが増えた場合はデータ抽出処理に加えて変換処理を都度実装する必要もありました。 どう解決したのか データ抽出処理を独立させることで、データリソースが増えた場合にも対応できるように拡張性(Scalability)を高める データ抽出以外のコンポーネントの入力形式をJSON Lines形式に統一し、変換処理の責務をデータ抽出処理に集約することで、拡張性(Scalability)を高める Design Docの活用 Design Docとは、システムの設計に関するドキュメントのことです。Design Docを用いて議論することで設計の品質を高めることを目的としています。今回のリアーキテクチャにおいても、Design Docを活用しました。 以下にDesign Docで共有した情報を紹介します。 なにを書くか 検索基盤部では、Design Docの雛形を各プロジェクトの要件に合わせてカスタマイズしています。今回のリアーキテクチャで共有した情報をいくつか紹介します。 Goal / Non-Goal プロジェクトの目的(Goal)と意図的に目指さないこと(Non-Goal)を記述します。リアーキテクチャの場合は、なぜ目的としているのかの背景や歴史的な経緯を記述し、途中から加わったメンバーにも目的を共有できるように意識しています。 用語集 プロジェクト内で用いる単語の定義を記述し、コミュニケーションの精度を高めることを目指しています。 全体のシステム構成図 システム全体が複数のコンポーネントから構成される場合、コンポーネント間の関係を示す図を作成します。この図によって、プロジェクトメンバーが全体像を把握しやすくなります。 議論段階では、考えられる設計案を複数提示しそれぞれのメリット・デメリットを記述するようにしています。リアーキテクチャの場合は現在のシステム構成図とリアーキテクチャ後に予定しているシステム構成図を比較しやすいようにしています。 コンポーネントの定義 システム全体を構成するコンポーネントの処理内容とインプットとアウトプットを記述します。さらに、インプットとアウトプットの例を記述し、処理のイメージを理解しやすくするように意識しています。 効果 今回Design Docを活用したことで、以下のような効果がありました。 リアーキテクチャ前のバッチシステムの課題を共有し、解決策を共有できた バッチシステムのドメイン知識やビジネスロジックを共有し関係者の理解を深めることができた Design Docの設計から実装がズレていないかをチェックできた 内部設計に関する知識や情報を共有できた まとめ 本記事では、バッチシステムの内部設計の品質を高めるためのポイントをリアーキテクチャの事例をもとに紹介しました。また、アーキテクチャ特性の観点で課題を整理し、それらの課題を解決するためのアプローチを紹介しました。 バッチシステムの外部設計に関するテックブログなどは散見されますが、内部設計に関する情報は多くありません。そこで、本記事がバッチシステムの開発や運用に携わるエンジニアの方々の参考になれば幸いです。 おわりに ZOZOでは一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください! corp.zozo.com
アバター
はじめに こんにちは、MA部の中原です。 MA部ではメルマガやLINE、アプリプッシュ通知を配信するためのマーケティングオートメーションシステムを開発・運用しています。 2022年からこのマーケティングオートメーションシステムをリプレイスするためのプロジェクトをMA部で進めています。リプレイス後の新しいマーケティングプラットフォームを「ZOZO Marketing Platform(略称:ZMP)」と呼んでいます。ZMPの概要については以下のテックブログをご覧ください。 techblog.zozo.com 本記事では、マス配信バッチのリプレイスについてご紹介します。 目次 はじめに 目次 配信の種類 システム全体の課題 既存のマス配信のシステムと運用について 既存システム (1) 対象者抽出 (2) コンテンツの抽出 (3) 配信処理 運用 既存のシステムと運用の課題 マーケターの自由度が低い 簡易的なCMSの限界 配信処理が基盤ごとに分かれている リプレイス概要 1. キャンペーンごとのDigdagのワークフローの処理を共通化 2. 新規開発した管理画面からのパラメータを使う 新基盤の仕組み (1) キャンペーン設定 (2) 登録 (3) 配信データ取得 (4) 実行 (5) 実績データ登録 (6) 配信リクエスト 3. ABテストの導入 4. テスト配信の仕組みづくり データセットを分ける 配信リストを上書き 5. 配信処理を配信基盤に任せる リプレイスの課題 解決策 ジョブの導入 バッチ処理の流れ まとめ さいごに 配信の種類 ZOZOTOWNでは、マス配信とパーソナライズ配信の大きく2種類を配信しています。 マス配信 パーソナライズ配信 特徴 ・特定のセグメント(配信対象者)に対して特定のコンテンツ(届けたい情報)を特定日時に配信 ・ユーザの利用状況に応じて表示するアイテムや並びなどをパーソナライズする場合もあり ・ある条件をトリガーとして一人ひとりにリアルタイムで配信 ・ユーザの行動に応じて配信時間を最適化するものもあり 配信例 コスメに関心がありそうな世代・性別に対して、コスメに関するキャンペーン情報を配信 ・お気に入り登録したアイテムの在庫がわずかになった際の通知 ・閲覧したアイテムの値下げ通知 など マーケターの運用 キャンペーンの配信スケジュールやセグメント等を決め、CMSを使って運用 システムが特殊なつくりになっているため、設定を変更したい場合はマーケターからエンジニアに依頼 マス配信とパーソナライズ配信は基盤が分かれています。 システム全体の課題 システム全体で存在していた課題について以下のテックブログで詳しく紹介しています。 techblog.zozo.com 簡単にまとめると課題は大きく以下の3つがありました。 配信の種類ごとに使用技術や基盤が異なり、運用・保守コストが肥大化 運用・保守コストが大きいため、新規開発にリソースが割けない マーケターのみでキャンペーン実施作業が完結しない 既存のマス配信バッチにおいては特に3つ目の課題が顕著でした。次の章以降では、既存のマス配信バッチに焦点を当て、システム内容、運用、課題、リプレイス内容を説明します。 既存のマス配信のシステムと運用について 既存システム 既存のマス配信の仕組みでは Digdag を使用しています。Digdagとはオープンソースのワークフローエンジンで、複数のタスクをワークフローとして定義し、バッチ処理を行えるものです。キャンペーンの種類ごとにDigdagのワークフローが作成されており、クーポンメール用、タイムセールの告知メール用、アプリプッシュ通知用などがあります。これらのワークフローは、以下の大きく3つのステップで構成されています。 (1) 対象者抽出 対象者は基本的にアクティブな会員が対象です。BigQueryで作成したセグメントのビューを元に、特定の対象者に絞り込むこともあります。 (2) コンテンツの抽出 キャンペーンごとに様々な条件でコンテンツを抽出し、デザイン(お知らせ内容)を表示する上で必要なパラメータ(商品名や商品の値段など)を取得します。 (3) 配信処理 対象者とコンテンツのパラメータを組み合わせて配信リストを作成します。配信時にデザインのテンプレートにコンテンツ抽出時のパラメータが埋め込まれ、デザインが表示される状態で配信します。 運用 配信の設定は Google Sheets(スプレッドシート) と Google Apps Script(GAS) を利用した簡易的なCMSから行います。マーケターはこのCMSを使って各キャンペーンの配信日時やセグメント、Digdagのワークフローに渡すパラメータ等を設定し、運用しています。Digdagのワークフローに渡すパラメータには、掲載商品をセール商品のみにするかどうかのフラグやBigQueryで作成しているビューなどがあります。これらのパラメータはDigdagのワークフローの処理内で、対象者や掲載商品の絞り込みに使用されます。以下に運用時の設定例を示します。 簡易的なCMSの詳しい仕組みについては以下のテックブログをご覧ください。 techblog.zozo.com また、以下のようにDigdagのワークフローの作成や修正が必要な場合マーケターだけではできないのでエンジニアへ依頼します。 新規キャンペーンの追加 エンジニアにDigdagのワークフローの作成を依頼し、CMSからそのワークフローを指定する 生成するコンテンツの内容を変更 表示するおすすめ商品から特定の商品を除外する等 機能の追加 ABテストの機能を追加する等 既存のシステムと運用の課題 既存のマス配信バッチと運用には以下の課題がありました。 マーケターの自由度が低い 紹介したように既存の仕組みはエンジニアありきの運用だったため、マーケターが主体となってキャンペーンの実施ができない状態でした。 既存のマス配信バッチでは、新規キャンペーンの開発や条件の変更が必要な場合、エンジニアに依頼する必要がありました。そのため、キャンペーン実施までのリードタイムが長くなってしまいます。 また、既存のシステムでは機能不足より実現できないことがありました。例えば既存のマス配信バッチには、ABテストの機能が標準で備わっていませんでした。ABテストとは、複数のバリエーションを用意し、それぞれの効果を比較することで最適な配信をするための手法です。各キャンペーンの処理が共通化されていなかったため、エンジニアはマーケターからの依頼がある度にそのキャンペーンに対してABテストのロジックを実装する必要がありました。また、バリエーションの数やそれぞれに対象者を振り分ける比率は外部(CMS)から指定できない仕組みだったため、マーケターが自由に設定できずABテストの実施のハードルが高くなっていました。 簡易的なCMSの限界 簡易的なCMSはあるものの、スプレッドシートとGASでは操作性やバリデーション等のできることが限られているため、マーケターの運用ミスが起きやすい状態でした。 配信処理が基盤ごとに分かれている 先述の通り、マス配信とパーソナライズ配信では基盤が分かれています。メール配信の場合は外部のサービスにリクエストを送って配信します。それぞれの基盤から1つのメール配信サービスにリクエストを送るため、大量に配信すると流量制限ができずメール配信サービスに負荷をかけてしまい配信の遅延やエラーが時折発生していました。そのため、配信基盤を1つにしてメール配信サービスへのリクエスト流量をコントロールすることが求められていました。 リプレイス概要 マス配信バッチのリプレイス概要は以下の通りです。 キャンペーンごとのDigdagのワークフローの処理を共通化 新規開発した管理画面からのパラメータを使う ABテストの導入 テスト配信の仕組みづくり 1. キャンペーンごとのDigdagのワークフローの処理を共通化 ZMPの概念モデルに沿ってキャンペーンごとのDigdagのワークフローの処理を共通化・汎用化しました。概念モデルとは、ZMPにおけるデータの構造やデータの流れ、データの管理方法などを定義したものです。以下に概念モデルの一部を示します(初期検討時のもの)。 概念モデルの詳しい内容については こちら のテックブログをご覧ください。 以下はリプレイス前とリプレイス後のワークフローの実装イメージです。 既存のマス配信システムでは、「クーポンメール用」「タイムセールメール用」のようにキャンペーンの種類ごとにDigdagのワークフローが分かれていました。共通化することで1つのワークフローでチャネルやキャンペーンに関わらずすべての配信が可能になりました。また、マーケターの自由度の高い設定内容で柔軟に処理できるようにしました。 2. 新規開発した管理画面からのパラメータを使う 既存のマス配信バッチでは、スプレッドシートとGASを使って設定内容をBigQueryに登録し、その情報を使ってDigdagのワークフローを実行していました。新基盤では、スプレッドシートではなく、ZMPの管理画面から設定された情報を元にします。その他の仕組みは基本的に既存と同じにしました。 新基盤の仕組み 新基盤の仕組みは以下の通りです。 (1) キャンペーン設定 マーケターがZMPの管理画面でキャンペーンの設定情報を登録します。 (2) 登録 管理画面で登録された情報をBigQueryに登録します。登録する情報は既存のスプレッドシートに似た内容です。例えば、キャンペーン名、配信日時、セグメント、コンテンツの内容などです。 (3) 配信データ取得 3分おきに動いているDigdagのスターターというワークフローがBigQueryに登録されているデータを参照し、配信日時を迎えたキャンペーンの設定情報を取得します。スターターの仕組みについては こちら のテックブログで紹介しているものと同様です。 (4) 実行 (3)で取得した情報をパラメータとしてマス配信バッチ処理のDigdagのワークフローに渡して実行します。以下にパラメータ例を示します。 campaign : id : 1 name : "クーポンメール" segment : id : 1 query : "select * from `project.dataset.target_segment`" action : id : 1 channel : "mail" ab_test : settings : salt : 100 # ハッシュ生成のためのsalt control_group : percentage : 20 treatment_group : - contents_id : 1 percentage : 40 - contents_id : 2 percentage : 40 contents : - id : 1 name : "クーポンメール_パターンA" template : subject : "クーポンメール_パターンAの件名" body : "クーポンメール_パターンAの本文" - id : 2 name : "クーポンメール_パターンB" template : subject : "クーポンメール_パターンBの件名" body : "クーポンメール_パターンBの本文" jobs : - id : 1 project : "sample-project" # Digdagのプロジェクト名 workflow : "coupon_mail_job" # Digdagのワークフロー名 (5) 実績データ登録 BigQueryに実績データを登録します。実績データは配信したユーザの情報や配信したコンテンツの情報などです。既存のマス配信バッチでは細かい実績データは登録していませんでしたが、過去に配信したキャンペーンの分析や調査がしやすくなるように様々な実績を登録します。また、処理のステータス(実行前・実行中・成功・失敗)も書き込み、配信処理の状況を管理画面から確認できるようにします。 (6) 配信リクエスト 配信処理を行う配信基盤にリクエストして配信します。 3. ABテストの導入 既存のマス配信バッチで一部実装されていたABテストの仕組みを共通処理として導入しました。ABテストの設定がある場合、対象者を管理画面から指定された比率で振り分けます。具体的には、 FARM_FINGERPRINT でハッシュを生成し、 ABS で絶対値に変換し、 MOD で値が0から100に収まるように調整します。その値を使って、指定された比率でランダムに分割します。サンプルクエリを以下に示します。波括弧で囲まれた部分は管理画面から設定された値です。 WITH target_segment_with_seed AS ( SELECT unique_id,  -- ユニークID member_id,  -- 会員ID email_id,  -- メールID push_notification_uid,  -- アプリプッシュID mid,  -- LINE用のID CASE WHEN member_id IS NOT NULL THEN CAST (member_id AS STRING) ELSE COALESCE ( CAST (email_id AS STRING), '' ) || ' _ ' || COALESCE (push_notification_uid, '' ) || ' _ ' || COALESCE (mid, '' ) END AS seed FROM `project.dataset.target_segment` ), split_target_segment AS ( SELECT unique_id, member_id, email_id, push_notification_uid, mid, MOD ( ABS (FARM_FINGERPRINT( CAST (seed AS STRING) || CAST ( ' {{ salt }} ' AS STRING))), 100 ) AS treatment FROM target_segment_with_seed ) SELECT unique_id, member_id, email_id, push_notification_uid, mid FROM split_target_segment WHERE {{ rate_from }} <= treatment AND treatment < {{ rate_to }} ; 4. テスト配信の仕組みづくり 既存のマス配信バッチでは、テスト配信対象者にしか配信されない設定になっているQA環境で本配信同等の配信設定をしてテスト配信を行なっていました。新基盤では、本番環境のZMPの管理画面からテスト配信ができるようにしました。具体的な仕組みは以下の2つです。 データセットを分ける 配信リストを上書き データセットを分ける 本配信とテスト配信でBigQueryのデータセットを分けました。これにより、本番環境で本配信とテスト配信のデータが混ざることを防ぎます。マス配信のバッチ処理のDigdagのワークフローにテスト配信かどうかを判別するためのパラメータを追加し、データの書き込みや参照するデータセットを切り替えるようにしました。 配信リストを上書き テスト配信時は管理画面からテスト配信対象者を選択します。本配信と同じ処理を行い配信リストを作成した後、ランダムにデータを選び、テスト配信対象者のみになるように上書きします。この際、ユーザの名前やメールアドレスのような個人情報はテストアカウントのデータに置き換えます。 5. 配信処理を配信基盤に任せる 既存のマス配信バッチの中で行っていたメールの配信処理を配信基盤に任せます。先述の通り、メール配信サービスへのリクエスト流量をコントロールするためにどの基盤からも使えるメール配信基盤が必要になり作成したので、それを使うことにしました。ZMPではメールの配信基盤を新規で作成し、以下のテックブログで紹介しています。 techblog.zozo.com リプレイスの課題 前の章で述べた通り、キャンペーンの種類ごとに分かれていたDigdagのワークフローは、概念モデルに沿って抽象化することで共通化しました。これにより、1つのワークフローでチャネルやキャンペーンに関わらずすべての配信が可能になり、管理画面でワークフローを意識する必要がなくなりました。また、ABテストの導入やテスト配信の仕組みも組み込まれたことで、マーケターの自由度が高い設定内容で柔軟に処理できるようになりました。しかし、任意のタイミングで特殊な処理をしたい場合があります。例えば以下のような場合です。 特定のセグメントを抽出する前にセグメントのビューの中で参照しているテーブルが更新されるのを待ちたい コンテンツ抽出時にキャンペーンによって異なる方法で抽出したい 配信前に特定の外部サービスやZOZOTOWN本体にデータ連携やAPIリクエストをしたい 1つ目について例をあげて説明します。例えば、セグメントのビューの中で別のバッチによって更新されるテーブルを参照する場合、そのテーブルが更新された後に対象者を抽出しないと古いデータを参照してしまい、送りたい対象者に送ることができません。そのため、セグメント抽出前に「テーブルの更新を待つ」という処理が必要になります。 上記以外にも今後任意のタイミングでしたい処理が出てくる可能性があるため、別の概念を導入して補う必要がありました。 解決策 ジョブの導入 ZMPの概念モデルに初期検討時にはなかったジョブという概念を導入することで、必要に応じて任意のタイミングで特殊な処理ができるようになりました。ジョブとは指定されたDigdagのワークフローを実行できるようにするための概念です。特殊な処理が必要な場合、事前にジョブとしてその処理を行うDigdagのワークフローを作成しておきます。ジョブの情報はZMPの管理画面から設定され、指定されたタイミングでそのワークフローを実行することで、特殊な処理が必要な場合に補うことができます。 バッチ処理の流れ コンテンツ抽出時にジョブ(特定のセグメントを抽出する前にセグメントのビューの中で参照しているテーブルが更新されるのを待ちたい)を設定した場合を例に、バッチ処理の流れを以下に示します。 対象者抽出 管理画面から指定されたジョブを実行し、セグメントのビューの中で参照しているテーブルが更新されるのを待ちます。 ジョブ完了後、管理画面から指定されたセグメント(BigQueryのビュー)で対象者を抽出します。 コンテンツ抽出 管理画面から指定されたコンテンツ情報に基づいてコンテンツを抽出します。 対象者とコンテンツのパラメータを組み合わせて配信リストを作成します。 配信処理 配信リストを使って配信基盤にリクエストして配信します。 例としてあげた「特定のセグメントを抽出する前にセグメントのビューの中で参照しているテーブルが更新されるのを待ちたい」の場合、事前に対象の更新バッチを待つジョブを作成することになります。管理画面からはそのジョブを選択できます。また、ジョブはセグメント抽出の前か後かなど、実行するタイミングも指定できます。セグメント抽出の前処理として作成したジョブを指定することで、セグメント抽出前にジョブが実行され、セグメントのビューの中で参照しているテーブルが更新されるのを待つことができます。 まとめ マス配信バッチのリプレイスについてご紹介しました。任意のタイミングで特殊な処理をするための仕組みが必要でしたが、特殊な処理が必要な場合でもジョブという概念で補うことができました。汎用的で柔軟なシステムの概念モデルを構築できたと思います。汎用化したことでデータ管理が統一でき、統一した方法で管理画面でのキャンペーン登録ができるようになりました。本記事が皆様の参考になりましたら幸いです。 さいごに ZOZOでは一緒にプロダクトを開発してくれるエンジニアを募集しています。ご興味のある方は下記リンクからぜひご応募ください! hrmos.co hrmos.co
アバター