TECH PLAY

株式会社ZOZO

株式会社ZOZO の技術ブログ

938

はじめに こんにちは、ZOZOTOWN開発2部Androidブロックの小林( @kako_351 )です。普段はZOZOTOWN Androidアプリの開発を担当しています。今年の3月に入社して機能改修や既存機能の調査などの業務に携わってきました。その中でZOZOTOWN Androidアプリについて知見を持っていないため、調査や開発の際に学習コストがかかるといった課題が見えてきました。本記事ではAndroidアプリの実装を把握するアプローチをご紹介します。 目次 はじめに 目次 背景 実装を把握するアプローチの全体像 ドキュメントの把握 目的 アプローチ モジュール構成や画面遷移などの全体構造の把握 目的 モジュール構成 画面遷移 アーキテクチャの把握 目的 アプローチ ライブラリや使用技術の把握 目的 アプローチ ビルドやデプロイなどCI/CD環境の把握 目的 アプローチ テストの把握 目的 アプローチ 実際にコードを読む 目的 アプローチ コンポーネントの特定 レイアウトファイルから辿る Layout Inspectorを使う その他の方法 詳細を読み解く チームメンバーとのコミュニケーション 目的 アプローチ まとめ 背景 初めて触れるプロダクトへの知見が全くない状況で、調査や開発時に学習コストがかかっていました。そのため既存のメンバーよりもどうしても調査や開発に時間がかかっていました。ZOZOTOWN Androidアプリの規模の大きさから、調査や開発の度に実装を1から読んでいくと毎回学習コストがかかってしまうため、全体や方針を把握してこの学習コストを減らせないか考えました。 実装を把握するアプローチの全体像 基本的なアプローチとして、プロジェクトの大枠から詳細へと理解を深めていきます。 ドキュメントの把握 モジュール構成や画面遷移などの全体構造の把握 アーキテクチャの把握 ライブラリや使用技術の把握 ビルドやデプロイなどCI/CD環境の把握 テストの把握 コードを読む チームメンバーとのコミュニケーション ドキュメントの把握 目的 開発時に参考となるドキュメントが存在している場合があります。これらのドキュメントを把握することで、開発時に必要な情報を得ることができます。 アプローチ ドキュメントをキャッチアップします。ZOZOTOWN Androidチームでは以下のようなドキュメントがまとめられています。 アーキテクチャの説明 コーディング規約 ライブラリ ブランチ運用、GitHub運用 ライセンス管理 リリースフロー Android Studioの設定 開発環境 さらに、オンボーディングの一環でこれらのドキュメントがメンターから共有されます。入社したばかりのタイミングではどこに情報があるのかわからないので、このサポートは非常に嬉しいです。 モジュール構成や画面遷移などの全体構造の把握 目的 依存関係やファイルの配置場所など、どのような実装がどこに配置されているのかを把握します。また、自分が開発する際に何をどこに配置するべきかを理解します。 モジュール構成 最近のAndroidアプリは、マルチモジュール化していることが多いのでモジュール構成を把握します。 プロジェクトにモジュールの依存関係グラフを生成するGradleタスクがある場合はそれを活用できます。例えば、 projectDependencyGraph のようなタスクがあります。ZOZOTOWN AndroidアプリにもprojectDependencyGraphが存在しているので、依存関係グラフを生成してみました。 モジュール名から未公開情報が推測できるため、一部伏せています この図はおおまかな構成の予測を立てるために活用します。楕円形がモジュールを表し、矢印が依存関係を表しています。例えば、「:app」モジュールから複数の矢印が伸びていて、その先を見ると「:feature:hoge」や「:feature:fuga」というモジュールがあります。このことから、機能単位でモジュールを作成していることが読み取れます。また、複数の「:feature」モジュールから「:common」というモジュールに矢印が伸びています。これは「:common」という名前と複数のモジュールから矢印が伸びていることから、共通部品を管理しているモジュールであることが読み取れます。 このようにモジュール名と依存関係から情報が読み取れます。例えばZOZOTOWN Androidアプリでは以下のような情報が読み取れました。 featureモジュールで各機能単位をモジュール化している 共通部品はcommonモジュールで管理している ModelやRepositoryなどのデータレイヤーはdataモジュールに集約させている モジュール名から推測する役割に被りがあるのでプロダクトの成長と共にモジュール構成の見直しが行われている モジュール構成を簡略化した図としては次のようになります。 かなり簡略化しましたが、このような構成であることがわかりました。 画面遷移 画面遷移をどのように管理しているのか把握します。いくつか考えられる候補があります。 Jetpack Navigation FragmentManager Navigation Compose Fragmentベースであれば、Jetpack NavigationやFragmentManagerを用いた画面遷移が考えられます。一方、フルComposeのアプリならNavigation Composeが候補に挙がってきます。 ZOZOTOWN AndroidアプリはFragmentベースで、Navigation Graphは存在しない構成です。よってFragmentManagerで画面遷移を実現している事が分かります。 モジュール間を跨ぐ画面遷移をどのように実現しているか把握します。ZOZOTOWN AndroidアプリではEventBusを利用してappモジュールを仲介する形で画面遷移を実現しています。 例えば、featureAモジュール内のFragmentAからfeatureBモジュール内のFragmentBに遷移する場合、featureAモジュールからFragmentBは直接参照できません。ZOZOTOWN AndroidアプリではEventBusを使いappモジュールのMainActivityにコールバックして、FragmentAからFragmentBへ画面遷移しています。図にすると以下のようなイメージです。 アーキテクチャの把握 目的 保守性や変更性、開発生産性のためにアーキテクチャを把握します。 アプローチ ドキュメントが存在している場合はその内容をキャッチアップします。ただし、プロダクトで採用するアーキテクチャにも変化がありえます。アーキテクチャの候補としては以下のようなものがあります。 MVVM MVP MVI Flux Androidアプリでは Android公式のアーキテクチャガイド が存在しており、これを参考にしている場合があります。 ZOZOTOWN Androidアプリはアーキテクチャに関するドキュメントが存在しています。また、ZOZOTOWN Androidチームにはアーキテクチャについて議論するアーキテクチャ座談会という取り組みがあります。その議事録からも現在の方針を把握できます。 ZOZOTOWN Androidアプリはその歴史の長さや規模感から、画面や機能によってアーキテクチャが異なります。現在の方針は、MVVMに近い形が採用されているようでした。ただし、一部の画面はRedux 1 で実装されているなど、画面や機能によって異なるアーキテクチャが採用されていることがわかりました。 ライブラリや使用技術の把握 目的 使用しているライブラリや技術を把握することで既存実装を理解します。 アプローチ ドキュメントとGradleを見ることで把握していきます。最近であればversion catalogでライブラリ管理している場合があるので、TOMLファイルなどを参照すれば理解が早いかもしれません。 ZOZOTOWN Androidアプリでもversion catalogを利用しています。一例ですが、具体的には次のようなライブラリを把握できました。 UI系 Jetpack Compose Epoxy API通信 Retrofit2 OkHttp Gson 非同期処理 Kotlin Coroutines RxJava 2 画像系 Picasso Coil DB greenDAO Room テスト MockK JUnit Robolectric Truth その他 Dagger Hilt Firebase また、CoroutinesやComposeはBOMで管理しています。具体的には以下のような記述からBOM管理であることがわかります。 [versions] compose-bom = "2024.01.00" [libraries] compose-bom = { group = "androidx.compose" , name = "compose-bom" , version.ref = "compose-bom" } compose-ui = { module = "androidx.compose.ui:ui" } compose-foundation = { module = "androidx.compose.foundation:foundation" } # .. . ビルドやデプロイなどCI/CD環境の把握 目的 テストやリリースフロー、Lintチェック、ライセンスチェックなどCI/CDで自動化している場合があります。それらを把握し、チームの開発・運用を理解します。 アプローチ Pull Requestを確認することでジョブと実行環境が確認できます。ZOZOTOWN AndroidアプリではGitHub Actionsを採用しています。 もしくは、プロジェクトのフォルダやファイルを確認することでどのようなジョブが設定されているか確認できます。例えば、GitHub Actionsでktlintによる静的解析を自動チェックしている環境であれば以下のようなフォルダ、ファイルが存在していると思います。 .github ∟ workflows ∟ ktlint.yaml リリース版アプリのビルドからGoogle Play Consoleへのアップロードも自動化している場合があります。ZOZOTOWN AndroidアプリでもビルドからリリースまでのフローはCI/CDで自動化されていました。 テストの把握 目的 開発時に期待されているテストの内容を把握します。 アプローチ ユニットテストを書く文化があるかやどこまで書いているか、また自動化されているかを把握します。自動化は前述のCI/CD環境の把握の際に、ユニットテストを自動化していることなどを把握できます。カバレッジを収集している場合は、カバレッジレポートをどのように活用しているかなども合わせて確認します。 どのようなテストを書くべきかを理解しておくと開発時にスムーズでいいかもしれません。例えば、以下のような方針やルールがあるかもしれません。 実装の詳細ではなく振る舞いをテストする テスト名は命名規則に従う ZOZOTOWN Androidチームでは、テックリードがチーム向けにユニットテストについて説明した資料がありました。その資料や過去のPull Requestを拝見することでテストの内容を参考にできます。 それらを参考にZOZOTOWN Androidチームはテスト文化があることや、ユニットテストの自動化もされていることがわかりました。また一部のメンバー間ではTDD(テスト駆動開発)で開発を進めていることもわかりました。 実際にコードを読む 目的 アプリの機能や画面がどのように実装されているのかを理解し詳細を把握します。 アプローチ 具体的なタスクを持つとモチベーションになるので、調査や開発タスクを進める過程でコードを読んでいくと理解が進みやすいと思います。そのようなタスクがない場合、機能単位でコードを見ていくとよいと思います。 ここではZOZOTOWN Androidアプリの商品詳細画面を例に挙げ、コードを読んでいく過程を紹介します。 コスメなど一部商品の場合、商品詳細画面に「衛生商品のため、返品・交換対象外です」といったメッセージを表示する以下のようなコンポーネントが存在します。 今回の例では、このコンポーネントがどのように表示されているかを特定していきます。 この記事で紹介するコードは、実際のZOZOTOWN Androidアプリのコードとは一部異なります。 コンポーネントの特定 まずはコンポーネントの特定です。以下のような方法があります。 レイアウトファイルから辿る Layout Inspectorを使う Flipperなどのデバッガーツールを使う ZOZOTOWN AndroidアプリはFragmentManagerで画面遷移をしていることがわかっているので、Fragmentのレイアウトファイルからコンポーネントを特定できます。ここでは、Fragmentから辿る方法とLayout Inspectorを使う方法を紹介します。 レイアウトファイルから辿る まずはFragmentを特定します。Fragmentベースのアプリであれば以下のadbコマンドで現在表示しているFragmentを特定できます。 adb shell dumpsys activity top | grep ' Added Fragments ' -A 1 # result # ... # Added Fragments: # #0: ItemDetailFragment{e53881d} (2e9ebbae-534f-4f02-90d1-dda3c302a8fb id=0x7f0a0147 tag=PACKAGE_NAME) Fragmentが特定できたらそのFragmentのレイアウトファイルを見ていきます。レイアウトファイルからコンポーネントを特定できます。 Layout Inspectorを使う Android StudioのLayout Inspectorでレイアウトをツリー構造で確認できます。ここから特定のコンポーネントを見つけることが可能です。 その他の方法 他には Flipper などのデバッガーツールを利用することでLayout Inspectorのようにレイアウトを確認できます。こちらはプロジェクトに導入済みであれば利用できますが、Layout Inspectorと同等の機能なのでこの記事では説明を割愛します。 詳細を読み解く コンポーネントを特定できたら、UIレイヤーからデータレイヤー方向へコードを読み進めていきます。この時、これまで説明したアーキテクチャやライブラリを念頭におきながらコードを読んでいくと理解しやすいかと思います。 例えば、「衛生商品のため返品・交換対象外です」のメッセージがどのように表示されているかを調べるとします。 まずは特定したコンポーネントを見ていきます。この画面全体はFragmentですが、コンポーネントはJetpack Composeで実装されています。 @Composable fun ItemStatusInfo( viewData: ItemStatusInfoViewData, // ... ) { when (viewData) { is ItemStatusInfoViewData.Visible.NonReturnable -> { NonReturnableItemStatusItem( viewData = viewData.nonReturnableViewData, ) } // ... } } ItemStatusInfoViewDataにより、表示するUIを分岐していることがわかりました。このItemStatusInfoViewDataの値がどのように確定されるのか見ていきます。 class ItemStatusInfoViewDataMapper { companion object { @JvmStatic fun fromDomainModel(itemStatusInfo: ItemStatusInfo) { if (itemStatusInfo.returnType != RETURN_TYPE_POSSIBLE /* 返品不可を表すType */ ) { return ItemStatusInfoViewData.Visible.NonReturnable( /* ... */ ) } // ... その他の分岐 } } } ItemStatusInfoViewDataMapperでDomainModelのデータからItemStatusInfoViewDataの中身を決定しています。この先はデータの取得部分を読んでいきます。 事前のアーキテクチャの把握で、一部画面はReduxで実装されていることがわかっています。この商品詳細画面もReduxで実装されているので、その点を意識しながらコードを読んでいきます。 itemStatusInfoをどのように取得しているのかを読んでいきます。 package example.itemdetail.model data class ItemDetailState( // ... val itemStatusInfo: ItemStatusInfo, ) itemStatusInfoはItemDetailStateの中に存在しています。続いて、ItemDetailStateはReducerで作られているので中身を見ていきます。ReducerはActionに応じて新しいStateを生成する役割を持っています。 class ItemDetailReducer { suspend fun reduce(action: ItemDetailAction, state: ItemDetailState): ItemDetailState { return when (action) { is ItemDetailAction.GetItemDetailSucceeded -> { val itemDetail = action.itemDetail ItemDetailState( /* itemDetailを元にステートを更新して返す */ ) } } } } ItemDetailActionが持つパラメータのitemDetailを元にItemDetailStateを作成しているのがわかりました。 次にitemDetailがどのように生成されるのか見ていきます。 ZOZOTOWN AndroidアプリにおけるReduxでは、APIリクエストなどの非同期処理はMiddlewareで行われています。以下のようなGetItemDetailMiddlewareが存在します。この中でItemDetailRepository.getItemDetailを経由してitemDetailを取得しています。 class GetItemDetailMiddleware( private val itemDetailRepository: ItemDetailRepository ) { fun dispatch(): Dispatcher<ItemDetailAction> : (Dispatcher<ItemDetailAction>) -> Dispatcher<ItemDetailAction> = { next -> return when (action) { is ItemDetailAction.GetItemDetail -> { val itemDetail = itemDetailRepository.getItemDetail( /* parameters */ ) ItemDetailAction.GetItemDetailSucceeded(itemDetail) } } } } class ItemDetailRepositoryImpl @Inject constructor ( private val apiService: ItemDetailApiService, ): ItemDetailRepository { override suspend fun getItemDetail( /* parameters */ ) { val response = apiService.getItemDetail( /* parameters */ ) // ... 後続処理 } } apiService.getItemDetailでAPIリクエストをどのように実装しているか見ていきます。API通信においてRetrofit2が利用されていることを既に知っているため、以下のコードを見てインタフェースを理解して終わりです。 interface ItemDetailApiService { @GET ( /* endpoint */ ) suspend fun getItemDetail( /* query parameters */ ): Response<ItemDetailResponse> } このようなアプローチで事前に把握した情報を合わせながら、コードを読んでいくと理解が進みやすいと思います。 チームメンバーとのコミュニケーション 目的 コードや資料のみではわからないことも存在します。そのため、メンバーとのコミュニケーションを通して実装がそうなっている理由や経緯を理解します。 アプローチ Slackなどの社内チャットツールやメンター制度があればその場を活用するとよいと思います。質問する際には、参考にした情報、聞きたい内容を明確にすると適切な回答が得られやすいです。 ZOZOTOWN Androidチームには、開発に関する困りごとを気軽に相談できるSlackチャンネルが存在しています。そのSlackチャンネルで相談すると誰かしらがすぐに反応してコメントくれるので困る事が少なかったです。また、私がZOZOに入社したばかりの頃はメンターに口頭でも相談していました。 チーム側にこのようなフォロー体制があることで、JOINした側としては安心できました。 実際のSlackでのやりとり まとめ 本記事では大規模なAndroidアプリの実装を紐解いていくアプローチを紹介しました。構成や実装の解像度が上がったことで以前よりも学習コストを減らせたと思います。新しい環境になり既存のプロダクトへの知見がなく困っている方がいれば、ぜひ参考にしてみてください。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com Reduxは元々JavaScriptのライブラリでStateを管理するためのフレームワークです。ZOZOTOWN AndroidアプリではReduxをカスタマイズして一部画面で導入されています。Reduxの詳しい説明は Reduxの公式ウェブサイト を参考にしてください。 ↩
アバター
はじめに こんにちは、ZOZOMO部SREブロックの蔭山です。普段は Fulfillment by ZOZO や ZOZOMO のSREを担当しています。 今回ZOZOMOで提供しているサービスの1つである「ブランド実店舗の在庫確認・在庫取り置き」のマイクロサービス(通称realshop-api)にてMySQLにアクセスできる運用ユーザーの権限管理の最適化を行いました。本記事でその取り組みについてご紹介いたします。 目次 はじめに 目次 なぜ権限管理を最適化したのか 権限管理が複雑化してきた 秘密情報を閲覧できるメンバーを制限する必要がでてきた どのように最適化したか ロール機能を使った権限の標準化 秘密情報の保護自動化 秘密情報カラムの管理 秘密情報カラムへの権限剥奪を自動化 秘密情報カラムを除いたVIEWの自動作成 実施した結果 まとめ なぜ権限管理を最適化したのか realshop-apiではDBにAmazon DynamoDBとAmazon Aurora MySQLの2つを採用しています。どのように2つのDBを使っているのかについては過去テックブログでご紹介していますので、興味のある方は以下のリンクからご覧ください。 techblog.zozo.com 運用作業や調査の一環でAurora MySQLへアクセスする必要があり、リリースから暫くの間は以下のような状態で運用していました。 運用メンバーごとにMySQLユーザーを発行 権限は必要となったタイミングで個別にMySQLユーザーに付与 しかし、サービスやチームの成長に伴って以下のような問題が出てきました。 権限管理が複雑化してきた チームメンバーの増加や様々な運用作業や調査をおこなっていくにつれてどのMySQLユーザーにどの権限が付与したのかが把握しづらくなってきました。実際に各メンバーが作業する上でもメンバーAは特定のクエリが実行できてメンバーBは実行できずに権限の付与依頼を行うなどのタスクが発生し、効率の悪化が目に見えてわかるような状態となってきました。 また権限の棚卸しを実施するにも、どのユーザーにどのような権限が付与されているのか、どの権限を付与・剥奪すべきかが定まっておらず、権限の棚卸し作業自体が困難になってきました。 秘密情報を閲覧できるメンバーを制限する必要がでてきた サービスの拡張に伴い、Aurora MySQL上に秘密情報を保持する必要が出てきました。社内の開発ルールでは秘密情報を閲覧できるメンバーをごく少数に絞る必要がありましたが、上記のように複雑化した権限の状態で更にカラムごとでの権限を制御するのは難しい状態でした。 どのように最適化したか 上記の問題もあり、このタイミングで権限管理を1から見直すこととしました。今回どのように最適化していったのか実例をご紹介します。 ロール機能を使った権限の標準化 MySQL 8.0より ロール 機能が実装されています。MySQL 8系をベースとしているAurora MySQL 3系でも利用できる機能です。ロールに対して権限を付与し、各MySQLユーザーにロールを割り当てることによりロールに紐づいた権限をユーザーにも継承できる機能です。 今回、まずはこのロール機能を使って付与権限の標準化を行うことにしました。権限の標準化にあたっては、運用メンバーのユースケースに合わせて以下のように定義しました。 権限名 ロール名 付与想定ユーザー 付与する権限 参照権限 read_only_developer_role 更新操作をする必要がないユーザー。特殊な権限を保つ必要がない運用メンバーにのみ付与。 秘密情報を含まないテーブルへのSELECT 更新権限 power_user_developer_role 更新操作をする必要があるユーザー。一部の運用メンバーにのみ付与。 秘密情報を含まないテーブルへのINSERT、SELECT、UPDATE、DELETE 管理者権限 admin_developer_role 秘密情報を含むテーブルへもアクセスする必要があるユーザー。チームマネージャーのみに付与。 すべてのテーブルへのINSERT、SELECT、UPDATE、DELETE 定義後はロールを作成し、ユーザーに今まで付与した権限をすべて剥奪後ロールを付与することでユーザーごとの権限の差異がなくなり、標準化が実現できました。 秘密情報の保護自動化 次に秘密情報を全運用ユーザーが閲覧できないような状態を作るために秘密情報の保護の自動化に取り組みました。ここからはどのように秘密情報の保護を自動化していったのか順を追ってご紹介します。 秘密情報カラムの管理 自動化するために、まずは秘密情報を保持するカラムの管理をシステムがわかりやすいようにしました。今回は秘密情報を保持したテーブル・カラムを管理するテーブルを作成しデータとして保持する方針としました。 実際には以下のような情報を保持するテーブルを作成しました。今回は sensitive_columns という名前でテーブルを作成しました。 カラム名 保存する内容 サンプル table_name 秘密情報を持つテーブル名 secret_tables column_name 秘密情報を持つカラム名 secret_column type どのような秘密情報を持っているか(会員名・住所・メールアドレスなど) email また開発ルールとして秘密情報を持つカラムが追加された場合、DBマイグレーションツールを使って上記テーブルへINSERTを行うようなルールとしました。 秘密情報カラムへの権限剥奪を自動化 次に秘密情報カラムへの権限剥奪の自動化を行いました。 realshop-apiではDBマイグレーションに Flyway を利用しています。新規でバッチなどは準備せず、開発コストを最小化して実現するためDBマイグレーション実行後にトリガーされる afterMigrate を使って権限の付け替えを実施することにしました。 しかしFlywayのafterMigrateではSQLで記載されている必要があるため、権限の付け替えロジックをストアドプロシージャで定義し実行することにしました。実際には以下のようなストアドプロシージャを定義しました。 DELIMITER // CREATE PROCEDURE sp_operation_set_sensitive_roles() BEGIN DECLARE table_name TEXT; DECLARE column_names TEXT; DECLARE done BOOL DEFAULT FALSE ; -- テーブルごとにアクセス可能なカラム一覧を取得 DECLARE tablesCursor CURSOR FOR SELECT t.TABLE_NAME AS table_name, GROUP_CONCAT(c.COLUMN_NAME ORDER BY c.ORDINAL_POSITION) AS column_names FROM INFORMATION_SCHEMA.TABLES t INNER JOIN INFORMATION_SCHEMA.COLUMNS c ON t.TABLE_NAME = c.TABLE_NAME WHERE t.TABLE_SCHEMA = ' DB名 ' AND t.TABLE_TYPE = ' BASE TABLE ' AND NOT EXISTS ( SELECT 1 FROM sensitive_columns WHERE sensitive_columns.table_name = t.TABLE_NAME AND sensitive_columns.column_name = c.COLUMN_NAME) GROUP BY t.TABLE_NAME; DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE ; OPEN tablesCursor; read_loop: LOOP FETCH tablesCursor INTO table_name, column_names; IF done THEN LEAVE read_loop; END IF ; -- Power Userロールへの権限付与 IF EXISTS (SELECT 1 FROM mysql. user WHERE user = ' power_user_developer_role ' ) THEN SET @power_user_grant_sql = CONCAT ( ' GRANT SELECT ( ' , column_names, ' ), UPDATE ( ' , column_names, ' ) ON DB名. ' , table_name, ' TO '' power_user_developer_role ''' ); PREPARE power_user_grant_stmt FROM @power_user_grant_sql; EXECUTE power_user_grant_stmt; DEALLOCATE PREPARE power_user_grant_stmt; END IF ; -- Read Onlyロールへの権限付与 IF EXISTS (SELECT 1 FROM mysql. user WHERE user = ' read_only_developer_role ' ) THEN SET @read_only_table_grant_sql = CONCAT ( ' GRANT SELECT ( ' , column_names, ' ) ON DB名. ' , table_name, ' TO '' read_only_developer_role ''' ); PREPARE read_only_table_grant_stmt FROM @read_only_table_grant_sql; EXECUTE read_only_table_grant_stmt; DEALLOCATE PREPARE read_only_table_grant_stmt; END IF ; END LOOP ; CLOSE tablesCursor; END // DELIMITER ; 内容は以下の通りです。 MySQLのテーブル・カラム情報を保持している INFORMATION_SCHEMA.TABLES と INFORMATION_SCHEMA.COLUMNS 、秘密情報を保持している sensitive_columns を使って通常通り閲覧できるカラムを抽出 抽出した結果をもとに権限ごとでGRANT文を生成 生成したGRANT文を実行 上記で定義したストアドプロシージャをafterMigrateで実行することにより、権限の付け替えを自動で実施できるようにしました。 秘密情報カラムを除いたVIEWの自動作成 秘密情報カラムへの権限剥奪は実現できました。しかしこの対応によって秘密情報へ参照するクエリがすべてエラーとなるため、運用体験が悪化してしまう懸念がありました。 そこで社内でも実績があった秘密情報カラムを除いたVIEWをAurora MySQL上でも実現することにしました。テーブル名の先頭に v_ を付与することで既存クエリのエラーを少しでも防ぎ、運用体験の悪化を防ぐことを目的としました。 今回参考にしたSQL Serverでの秘密情報の保護に関しても過去テックブログでご紹介しています。こちらも興味のある方はぜひご覧ください。 techblog.zozo.com 秘密情報カラムを除いたVIEWに関しても前章と同じく、FlywayのafterMigrateで特定のストアドプロシージャーを実行することにしました。実際には以下のようなストアドプロシージャを定義し実行する形としました。 DELIMITER // CREATE PROCEDURE sp_operation_create_views() BEGIN DECLARE upsert_view_sql TEXT; DECLARE done BOOL DEFAULT FALSE ; -- テーブルごとにCREATE VIEW文を生成 DECLARE upsertSQLCursor CURSOR FOR SELECT CONCAT ( ' CREATE OR REPLACE VIEW v_ ' , t.TABLE_NAME, ' AS SELECT ' , GROUP_CONCAT( CASE WHEN EXISTS ( SELECT 1 FROM sensitive_columns WHERE sensitive_columns.table_name = t.TABLE_NAME AND sensitive_columns.column_name = c.COLUMN_NAME) THEN CONCAT ( ''' ******** '' AS ' , c.COLUMN_NAME) ELSE c.COLUMN_NAME END ORDER BY c.ORDINAL_POSITION), ' FROM ' , t.TABLE_NAME, ' ; ' ) as upsert_view_sql FROM INFORMATION_SCHEMA.TABLES AS t INNER JOIN INFORMATION_SCHEMA.COLUMNS AS c ON t.TABLE_NAME = c.TABLE_NAME WHERE t.TABLE_SCHEMA = ' DB名 ' AND t.TABLE_TYPE = ' BASE TABLE ' GROUP BY t.TABLE_NAME; DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE ; OPEN upsertSQLCursor; read_loop: LOOP FETCH upsertSQLCursor INTO upsert_view_sql; IF done THEN LEAVE read_loop; END IF ; SET @upsert_view_sql = upsert_view_sql; PREPARE stmt FROM @upsert_view_sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; END LOOP ; CLOSE upsertSQLCursor; END // DELIMITER ; 内容は以下の通りです。 MySQLのテーブル・カラム情報を保持している INFORMATION_SCHEMA.TABLES と INFORMATION_SCHEMA.COLUMNS 、秘密情報を保持している sensitive_columns を使ってCREATE VIEW文を生成 生成したGRANT文を実行 上記の対応によって権限の付け替えと同様に自動化できました。 実施した結果 このように権限管理の最適化を実施した結果、問題としていた権限管理の複雑さはロールによる標準化で解消されました。またどのユーザーにどのロールが付与されていたかもわかりやすくなったため、棚卸しも実施しやすい状態にできました。 また秘密情報カラムに関しても必要最低限のメンバーしかアクセスできない状態にできました。秘密情報カラムを除いたVIEWも準備したことで運用メンバーの運用体験に大きな影響を与えることなく秘密情報の保護が実現できました。 今回ここまででご紹介してきた形で実現できたものの以下のような改善点が見えてきており、こちらに関しては今後解消していく予定です。 秘密情報の区分に合わせたマスクされる形式の変更 一時的な秘密情報カラムの権限付与 まとめ 本記事ではrealshop-apiで実施したMySQLでの権限管理の最適化についてご紹介しました。権限管理にお困りの方はぜひ参考にしてみてください。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください! corp.zozo.com
アバター
こんにちは。ZOZO Researchの研究員の古澤・川島・平川、ZOZOのデータサイエンティストの荒木・小林です。2024年8月6日(火)から8月9日(金)にかけて熊本で開催された画像の認識・理解シンポジウムMIRU2024に参加しました。この記事では、MIRU2024でのZOZO Research・ZOZOのメンバーの取り組みやMIRU2024の様子について報告します。 目次 目次 MIRU2024 企業展示 全体の動向 招待講演・インタラクティブセッション [IS2-35] The Niau Dataset: A Comprehensive Resource for Fashion Image Recognition [IS2-126] 大規模視覚言語モデルを用いた「似合う」の自動評価法 [IS-2-097] Moon & SpencerのAesthetic Measureを用いたファッションのカラー・コーディネート評価の検討 気になった研究発表 [IS1-119] Neural Lookup TableとPrompt Guidance Lossを用いた解釈可能な画像補正 [OS-1D-07] NeuraLeaf: Neural Parametric Leaf Models with Shape and Deformation Disentanglement [OS-1E-04] スパイキングニューラルネットワークによる画像生成拡散モデル [IS3-39] 集合データを対象とした識別と生成のマルチタスク学習と信頼度較正への応用 [IS3-56] 協調フィルタリングにおける潜在因子モデルの埋め込み表現のICAを用いた線形構造の分析 まとめ 最後に おまけ MIRU2024 MIRUとは、Meeting on Image Recognition and Understandingという画像の認識・理解についてのシンポジウムです。2024年の今回は熊本城ホールにおいて原則オフラインの形で開催されました。今年は過去最多の1591名もの方々が参加されたとのことで、会場が大きな賑わいを見せていました。ZOZO NEXTは、この MIRU2024 にゴールドスポンサーとして協賛させていただきました。 また、今年は5年ぶりにバンケットも開催されました。バンケットは立食形式で行われ、おいしい食事やお酒、熊本ラーメンを堪能しながら他の参加者たちと交流しました。このように、同じ分野で研究している他の研究者たちと現地で交流したり、研究内容についてカジュアルに話し合ったりできるのもシンポジウムの良さのひとつです。私たちも、初めてお話しする方はもちろんのこと、過去に研究を通して知り合った方との交流を楽しみ、明日の研究への良い刺激をいただきました。 昨年のMIRU2023に参加した際のレポートは以下の記事をご覧ください。 techblog.zozo.com 企業展示 企業展示ブースでは、ZOZO NEXTの取り組みをポスター形式でご紹介しました。ZOZOの多角的なファッションサービスと多様なデータ資産に加え、機械学習や最適化問題の実サービスへの応用事例、そして、ZOZO Researchが近年発表した論文についてご説明しました。多くの方々からご関心をお寄せいただき、お話をさせていただけたことを大変嬉しく感じています。ブースにお越しいただいた皆さま、誠にありがとうございました。展示していたポスターはこちらです。 また、ブースでご案内したZOZO NEXTの求人の最新情報はこちらからご覧になれます。 zozonext.com 全体の動向 昨年のMIRUでは、Stable Diffusionをはじめとする生成モデルや基盤モデルを活用した研究が多く見られました。今年は、これらの手法がさらに浸透し、研究のトレンドは「当たり前に使う」段階へとシフトしています。これに伴い、アルゴリズムの詳細な検討や、モデルが持つ事前知識を効果的に活用する方法に焦点が当てられるようになったと感じました。また、ファインチューニングや転移学習を行う際に生じるドメインギャップを埋めるためのドメイン適応の研究も目立ちました。加えて、Neural Radiance Fieldやそれを用いたComputational Photographyに関する研究も盛んに発表されていました。 チュートリアルでは「自動運転のためのビジョン技術」「ビジョン研究のための評価方法」「様々なセンサやモダリティを用いたシーン状態推定」という3つの講演が行われました。 まず「自動運転のためのビジョン技術」講演では、自動運転におけるセンサーやカメラの基本的な役割から最新の深層学習モデルまで、幅広い内容が取り上げられました。単一の鳥瞰図の特徴量を元に、主タスクである経路計画に加え、動作予測などの補助タスクを同時に学習させることで、主タスクの性能を向上させるPARA-Drive手法が非常に興味深いものでした。 次に、「ビジョン研究のための評価方法」では、機械学習の評価手法に関する議論が行われました。この講演では、評価指標が本当に評価したい対象を正確に捉えているのか、また評価指標そのものの性能やデータ選択のノイズの影響、さらに評価データに潜む問題点などが指摘されました。評価手法を評価するための基準を確立するには、多くの小さな問題を考慮する必要があり、その難しさを再認識しました。 最後に、「様々なセンサやモダリティを用いたシーン状態推定」はRGB画像以外の様々な認識手法に関する講演でした。イベントカメラやTime of Flightカメラ、アクティブ音響センシングなどが取り上げられ、これらの技術が従来のRGB画像ベースの手法と異なる環境で有効ということが紹介されました。特に、悪照明環境や遮蔽のある環境での性能向上に寄与するこれらの技術の可能性には驚かされました。RGB画像と比較すると研究例がまだ少ない分野ではありますが、RGB画像以外の認識方法に触れることで、認識技術の世界がさらに広がったと感じました。 招待講演・インタラクティブセッション 招待講演ではZOZOの機械学習エンジニアの住安がCVPR2024に採択された論文について発表しました。こちらの研究については CVPR参加レポート で紹介しているので、ぜひご覧ください。加えて、インタラクティブセッションでは、ZOZO Researchから2件、ZOZOから1件の研究をポスター形式で発表しました。各研究の要約は以下の通りです。 [IS2-35] The Niau Dataset: A Comprehensive Resource for Fashion Image Recognition Sai Htaung Kham , 森下和哉 , 和田崇史 , 中村拓磨 , 平川優伎 , 斎藤侑輝 ( ZOZO Research, BuySell Technologies Co., Ltd) ファッションにおいて、似合うかどうかの数値化は個人のスタイルを理解し、適切な提案をするために重要です。本研究では、ZOZOが運営するファッションSNSであるWEARに投稿された画像とスタジオで撮影した6,000枚のファッション画像からなるNiauデータセットを構築しました。各ファッション画像には、体型、ヘアスタイル、顔の形、着用アイテム、年齢といったラベルに加え、Niauスコアが付与されています。このNiauスコアは、人間のアノテーターに画像のペアを提示し、「どちらがより似合っているか」という質問の回答に基づいて算出された数値です。特に、OpenSkillアルゴリズムを使用することで、得られた回答からスコアを計算し、次の対戦ペアを生成しました。データ品質を確保するために、989名と981名の2つのアノテーターグループが画像を評価し、各グループから得られたNiauスコアの相関をスピアマン順位相関係数およびピアソン相関係数によって確認しました。 [IS2-126] 大規模視覚言語モデルを用いた「似合う」の自動評価法 平川優伎, 森下和哉, 和田崇史, 清水良太郎, 古澤拓也, Sai Htaung Kham, 斎藤侑輝 (ZOZO Research) 上記のNiauスコアの算出には多大なアノテーション費用がかかるという課題があります。本研究では似合う度の評価における大規模視覚言語モデル (Large-scale Vision Language Models, LVLMs) のゼロショット推論と人間による評価の整合度を検証しました。クラウドソーシングを利用して検証用データセットを構築し、人間による評価と主要なLVLMsの評価との間に一定の相関が存在することを確認しました。この結果は、LVLMsに埋め込まれた世界知識と視覚的な認識能力の画像を元にした似合う度の自動評価における有効性を示唆しています。 [IS-2-097] Moon & SpencerのAesthetic Measureを用いたファッションのカラー・コーディネート評価の検討 小林めぐみ , 吉本一平 , 光瀬智哉 ( ZOZO, ex-ZOZO) ファッションにおけるカラー・コーディネートは、視覚的な美しさや調和を重視する重要な要素の1つです。しかし、コーディネートの定量的な評価手法は未だ確立されておらず、主観的な判断に依存しているのが現状です。この研究では、配色の美的評価において広く利用されるMoon & Spencerの色彩調和指標 (Aesthetic Measure, AM) をカラー・コーディネートに適用できるかを検証しました。まず、ランダムな配色と比較してコーディネートされた配色の方が高いAMの値 (M) を持つという仮説を立て、両者の平均値を比較しました。コーディネートのlike数とMとの関連性を調査し、これを可視化することで、AMがファッションのカラー・コーディネート評価に単純には適用できない可能性があることを示しました。 気になった研究発表 今回の学会では多くの興味深い研究発表がありました。特に私たちが興味を持った研究についていくつか紹介します。 [IS1-119] Neural Lookup TableとPrompt Guidance Lossを用いた解釈可能な画像補正 小林哲 (東工大) 画像補正技術は、InstagramやPhotoshopなど様々なプラットフォームにおいて、写真の印象を操作するために利用されている技術です。旧来のフィルタ技術では、ピクセル間の変換処理をLookup Table (LUT) として保持しておき、入力された画像の各ピクセル値をLUTに従って変換する処理により実現されています。近年では、複数のLUTの加重平均をとることにより、複雑なフィルタ処理を学習ベースで獲得する研究が行われていますが、フィルタ名との対応を人間が解釈可能な形式で取ることができないという課題がありました。この研究では、学習可能かつ解釈可能な画像補正フィルタを実現するために、補正内容を表すテキストと補正後の画像に対して、CLIP-IQA(画像の品質評価に特化したCLIP)を用いて類似度を計算します。特に、負例のテキストと比較して類似度が大きくなるようにフィルタを学習する手法が提案されていました。また、従来手法ではLUTの加重平均ベースのモデルであったのに対し、こちらの研究では比較的シンプルなMLPが導入されており、ベンチマークにおいても良好な結果が得られたそうです。テキストベースで画像の変換処理を行う技術は近年のトレンドであり、生成画像の不自然な点をテキストベースで編集するアプローチなどは応用の幅も広く、弊社としても今後の重要な研究課題になりそうです。 [OS-1D-07] NeuraLeaf: Neural Parametric Leaf Models with Shape and Deformation Disentanglement Yang Yang, Hiroaki Santo, Yasuyuki Matsushita, Fumio Okura (Osaka Univ.) こちらは、葉の3Dモデルの再構築・形状生成についての研究です。人間や動物に関する3Dモデリングと再構築の研究は活発になされていますが、植物、特に葉のモデリングにおいては、多様な形状と柔軟な変形を正確に表現することに課題があります。例えば、人間や動物のモデルで用いられるような骨格推定や明確なパーツのセグメンテーションは、葉のモデルに直接適用できません。この研究では、事前学習済みの画像特徴抽出器を用いて葉を擬似的なパーツに分割しています。葉の3次元構造を、2次元の基本形状と3次元の変形に分離し、潜在コードで表現することで、葉の再構成と生成が可能な新しいパラメトリックモデルであるNeuraLeafを提案しています。また、数千枚の葉をスキャンして新しい3次元データセットDeformLeafも作成されていました。対象である葉の特性や特徴について実物をよく観察したうえで、うまく課題を切り分けていると感じました。 [OS-1E-04] スパイキングニューラルネットワークによる画像生成拡散モデル 渡邊諒 (東大), 椋田悠介 , 原田達也 ( 東大, 理研) スパイキングニューラルネットワーク (SNN) は、人間の脳内における信号伝達の仕組みを模倣したニューラルネットワークです。SNNは生物学的に妥当でありながらも計算効率が良いことで、エッジコンピューティングへの実装にも適しています。この研究は、近年話題の拡散モデルによる画像生成ネットワークを、SNNのみを用いて実現することを目的としています。通常の拡散モデルではニューラルネットワークによりガウス分布のパラメータを推定しますが、SNNのニューロンの出力はスパイク列(バイナリ)であるため、ガウス分布のパラメータ推定が困難という課題があります。また、SNNを拡散モデルに適用する場合、確率分布のパラメータを計算するためにSNNの出力をデコードし、再サンプリングする必要がありますが、単純なSNNではこれを解決できません。この研究では、通常の拡散モデルの拡張として、Fully Spiking Denoising Diffusion Implicit Modelを提案しています。さらに、拡散モデルにおける生成過程の各ステップをSNNの計算に置き換えるため、シナプス電流学習も提案し、先述の問題を解決しています。データセットを用いて生成性能を比較した結果、提案手法は既存のSNN画像生成モデルよりも良い性能を示しました。加えて、提案モデルと同じ構造を持つ通常のニューラルネットワークと比べて、計算効率も大幅に向上しているとのことです。一方で、生成性能に関しては通常のニューラルネットワークに及ばず、今後、U-Net部分の性能を向上させるといった改善の余地があるようです。近い将来、モバイル端末などでも拡散モデルを用いた画像生成が活用されていく未来が想像できますし、そんな社会に向けて、本研究は非常に意義のあるものになると思いました。 [IS3-39] 集合データを対象とした識別と生成のマルチタスク学習と信頼度較正への応用 佐藤文興, 早志英朗, 長原一 (阪大) 半教師あり学習は多数のラベルなしデータと少数のラベルつきデータを有効に用い、ラベルつきデータのみを用いた場合より高性能な予測器の構築を目指すタスクです。この研究では集合データの半教師あり学習に際して、教師なしの確率的生成モデルと教師ありの分類器の2つのネットワークを用意します。それらのネットワークでエンコーダ部を共有させることで、ラベルなしのものを含めたデータ分布の再現とラベルつきデータの識別を同時にこなすマルチタスク学習として半教師あり問題を扱うことができます。生成モデルのみに注目すれば教師なし学習なので、ラベルなしデータも有効に活用できると言えます。加えて、このようなアプローチは識別器が出力する信頼度の較正にも有用であることが知られているようです。ところで集合データを扱うネットワークには、入力集合内の要素の並べ替えに対する出力値の不変性 (permutation invariance) など特別な性質が要求されます。これらを満たすSetVAEとよばれる確率的生成モデルを組み込むことで、多くのデータセットに対してラベルなしデータを使用しない場合よりも識別精度が向上し、識別器の出力確率の較正効果も確認できたそうです。集合データの機械学習はZOZO Researchにおいても積極的に取り組んでいるテーマのひとつであり、集合データ特有の難しさがあるのかどうかなど、より精緻な議論が発展していくことを期待したいです。 [IS3-56] 協調フィルタリングにおける潜在因子モデルの埋め込み表現のICAを用いた線形構造の分析 岡村洋希, 前田圭介, 藤後廉, 小川貴弘, 長谷山美紀 (北大) 協調フィルタリングは「類似した消費行動を行う消費者は類似した嗜好をもつ」という信念にもとづいて、各消費者にパーソナライズされた推薦をする手法です。このとき潜在因子モデルを用いると、消費者やアイテムの埋め込み表現を得ることができます。得られた埋め込み表現の解釈は実用上重要な課題ですが、埋め込み表現は一般に高次元であるため、3次元空間の住人である我々にとって容易ではありません。近年、自然言語処理分野において、単語の埋め込み表現を独立成分分析 (ICA) によって次元削減することにより、解釈可能性に優れた軸を得ることができるという報告がなされました。この研究では同様のアプローチを潜在因子モデルによって得られたアイテムの埋め込み表現に適用し、推薦システムの文脈にも有効であることを示しました。具体的には、MovieLensと呼ばれる複数のユーザによる映画の評価データに適用したところ、ICAによって得られた軸の一部が映画のジャンルに対応することが確認されました。ICAは線形変換であるため、一度有効な軸が得られれば新しく追加されたアイテムの埋め込み表現にも適用可能であるなど、扱いやすさの点で優れています。近年は非線形の次元削減が頻繁に用いられますが、「本当に線形の手法ではダメなのか」ということは手法選択の上で常に自問するべきであると改めて感じました。 まとめ 本記事では、MIRU2024の参加レポートをお伝えしました。MIRU2024に参加し、多くの新たな知見を得ることができました。今年も現地で研究の最前線に触れ、最新の技術動向を直接感じることができたのは大変貴重な経験でした。ここで得た知見を今後の研究活動に積極的に取り入れ、さらなる成果を目指していきたいです。 最後に ZOZO NEXTでは次々に登場する新しい技術を使いこなし、プロダクトの品質を改善できるエンジニアや研究開発を行うリサーチャーを募集しています。ご興味のある方は、以下のリンクからぜひご応募ください。 zozonext.com おまけ 学会期間中は熊本名物である馬刺しにすっかり魅了され、毎日舌鼓を打ちながら味わっていました。
アバター
はじめに こんにちは、計測プラットフォーム開発本部SREブロックの 山本 です。普段はZOZOMATやZOZOGLASSなどの計測技術に関わるシステムの開発、運用に携わっています。 我々のチームは、複数サービスを運用する中で障害対応の経験不足や知見共有の難しさといった課題に直面していました。そこで、半年ほど前にカオスエンジニアリングの導入を開始しました。 本記事では、カオスエンジニアリングを一過性のものではなくチームの文化として根付かせ、継続的な改善サイクルを生み出すための導入から運用まで、我々のチームでの実践から得られた具体的な方法をお伝えします。 これからカオスエンジニアリングを始めようとしている方はもちろん、すでに導入しているものの効果的な運用に悩んでいる方の参考になれば幸いです。 目次 はじめに 目次 背景・課題 カオスエンジニアリング導入の流れ 1. 目標とKPIの設定 1.1 目標設定 1.2 KPIの設定 1.3 KPIの測定方法 1.4 KPI設定時の考慮点 2. 障害シナリオの作成とツール選定 2.1 シナリオ作成のプロセスとシナリオ例 2.2 シナリオによる影響予測 3. カオスエンジニアリングの実施フローの整備 3.1 カオスエンジニアリングの実行者と対応者を決める 3.2 (実行者のみ)障害内容を決め、システムの挙動を予測する 3.3 カオスエンジニアリング実施のアナウンスを行う 3.4 カオスエンジニアリングを開始する 3.5 障害対応をする 3.6 振り返りを行う 4. 実践と改善 カオスエンジニアリング導入の効果 効果1. 月に1回GameDayを行う文化の醸成 効果2. 障害対応フローの効率化 効果3. システムへの理解度向上・障害リスクの把握 効果4. アラート・モニタリング設定の最適化 カオスエンジニアリングの導入後に感じた課題と対策 課題1. 障害が発生していないのか負荷がかかっていないだけなのかわからない問題 問題の詳細 対策 課題2. GameDayをスキップしがちになってしまう問題 問題の詳細 対策 課題3. 改善タスクが作りっぱなしになってしまう問題 問題の詳細 対策 終わりに 背景・課題 我々のチームでは、オンコール担当をローテーションで回しながらシステムの安定運用に努めています。しかし、以下のような課題に直面していました。 障害対応の経験不足 システムの安定性が向上し、実際の障害の発生頻度が低下したことで、チームメンバーが障害対応の経験を積む機会が減少した 役割の固定化 障害発生時、特定のメンバーが特定の役割を担当する傾向があった それにより障害が発生したタイミングで、メンバーの負荷状況に応じて柔軟に対応することが難しくなっていた 知見の偏り システム障害時の勘所や、調査時の必須知識(コマンドや監視ツールのどこを見るべきか、などといった知見)がチーム全体に浸透していなかった これらの課題を解決し、チーム全体の障害対応の能力を向上させるため、カオスエンジニアリングの導入を決定しました。 カオスエンジニアリング導入の流れ 我々のチームではカオスエンジニアリングの導入を以下のステップで行いました。 目標とKPIの設定 障害シナリオの作成とツール選定 実施フローの整備 実践と改善 それぞれのステップについて説明します。 1. 目標とKPIの設定 カオスエンジニアリングを効果的に実施するため、まずチーム目標に基づいた具体的な目標とKPIを設定しました。これは非常に重要なステップで、これをやらないとカオスエンジニアリングの効果が半減すると言っても過言ではないと思います。 1.1 目標設定 前述した課題の解消を目指し、目標を以下のように設定しました。 チームメンバーの障害対応の能力を向上させ、障害対応フローを効率化する システムの障害リスクを特定しチーム全体で把握する システムの耐障害性と安定性を向上させる 目標を設定することで、チーム内外でのカオスエンジニアリングに対する認識を揃え、今後何をやって何をやらないか判断できるようになります。 1.2 KPIの設定 各目標に対し、以下のようにKPIを定めました。 チームメンバーの障害対応の能力を向上させ、障害対応フローを効率化する 各メンバーのカオスエンジニアリング実施回数:1回/月以上 インシデント対応にあたったメンバーの自己評価スコア:80点以上 システムの障害リスクを特定しチーム全体で把握する カオスエンジニアリング実施時の挙動予測スコア:80点以上 障害発生から検出までの時間:3分以内 システムの耐障害性と安定性を向上させる 障害発生から復旧までの時間(MTTR):10分以内 カオスエンジニアリング実施時のエラーバジェット消費率:エラーバジェット枯渇までの期間がSLO目標の全期間の25%以上をキープ 例えば、SLO目標の全期間が30日の場合に7.5日でエラーバジェットが枯渇するような消費率を超えるとアウト、ということ これらの目標とKPIは、カオスエンジニアリングの実施ごとに見直し、改善を重ねています。 1.3 KPIの測定方法 各KPIの測定方法について、いくつか例を挙げます。 カオスエンジニアリング実施回数 実施ごとにドキュメントを残し、実施の都度確認する 自己評価スコア 評価項目を事前に用意し、実施後に自己採点する 障害検出までの時間 カオスエンジニアリング開始時からアラート通知までの時間を計測する 検知されない場合はKPI未達とする エラーバジェット消費 Datadogの SLOバーンレートアラート を使用し、アラートが発火された時点でKPIは未達となる これらのKPIは、カオスエンジニアリング実施の都度確認し、目標の達成度を測ります。なお、実際の自己評価の項目は後述の『実施フローの整備』の部分で詳細に紹介しています。 1.4 KPI設定時の考慮点 KPIを設定する際は、以下の点を考慮しました。 目標とKPIの整合性 KPIの達成が目標の達成に繋がること 現実的な計測可能性と改善可能性 計測が出来る指標であることと、改善が現れる指標であること KPIとしての有用性 数値が正常化されることで改善に繋がるKPIであること 逆に数値が正常化されたからといって改善に繋がっているとは限らないKPIはNG このように目標とKPIを設定することで、カオスエンジニアリングの効果を具体的に測定し、継続的な改善サイクルを確立することが出来ました。また、これにより取り組みの成果を可視化し、チーム全体で改善に向けた意識を高めることが出来ています。 カオスエンジニアリングの導入を検討されている方々は、チームの状況に合わせて適切な目標とKPIを設定することをお勧めします。 2. 障害シナリオの作成とツール選定 カオスエンジニアリングを効果的に実施する上では、起こりうる障害シナリオとその影響を事前に予測しまとめておくことが重要です。 これは以下の記事でも紹介されているので、ぜひご覧ください。 techblog.zozo.com このステップにより、システムの障害リスクに気づくきっかけとなるだけでなく、実際のカオスエンジニアリング実施時に予期せぬ影響が見つかった際により大きな学びが得られます。 2.1 シナリオ作成のプロセスとシナリオ例 前提として、カオスエンジニアリングの対象とするサービスは1つに絞った上で、以下のステップでシナリオを作成しました。 過去の障害ログの分析 過去に発生した実際の障害事例を確認 システム構成図からの検討 利用しているプラットフォーム(Datadog, Kubernetes, AWS)ごとに起こりうる障害の洗い出し 重要度と実現可能性による絞り込み 影響の大きさと、カオスエンジニアリングツールでの再現可能性を考慮 これらのステップを通して、以下のシナリオを扱うことに決定しました。 Availability Zoneネットワーク障害 Dynamo DBネットワーク障害 ElastiCacheインスタンス障害 S3ネットワーク障害 Pod CPU圧迫 Pod Memory圧迫 なお、シナリオの実現可能性を考慮し、ツールとしては AWS Fault Injection Service (以降、FISと記載)を使用することにしました。FISはAWSリソースへの障害注入に特化しているだけでなく、EKS上のPodへの障害注入も可能で、カオスエンジニアリングの対象とするサービスのインフラ構成に最適であったためです。 ツールの比較・検討をする中で、 Litmus Chaos はGitOps対応やFIS経由の障害注入など魅力的な特徴がありましたが、現時点ではFISのみで十分と判断しました。 2.2 シナリオによる影響予測 次に、決定されたシナリオの実行時にシステムが受ける影響を予測します。各シナリオで以下の項目を考慮して進めました。 障害の影響範囲 システムへの影響予測 影響を受けるメトリクスとSLI/SLO アラート・モニタリングでの検知可能性 障害検知と復旧までの予想時間 なお、ここでは1つを深掘りし過ぎないようにし「予測→実践→改善」のサイクルを回してブラッシュアップしていくことを優先する意識をしていました。 以下は、これらを踏まえて『S3ネットワーク障害』での影響を予測した例になります。 3. カオスエンジニアリングの実施フローの整備 シナリオまで考えることが出来れば、あとは実施していくだけなのですが、長期的に見ると「いかにカオスエンジニアリングを形骸化させず継続的にチーム全員で運用していくか」が重要です。 そのため、以下のようにカオスエンジニアリングの実施フローを整備しました。 カオスエンジニアリングの実行者と対応者を決める (実行者のみ)障害内容を決め、システムの挙動を予測する カオスエンジニアリング実施のアナウンスを行う カオスエンジニアリングを開始する 障害対応をする 振り返りを行う 実施フローの各項目について説明します。 3.1 カオスエンジニアリングの実行者と対応者を決める 我々のチームでは、障害を注入する実行者を一人決め、他のチームメンバーはそれに対応する対応者として役割を分けることにしました。 一般的にはチーム全体で障害シナリオとその影響を考えることが多いと思いますが、起こる障害が事前に分かっていないことで、対応者の障害対応スキル向上に繋がると考えてこのような形式にしています。これは「チームメンバーの障害対応の能力を向上させ、チームとしての対応フローを効率化する」という目標に沿っています。 なお、実行者と対応者はツールを使ってランダムに選出するようにしています。 3.2 (実行者のみ)障害内容を決め、システムの挙動を予測する このステップでは実行者が実施する障害と障害を継続しておく時間を決め、システムの挙動を予測します。これは先ほどの『障害シナリオの作成』のセクションでやったことそのものです。 もちろん、アプリケーションにこっそりバグを混入させるなどしてFISを利用せずに障害を起こすのもありです。 なお、実施内容と挙動の予測はカオスエンジニアリングを実施する前にドキュメント化し、まとめておきます。 3.3 カオスエンジニアリング実施のアナウンスを行う カオスエンジニアリングを行う環境を使っている他チームに影響を与えてしまう可能性があるので、前日までにその旨を伝えておきます。 3.4 カオスエンジニアリングを開始する ついに障害を注入していきます。 理由は後述しますが、我々のチームではステージング環境でカオスエンジニアリングを実施することにしています。そのため、障害発生中にサービスにより近い状態を再現すべく、ローカルから負荷をかけながら障害を注入していきます。 具体的には、FISを使って障害注入をする場合、 aws fis start-experiment コマンドを実行することで事前に用意したシナリオを発火します。 3.5 障害対応をする 我々のチームでは、障害対応時の役割として指揮者・コミュニケーション担当・記録者・オペレータを決めるルールにしているので、初めに対応者間で役割を確認します。 カオスエンジニアリングが開始されたら、対応者はアラートなどを元に原因を理解し、サービスを継続させるための対策をしていきます。なお、「ユーザーが問題なくサービスを受けられている」ことを「サービス継続」と定義しています。 ポストモーテムもカオスエンジニアリング用にドキュメントとして作成します。 また、実行者はこのタイミングで「カオスエンジニアリング実施中のタイムライン」をまとめておきます。 3.6 振り返りを行う 障害注入が終了し、障害対応が完了したら実行者と対応者で振り返りミーティングを行います。振り返りミーティングの内容は以下のような流れになっています。 概要 詳細 1. 実行者から障害内容について共有 事前準備で用意した障害シナリオとシステムへの影響予測、カオスエンジニアリング実施中のタイムラインを共有する。対応者からすると、ここで答え合わせが行われるイメージ。 2. 対応者から対応の流れの共有 障害対応時に用意したポストモーテムを元に対応の流れを振り返る。 3. KPIの達成度を記入 各KPIの達成度をメンバーごとに評価する。 4. 改善点のブレスト KPIの達成度を元に、実行者・対応者のそれぞれの観点で改善点をブレストする。 5. ネクストアクションの整理 ブレストで挙がった改善点のうち、必要なものをタスクに落とし込む。 なお、KPIの達成度は、以下のように各項目を数値化し得点や結果を残すようにしています。 KPI KPIの項目 数値化する方法 1. 各メンバーのカオスエンジニアリング実施回数が1回/月以上 今月の実施回数を数える 2. (対応者のみ)インシデント対応にあたったメンバーの自己評価スコアが80点以上(7項目合計) 初動対応の適切さ 障害発生時の最初の対応は適切かつ迅速だったかを振り返り、対応者全員が点数をつける(15点満点) 障害対応時のチームワーク 障害対応時に適切に各メンバーのロールを決めて動けたか・連携は適切だったかを振り返り、対応者全員が点数をつける(15点満点) コミュニケーションの適切さ チーム内外への情報共有が明確かつタイムリーに行われたかを振り返り、対応者全員が点数をつける(15点満点) 障害記録の適切さ 障害の詳細、原因などポストモーテムに適切に記録できたかを振り返り、対応者全員が点数をつける(15点満点) 障害の原因特定までの速さ 障害の原因をいかに迅速に特定できたかを振り返り、対応者全員が点数をつける(15点満点) 障害の原因特定の正確さ 障害の原因をいかに正確に特定できたかを振り返り、対応者全員が点数をつける(15点満点) ツール活用の度合い 利用可能なツールやリソースを最大限活用して障害対応にあたることができたかを振り返り、対応者全員が点数をつける(10点満点) 3. (実行者のみ)カオスエンジニアリング実施時の挙動予測スコアが80点以上(5項目合計) 事前準備における「システムに起こること」の正確さ 事前に予測した障害が与えるシステムへの影響がいかに正確であったかを振り返り、実行者が点数をつける(20点満点) 事前準備における「障害の影響が想定される範囲」の正確さ 事前に予測した「障害の影響が想定される範囲」がいかに正確であったかを振り返り、実行者が点数をつける(20点満点) 事前準備における「障害により影響を受けるメトリクス・SLI/SLO」の正確さ 事前に予測した「障害により影響を受けるメトリクス・SLI/SLO」がいかに正確であったかを振り返り、実行者が点数をつける(20点満点) 事前準備における「アラート・モニタリングで気づけるか」の正確さ 事前に予測した「アラート・モニタリングで気づけるか」がいかに正確であったかを振り返り、実行者が点数をつける(20点満点) 事前準備における「気づくまでにかかる時間・復旧にかかる時間」の正確さ 事前に予測した「気づくまでにかかる時間・復旧にかかる時間」がいかに正確であったかを振り返り、実行者が点数をつける(20点満点) 4. カオスエンジニアリング実施時の障害発生後から検出されるまでの時間が3分以内 カオスエンジニアリング終了後に検出までの時間を計算する 5. カオスエンジニアリング実施時の障害発生から復旧までの時間(MTTR)が10分以内 カオスエンジニアリング終了後に復旧までの時間を計算する 6. カオスエンジニアリング実施時において、エラーバジェット枯渇までの期間がSLO目標の全期間の25%以上をキープ DatadogのSLOバーンレートで計測する フローをドキュメント化し、チームの合意を得たことで、属人化を防ぎ、全員でカオスエンジニアリングを継続的に運用しやすくなりました。 4. 実践と改善 フローの整備まで完了すれば、あとはフローに沿ってカオスエンジニアリングを実施していくだけです。 我々のチームでは、毎月GameDayとしてカオスエンジニアリングを実施し、その都度振り返りを行うことにしています。 また、我々は現在、ステージング環境でカオスエンジニアリングを実施しています。これは「本番環境での直接検証」を推奨する Principles of chaos engineering の内容とは異なりますが、リスク軽減のため現段階では適切と判断しています。 本番環境での実施も検討していますが、ツール等の進化により、ステージング環境でも本番同様のトラフィックでの試験が可能になってきているため、安全かつ本番と同様の状況下での試験方法を探っています。 カオスエンジニアリング導入の効果 カオスエンジニアリングの導入により、以下のような効果が得られました。 月に1回GameDayを行う文化の醸成 障害対応フローの効率化 システムへの理解度向上・障害リスクの把握 アラート・モニタリング設定の最適化 それぞれ説明していきます。 効果1. 月に1回GameDayを行う文化の醸成 カオスエンジニアリングを導入して以来、月に1回必ずGameDayを実施するようになりました。 ここまでカオスエンジニアリングが浸透したのは「小さく始めて敷居を下げ、徐々に拡大していく」というアプローチが要因の1つだと考えています。 個人での「セルフカオスエンジニアリング」からスタート SRE内での実施へ拡大 開発チームを巻き込んだ全体での実施へ この段階的なアプローチにより、チームメンバーの心理的ハードルを下げることが出来たのだと思います。また、既存のツール(FIS)を活用し、最もシンプルな構成のプロダクトから始めたことも、スムーズな導入に寄与しました。 さらに、明確な目標とKPIを設定し、毎回の振り返りで「次はこうしよう!」という前向きな議論が生まれたことも、継続的なカオスエンジニアリングの実施を後押ししています。 効果2. 障害対応フローの効率化 振り返りの際にKPIをベースに足りなかった点・改善点を話し合う中で、既存の障害対応フローで至らない点を改善するサイクルができました。 効率化のアイデアも挙がり、それを次回のGameDayで試してみようという流れも出来ています。 例えば、我々のチームでは障害時には専用のSlackスレッドを作り、その中でそれぞれが調査内容などを自由に投稿していくスタイルでした。しかし、これだと他の人が何を調査しているのかが見えず作業が被ってしまうケースがありました。そこで、Confluenceの同時編集の機能を活用し、それぞれが作業している内容とその調査結果をリアルタイムで見られるようなフローにするアイデアが出たこともありました。 さらに、GameDayの中で普段よくやる・やったことのある役割は出来るだけやらないようにすることで、役割の固定化も解消することが出来ています。 指揮者と調査担当者とのコミュニケーションの形はどうするとやりやすいか 原因特定か影響範囲の割り出しのどっちを優先すべきかを指揮者の立場から判断しかねたので、どうすべきだったか など、役割ごとに「どのような動きが望ましいか」を振り返りでフィードバックし合うようにしたことも影響していると思います。 効果3. システムへの理解度向上・障害リスクの把握 GameDayを行う際には、障害を仕掛ける側は毎度システムにどのような作用があるか仮説を立て、それを振り返りで採点するフローになっているため、システムの理解度が上がりました。 また、システムへの作用について仮説を立て、実際にカオスエンジニアリングを実施する中で、予想外の障害リスクを発見できました。 例えば、GameDayでS3ネットワーク障害を発生させたところ、ECRからimageのpullが出来なくなり、デプロイが失敗するようになることがわかりました。これは、『2.2 シナリオによる影響予測』のセクションで例に挙げたS3ネットワーク障害の影響予測には記載されていなかった影響で、完全に予想外の障害リスクでした。 障害を仕掛ける役割をローテーションすることでこのような発見をするチャンスが全メンバーに与えられるだけでなく、振り返りで共有も行われるので知見の偏りを防ぐことができるようになりました。 効果4. アラート・モニタリング設定の最適化 カオスエンジニアリングによって、いくつかのアラートやモニタリング設定の穴に気づくことも出来ました。 例えば、GameDayで障害対応をする中で、ログが構造化されておらず検索効率の悪いアプリケーションがあることに気づき、改善につながることがありました。 また、ElastiCacheのfailoverを発生させた際には、本来発生するはずのアラートが発生しないケースもありました。そこで、なぜアラートが発生しなかったのかを分析したところ、必要な監視項目が不足していたことが判明しました。また、そもそもElastiCacheは構成上必要ないかもしれないという仮説も生まれました。 細かい点だと、我々のチームでは複数プロダクトを管理しているため、不慣れなプロダクトでアラート発生した際にどの情報を確認すべきかを素早く判断することが難しいと分かりました。そこで、即座に対応できるよう、アラートのDescriptionに「監視設計のリンク」や「確認すべき情報源」を明記するなど小さな改善も行われています。 カオスエンジニアリングの導入後に感じた課題と対策 ここまで、我々のチームにカオスエンジニアリングを導入した方法とその効果を紹介してきましたが、当然全てがいきなり上手くいったわけではありません。 カオスエンジニアリングを導入し実践していく中で感じた課題がいくつかあったので、ここではそれらの課題と対策について共有します。 課題1. 障害が発生していないのか負荷がかかっていないだけなのかわからない問題 問題の詳細 前述の通り、我々はステージング環境でカオスエンジニアリングを実施しているため、本番環境の負荷を再現するために負荷試験を流しながら障害を実行しないといけません。 その制約により、GameDayが始まったにもかかわらずアラートが発生しない場合に「異常を検知できていない」のか「実行者が負荷をかけておらずエラーになっていない」のか判別できないという問題がありました。 特に、特定のプロダクトに閉じない障害を実施した際に1つのプロダクトのみでアラートが発生している場合、障害対応のミスリードを誘ってしまうケースがありました。具体的には、S3ネットワーク障害時にZOZOGLASSにのみ負荷をかけた場合、ZOZOGLASSのみでアラートが発生し「障害はZOZOGLASSに限定されている」と誤解するケースです。 対策 そこで、対策として『SREで管理するプロダクト全てに対し負荷をかけながら障害を注入する』というルールを決めました。こうすることで、前述のミスリードが無くなるだけでなく、もし特定のプロダクトで異常が検知出来ていない場合に気付きやすくなりました。 また、このルールを実現するために全プロダクトで負荷試験の整備も行ったので、副次的に不備の改善にも繋がりました。 課題2. GameDayをスキップしがちになってしまう問題 問題の詳細 当初、GameDayは丸一日使って障害の実行・障害対応・振り返りまで全て行っていました。 しかし、振り返りは「障害の流れの共有→KPIの評価→改善点のブレスト→タスク化」とかなりやることが多く、継続的なシステムの改善のためには必須であるものの時間がかかります。ブレストの際にはファシリテーターが上手くまとめないと議論が発散し過ぎてしまうこともあります。 GameDayに参加する上で多くの時間が必要となると、優先度の高いタスクを抱えていて参加出来ないメンバーが出てきてしまうことも往々にしてあります。そういった理由から敷居が上がってしまい、GameDayをスキップすることが増えていました。それにより、メンバー全員に障害対応のノウハウが行き渡りにくく、改善が回りづらい状況でした。 対策 そこで『GameDayを丸一日ではなく、数時間に縮小する』ことで対策しました。具体的には、「障害対応は午前から午後にかけて1〜2時間程度、振り返りは夕方に1時間程度」といった具合に各作業を細かく分けました。 これにより、GameDayをする日でも他のタスクに集中できる時間を作れるため参加しやすくなり、スキップすることはかなり少なくなりました。 ただ、『丸一日カオスエンジニアリングにだけ集中する時間にする』という方法にも「障害対応にしっかり時間をかけることが出来る」・「それにより大規模な障害の訓練も可能になる」などメリットはあります。そのため、この対策は参考程度に留め、チームの状況に応じて適切な方法を選択していただくのがベターだと思います。 課題3. 改善タスクが作りっぱなしになってしまう問題 問題の詳細 GameDayを経て得られた改善点は、実際に改善に繋げなければ意味がありません。しかし、「カオスエンジニアリングから得られた改善タスクが放ったらかしになってしまう」・「他タスクで手が空かず次のGameDayを迎えてしまう」といったことがよくありました。 これは「チーム運営のフロー上、カオスエンジニアリングで挙がったタスクが拾いきれていなかったこと」・「カオスエンジニアリングから得られた改善タスクの優先度が明確に出来ていなかったこと」が原因でした。 対策 そこで『カオスエンジニアリングの振り返りの時点で、改善タスクをバックログに入れて優先度もつけておく』というフローにすることで対策しました。 我々のチームではスクラムを模したタスク管理方法をとっているのですが、この対策で、プランニング時に改善タスクを見落とすことが無くなり、次のGameDayまでに取り組むべき改善がより明確になりました。 弊チームのタスク管理方法については以下の記事をご覧ください。 techblog.zozo.com 「改善タスクの優先度を明確にすることで放ったらかしにしない」というのは今考えれば当たり前のことですが、実際にカオスエンジニアリングを運用して初めて気付いたポイントでした。同じ轍を踏まないよう、ぜひ参考にしていただければ幸いです。 終わりに 本記事では、計測プラットフォーム開発本部SREブロックにおけるカオスエンジニアリングの導入プロセスとその効果について紹介しました。 まだ半年程度の運用ですが、すでにカオスエンジニアリングの導入によって、チーム全体の障害対応の能力を向上させシステムの信頼性を高めることが出来たと言って良いほどの効果がありました。カオスエンジニアリングを始める前に目標とKPIを定め、それを元に振り返りを行なっていくことで、継続的に改善のサイクルを生み出せていることがポイントだと思います。 同様の課題を抱えているチームがいれば、ぜひ参考にしてみてください。 今後は、ステージング環境での本番環境の再現や、カオスエンジニアリングの自動化などを検討していきたいと考えています。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com
アバター
はじめに こんにちは、ブランドソリューション開発本部でWEAR by ZOZOのiOSアプリの開発を担当している山田( @gamegamega_329 )です。 2024年の5月、WEARはAIを活用したファッションジャンル診断などの新たな機能やコンテンツを導入し 「WEAR by ZOZO」 (以下、WEAR)としてリニューアルしました。私が入社してからすぐにWEARアプリのリニューアルに取り組んできました。 当時、私は業務経験の浅い新卒1年目であり、リニューアルにおいてどのようにチームに貢献できるか不安を抱えていました。しかし、業務経験が未熟な中でも、自分の力で挑戦できるタスクを見極め、それに取り組み解決することでチームに貢献しました。 本記事では、大規模リニューアルプロジェクトに参画する中で私が直面した課題と、その課題を解決するための取り組みをご紹介します。業務に対して不安を感じる新卒iOSエンジニアの参考になれば幸いです。 目次 はじめに 目次 リニューアルプロジェクト概要 プロジェクトの背景と目的 チーム構成 進め方 自身の担当範囲 課題と解決アプローチ 課題1.好みのジャンル傾向のグラフの開発工数が膨らむ スライド提案で仕様調整することで工数削減 実装可能な仕様に落とし込んで工数を削減 結果と学び 課題2.ビルドおよび配布プロセスにおけるエラーに気づきにくい Xcode Preview専用ターゲットのビルド失敗を検知する 配布失敗をSlackbotで通知する 結果と学び 課題3.スプレットシートで管理している職種間のタスクを把握しづらい Slackbotでタスクの締め切りを通知する 結果と学び 最後に リニューアルプロジェクト概要 プロジェクトの背景と目的 このプロジェクトは、 経営戦略 「MORE FASHION × FASHION TECH ~ ワクワクできる『似合う』を届ける ~」を掲げて進められました。WEARでは、ユーザーが自分に「似合う」ファッションコーディネートを見つけられるアプリとして、その価値を提供することを目的としています。 チーム構成 WEARはマトリックス型のチームで構成され、「ホーム」「探す」「メイク」の3つのチームに分かれています。マトリックス型のチームとは、組織内で複数の部門や専門分野のメンバーが集まって構成されるチームのことです。1つのチーム内に、PM、デザイナー、エンジニア(iOS / Android / バックエンド / QA)が所属しています。 私は「探す」チームに所属し、検索を中心とした似合うコーディネートを探せる機能の実装を担当しました。「探す」チームは4名のiOSエンジニアで開発を担当しました。 進め方 プロジェクトが開始してからリリースまでの主な流れは以下の通りです。 仕様のフィジビリティ調査 工数見積もり 仕様 / デザインの調整 設計 / 実装 デザインレビュー / バグ修正 リリース 自身の担当範囲 私が主に担当した範囲は以下の通りです。 探すタブのトップ画面 :ユーザーが探すタブを開いたときに表示されるトップ画面の設計と実装 好みのジャンル傾向のグラフ :ジャンルごとの傾向を視覚的に表現するグラフの作成 ジャンルで絞り込む機能 :ユーザーがジャンルで絞り込み検索をするためのコンポーネントの実装 CI/CDの運用改善やマトリックスチームの効率化 チームメンバーに難易度の高い機能に挑戦したいとお願いしたところ、「好みのジャンル傾向」のグラフを担当することになりました。好みのジャンルとは、好みに近いスタイルを見つけるために12種類に分類されたファッションのカテゴリのことです。例えば、ラフ、きれいめ、モードなどがあります。グラフのイメージは以下の通りです。 課題と解決アプローチ リニューアルプロジェクトが進む中で、私が直面した課題とその解決アプローチについてご紹介します。 課題1. 好みのジャンル傾向のグラフの開発工数が膨らむ 課題2. ビルドおよび配布プロセスにおけるエラーに気づきにくい 課題3. スプレットシートで管理している職種間のタスクを把握しづらい 課題1.好みのジャンル傾向のグラフの開発工数が膨らむ グラフの実装は、見積もりの段階で大きな工数がかかりました。要因は以下の通りです。 その1. ジャンルの割合を変更するデザインの難易度が高い その2. 12種類のラベルの位置の選定方法の難しさ リリース後には大型プロモーションを予定しており、関係各部署と足並みを揃える必要があったため、リリースまでの時間が限られていました。この状況を踏まえ、リリース日に全ての実装を完了させるには、仕様やデザインを調整し、工数を削減する必要がありました。 スライド提案で仕様調整することで工数削減 「その1. ジャンルの割合を変更するデザインの難易度が高い」に対して、開発の観点から工数がかかる要因と代替案をスライドでビジュアル化し、デザイナーに提案しました。デザイナーに別のデザインを考えてもらうためには、なぜ工数がかかるのかを理解してもらう必要がありました。しかし、文字だけで伝えるのは専門外の人には難しく、納得してもらうのも困難だと思いました。 そこで、図や表を使ったスライドを作成し、ビジュアルで伝えることで理解してもらうようにしました。工数削減のためにエンジニアの観点から提案し、どのようなコンポーネントであれば工数を削減できるか、デザイナーが判断しやすくなるような材料を提供しました。 仕様変更により初回リリースには含まれませんでしたが、実際に使用したスライドは以下の通りです。 実装可能な仕様に落とし込んで工数を削減 「その2. 12種類のラベルの位置の選定方法の難しさ」に対して、現実的な工数で実装可能な仕様に落とし込み、工数を削減しました。デザイン要件と現実的な工数のバランスの取れた仕様にするまでに、多くの試行錯誤がありました。ジャンルを示すラベルに対し、提示された基本的なデザイン要件は、以下の内容でした。 ジャンルは12種類 1行と2行の2パターン 円とラベルの間は適度なスペースを保つ この基本要件を保ちつつ、複数のジャンルが選択された状態では、以下の手法Aのような規則でのコンポーネントの配置が求められました。 手法A:ラベルの高さと円グラフのサイズを考慮して赤枠のサイズを調整する 一方で私は、保守性を重視するためには、手法Bでの設計をするのが望ましいと考えました。 手法B:ラベルを配置してぶつかったところで少しずらす デザイン要件が複雑であるため、手法Aや手法Bを採用しても、デザイン通りに実装することが難しい状況でした。納期を優先するために、チームリーダーと相談しながらデザイナーに別の手法を提案して、デザイン要件を擦り合わせることを考えました。 新たに考えた手法Cのイメージは以下の通りです。 手法Cは、4つの領域に分割し、白円の中心座標に対し、各領域のテキスト位置を決定するような方法です。 それぞれの手法を比較した結果、以下の通りです。 手法 実装コストが低いか? デザイン要件を満たすか? 変更に柔軟か? A × ◯ × B △ △ ◯ C ◯ ◯ △ 最終的に、納期を厳守しながら、デザイン要件も満たせる手法Cを選択し、実装しました。 結果と学び 結果として、ユーザ体験を損なうことなく現実的な仕様に落とし込み、無事にリリースできました。また、リリース後も致命的なクラッシュや表示のズレは発生せず、安全に実装できました。 保守性の観点からいくつかの課題があります。例えば、ジャンルの種類が増えた場合や多言語対応する際、文字が重なってしまう可能性があります。この点については、現在も設計の見直しとリファクタリングを進めて対応しています。 この経験を通じて、 優先すべきことを明確 にし、 トレードオフを理解しながら最適な落とし所を見つけること を学びました。プロジェクトの状況次第ですが、仕様通りに実装することだけにこだわらず、限られた時間の中で現実的でバランスの取れた解決策を見つけることも重要だと思いました。 課題2.ビルドおよび配布プロセスにおけるエラーに気づきにくい ビルドおよび配布プロセスにおけるエラーに気づきにくいことで2つの問題がありました。 1つ目. 他の開発者がXcode Previewを表示できない 2つ目. デザイナーとエンジニアに余分な確認作業が発生 1つ目に関して、WEAR iOSチームはビルド時間短縮のためXcode Preview専用ターゲットを作成しましたが、ファイルの追加忘れで他の開発者がXcode Previewを表示できない問題が多発していました。 2つ目に関して、デザイナー確認のために毎日ビルドを配布していましたが、配布が失敗して変更が反映されていないことに気づかないことがありました。 Xcode Preview専用ターゲットのビルド失敗を検知する 「1つ目. 他の開発者がXcode Previewを表示できない」に対して、CI/CDツールとして利用しているBitriseのワークフローを改善しました。 プルリクエストを親ブランチへマージする前、CI環境でXcode Previewターゲットをビルドし、失敗を事前に検知させました。 Xcode Previewターゲットをビルドするために、Bitriseが提供している「Xcode Build for Simulator」のステップ(特定のタスクを実行するためのスクリプト)を追加しました。bitrise.ymlの設定は以下の通りです。 // bitrise.yml - xcode-build-for-simulator@2 : inputs : - scheme : WEARPreview この設定により、プルリクエストを親ブランチへマージする前にビルドの失敗を検知できるようになりました。 配布失敗をSlackbotで通知する 「2つ目. デザイナーとエンジニアに余分な確認作業が発生」に対して、配布が失敗した時にBotを活用してSlackに通知を送りました。 Slack上で通知するために、Bitriseの「Send a Slack message」ステップを追加しました。この設定と一緒に、SlackのWebhook、APIトークン、およびSlackボットの設定が必要です。bitrise.ymlの設定は以下の通りです。 // bitrise.yml - slack@4 : title : Slack Notification failed deploy is_always_run : true run_if : ".IsBuildFailed" inputs : - webhook_url : "$SAMPLE_WEBHOOK_URL" 配布が失敗した時の検知は、 is_always_run: true 、 run_if: ".IsBuildFailed" を設定に加えることでビルドが失敗した時にステップを実行します。詳細は Running a Step only if the build failed をご確認ください。 Slackで通知された時のイメージは、以下の通りです。 結果と学び 結果として、チームの作業負担を減らせました。また、今回の解決策が他のチームにも役立てることができました。 今回の経験を通じて、 他のチームメンバーが開発しやすくなるように工夫することが重要 であると学びました。作業量自体は比較的少なかったものの、Swiftでのコーディングだけでなく、開発プロセス全体の改善にも取り組むことがチームに貢献するためには大切だと感じました。 課題3.スプレットシートで管理している職種間のタスクを把握しづらい 基本的にエンジニアはJiraを使ってタスクを管理していますが、PM、デザイナー、QAなど複数の職種が利用することを考慮し、スプレッドシートで主に仕様調整に関するタスクを管理していました。 しかし、締め切り、起票者、担当者、完了状況など多くの情報が混在していたため、タスクの把握が難しい状況でした。起票者と担当者を決め、締め切りまでに対応方針を確定する流れで進めていましたが、タスク量の増加と職種間のやり取りの多さから、タスクの把握がさらに難しくなっていました。 Slackbotでタスクの締め切りを通知する 他のチームで運用されていた締め切り通知Botを参考に、タスクの締め切りを通知するSlackbotを追加しました。締め切り当日のタスクをSlackbotを活用して通知します。 投稿の内容には、対応期日やタスクの内容、起票者、担当者を含めました。完了状況は週1回の全体ミーティングで確認するようにしました。スプレットシートで管理していたことから、Google App Scriptで実装しました。 結果と学び この取り組みの結果、タスクの締め切りを意識しやすくなり、タスクが遅れそうな場合は各自で調整するようになりました。チーム内で振り返りを行った際には、他のメンバーから感謝の言葉をもらいました。 この経験を通じて、 他チームのアプローチを参考にして自チームに適用すること を学びました。今回、私は初めてGoogle App Scriptを触りました。他のチームの参考コードを修正して自チームに適用することで、短時間で実装できるので非常に有用だと感じました。 最後に 本記事では、リニューアルにおけるいくつかの課題解決について紹介しました。業務経験が未熟な中でも、自分の力で挑戦できるタスクを見極め、それに取り組んで解決することで、微力ながらもチームに貢献しました。 未経験の実装や技術に挑戦できたのは、チームメンバーのサポートのおかげです。また、社内のiOSメンバーからも多くの協力と支援を受け、本当に感謝しています。ZOZOの人々の良さを改めて実感しました。業務に対して不安を感じる新卒iOSエンジニアの参考になれば幸いです。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 hrmos.co corp.zozo.com
アバター
はじめに こんにちは。ZOZOTOWN開発本部フロントエンドの菊地( @hiro0218 )です。 現在、ZOZOTOWNではWebフロントエンド技術のリプレイスプロジェクトが進行しています。以前の記事ではCSS in JSの技術選定をした際の背景や課題について紹介しました。 techblog.zozo.com その後、「 ZOZO Tech Meetup - Web フロントエンド 」でおよそ1年後の状況を簡単に共有させて頂きました。 speakerdeck.com 今回はZOZO Tech Meetupでお話した内容に加えて、CSS in JS導入から2年後の現状を改めて紹介したいと思います。 CSS in JS導入後の運用状況 ZOZOTOWNの開発体制は、Webフロントエンドだけでも5つのチームが存在し、さらに外部の業務委託メンバーも加えると、開発に携わるメンバーは執筆時点でのべ50名を超えています。これだけの多人数で開発をすると、開発環境のオンボーディングのようなイニシャルコストや日々の開発に関する問い合わせ対応は膨大なコストがかかってしまうため、以下のような取り組みを実施しています。 開発用ドキュメントの整備 Stylelintによるコードスタイルの統一 コードの記述方法に迷いが生じないような仕組み 単純に開発ドキュメントだけ整備するのでは伝達や統制に限界がありますが、細かい指針や推奨事項は、仕組み化していくことで開発者のスキルや経験に依存しない高品質な水準の開発を担保しています。また、イニシャルコストの低減だけではなく、コードレビューの効率化やメンテナンス性の向上も実現できています。 これらの中から、いくつかCSS in JSの運用に関わるものを紹介します。 styledの記法 ZOZOTOWNではCSS in JSのライブラリとして Emotion を採用しています。 Emotionでstyledの記法は「タグ付きテンプレートリテラル記法 (以下、テンプレートリテラル記法) 」と「オブジェクトスタイル記法」の2つの記法を利用できます。ZOZOTOWNのフロントエンド開発においては 「テンプレートリテラル記法」を推奨 しています。 それぞれの記法については、以下の通りです。 // テンプレートリテラル記法 const Section = styled.section ` background-color: #333; color: #fff; ` ; // オブジェクトスタイル記法 const Section = styled . section ({ backgroundColor : "#333" , color : "#fff" , }) ; 「テンプレートリテラル記法」を推奨した理由としては以下の通りです。 従来の CSS(Sass)と同様の記述方法ができる 導入当初、CSS in JSを初めて利用するメンバーも多く、書き慣れた従来のCSSの記述にすることで参入コストを少しでも下げたかった オブジェクトスタイル記法に比べて、CSSに慣れ親しんだメンバーにとっては直感的であり可読性や保守性が高い リプレイス前のCSSの実装を移行しやすい利点がある スタイルのネストの記述がしやすい 親子関係のスタイルや複雑な構造を表現するためのネスト構造 (例: &:hover ) を直接記述できる 複雑なスタイリングの記述がしやすい メディアクエリや擬似クラス、擬似要素など、CSSの高度な機能をそのまま利用できる CSSの継承やカスケードのルールをそのまま適用できるため、スタイルの一貫性を保ちやすい リプレイス前の環境と共通設定の Stylelint のルールが利用でき一貫性を保てる properties-order 系のプラグインなどオブジェクトスタイル記法だと一部動かないものがあった Visual Studio Codeの拡張機能をレコメンドする Visual Studio Code を利用している開発者が多いため、 .vscode/extensions.json ファイルをリポジトリ内に用意して、拡張機能をレコメンドしています。 例えばテンプレートリテラル記法は、エディタが標準ではスタイルとしてシンタックスハイライトをサポートしていないため、 vscode-styled-components のような拡張を利用します。この拡張は styled-components の拡張機能として提供されていますが、同様のシンタックスを有しているEmotionでも問題なく利用できます。 .vscode/extensions.json へ以下のように記述をするだけで拡張機能を開発者へレコメンドできます。 # . vscode / extensions . json { " recommendations ": [ " styled-components.vscode-styled-components " ] } 情報格差(拡張機能を知っているか)で開発効率に差を生まないようにしたい意図があります。 必要な情報はコンテキストに含める ThemeProvider のコンテキストには、端末の判定情報やSassの @mixin や @function に相当する関数を渡しています。各コンポーネントごとで関数を呼び出さずにstyled内からアクセスできるようにしています。 以下は、 font-weight に指定する値を function 経由で取得している例です。 const Text = styled.span ` font-weight: ${ ( { theme } ) => theme.function.fontWeight( "bold" ) } ; ` ; // iOS の Hiragino Sans のウェイトには W3, W6, W8 が含まれるが、`font-weight: bold`を指定すると 700 (W7) と同義になり意図よりも太い表示になる。 // 上記の function を利用すると iOS の場合は`font-wight: 600`の指定になり、それ以外の端末は`font-weight: bold`になる。 スタイリングへの関心事を ThemeProvider に集めることで実装者の迷いを減らしています。 Linterで誘導する スタイル向けのLinterとして Stylelint を導入しています。 Stylelintはリプレイス以前から導入しており、リプレイス後の環境のルールセットは基本的にそのままリプレイス以前のものを移行しています。 独自のルールとして、先述の「 font-weight に bold を直接記述しない」というルールがあります。このルールは、開発に参加したてのメンバーだと気付きづらく、またレビューでも指摘が漏れたり、テスト時に発覚してしまったりするといったケースも考えられます。これを事前に気付けるように declaration-property-value-disallowed-list を利用して、 bold の直接指定をルール上は許可しないようにしています。 // stylelint.config.js { rules : { "declaration-property-value-disallowed-list" : { "font-weight" : "bold" , } , } , } 社内の開発ドキュメントでも bold 指定は function を経由するようにと記載していますが、読み飛ばしや解釈違いもあるため、Linterによって二重で防ぐような処置をしています。 開発者の満足度や意見 開発メンバーに対して実施したアンケートの結果と、直近で行ったヒアリングの内容を共有します。 過去に実施したアンケート結果 導入から1年近く経ったZOZO Tech Meetupに際して、「CSS in JSの使い心地」に関するアンケートを実施しました。 対象 : リプレイス後の環境でCSS in JSを利用して開発を実施した部署内のフロントエンドエンジニア。 設問 (一部抜粋): CSS in JSに変わったことで、作業効率はどの程度変わりましたか? CSS in JS導入による全体的な満足度を評価してください CSS in JSはZOZOTOWNに適していると思いますか? CSS in JSを利用して新規に実装されたスタイルの品質はどうですか? 社内ドキュメントやインターネット上の情報を利用して開発が滞りなく進められていますか? CSS in JS(Emotion)のAPIの使い勝手はどうですか? その他、ご意見ご感想があれば記入してください 1. CSS in JSに変わったことで、作業効率はどの程度変わりましたか? 大いに向上した: 28.6% やや向上した: 57.1% 変わらない: 14.3% やや低下した: 0% 大いに低下した: 0% 2. CSS in JS導入による全体的な満足度を評価してください 非常に満足: 14.3% やや満足: 71.4% 普通: 14.3% やや不満: 0% 非常に不満: 0% 3. CSS in JSはZOZOTOWNに適していると思いますか? 非常に適している: 28.6% やや適している: 28.6% 普通: 42.9% やや適していない: 0% 全く適していない: 0% 4. CSS in JSを利用して新規に実装されたスタイルの品質はどうですか? 非常に高い: 14.3% やや高い: 57.1% 普通: 28.6% やや低い: 0% 非常に低い: 0% 5. 社内ドキュメントやインターネット上の情報を利用して開発が滞りなく進められていますか? 非常に満足: 42.9% やや満足: 28.6% 普通: 28.6% やや不満: 0% 非常に不満: 0% 6. CSS in JS(Emotion)の API の使い勝手はどうですか? 非常に使いやすい: 14.3% やや使いやすい: 71.4% 普通: 14.3% やや使いづらい: 0% 非常に使いづらい: 0% 7. その他、ご意見ご感想があれば記入してください リプレイス以前の環境でスタイルを新規で書く場合は、webpack用のentryファイルを用意する必要があり、それに比べて初期設定が簡単になりスタイルの記述までの作業が減った CSS in JS(Emotion)の使い勝手は非常に良い CSS in JSに不慣れなメンバーでも参入が容易だった CSS in JSを利用することでクラス名管理の問題などが解消された (Emotionの)日本語解説が少ないため、高度な利用例 (例:ThemeProviderにおけるThemeのマージ) に関する情報が不足していると感じた。今後はそのような知見を蓄積しドキュメントを整備する必要がある アンケートの総括 全体的にポジティブな回答が得られました。 CSS in JSライブラリ(Emotion)に対する不満が見られないことから、以下のような理由が考えられました。 初めて触れるメンバーでも参入しやすかった(簡単) ライブラリの機能性に足りない点や問題点が少なかった ドキュメントや情報が不足していると感じるメンバーもいるものの、全体的には満足度が高い結果だったと言えます。しかし、今よりも対象になる開発メンバーが少なかった事もあり、当然ながら現状とは状況や結果が異なっている可能性があります。 直近で実施したヒアリング結果 先のアンケートから時間も経過しており、関係するメンバーも増えたため、直近で改めて使用感に関するヒアリングを実施しました。 以下のような回答を得られました。 CSS設計を考える手間が減った リプレイス以前の環境では、CSSのクラス名の命名ルールにMindBEMding(BEMの派生)を採用していました 1 。この方法は堅牢な命名を可能にする一方で、適切なクラス名を考える手間も必要でした。CSS in JSの導入により、この手間が大幅に軽減され、コンポーネントの命名を集中して考えられるようになりました。 techblog.zozo.com CSSの変更が意図しない箇所やページにまで影響を与えてしまうことがあり、そのリスクを軽減するために命名規則のルールを設けていました。CSS in JSによって自動的にCSSのスコープが制限され、影響範囲も限られることで、それらの問題が発生しにくくなりました。 コードの可読性が向上した コンポーネント定義とスタイル定義を同じファイル内に共存できます。これによって、コードが見やすくなり、コード間の移動が減り実装スピードも向上しました。また、レビューも同じ観点でしやすくなりました。 複数の状態を持つコンポーネントの場合でも、コードが整理しやすく、読みやすさが向上したと感じています。 JavaScriptとCSSの統合性が向上した CSS in JSの導入によって、JavaScriptとCSSで共通定義を利用できるようになりました。これにより、細かい設定の一貫性を保つことができました (例:ヘッダーの高さをスタイリング用とスクロール時の位置操作用で同じ値を利用できる) 。 スタイルを操作するロジックがJavaScriptで簡潔に記述できるようになり(動的なスタイリングの実装)、複雑なデザイン要件にも対応できるようになりました。 CSS in JSへの課題感 開発者からはポジティブな意見が多く寄せられていますが、現状維持を良しとしているわけではなく、課題感も持って動向を注視しています。 パフォーマンスの懸念 ランタイムCSS in JSを利用していることから、パフォーマンスに関する課題感を持っています。 しかしながら、現状ではパフォーマンス上の問題が顕在化したことはありません。これは、ZOZOTOWN全体におけるリプレイス済みの機能(CSS in JSを利用している箇所)の割合がまだ低いため、問題が表面化していないという可能性も考えられます。 Zero-runtime CSS in JSへのライブラリのリプレイスについては、現時点で具体的な移行計画はありません。技術選定時の評価では、Zero-runtime方式はZOZOTOWNの開発要件を満たせないと判断しており、要件を満たすライブラリが登場しない限りは採用できないと考えています 2 。 今後、負荷テストなども実施しながら継続的にパフォーマンスを注視していきたいと思います。 可読性の課題 複雑なUIを実装しているとstyledの中で条件分岐が多くなり可読性を下げてしまうケースがあります。 以下のように簡単な分岐でも、条件分岐の記述方法がいくつか考えられます。どのような記述方法にするのが良いのか議論がありました 3 。 // (1) const Div = styled.div ` height: ${ ( { theme } ) => theme.device.isPc ? "55px" : "calc(55 / 375 * 100vw)" } ; ` ; // (2) const Div = styled.div ` ${ ( { theme } ) => { return theme.device.isPc ? css ` height: 55px; ` : css ` height: calc(55 / 375 * 100vw); ` ; } } ` ; 上記の記述方法としては、どちらも正しくコーディング規約などで記述方法を制限するようなことはしていません。意見としては以下のようなものが出ており、このような観点も記述の判断材料の1つとすると良いという結論に至りました。 (1): 他のプロパティが併記されている際にStylelintのソート( properties-order )が有効に機能する (2): PCとSPで大きく定義が異なる場合は可読性が高い 記述方法に厳格な制限を設けていないため、コードの統一性が多少失われることもあります。しかし、過度に複雑な記述 (例:多重の条件分岐や特殊な関数の利用) は避けるようにしています。これは、将来的なライブラリのリプレイス時の困難を防ぐためです。そのため、コードレビューやメンバー間での意見交換を通じて、適切な記述方法の意思統一を図っています。 まとめ 本投稿では、CSS in JS導入後の運用について紹介しました。CSS in JSの導入によって、可読性やメンテナンス性の向上といった多くのメリットがもたらされています。 技術選定では「どのようなスキルセットの開発メンバーが関与するか」「選定ライブラリが参入障壁にならないか」を意識していたため、参入障壁になっていないことがアンケートなどの結果から確認でき安心しました。一方で、Runtime CSS in JSのパフォーマンスに関する課題などは存在しているため、今後も技術的な観点で動向を注視していく必要はあると考えています。 同様の技術選定に悩む開発者の参考になれば幸いです。 最後に ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com ITCSSを採用して共同開発しやすいCSS設計をZOZOTOWNに導入した話 - ITCSSと命名規則(MindBEMding + 接頭辞)の組み合わせで実現できること ↩ ZOZOTOWN Web フロントエンドリプレイスにおける CSS in JS の技術選定で Emotion を選定した話 - LinariaZero-runtime-CSS-in-JS を検証する ↩ ZOZOTOWN開発本部のフロントエンドエンジニア有志で「スタイル分科会」を発足しており、スタイル周りの技術共有や各チームからの相談を受ける場として活動している。 ↩
アバター
はじめに こんにちは。技術本部ECプラットフォーム部マイグレーションブロックの小原です。 本記事では、Spring Bootの ApplicationRunner インタフェースを活用したバッチアプリケーション(CLIアプリケーション)の構築方法について解説します。 バッチ処理の実装において、SpringフレームワークはSpring Batchという強力なツールを提供しています。しかし、比較的単純なバッチ処理の場合、Spring Batchの使用はオーバーエンジニアリングとなる可能性があります。 そこで、軽量なアプローチとして ApplicationRunner を利用した実装方法を説明します。この方法は、シンプルなバッチ処理に適しており、Spring Bootの機能を活用しつつ、必要最小限の実装で効率的なバッチアプリケーションを構築できます。 なお、本記事は下記の環境にて検証しました。 Java 21 (Eclipse Temurin) Spring Boot 3.3.1 目次 はじめに 目次 ApplicationRunnerを選択した背景 ApplicationRunnerとCommandLineRunnerの比較 ApplicationRunnerを利用したバッチアプリケーションの実装 エントリポイントのコード 依存関係にspring-boot-starter-webを含まない場合 依存関係にspring-boot-starter-webを含む場合 バッチを起動するコマンド ExitCodeGeneratorを利用した終了コードの制御 テスト実装 @SpringBootTestを利用する方法 @ContextConfigurationを利用する方法 まとめ さいごに ApplicationRunner を選択した背景 今回のバッチアプリケーションにてSpring Batchではなく ApplicationRunner を選択した主な理由は以下の通りです。 マイクロサービスアーキテクチャの採用 : バッチの処理対象となるドメインではマイクロサービスアーキテクチャを採用しており、データ操作のためのAPIが既に存在していました。 インフラ構成のシンプル化 : データベース操作はマイクロサービスAPIの責務とし、バッチアプリケーション自体はデータベースを直接操作しない設計としました。これにより、バッチアプリケーションのインフラ構成をシンプルに保つことができました。 Spring Batchのオーバースペック : 上記の理由から、バッチアプリケーションはデータベースを直接操作することがありません。そのため、Spring Batchは学習曲線が高く、機能的にもオーバースペックであり、導入するメリットが薄いと判断しました。 軽量性 : ApplicationRunner を使用することで、必要最小限の機能を持つ軽量なバッチアプリケーションを構築できます。 これらの背景を踏まえ、 ApplicationRunner を活用した軽量バッチアプリケーションの構築方法を以下で詳しく解説します。 ApplicationRunner と CommandLineRunner の比較 Spring Bootには、アプリケーション起動時に処理を実行するためのインタフェースとして ApplicationRunner と CommandLineRunner が用意されています。以下に詳細な比較を示します。 特徴 ApplicationRunner CommandLineRunner メソッド run(ApplicationArguments args) run(String... args) 引数の処理 Spring Bootが解析済みの引数を提供 独自に引数を解析する必要がある オプション引数の扱い --key=value 形式を簡単に扱える 独自でパースが必要 非オプション引数の扱い getNonOptionArgs() で取得可能 配列の要素として直接アクセス 実行順序の制御 @Order アノテーションで制御可能 @Order アノテーションで制御可能 ここで、オプション引数とは --key=value の形式で指定される引数を指し、非オプション引数とはそれ以外の単純な値として渡される引数を指します。 例えば、 java -jar app.jar --input=data file1 file2 というコマンドでは、以下の通りとなります。 オプション引数 : --input=data 非オプション引数 : file1 と file2 本記事では、引数の扱いやすさから ApplicationRunner を選択しています。 ApplicationRunner および CommandLineRunner の詳細は、以下のJavadocを参照してください。 Interface ApplicationRunnerのドキュメント Interface CommandLineRunnerのドキュメント ApplicationRunner を利用したバッチアプリケーションの実装 以下に、 ApplicationRunner を利用したバッチアプリケーションの実装例を示します。 import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; import org.springframework.stereotype.Component; import org.springframework.web.client.RestClient; @Component public class BatchApplicationRunner implements ApplicationRunner { private final RestClient restClient; public BatchApplicationRunner(RestClient.Builder restClientBuilder) { this .restClient = restClientBuilder.baseUrl( "http://microservice/api" ).build(); } @Override public void run(ApplicationArguments args) throws Exception { // コマンドライン引数から特定のキーの値を取得 String inputData = args.getOptionValues( "input" ).get( 0 ); System.out.println( "Input data: " + inputData); // マイクロサービスのAPIを呼び出してデータ操作を行う String response = restClient.get() .uri( "/data?input=" + inputData) .retrieve() .body(String. class ); System.out.println( "API Response: " + response); // バッチ処理のロジックを実装 performBatchProcessing(response); } private void performBatchProcessing(String data) { // バッチ処理の実装 System.out.println( "Processing data: " + data); } } エントリポイントのコード バッチアプリケーションのエントリポイントとなるクラスを、依存関係に spring-boot-starter-web を含まない場合と含む場合で分けて示します。 依存関係に spring-boot-starter-web を含まない場合 import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class BatchApplication { public static void main(String[] args) { SpringApplication.run(BatchApplication. class , args); } } 依存関係に spring-boot-starter-web を含む場合 import org.springframework.boot.WebApplicationType; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuilder; @SpringBootApplication public class BatchApplication { public static void main(String[] args) { new SpringApplicationBuilder(BatchApplication. class ) .web(WebApplicationType.NONE) .run(args); } } 上記のコードでは、 SpringApplicationBuilder を使用して明示的にWebアプリケーションタイプを NONE に設定しています。これは、 spring-boot-starter-web が依存関係に含まれていても、Webサーバーを起動させないようにするためです。 この方法により、 RestClient や RestTemplate などのWeb関連のユーティリティを使用しつつ、Webサーバーを起動せずにバッチ処理を実行できます。 バッチを起動するコマンド バッチアプリケーションを起動する際のコマンドは以下のようになります。 java -jar app.jar --input=somedata このコマンドを実行すると、 BatchApplicationRunner の run メソッドが呼び出され、指定したデータを使ってバッチ処理が実行されます。 ExitCodeGenerator を利用した終了コードの制御 バッチアプリケーションが任意の終了コードを返すために、 ExitCodeGenerator インタフェースを実行できます。以下に例を示します。 import org.springframework.boot.ExitCodeGenerator; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; @SpringBootApplication public class BatchApplication { public static void main(String[] args) { System.exit(SpringApplication.exit(SpringApplication.run(BatchApplication. class , args))); } @Bean public ExitCodeGenerator exitCodeGenerator() { return () -> { // ここで適切な終了コードを返す // 例: 0は成功、1は一般的なエラー、2は特定のエラーなど return 0 ; }; } } この例では、 ExitCodeGenerator を実装したBeanを定義しています。 exitCodeGenerator メソッド内で、バッチ処理の結果に応じて適切な終了コードを返すロジックを実装できます。 テスト実装 ApplicationRunner を利用したバッチアプリケーションのテストには、主に2つのアプローチがあります。それぞれの特徴と使用方法を説明します。 @SpringBootTest を利用する方法 @SpringBootTest アノテーションを使用すると、 @SpringBootApplication アノテーションが付与された main メソッドのエントリポイントが実行されます。 @SpringBootTest を利用して、 main に引数を渡す場合のサンプルコードは次のとおりです。なお、テストコードは Spock を利用していますが、JUnitにおいても同様です。 import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.ApplicationArguments import org.springframework.boot.test.context.SpringBootTest import spock.lang.Specification @SpringBootTest(args = [ "--input=somedata" ]) class BatchApplicationSpec extends Specification { @Autowired ApplicationArguments applicationArguments def "バッチ処理のテスト" () { expect : // 期待される結果を検証 applicationArguments.getOptionValues( "input" ) == [ "somedata" ] } } このアプローチの主なポイントは以下の通りです。 @SpringBootTest アノテーションの args パラメータを使用して、コマンドライン引数をテストに渡すことができます。上記の例では、 --input=somedata という引数を指定しています。 ApplicationArguments インタフェースを @Autowired でインジェクションすることで、テストメソッド内で渡されたコマンドライン引数を取得し検証できます。 applicationArguments.getOptionValues("input") を使用して、特定のオプション引数の値を取得できます。この方法は、 ApplicationRunner の実装でコマンドライン引数を処理する方法と同じです。 アプリケーション全体のコンテキストが起動するため、実際の動作環境に近い状態でテストできます。 ただし、テスト実行時にバッチ処理が自動的に起動してしまうため、この挙動が問題となる場合は @ContextConfiguration を利用することで解消できます。 @ContextConfiguration を利用する方法 @SpringBootTest を使用すると、 @SpringBootApplication のエントリポイントが実行されてバッチ処理が自動的に起動してしまいます。 @ContextConfiguration アノテーションと適切なコンポーネントスキャンによりバッチ処理の自動起動を防ぎ、より細かいテスト制御が可能になります。 import org.springframework.boot.test.context.ConfigDataApplicationContextInitializer import org.springframework.test.context.ContextConfiguration import org.springframework.boot.test.context.TestConfiguration import org.springframework.context.annotation.ComponentScan import spock.lang.Specification @ContextConfiguration( classes = [TestConfig], initializers = [ConfigDataApplicationContextInitializer] ) class BatchApplicationSpec extends Specification { @TestConfiguration @ComponentScan(basePackages = [ "com.example.batch" ], excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = [BatchApplication])) static class TestConfig { // 必要であればテストに必要な設定を追加する } def "バッチ処理のテスト" () { expect : // 期待される結果を検証 } } このアプローチの主なポイントは以下の通りです。 TestConfig クラスで @ComponentScan アノテーションを使用し、バッチアプリケーションのコンポーネントをスキャンします。ただし、バッチのエントリーポイント( BatchApplication )を除外します。 @ContextConfiguration アノテーションにおいて、 TestConfig と ConfigDataApplicationContextInitializer を指定します。 Spring Bootの ApplicationContext をカスタム設定で初期化し、バッチ処理の自動起動を防ぎます。 ConfigDataApplicationContextInitializer を使用することで、 application.properties や application.yml の設定を読み込めます。 @SpringBootTest を利用した場合と同様の挙動です。 まとめ ApplicationRunner を利用することで、Spring Batchを使用せずに軽量で柔軟なバッチアプリケーションを構築できました。この方法は以下のような場合に適しています。 シンプルなバッチ処理 マイクロサービスアーキテクチャとの統合 データベースを直接参照しない処理 しかしながら、上記の状況に当てはまらないケースにおいてはSpring Batchの導入を検討することも視野に入れてください。プロダクトの要件に応じて適切なアプローチを選択しましょう。 さいごに ZOZOでは一緒にサービスを作り上げてくれる方を募集しています。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com
アバター
はじめに 技術評論社様より発刊されている Software Design の2024年5月号より「レガシーシステム攻略のプロセス」と題した全8回の連載が始まりました。 ZOZOTOWNリプレイスでは、段階的にシステムを置き換えるというアプローチによってマイクロサービス化を進めています。第3回は、マイクロサービス化の要となったAPI Gatewayの自社開発の話を中心に紹介します。 目次 はじめに 目次 はじめに API Gateway自社開発の動機 マイクロサービス化に向けたアプローチ API Gatewayの自社開発を決めた理由 開発の柔軟性 動作の軽量さを重視した技術選定 ZOZO API Gatewayが担う機能 ルーティング ユーザー認証 クライアント認証 トレースIDの発行 Istio導入以前に必要だった機能 リトライ リクエストタイムアウト 加重ルーティング ZOZO API Gatewayの運用 Istioの導入 ZOZO API GatewayとIstioの責務の整理と機能分担 監視 ZOZO API Gateway本番稼働開始から3年を経て ストラングラーフィグパターンの功績 自社開発のコストメリット まとめ はじめに ZOZOTOWNは2004年から運営を開始し、オンプレミス環境で動くモノリシックなシステムとして10年以上の間アーキテクチャを変えずに拡大してきました。ビジネスが順調に成長する一方で、古いシステムはスケーラビリティの限界、モノリシックなシステムの保守コストの増大、使用技術の陳腐化など多くの課題に直面していました。それらの課題を抜本的に解決すべく、ZOZOでは2017年からZOZOTOWNのマイクロサービス化を進めています。 この記事は、シリーズ連載の第3回として、ZOZOTOWNにおける段階的なマイクロサービス化に向けたアプローチと、その戦略の要となるZOZO API Gatewayを自社開発した話をします。一般的なマイクロサービス化に関する説明は割愛します。 前提として、ZOZOTOWNの旧システムはオンプレミス環境、移行先の新システムはクラウド環境にあります。また、本記事において「ZOZO API Gateway」という表現は、ZOZOで自社開発しているシステムを指します。Amazon Web Servicesなど他社のプロダクトにAPI Gatewayという名称が含まれるプロダクトについては、異なるシステムもしくはサービスであることがわかるよう、正式名称で記載します。 API Gateway自社開発の動機 マイクロサービス化に向けたアプローチ ZOZOTOWNのマイクロサービス化のためのアプローチとして、ZOZOではストラングラーフィグパターンを採用することを決めました。ストラングラーフィグパターンとは、リプレイス対象のシステムの機能を段階的に新しいマイクロサービスに置き換えていき、すべての機能を置き換え最終的に移行元のシステムを停止する戦略です。 ストラングラーフィグパターンには、フロントエンドにインターフェースを提供し、バックエンドの状態を隠蔽するファサードが必要です。ファサードが各マイクロサービスへの動的なルーティング機能を持ち、リクエストの送信先を徐々に新システムに切り替えていくことで、段階的なシステム移行を実現します。ZOZOTOWNの規模を考えると、マイクロサービスに移行して完全に旧システムを停止できるまでに、数年もしくはそれ以上の時間が必要と考えられました。その間に断続的に行われる置き換え作業をシームレスに実現するためのファサードとして、ZOZO API Gatewayを開発することを決定しました。リプレイス初期のシステム構成を簡略化すると、図1のようになります。 図1 ストラングラーフィグパターンによる旧システムから新システムへの処理の切り替え また、ZOZO API Gatewayはファサードであると同時に、API Gatewayパターンで定義されるAPI Gatewayの役割の一部も担います。フロントエンドに単一エントリポイントを提供することに加え、バックエンドに対してサービス横断的な機能も提供します。API GatewayパターンではAPI GatewayはBFF(Backends for Frontends)として振る舞う前提で語られることがありますが、ZOZOではそれらを別のシステムとして開発しています。BFFは複数のマイクロサービスからのレスポンスの集約や、ビジネスロジックに関するサービス横断的な機能の実現を担当しており、連載第6回で紹介予定です。 リプレイスが完了した時点でのシステム構成は図2のようになることを想定しています。 図2 旧システムからの切り替え後の理想的なシステム構成 API Gatewayの自社開発を決めた理由 ZOZOでは、API Gatewayをスクラッチで開発することを選択しました。自社開発という意思決定に至った決定的なメリットとして、開発の柔軟性と、動作の軽量さを重視した技術選定ができたことが挙げられます。 開発の柔軟性 マイクロサービス化を決定した当初、巨大化していたZOZOTOWNの機能や仕様をすべて洗い出すことは困難でした。そのため、リプレイスの具体的な進め方やサービス横断的に必要となる機能の要件が固まりきっていない状態でリプレイスプロジェクトをスタートせざるを得ませんでした。API Gatewayの仕様追加・変更に手間がかかる状態だと、バックエンドの開発を進められない状況を作り出し、API Gatewayがプロジェクト全体のボトルネックになるリスクがありました。 そこで、開発をすばやく柔軟に進められる状態にしておくために、自社開発が妥当と判断しました。既存のOSSやAPI Gatewayの機能を提供するクラウドサービスの使用も検討したものの、機能拡張のハードルの高さから開発スピードの低下が懸念されました。実際にプロジェクト開始後には、技術スタックやアーキテクチャがまったく異なる新旧両システムを連携させる役割をZOZO API Gatewayが担うことになり、会社特有の事情を考慮した開発も発生しました。結果として自社開発という選択は、開発コストとシステム保守の負担の軽減につながったと感じています。 動作の軽量さを重視した技術選定 ZOZO API Gatewayは、リプレイス完了時にはZOZOTOWNのほぼすべてのリクエストを通すことになります。そのため、レイテンシが低いこと、また障害発生時にも早急に復旧できる必要がありました。したがって、OSSやマネージドなクラウドサービスを使用して充実した多くの機能を付与できることよりも、軽量かつ起動が容易で、チューニングしやすいアプリケーションを作るメリットのほうが大きいと判断しました。ZOZOではサーバサイドのアプリケーション開発にJavaもしくはGoの採用を推奨しており、とくにGoは上記の条件を満たしていました。 ZOZO API Gatewayが担う機能 ZOZO API Gatewayの主な機能は次のとおりです。 ルーティング ユーザー認証 クライアント認証 トレースIDの発行 リトライ リクエストタイムアウト 加重ルーティング レートリミット 稼働を開始した当初は上記の機能をすべて提供していましたが、Istioを導入するにあたって責務の分担をし、現在はリクエストタイムアウト、リトライ、加重ルーティング、レートリミットの機能をIstioが担っています。この節ではZOZO API Gatewayの機能を説明し、Istioについての詳細は次節で説明します。 ルーティング ルーティングは、受け取ったリクエストを検証し、設定されたマイクロサービスもしくはオンプレミスシステムに転送する機能です。マイクロサービスの増加に伴いルーティングの設定も増えるため、ZOZO API Gatewayのアプリケーションとは切り離し、YAMLファイルで宣言的に設定をしています。 ルーティングを設定するためのYAMLファイルは routes.yaml (リスト1)と target_groups.yaml (リスト2)の2つがあります。この例では、リクエストのパスが正規表現で ^/sample/(.+)$ に一致した場合、targetAに転送します。また転送時には、 $1 がキャプチャグループのマッチした部分に置き換えられます。この設定を必要に応じてYAMLに記述し、ルーティングを実現しています。 - from : path : ^/sample/(.+)$ to : destinations : - target_group : targetA path : /$1 リスト1 routes.yaml targetA : targets : - host : sample.example.com port : 8080” リスト2 target_groups.yaml ユーザー認証 ユーザー認証は、トークンを用いてリクエスト送信者が誰であるかを確認する機能です。ただしログイン時に認証するのはマイクロサービスの1つであるID基盤サービスで、このサービスでログイン時にトークンを発行します。ZOZO API GatewayではID基盤APIを用いてリクエストに付与されたトークンを検証し、ユーザーの認証情報が正しいことを確認します。 クライアント認証 ZOZOTOWNのAPIは一般公開していません。そのためリクエストできるクライアントを制限しています。リクエストする際はトークンをヘッダに付与してもらうことで、ZOZOTOWNのネイティブアプリや社内サービスなどのクライアントを識別し、クライアントを認証します。クライアント認証後は、クライアントを識別できるヘッダを伝搬し、バックエンドの処理で認可に利用しています。 リクエストを許可するクライアントは、 routes.yaml のclientsで設定できます。リスト3の設定では、targetAにリクエストできるクライアントはserviceAだけに制限できます。トークンの詳しい設定方法については、セキュリティ上の理由から割愛します。 - from : path : ^/sample/(.+)$ clients : - serviceA to : destinations : - target_group : targetA path : /$1” リスト3 clientsを指定した routes.yaml トレースIDの発行 ストラングラーフィグパターンによりリクエストが新旧システムに振り分けられ、さらに複数のマイクロサービスを通る場合もあるため、リクエストの追跡が困難になりました。そこでZOZO API Gatewayで独自のトレースIDを発行し、転送先へ伝搬しました。新旧システムで受け取ったトレースIDをログに出力することで、サービスを横断したリクエストを追跡できます。 Istio導入以前に必要だった機能 リトライ、リクエストタイムアウト、加重ルーティングの機能は現在Istioに置き換えられており、ZOZO API Gatewayではすでに使われていないため、簡潔に紹介します。いずれも target_groups.yaml で設定しました。 リトライ リトライは、リクエストが何かしらのエラーで失敗した場合に再試行する機能です。次のパラメータを用いて、転送先ごとに設定できます。 max_try_count:リクエストを試行する最大回数 retry_cases:リトライする条件(サーバエラーやリクエストタイムアウト時など) retry_non_idempotent:冪等でないHTTPメソッド(POST、PATCH)に対するリトライ設定 retry_to:リトライ先の指定 リクエストタイムアウト リクエストに対するタイムアウトの設定も、リトライと同じく、転送先ごとに設定できます。 connect_timeout:1リクエストあたりのTCPコネクション確立までの間のリクエストタイムアウト値(ミリ秒単位) read_timeout:1リクエストあたりのリクエスト開始からレスポンスボディを読み込み終わるまでの間のリクエストタイムアウト値(ミリ秒単位) idle_conn_timeout:データが送受信されなかった場合にコネクションを維持する時間(ミリ秒単位) max_idle_conns_per_host:1ホストあたりに保持するアイドル状態のコネクションの最大数 加重ルーティング 新旧システムへのリクエスト比率の重みを設定できます。この比率を調整することで、旧システムから新システムへの移行の際にカナリアリリースを実現しました。リスト4の設定では、old-systemへ80%、new-systemへ20%の割合でリクエストされます。 targetA : targets : - host : old-system port : 8080 weight : 4 - host : new-system port : 8081 weight : 1” リスト4 加重ルーティングを適用した target_groups.yaml ZOZO API Gatewayの運用 Istioの導入 連載第2回(本誌2024年6月号) でお伝えしたとおり、ZOZOTOWNのマイクロサービスが稼働するKubernetesクラスタ「プラットフォーム基盤」では、オープンソースのサービスメッシュであるIstioを導入しています。導入の背景は、新たなトラフィック制御の要件です。リプレイスが進み、新しくマイクロサービスが他サービス(クラスタ外のサービスを含む)を呼び出す通信要件が発生しました。当時マイクロサービス起点のZOZO API Gatewayを経由しない通信では、一貫したトラフィック制御機能がありませんでした。そのため、各マイクロサービスは必要に応じて独自に機能を追加する非効率的な状況に陥っていました。そこで、プラットフォーム基盤で一貫したトラフィック制御機能の提供が課題となりました。この課題に対し、ZOZOでは3つの案を検討しました。 案1:マイクロサービス起点の通信でもZOZO API Gatewayを介し、ZOZO API Gatewayのトラフィック制御機能を使う 案2:トラフィック制御機能を提供する共通ライブラリを作成し各マイクロサービスに組み込む 案3:サービスメッシュを導入し、マイクロサービスを変更することなくサイドカーパターンでプロキシを注入して透過的にトラフィック制御機能を追加する 案1はZOZO API Gatewayへの負荷が大きく、スケールによるコスト増加が現実的ではありませんでした。また、案2はライブラリの作成やそのメンテナンス、ライブラリをマイクロサービスに組み込み逐次更新するといった手間が発生します。さらに、その作業の担当をどうするか、といった課題も挙がりました。最終的に、案3のサービスメッシュが最も現実的であるという結論に至りました。ZOZOでは次のような理由からIstioを選定し、2020年後半から検証を進め、導入を決めました。 サービスメッシュを実現するOSSの中で利用実績が多い 分散トレーシングに利用しているDatadogが、Istioとのインテグレーションをサポートしている Istioの導入により、マイクロサービスのPodにはistio-proxyというサイドカーコンテナが注入されます。マイクロサービス起点の通信はすべてistio-proxyをプロキシとして経由することになり、ZOZO API Gatewayを経由せずにIstioの設定による一貫したトラフィック制御ができるようになりました(図3)。 図3 Istioによるトラフィック制御 ZOZO API GatewayとIstioの責務の整理と機能分担 ZOZO API Gatewayはネットワークの入口でのみトラフィック制御機能を提供します。一方Istioは、メッシュネットワーク全体にトラフィック制御機能を提供します。IstioではVirtualServiceというカスタムリソースを用いて、各マイクロサービスでトラフィック制御をリスト5のように定義します。 apiVersion : networking.istio.io/v1beta1 kind : VirtualService metadata : name : virtualservice spec : hosts : - microservice http : - route : - destination : host : microservice.ns.svc.cluster.local subset : subset retries : attempts : 1 perTryTimeout” perTryTimeout : 3s retryOn : 5xx timeout : 4s リスト5 virtualservice.yaml リスト5の場合、メッシュネットワーク内でマイクロサービスへHTTPリクエストを行ったとき、3秒でリクエストタイムアウトとなります。HTTPステータスコードで5xx(リクエストタイムアウト504を含む)が返却された場合、1回リトライします。リトライを含むリクエスト全体で4秒経過するとクライアントにリクエストタイムアウトが返ります。ZOZO API Gatewayをメッシュネットワークに追加する際、トラフィック制御を両方設定しているとトラフィック制御が二重で行われてしまう状態となります。その場合、意図しないトラフィック制御により正常なリクエストができずZOZOTOWNの画面が表示されないなどの事象を引き起こす可能性があります。この課題に対し、ZOZOでは次の方針で重複する機能の責務を整理しました。 一貫した機能提供のため、メッシュネットワーク全体に関わる機能はIstioの責務とする 入口のGatewayレイヤーでのみ必要となる機能はZOZO API Gatewayの責務とする この責務に従い、各機能を表1のとおり分担することにしました。 機能 ZOZO API Gateway Istio ルーティング ○ ユーザー認証 ○ クライアント認証 ○ トレースIDの発行 ○ リトライ ○ リクエストタイムアウト ○ 加重ルーティング ○ レートリミット ○ 表1 責務整理後における機能の分担結果 ZOZO API Gatewayで不要となる機能はサービスメッシュへの追加と併せて削除していきました。結果、意図しないトラフィック制御などの問題が発生することなく、プラットフォーム基盤全体にIstioを導入できました。このように、Istioの導入でZOZO API Gatewayは責務を変え、Gatewayレイヤーで必要な機能のみ提供する形になりました。 監視 リプレイスプロジェクトではDatadog APMを使用し、分散トレーシングを実現しています。ZOZO API Gatewayでは、traceのspanタグにクライアント識別子を追加することでリクエスト元を判断できるようにしており、エラー発生時の可観測性を向上させています。ZOZO API GatewayはZOZOTOWNのリクエストのほぼすべてを処理する前提で開発していることから、このようなしくみによってすべてのサービスのエラーを検知し、分析できます。 監視の運用については特有の難しさもあります。どのサービスが発したエラーであれ、システム異常の可能性があることに変わりはないため、ZOZO API Gateway開発チームでは、検知したエラーにはほぼすべて対応します。チームはアラートを受けて、エラーが発生したサービスの通知が流れるSlackチャンネルを確認したり、担当チームにヒアリングを行ったり、事情を考慮してZOZO API Gateway側でのエラー通知の基準を調整したりなどの意思決定をします。現状、マイクロサービスが障害を起こす頻度は低く、監視で対応するケースは激しいスパイクアクセスを処理する機能を提供するサービスに起因した避けられないエラーの通知がほとんどです。しかし、安全な運用のために、ZOZO API Gatewayを開発するチームは関連するマイクロサービスの性質についてもある程度幅広く知っておく必要があります。 ZOZO API Gateway本番稼働開始から3年を経て ZOZO API Gatewayは2020年4月に稼働を開始し、2024年時点で3年以上の間本番環境で動いています。本記事の締めくくりとして、この節ではリプレイスプロジェクトにおける具体的な成果を紹介します。以降の説明で使用されるデータは、すべて2024年4月上旬時点のものです。 ストラングラーフィグパターンの功績 ファサードが存在することにより、バックエンドの開発が完全にフロントエンドから切り離され、リプレイスの戦略に大きな柔軟性を生みました。フロントエンドはAPIのホストが新旧システムのどちらであるかを意識する必要はなく、ZOZO API Gatewayの設定を変更するだけで新システムでの処理に切り替えることができます。これは、たとえばオンプレミスDBからマイクロサービスDBへのデータ移行時に役立ちます。リプレイスプロジェクトでは、ZOZOTOWNの稼働を止めずにデータを移行するために、書き込みを行うAPIとデータ取得のAPIを異なるタイミングでリリースします。ファサードがなければリクエストの切り替えに都度フロントエンドの修正が必要ですが、ストラングラーフィグパターンではフロントエンドのチームがバックエンドの事情を考慮する必要はありません。データ移行戦略の詳細についてはテックブログ *1 をご覧ください。 プロジェクト全体としても、マイクロサービス開発組織のスケールを実現できました。現在20以上のサービスの開発が同時に進んでおり、ZOZOTOWNを止めることなくリプレイスを進めています。エンジニアの採用も積極的に行い、リプレイスプロジェクトに携わるエンジニアの数は150人程度に増えました。 自社開発のコストメリット ZOZOTOWNのアクセス量の多さから、Amazon API Gatewayのような従量課金の料金体系のサービスを使用するよりも、自社でサービスを開発したほうが安くなるのではないかというもくろみが開発当初からありました。実際にコストメリットがあるのか、試算してみます。ここでは、Amazon API Gatewayサービスと、ZOZO API Gatewayが稼働しているAmazon EC2(以下EC2)の料金を用います。 執筆時点のAmazon API Gatewayの料金体系では、最初の3億リクエストは100万件当たり1.29USD、それを超えた分は100万件当たり1.18USDかかります。一方、ZOZO API GatewayはAmazon Elastic Kubernetes Service上で稼働しているため、そのEC2ノードにかかるコストが主な利用料金となります。リプレイス完了後にZOZO API Gatewayがどの程度のアクセスを処理することになるかは現時点では明言できませんが、たとえば月間のリクエスト数を10億とした場合、それぞれの概算コストは表2のようになります。リクエスト数はアプリケーションの設計にもよりますが、試算のとおり、月間10億リクエスト程度であれば、ZOZO API GatewayはAmazon API Gatewayと比べて60%程度のコストでアクセスをさばくことができると言えます。 Amazon API Gateway ZOZO API Gateway 試算 3億✕$1.29/100万 + (10億-3億)✕$1.18/100万 省略 (弊社実績とEC2の料金体系からの概算) コスト(月額) $1,213 $734 表2 Amazon API GatewayとZOZO API Gatewayの概算コスト比較 実際にZOZO API Gatewayが処理するアクセス数はより多く、よりZOZO API Gatewayにコストメリットがある状態です。またZOZO API GatewayはEC2のリザーブドインスタンスを契約するなどの工夫により、試算した金額感よりもさらにコストを抑えることができています。そのため、当初のもくろみどおりZOZO API Gatewayの自社開発はマネージドサービスの従量課金と比較してコストメリットがあることがわかります。 一方で、Amazon API GatewayにはAPIの作成、保守運用などに必要な豊富な機能が備わっています。ZOZOTOWNでのユースケースでは自社開発が結果的にコスト抑制にもつながりましたが、初期開発コストを抑えてより一般的な機能を実現したい場合や、アプリケーションの要件によっては、Amazon API Gatewayを採用するほうが妥当な可能性があります。 まとめ ZOZOTOWNではストラングラーフィグパターンによるマイクロサービス化を進めており、その戦略の要となるファサードとしてZOZO API Gatewayを開発しました。ZOZO API Gatewayは一般的なルーティングなどの役割を担い、Istioの導入によってリクエストタイムアウトやリトライなどの一部機能を分担し、開発規模は比較的小さく保っています。プロジェクトの進行に伴い、一部新旧システムの仕様の差を吸収する役割を担うことになったものの、スクラッチから開発したことにより柔軟に開発を進めることができました。ZOZOTOWNのケースでは、API Gatewayの自社開発はリプレイスプロジェクトのスムーズな進行に大きく寄与し、コスト低減にも役立っています。 本記事は、技術本部 ECプラットフォーム部 会員基盤ブロックの富永 良子、吉江 守弘と、技術本部 SRE部 プラットフォームSREブロック ブロック長の亀井 宏幸によって執筆されました。 本記事の初出は、 Software Design 2024年7月号 連載「レガシーシステム攻略のプロセス」の第3回「API Gatewayとサービスメッシュによるリクエスト制御」です。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com *1 : サービス無停止を実現するデータ移行戦略
アバター
こんにちは、SRE部カート決済SREブロックの飯島です。普段はZOZOTOWNのカート決済機能のリプレイス・運用・保守に携わっています。またSplunkの管理者としても活動しています。 本記事ではSplunk CloudにおけるInfrastructure as Code(IaC)についてご紹介します。 背景 Splunkに対して感じていた課題 Splunk CloudのIaC化検討 Splunk Appを使ったIaC App API IaC対象のSplunkリソース 既存のリソースのエクスポート IaCの全体像 Appのディレクトリ構成 CI/CDパイプライン 検証の詳細 デプロイの詳細 効果、メリット 終わりに 背景 弊社では様々な用途でSplunkを活用しています。過去にいくつかテックブログを公開していますので、興味のある方はぜひご覧ください。 techblog.zozo.com techblog.zozo.com Splunkには、オンプレミスやクラウドプロバイダーにインストールして運用するSplunk Enterpriseと、SaaSとして提供されるSplunk Cloudの2種類があります。弊社では後者のSplunk Cloudを利用しています。 Splunkに対して感じていた課題 冒頭でも説明した通り、弊社ではSplunkを様々な用途で活用しており、その重要性は導入時よりも高まっています。一方で以下の課題が生じていました。 SPLの習得難易度が高く新規利用の開始までのハードルが高い コード管理されていないためダッシュボードなどがいつの間にか変更されていることがある 何の用途で作成したか不明なリソースが存在する IaCを導入すると以下のメリットが生まれ、上記の課題を解決できます。 Splunkが不慣れでも他のコードを参考にして効率よくリソースの作成、変更が可能 コード管理されているとSplunkを触ったことがない人にも勧めやすい コード管理ツール(例えばGitHub)に変更履歴が残る コード管理ツールでレビューできる/レビューが受けられる Splunk CloudのIaC化検討 上述した課題を解決するためにSplunk Cloudをコード管理する方法を検討しました。 社内でインフラ構成管理ツールとしてTerraformを利用しているケースが多かったため、まずはTerraformでの管理を検討しました。しかしSplunk CloudではTerraformで管理できるリソースが一部に限られており、ダッシュボードなどのコード管理に対応できないことから課題解決に至らないと判断しました。 補足としてSplunk Enterpriseはほぼ全てのリソースがTerraformに対応しているため、Splunk Cloudについても今後対応リソースが増えていく可能性は十分あります。 github.com Splunk Appを使ったIaC そこで今回Splunk App(以下、App)と、Splunk社から提供されるApp用の2つのAPIを使ってIaCを実現しました。 App Appとは、Splunkの設定を集約して管理するための単位です。デフォルトではSearch & ReportingというAppがあらかじめ利用可能になっています。ユーザー自身でAppを新規作成したり、 Splunkbase からAppをインストールしたりもできます。 今回Search & Reportingの既存リソースを、新規作成したAppに移行してコード管理する方針としました。 API 以下が使用した2つのAPIです。 AppInspect API Appの品質と準拠性を検証するためのAPI(ref. Validate your private app ) Admin Config Service (ACS) API AppをSplunk Cloudにデプロイすることが可能(ref. Install an app ) デプロイするにはAppInspect APIの検証に合格する必要がある IaC対象のSplunkリソース 弊社でよく利用されているダッシュボードとアラート・レポートを対象としました。 Appを使ったIaCでは、Terraformのようにリソースを インポート して既存リソースをコード管理下に置く手段がありません。リソースをコードで定義すると、新規リソースとして作成されます。そのため、既存リソースを新規リソースに置き換えることが難しいもの(例えばIndex)は対象外としました。 既存のリソースのエクスポート 既存リソースの構成ファイルはエクスポート可能です。Splunk Enterpriseでは利用者自身でエクスポート可能ですが、Splunk Cloudではこの機能が存在せずSplunkサポートに依頼する必要があります。 今回既存のリソースが集約されているSearch & Reporting Appをエクスポートし、IaC対象のダッシュボードとアラート・レポートの構成ファイルを入手しました。 IaCの全体像 Appの構成ファイルはGitHubで管理しています。また、AppInspect APIとACS APIを使用し、GitHub ActionsのWorkflowを利用して、アプリの検証とデプロイを行うCI/CDパイプラインを構築しました。 Appのディレクトリ構成 Appは 開発者ガイド にある通り、決められたディレクトリ構成に準拠する必要があります。もし準拠できていない場合、AppInspect APIの検証に失敗します。 以下はAppの必要最低限のディレクトリ構成に、ダッシュボードとアラート・レポートを追加した構成です。 . ├── bin │ └── README ├── default │ ├── app.conf │ ├── data │ │ └── ui │ │ ├── nav │ │ │ └── default.xml │ │ └── views │ │ └── README │ │ └── sample_dashboard.xml │ └── savedsearches.conf ├── metadata │ └── default.meta └── static ├── appIcon.png ├── appIconAlt.png ├── appIconAlt_2x.png └── appIcon_2x.png 以下は上記の構成の主なファイルの説明です。 ファイル 説明 ./default/app.conf Appの設定 ./default/savedsearches.conf アラート・レポートの設定 ./default/data/ui/views/dashboard.xml ダッシュボードの設定 ./metadata/default.meta 各種リソースのアクセス権限の設定 CI/CDパイプライン GitHub上でPRを作成すると、ダッシュボードのXMLファイルのLintと、AppInspect APIを使用したAppの検証が実行されます(CI)。マージすると再度CIが実行され、その後ACS APIを使用したAppのデプロイと(CD)、CI/CDの結果のSlack通知が実行されます。 以後、Appの検証とデプロイの処理について説明します。 検証の詳細 以下がAppの検証処理の流れです。 splunk.comにログインしてアクセストークンを取得 -(1) Appのコードをtarコマンドでgzip圧縮 -(2) (1)と(2)を添付してAppInspect APIを実行 AppInspectの結果から検証の合否を判定 以下サンプルコードです。 # splunk.comにログインしてアクセストークンを取得 loginstatus = $( curl -u " $username : $password " --url " https://api.splunk.com/2.0/rest/login/splunk " ) access_token = $( echo " ${loginstatus} " | jq .data.token | tr -d ' " ' ) # Appのコードをtarコマンドでgzip圧縮 COPYFILE_DISABLE = 1 tar --format ustar -cvzf " ${appname} " .tar.gz " ${appname} " tarfile_path = $( realpath " ${appname} .tar.gz " ) # AppInspect API実行 request = $( curl -X POST \ -H " Authorization: bearer ${access_token} " \ -H " Cache-Control: no-cache " \ -F " app_package=@ ${tarfile_path} " \ -F " included_tags=cloud " \ --url " https://appinspect.splunk.com/v1/app/validate " ) # AppInspectの進捗を確認 request_id = $( echo " ${request} " | jq .request_id | tr -d ' " ' ) appinspect_status = " PROCESSING " while [ " ${appinspect_status} " = "PROCESSING" ]; do echo " waiting the app inspect reviewing " echo " fetch to check the status of inspecting from https://appinspect.splunk.com/v1/app/validate/status/ ${request_id} " sleep 10 status_request = $( curl -s -X GET \ -H " Authorization: bearer ${token} " \ --url " https://appinspect.splunk.com/v1/app/validate/status/ ${request_id} " ) echo ""; echo " -------------- App Inspect Status -------------- " echo " ${status_request} " | jq . appinspect_status = $( echo " ${status_request} " | jq -r .status ) done # AppInspectの成否の判定 errors = $( echo " ${status_request} " | jq -r .info.error ) failures = $( echo " ${status_request} " | jq -r .info.failure ) result = " failed " if [ " ${errors} " -eq 0 ] && [ " ${failures} " -eq 0 ]; then result = " success " fi AppInspectの結果(上記サンプルコードの status_request )には以下の項目が含まれます。errorとfailureが0であれば検証は合格です。 " status ": " SUCCESS ", " info ": { " error ": 0 , " failure ": 0 , " skipped ": 0 , " manual_check ": 0 , " not_applicable ": 0 , " warning ": 0 , " success ": 1 } AppInspectの結果の詳細はレポートで確認できます。レポートにはJSON形式とHTML形式があり、Content-Typeでファイル形式を選択できます。 # JSON形式 curl -X GET \ -H " Authorization: bearer ${access_token} " \ -H " Cache-Control: no-cache " \ -H " Content-Type: application/json " \ --url " https://appinspect.splunk.com/v1/app/report/ ${request_id} " # HTML形式 curl -X GET \ -H " Authorization: bearer ${access_token} " \ -H " Cache-Control: no-cache " \ -H " Content-Type: text/html " \ --url " https://appinspect.splunk.com/v1/app/report/ ${request_id} " デプロイの詳細 以下がACS APIを実行してSplunk CloudにAppをデプロイする処理です。 curl -X POST " https://admin.splunk.com/ ${STACK} /adminconfig/v2/apps/victoria " \ --header " X-Splunk-Authorization: ${access_token} " \ --header " Authorization: Bearer ${json_web_token} " \ --header " ACS-Legal-Ack: Y " \ --data-binary " @ ${tarfile_path} " | jq . CIで取得したアクセストークンとtarコマンドでgzip圧縮したAppのコードを添付します。またJSON Web Tokenが必要になるため、事前にSplunk CloudのUIもしくはACS APIで作成します。 効果、メリット IaC化により、他のコードを参考にしてリソースの作成や変更が可能となりました。これにより、Splunkに不慣れな場合でも効率的にリソースの作成や変更ができ、Splunkに触っていない人にもSplunkの利用を勧めやすくなりました。またGitHub上のPRで変更内容をレビューでき、その変更内容が履歴に残るメリットも得られました。 終わりに 弊社と同様にSplunk Cloudを使い、IaC化を検討している方の参考になれば幸いです。 今回Splunkのカスタマーサクセスチームの皆様には多大なご協力をいただきました。この場を借りて、心から感謝申し上げます。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com
アバター
はじめに こんにちは、データシステム部推薦基盤ブロックの寺崎( @f6wbl6 )です。 私たちのチームではZOZOTOWNにおけるパーソナライズ機能と推薦システムを開発しており、2022年6月のテックブログではZOZOTOWNのホーム画面をパーソナライズした事例の1つを紹介しました。 techblog.zozo.com 今回は上記記事の続編として、ホーム画面での商品訴求の単位である「モジュール」の並び順をパーソナライズした取り組みをご紹介します。本記事がパーソナライズ機能や推薦システムを開発している方の参考になれば幸いです。 目次 はじめに 目次 ZOZOTOWNのホーム画面における「モジュール」について パーソナライズ施策の比較 パーソナライズの方針 モジュール並び順パーソナライズの要件 パーソナライズ方法 概要 モデル構成 モジュールのスコア算出方法 システム構成 1.Two-Towerモデル学習パイプライン 2.商品ベクトル化パイプライン 3.モジュール並び順予測パイプライン A/Bテスト 概要 結果 A/Bテストを経て挙がった課題 定性的な課題 定量的な課題 最後に ZOZOTOWNのホーム画面における「モジュール」について まずはじめに、前回記事のおさらいとしてZOZOTOWNのホーム画面における「モジュール」について簡単に説明します。 モジュールは商品やブランド、ショップなどのコンテンツを表示させるための枠の総称で、ZOZOTOWNのホーム画面はこれらのモジュールによって構成されています。 2024年7月19日時点でのZOZOTOWNホーム画面 モジュールは訴求内容や更新頻度に応じて「定常モジュール」と「企画モジュール」の2種類に大別されます。各モジュールの違いは下表の通りです。 運用方法 更新頻度 定常モジュール 常に同じ訴求内容のコンテンツを掲載する ほぼ更新なし 企画モジュール ビジネスサイドが自由に訴求内容を企画・管理する 1~2週ごとに更新 定常モジュールは「チェックしたアイテム」や「世代別アイテムランキング」など、ファッショントレンドなどの外的要因によらず継続的に特定の内容を訴求するものです。一方、企画モジュールはビジネスサイドの企画チームがトレンドやキャンペーンに応じて柔軟に訴求内容を変更するもので、定期的に更新される点が特徴です。 企画モジュールには商品の検索条件が紐づいており、この検索条件を元に商品検索用のAPI(検索API)から取得した商品をモジュールに表示する仕組みとなっています。そのため企画チームは商品の検索条件を設定するだけで多様な訴求軸のモジュールを作成できます。 パーソナライズ施策の比較 「ホーム画面のモジュールをパーソナライズする」と一口に言ってもパーソナライズのパターンがいくつかあるため、ここではそのパターンを列挙しつつ過去の施策と今回の施策がどのパターンに該当するかを説明します。 モジュール内に表示される商品の表示順序を変える モジュール内に表示される商品の検索条件を変える 表示されるモジュールの順序を並び替える 過去のテックブログ で紹介したパーソナライズ施策は上記のうち「 2.モジュール内に表示される商品の検索条件を変える 」に該当します。前述の通りモジュールには特定の検索条件で取得した結果を表示しているため、ユーザーごとにパーソナライズしたブランドやカテゴリを検索条件に反映させることでモジュール内の商品をパーソナライズしていました。 過去のパーソナライズ施策のイメージ 今回のパーソナライズ施策は「 3.表示されるモジュールの順序を並び替える 」に該当します。モジュールそのものの並び順をユーザーごとに変化させ、ユーザーが興味を持つと考えられるモジュールを上位に表示することを目指しています。 今回のパーソナライズ施策のイメージ また、パーソナライズの対象となるモジュールは前述した「 企画モジュール 」に限定しています。定常モジュールはいわゆるマス向けの訴求軸であるため、パーソナライズの効果はそこまで期待できません。一方で企画モジュールは企画チームが多様な訴求軸のモジュールを作成できるため、パーソナライズによる効果が大きいと考えられます。 パーソナライズの方針 過去の施策ではパーソナライズの目的として「 既存購入者の来訪頻度・購入頻度の増大 」を掲げていましたが、今回の施策ではまず「 より良いモジュールの並び順とはどのような状態か 」を定義することから始めました。 ユーザーにとって最適な並び順でモジュールを表示することは長期的には購入頻度や受注金額の増大に繋がると考えられますが、短期的には興味のあるモジュールをクリックする頻度が増えるはずです。ここから「良いモジュールの並び順」を「ユーザーが興味を持ってクリックするモジュールが上から順に並んでいる状態」と定義し、まずは「 モジュールのクリック数増大 」を目指すこととしました。 この目的を元に、「ユーザーにクリックされるモジュールを優先的に表示する」という方針でパーソナライズシステムを開発しました。以降、モジュールの並び順のことを「モジュール並び順」と記述します。 モジュール並び順パーソナライズの要件 今回のプロジェクトで挙げられた要件をいくつかピックアップします。 会員ごとにパーソナライズされた並び順でモジュールを表示する パーソナライズされた並び順が定期的に更新される パーソナライズロジックのA/Bテストを実施できる モジュール並び順のパーソナライズができないユーザーに対してデフォルトの並び順でモジュールを表示できる 任意のモジュールを特定の位置に固定表示できる 1〜3番目の要件については特に違和感はないと思います。 4番目の要件は、ユーザーがパーソナライズに必要な行動ログを持たない場合やパーソナライズシステムが障害等で稼働できない状況を想定したものです。パーソナライズ機能に依存してZOZOTOWNのホーム画面にモジュールが表示されなくなる、という状況を避けるためにデフォルトの並び順を表示する要件を設けています。 5番目の要件はビジネス要求に基づくものです。ZOZOTOWNでは特定のブランド・ショップの商品の訴求を強めたり、セール期間中にセール商品の訴求を強めたりするケースがあり、そういった際に一部のモジュールを特定の位置に固定表示します。また広告系のモジュールやパーソナライズ以前から訴求効果が高かった「チェックしたアイテム」モジュールなど、表示位置を迂闊に変えるべきでないモジュールもいくつかあります。今回のプロジェクトではこれらのモジュールはパーソナライズの対象外として特定の位置に固定表示しています。 パーソナライズ方法 ここからはメイントピックであるモジュール並び順のパーソナライズ方法について説明します。 概要 モジュール並び順のパーソナライズにはユーザーとモジュールの相性を表すスコアを利用しており、このスコアの降順にモジュールを配置することでパーソナライズを実現しています。モジュールのスコアを計算する方法の概念図を以下に示します。 パーソナライズ方法の概念図 ユーザーを表現するベクトルはZOZOTOWN内での回遊行動をもとに作成し、モジュールを表現するベクトルはモジュールに表示される商品の情報をもとに作成しています。モジュールのベクトルはそのモジュールに表示される商品ベクトルの集合と考えられるため、商品ベクトルの集合とユーザーベクトルを用いてモジュールに対するスコアを計算します。 このパーソナライズ方法を実現するには ユーザー情報と商品情報をベクトルにするための仕組み が必要になります。次節ではこのベクトル化に必要な機械学習モデルとシステム構成について説明します。 モデル構成 ユーザー情報や商品情報をベクトル化する手法は多々ありますが、今回は弊社の別のパーソナライズシステムでも利用されている Two-Towerモデル を採用しました。Two-Towerモデルはドメインが異なるデータを同一のベクトル空間にマッピングする2つのエンコーダーから成っています。これらのエンコーダーを使ってユーザーと商品それぞれのベクトルを獲得します。 Two-Towerモデルの概念図 モデルの学習方法や使用したライブラリについては以下の通りです。 項目 内容 学習の方針 商品閲覧を最適化 モデル構築に使用したライブラリ TensorFlow Recommenders 近似最近傍探索の手法 ScaNN モジュールのスコア算出方法 ユーザーベクトルと商品ベクトルを使ってモジュールのスコアを決めますが、スコアの算出方法はパーソナライズ結果に大きく影響を与えるため、オフラインでの実験時にいくつかのパターンを試しました。 全商品ベクトルの平均をモジュールのベクトルとみなし、 ユーザーベクトルとのコサイン類似度をモジュールのスコアとする 全商品ベクトルの各次元でmaxをとったベクトルをモジュールのベクトルとみなし、 ユーザーベクトルとのコサイン類似度をモジュールのスコアとする ユーザーベクトルと各商品ベクトルのコサイン類似度を算出し、 得られたコサイン類似度の平均値をモジュールのスコアとする ユーザーベクトルと各商品ベクトルのコサイン類似度を算出し、 得られたコサイン類似度の最大値をモジュールのスコアとする 上記のスコア算出方法に対して定量・定性評価を実施した結果、案4の算出方法が定量的・定性的に優れていたことから案4を採用しました。 設計時は案3を採用する想定でしたが、定性評価をしてみると意図しない・もしくは意図がわからない推薦をしているものが多い結果となっていました。これはモジュールに掲載される商品では必ずしも統一感があるわけではなく、そうした商品のスコアの平均を取るとスコアが平滑化されてしまうことが原因と考えています。 商品スコアの最大値を取ることで「ユーザーの興味を惹く商品が少なくとも1つ以上ある」という条件を満たすモジュールを優先的に表示することになります。しかし、必ずしもそうした商品がモジュールの先頭に表示されているわけではないので、商品の表示位置を考慮した重み付けなど改善の余地があると考えています。 システム構成 Two-Towerモデルの学習から予測結果のサービングまでを含めた全体のシステム構成は以下のようになっています。 パーソナライズシステムの構成図 モジュール並び順を予測するためにVertex AI Pipelinesを用いたバッチシステムが3つ存在します。以下ではそれぞれのシステムの役割を説明していきます。 1.Two-Towerモデル学習パイプライン このパイプラインは定期的にTwo-Towerモデルを学習し、学習済みモデルをVertex AI Model Registryに登録することを目的としています。先述した通りTwo-Towerモデルからはユーザーと商品それぞれをベクトル化するエンコーダーが得られるため、後続システムではこれらのエンコーダーを使ってベクトル化を行います。図中ではユーザーと商品それぞれのエンコーダーを User tower , Item tower と記述しています。 2.商品ベクトル化パイプライン このパイプラインは、モジュールに表示される商品のベクトル化を目的としています。モジュールに表示される商品の情報は商品検索用のAPIから取得し、Item towerを使ってこれらの商品情報をベクトル化します。ベクトル化された商品情報はBigQueryに保存され、後続システムであるモジュール並び順予測パイプラインで使用されます。 運用上の理由から、モジュールに表示される商品リストは1日に複数回更新されることがあります。そのためこのパイプラインは1日に数回実行し、モジュールに紐づく商品ベクトルが最新の状態となるようにしています。 3.モジュール並び順予測パイプライン モジュールの並び順を予測するためのパイプラインです。Vertex AI Model Registryに格納されている学習済みのUser towerを使用し、BigQueryから取得したユーザー情報をベクトル化します。またBigQueryにはモジュールに紐づく商品のベクトルも格納されているため、これらのベクトルを使ってモジュールのスコアを計算し、モジュールの並び順を決定しています。 予測結果はBigtableに保存され、リアルタイムにリクエストを処理する推薦APIが会員IDごとのモジュール並び順を取得する構成となっています。 A/Bテスト 概要 モジュール並び順のパーソナライズによる効果を評価するために、ZOZOTOWN会員を対象として約1か月のA/Bテストを実施しました。Control群とTreatment群を比較すると以下のような変化が生じます。 今回の施策ではすべてのモジュールがパーソナライズされるわけではなく、ビジネス的な要望から常に特定の位置に固定表示されるモジュールがある点に留意ください。例えば上図の「モジュールA(固定)」と記載されているモジュールはA/Bの割り振り関係なしで上から2番目に固定表示されています。 結果 A/Bテストの結果サマリを以下に示します。 指標 備考 結果(T/C比) ホーム画面訪問者の受注金額 ホーム画面にランディングしたユーザーの合計受注金額 100.1 (%) モジュール経由の受注金額 モジュールに表示されている商品の合計受注金額 100.1 (%) モジュール経由の受注点数 モジュールに表示されている商品の合計受注商品点数 100.4 (%) モジュール掲載商品のインプレッション数 モジュールに表示されている商品の合計インプレッション数 105.2 (%) モジュール掲載商品のクリック数 モジュールに表示されている商品の合計クリック数 103.2 (%) 受注系の指標はControlと比較して大きなアップリフトはありませんでしたが、 モジュールに掲載されている商品のインプレッションや閲覧に関する指標は大きく改善している ことがわかります。このことから、ユーザーはモジュールに興味を持ってホーム画面を回遊してより多くのモジュールを閲覧し、モジュールから商品ページに遷移する頻度が増えたと考えられます。 一方で受注系の指標に特段の変化がなかったことから、購入するほどの商品は提示できていなかったか、1か月という期間ではユーザーの購入体験を変えるほどの効果は計測できなかったと考えられます。 受注系の指標まで改善すれば理想的な結果でしたが、まずはプロジェクトの目的である「 モジュールのクリック数増大 」は達成できたと言える結果となりました。 A/Bテストを経て挙がった課題 上述した課題点も含め、A/Bテストを経て挙がった定性・定量的な課題と改善案について簡単に紹介します。 定性的な課題 「訴求軸は同じだが並んでいる商品が微妙に異なるモジュール」が連続して表示される 見たことのないカテゴリの商品が表示されにくい どちらの課題もモジュールのスコア算出で使用しているTwo-Towerモデルの最適化方針に原因があると考えられます。Two-Towerモデルは「次に閲覧する商品を予測する」という問題設定で学習しているため、ユーザーが直近で閲覧しそうな商品を提示することには成功しています。しかし、 その結果モジュールに掲載される商品が同じカテゴリやブランドに偏ってしまう という傾向が確認されました。 極端な例では以下のように全く同じ商品がファーストビューに並んでいるケースを確認しています。 この課題はアルゴリズムの設計時点で考慮事項として挙げられていたものの、日々の運用でカバーできるものと判断して対策をしていませんでした。 改善案として同じ商品が並んでいるモジュールを除外するルールベースのアプローチや、 MMR(Maximal Marginal Relevance) といったナイーブなリランキング方法の導入が考えられます。また今回はパーソナライズ方法としてモジュールのスコアを降順に並べただけのシンプルな方法を採用していますが、機械学習モデルでリランキングするなどの方法も考えられ、まだまだ改善の余地を残しています。 定量的な課題 受注系の指標がアップリフトしていない 特定カテゴリで各種指標が低下している 受注系の指標がアップリフトしていない原因として、前述の通りモジュールに表示される商品がユーザーにとって購入意欲を喚起するほどのものではなかったことが挙げられます。この課題の対処法としてパーソナライズロジック自体を改善するのはもちろんですが、 パーソナライズに使用されるモジュールの訴求軸を多様にする というアプローチが考えられます。 今までは全てのユーザーに同じモジュールを表示する都合上マス向けの訴求に偏る傾向がありましたが、これからは多種多様な訴求軸でユーザーに合ったモジュールを提示できます。この「多種多様なモジュール」を企画するのはビジネスサイドの企画チームであるため、モジュール並び順のパーソナライズを改善するには企画チームとの密な連携が欠かせません。 2つ目の課題はユーザーに閲覧される頻度が多くないカテゴリに関して閲覧や受注系の指標が悪化している、というものです。今まではモジュールの企画チームがその時訴求したい内容をモジュールで表現してユーザーに提示することで、カテゴリごとの閲覧や受注のばらつきが少ない状態でした。モジュール並び順がパーソナライズされた後はそうしたカテゴリごとのばらつきなどが意識されていないため、特定の期間で特定カテゴリの訴求を強める仕組みなどを検討しています。 最後に 本記事ではZOZOTOWNのホーム画面に表示するモジュールの並び順をパーソナライズするシステムとその効果について紹介しました。今回取り上げた部分以外にも改善すべき箇所が大量にあるので、これからも1つずつ改善していくことでユーザーにとってより良いZOZOTOWNを提供できるよう邁進していきます。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com
アバター
こんにちは、技術本部 SRE部 基幹プラットフォームSREチームの斉藤です。普段はZOZOの持っている倉庫システムやブランド様が触る管理ページなどのサービスのオンプレミスとクラウドの構築・運用に携わっています。またDBREとしてZOZOTOWNのデータベース全般の運用・保守も兼務しております。 7月11日、12日に行われた「 db tech showcase 2024 」に、DBREから5名のエンジニアが参加しました。この記事では会場の様子と印象に残ったセッションについてご紹介します! db tech showcaseとは 会場の様子 セッションレポート おわりに db tech showcaseとは 国内最大規模のデータとデータベース関連のカンファレンスです。このイベントでは、データベースの専門家やエンジニア、IT業界のリーダーたちが一堂に会し、新しい技術やソリューション、事例、ノウハウを共有します。 db tech showcaseの主な内容には以下のようなものがあります。 講演:データベース技術の最新トレンド、事例や実践的なアプローチについての講演が行われます。 展示:企業やベンダーが最新の製品やサービスを展示し、デモンストレーションします。 ネットワーキング:業界の専門家や同僚と交流する機会が提供され、情報共有やビジネスの機会が広がります。 www.youtube.com 会場の様子 入口 db tech showcase 2024はTKP市ヶ谷カンファレンスセンターで開催されました。受付を終えるとパスが発行され、イベントのパンフレットが配布されました。 EXPO データベースに関連する企業のブースが並んでいました。セッションの間で立ち寄ったOracle社のブースではHeatWaveの話題で盛り上がり、セッションさながらの事例や最新情報を熱弁頂きました。他にも、休憩スペースでセッションの感想で盛り上がってる人達がいたり、ミニセッションも開催されていたりしました。EXPOエリアではこうした交流が盛んに行われいて会場全体に活気がありました。 セッション 2日間で75以上のセッションが行われました。 db tech showcase 2024セッションスケジュール その他 企業の各ブースでは魅力的なグッズを配布していました。 セッションレポート DBREのメンバーが気になったセッションを紹介します。 MySQLアプリケーション開発を爆速にする最新手法 Amazon Bedrockで検証!データベース移行時のクエリ修正を簡素化するLLMの活用法 爆速成長中のFindyがぶつかった課題と解決に向けた実践手法 【出光興産様事例紹介】SAP DB運用の課題と今後の計画 SQL Server 技術支援のための動作の調査 - dbts2024 版 - MySQLアプリケーション開発を爆速にする最新手法 基幹プラットフォームSREチームの斉藤です。ZOZOのメインデータベースはSQL Serverを使用していますが、マイクロサービス化が進んでいるシステムではAurora MySQLを採用しています。MySQLの最新情報や運用に使用できるツールの事例などをキャッチアップすべく本セッションに参加させていただきました。 MySQLとVS Codeの統合 VS Codeで利用できるオープンソースの拡張機能として、MySQL Shell for VS Codeが提供されています。MySQL WorkbenchのEOLが迫っている中で後継となるもので、MySQLの運用には欠かせない機能になっていくのではないだろうかと思います。MySQL WorkbenchからはSQLエディタやスキーマの確認・データの検索結果の表示など主要な機能は全て継承されていると思われます。さらに「MySQL Shell for VS Code」はただのMySQL Workbenchの代わりではなく、VS Codeを使用してきた開発者とMySQL Workbenchを使用してきた管理者が共通して使用できるIDEとして設計されています。NotebookというエディタからはSQLだけではなくJavaScriptやTypeScriptを実行し結果をグラフで表示できます。これらの機能を使用してシステム開発が効率化されていると感じました。 MySQL Shell for VS Codeついては公式ドキュメント「 MySQL Shell GUI / MySQL Shell for VS Code 」を参照してください。 MySQLのJavaScriptサポート MySQL Enterprise Editionの機能でストアドプロシージャや関数をJavaScriptで作成し実行できます。そのストアドプロシージャや関数は実行するSQL上でも呼び出すことができ、処理の幅が広くなりました。 MySQLでJavaScriptを使用するできるメリットは次の通りです。 高い表現力 JavaScriptアプリケーション開発者向けの大規模なツールのエコシステム サードパーティライブラリの充実 人気のある開発言語の1つで、1300万人もの開発者が存在する MySQLのJavaScriptサポートついての情報はThe Oracle MySQL Japan Blogの「 MySQLのJavaScriptサポートについて 」で詳しく紹介されております。 MySQL REST Service MySQL REST ServiceはMySQL Routerの拡張機能として提供されています。RESTを使ってHTTP経由でMySQLのデータを参照、更新ができます。MySQL Shell for VS Code上から設定し運用します。 基本的なアーキテクチャ 画像引用元: The Oracle MySQL Japan Blog「 MySQL REST Serviceの紹介 」 MySQL REST Serviceついての情報はThe Oracle MySQL Japan Blogの「 MySQL REST Serviceの紹介 」で詳しく紹介されております。 感想 MySQLは「MySQL Shell for VS Code」やJavaScriptサポート、「MySQL REST Service」などの最新機能により、開発効率と運用性が大幅に向上し、現代のアプリケーション開発に重要なソリューションへと進化を続けていると感じました。 Amazon Bedrockで検証!データベース移行時のクエリ修正を簡素化するLLMの活用法 カート決済SREの遠藤です。 DBのバージョンアップや異なるDBへリプレースする際、クエリの互換性が課題になることがあります。この課題に対し、昨今注目されるLLM(大規模言語モデル)を活用してクエリの修正工数を減らせるかの検証デモが行われました。 現在、ZOZOTOWNで利用しているSQL Serverのサーバー分離とバージョンアップを担当しており、LLMを使ったクエリ修正に興味がありこのセッションに参加しました。 内容 異なるDBMS製品への移行や、バージョンアップに伴うクエリ修正の例が5つほど紹介されました。まず、何も工夫をせずLLMに対してクエリ修正を指示した場合、半分以上が間違った回答でした。こうしたハルシネーション(生成AIの嘘)を防止するために、よくある工夫をプロンプトに取り入れることで精度が向上しました。 クエリ互換性に関する内容であることの記述 専門家としての視点の強調 思考の連鎖を促す指示(step by step) 事例の提供(Few-Shotプロンプティング) また、RAG(検索拡張生成)としてAmazon Bedrockを使い、DBMS製品の公式リリースノートをナレッジベースとすることでさらに精度が向上しました。 Bedrockの場合、ナレッジベースをS3に格納するのですが、以下のような加工をすることでさらに精度が向上しました。 リリースノートからSQLの互換性に関係のない記述を削除 Markdown形式に変換 最終的に、5つのデモ全てにおいてクエリを正しく修正できていました。100%の精度は保証されないものの、LLMはSQL変換タスクにも有用であり、修正工数を大幅に減らすことができそうです。 感想 ChatGPTをはじめとしたLLMはすでに業務で活用していますが、ここまで具体的にDB移行業務に活用できる例を見ることができ、新たな発見がありました。LLMでは100%正しい回答が得られるとは限らないため、人力でのチェックは必須です。そこでRAGを利用することにより、ナレッジベースを元に回答の根拠も示してくれるため、ハルシネーションが発生しても比較的簡単に正当性を判断できそうです。 今回のセッションではOracleDBからPostgreSQLへの移行でしたが、弊社ではSQL ServerやMySQLを採用することが多いためそちらでも試してみたいと思いました。また、弊社ではテーブル設計ガイドラインを作成し、日常的にDBREがテーブル設計レビューを行っています。ガイドラインをナレッジベース化することで、テーブルのCREATE文を元に一次レビューを自動化できるかもしれません。 アプリケーション開発ではGitHub CopilotをはじめとしたLLMによる開発のサポートが既に充実しています。しかし、今後SQLの開発にも同様のことが行えるようになり、DBA/DBREの働き方も大きく変わってくると感じました。 爆速成長中のFindyがぶつかった課題と解決に向けた実践手法 基幹システム本部・物流開発部の黒岩です。 普段はZOZO社内で使用している基幹システムのリプレイスをする傍ら、DBREとしても活動しています。db tech showcaseは今回初めての参加でしたが、どの発表も内容が幅広く、興味深いセッションが多かったです。 私はファンディ株式会社 @gessy0129 げっしーさんの「爆速成長中のFindyがぶつかった課題と解決に向けた実践手法」というセッションについて紹介します。 DB面での課題と解決方法 サービスが急成長し、多くのユーザーに使われていく中で直面したDB面での課題とその解決方法が紹介されました。 .tal{text-align:left !important;} 性能問題 解決方法 DB負荷が高まり、CPU使用率が上昇 RDSからAuroraに移行してリソース利用を効率化 AuroraをARMベースのインスタンスに変更し、同じ性能をより低コストで実現 SQLのレスポンスが悪化 Performance Insighsを活用してクエリパフォーマンスを把握し、ボトルネック箇所を可視化 クエリの実行計画を分析して足りないインデックスを特定、インデックスを追加 インデックスが使われないクエリの形を修正(例:IN句の中に大量のデータが入っている、LIMITの数が適切でない場合など) DBエンジニアとアプリケーションエンジニアの協力 セッションで印象に残ったのは、スピーカーのげっしーさんが言った「性能試験は総合格闘技」という言葉でした。この言葉通り、DBエンジニアだけで解決するのではなく、アプリケーションエンジニアと密に連携して問題解決に取り組んだ例が紹介されていました。具体的には、以下2つの取り組みが印象的でした。 ReaderとWriterのインスタンスノードの振り分け AuroraのReaderインスタンスが有効に活用されていない課題に対して、DBエンジニアとアプリケーションエンジニアがペアプロしながら解決した事例です。DBエンジニアがReaderインスタンスの役割や性能向上の仕組みを説明し、アプリケーションエンジニアがその知識をもとにアプリケーションコードを改修して、書き込み操作時にはWriterインスタンス、読み込み操作時にはReaderインスタンスへ分けるようにしたそうです。これにより、読み込み処理時にReaderインスタンスが使われるようになり、効率的な負荷分散を実現したとのことでした。この取り組みでは、両者がそれぞれの専門知識を共有し、リアルタイムでコードの変更とその効果を確認しながら進めたことが成功の鍵となったそうです。 アプリケーションコード内のクエリ最適化 もう1つの課題として、より複雑な処理をクエリで一括処理するか、データを分割して取得した上で、アプリケーション側で処理するかという課題があったそうです。ここでも相互にペアプロなどで検証しながら性能を検証し、例えば「MySQLが苦手とする集計処理」について、クエリで一括処理していた部分を一部アプリケーション側で処理することで、DBの負荷を軽減した例が紹介されていました。 感想と今後の取り組み このセッションを通じて、DBエンジニアとアプリケーションエンジニアの協力がDBの負荷軽減において非常に重要であることを再認識しました。現在、弊社でも基幹DBの負荷軽減を図るためにクエリチューニングを進めていますが、この取り組みはまだ効果が限定的です。今回のセッションで学んだように、DBの性能課題に真摯に向き合い、基幹システムに関わるチーム全体で性能問題解決に取り組む姿勢を大切にし、今後もさらなる協力と改善を進めていきたいと思います。 【出光興産様事例紹介】SAP DB運用の課題と今後の計画 基幹システム本部・データ連携ブロックの馬場です。現在ZOZOTOWNの基幹システムで使用しているデータベースの性能改善を課題としています。 社内システム統合時に発生したデータベースのスローダウンに対し、どのようなツールを利用し課題対応しているか、という事例紹介のセッションに参加しました。 MaxGauge スローダウンが発生しているボトルネッククエリを調査すべく、データベース監視ツールである「MaxGauge」が紹介されました。「MaxGauge」はデータベースサーバにエージェントを導入することで、リアルタイムにSQLのトラッキングを行い、蓄積されたトラッキングデータにより、パフォーマンス低下対象の事後分析が可能です。 弊社ではDPMツールとして「Datadog」を導入していますが、情報収集に掛かるデータベース側への負荷や、クエリの取得間隔等を比較してみたいです。 MAJESTY 次にデータベース開発支援ツールの「MAJESTY」の紹介です。パフォーマンスが低下しているクエリに対し、最適なインデックス設計をアドバイスしてくれることで効率的に性能分析・改善が可能です。最大の特徴は「アクセスパターン分析」を実装しており、テーブルへのアクセスをパターン化し、最適なアクセスパスを分析することです。これにより、SQL単体を分析する手法よりも大幅にコストを抑えた性能改善が実現可能です。 感想 今回の事例ではSQL Serverを利用し、基幹システムに性能問題を抱えている境遇が弊社と似ているため参考になるセッションでした。 チューニングを行うにはデータベースに対し一定の習熟度が必要になりますが、社内のエンジニア全員が同じ習熟度を保つことは難しいです。本セッションで紹介されたツールを導入することにより、誰もが同じ視点で性能改善を実施し、日々の性能改善コストが大幅に削減できる環境が作れると良いと思います。 SQL Server 技術支援のための動作の調査 - dbts2024 版 - 技術本部SRE部カート決済SREブロックの伊藤です。 こちらのセッションはMicrosoft MVPの小澤さんが、SQL Serverの挙動について、実演を交えて調査方法を紹介してくれるセッションとなっていました。例えば、クエリストアや拡張イベントを有効化する場合にクエリのスループットにどの程度影響が出るかをどのように調べるか、といった内容です。 内容が濃密なこともあり、事例全てを紹介していただくことは時間の都合上できませんでしたが、会場を巻き込んで挙手制でアンケートを取りながら紹介事例を選ぶ姿は、オフラインイベント感を感じる一幕でした。 事例の中で私が最も印象に残ったのは「オンラインでのインデックス再構築による同時実行性の低下の有無」です。 オンライン再構築による同時実行性の低下の有無 SQL Serverではオンラインでのインデックス操作に対応しています。 ONLINE オプションは、このようなインデックス操作中に基になるテーブルまたはクラスター化インデックス データ、ならびに関連付けられた任意の非クラスター化インデックスへ同時ユーザー アクセスを可能にします。 たとえば、あるユーザーがクラスター化インデックスを再構築している最中に、そのユーザーと他のユーザーが基になるデータの更新やクエリを続行できます。 引用元: オンラインでのインデックス操作の実行 この時に、ロックは一切かからないのかを解説交えながら検証の実演が行われました。具体的には以下のような手順です。 長時間かかるようなSELECT文を実行する SSMSから実行するときには結果を出力しないようにクエリオプションで破棄する設定にしておく この時 sys.dm_tran_locks などでロックの状況を確認すると共有ロックがかかっている状態が見られる バックグラウンドでSELECTを大量に実行しておく Ostress を使うことで並列に大量のクエリを流し続けておくことができる REBUILD INDEX WITH(ONLINE=ON)を実行する 最終段階でSch-Mのロックが必要になるため1の処理と競合してロックの解放待ち状態となる この時に2で実行していたクエリをブロックする可能性がある 発生の有無はロックパーティションの状態次第 CPUのコア数で挙動が変わる部分でもある ロックタイムアウトの活用でブロッキングの連鎖は防ぐことができる 後日実際に自分の環境でも試してみた 上記の内容を改めて実際に試してみました。これは過去にオンラインでインデックス作成が行われた際にブロッキングの発生を観測したことがあり、ユーザーに影響するものなので対策含めて運用ドキュメントに取り入れていきたいという思いがあったからです。 実際に流したクエリは以下の通りです。 -- 長時間かかるSELECT文 SELECT a.SampleColumn FROM _testTable AS a CROSS JOIN _testTable AS b -- Ostressから実行したバックグラウンドで動かすSELECT文 SELECT TOP( 1 ) * FROM [dbo].[_TestTable] tablesample( 1 percent) WITH (NOLOCK) -- インデックスのリビルド ALTER INDEX [IX__TestTable_SampleIndex] ON [dbo].[_TestTable] REBUILD WITH ( ONLINE = ON ) GO 上記のクエリをそれぞれ流したところ、インデックスリビルドの最終段階でSch-Mのロック要求が発生していることを確認できました。 sys.dm_tran_locksから確認したロック情報 Datadogが入っている検証環境ということもあり、Datadogからも確認したところ、ブロッキングが連鎖してOstressで流していたクエリも巻き込まれていることが確認できました。 Datadog Database MonitoringのBlocking Queriesからのキャプチャ これに対し、インデックスリビルド時に SET LOCK_TIMEOUT を付けて同様に再現したところロック要求のタイムアウトの発生を確認できました。インデックスのリビルドは失敗するため、改めて実施する必要はありますが、これでユーザー影響を抑えることができます。 -- 1秒ロックが取れなかったらインデックスのリビルドを中断する SET LOCK_TIMEOUT 1000 ; ALTER INDEX [IX__TestTable_SampleIndex] ON [dbo].[_TestTable] REBUILD WITH ( ONLINE = ON ) GO エラーメッセージが出力されて処理が中断 実演を見た上で自分でも試してみて、インデックス操作時の挙動及び調査方法への理解を深められました。インデックス操作時の SET LOCK_TIMEOUT 指定の必須化などは弊社内での運用にも取り入れていこうと思います。 おわりに 今回のセッションを通じて多くの知見を得ることができました。セッション以外にも、企業ブースや他社のエンジニアとの交流を通じて、多くの学びと刺激を受けました。実際に現地に足を運ぶことで得られるものは非常に大きかったです。 db tech showcase 2024で学んだことをZOZOのデータベースシステムの向上に活かしていこうと思います。 ZOZOでは、一緒にサービスを作り上げてくれる仲間を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください! corp.zozo.com
アバター
はじめに こんにちは。WEARバックエンド部SREブロックの 春日 です。普段は WEAR というサービスのSREとして開発・運用に携わっています。本記事では、約60%のコスト削減に成功した NATゲートウェイ の通信内容の調査方法と通信量の削減方法についてご紹介します。 目次 はじめに 目次 背景 コストの把握 NATゲートウェイの通信内容の把握 CloudWatchメトリクスでの確認 VPCフローログでの確認 リゾルバーでのクエリログでの確認 調査結果をもとにNATゲートウェイ経由での通信量を削減する AWSサービスとの通信 Datadogとの通信 WEARのAPIとの通信 ECRパブリックリポジトリとの通信 結果 まとめ 背景 ZOZOではより効果的な成長を目指してコストの最適化を進めています。コストの増大はサービスの拡大を鈍化させる原因となるため、常に最適な状態に保つことが必要です。WEARでも不要なコストを可能な限り削減し適正化すべく、コスト把握と対応を続けています。 コストの把握 まずはコストを把握します。AWSのコストは AWS Cost Explorer(以下、Cost Explorer) で確認できます。WEARでは『Elastic Container Service for Kubernetes』『S3』『CloudFront』に次いで『EC2その他』に料金がかかっていました。『EC2その他』の料金がそこまで高いことは想定外だったため、『EC2その他』の内訳を確認します。フィルターのサービスを『EC2 - Other』、グループ化の条件のディメンションを『APIオペレーション』とすることで、API単位で料金を確認できます。結果は以下のグラフの通りです 1 。 内訳を確認すると、コストの3分の2ほどがNATゲートウェイのコストでした。さらに詳細に内容を確認します。先ほどまでのレポートパラメータを解除し、フィルターの『APIオペレーション』を『NatGateway』、グループ化の条件のディメンションを『使用タイプ』にします。 NATゲートウェイの料金に関するドキュメント を確認すると、『NATゲートウェイあたりの料金(USD/時)』と『処理データ1GBあたりの料金 (USD)』で構成されています。グラフを見ると、NATゲートウェイの起動時間による料金よりも、NATゲートウェイのデータ処理に関する料金が圧倒的に高いことがわかります。つまり、NATゲートウェイを経由して大量の通信が行われているということが読み取れます。 次章では、NATゲートウェイを経由する通信を詳しく調査します。 NATゲートウェイの通信内容の把握 CloudWatchメトリクスでの確認 まずは Amazon CloudWatch(以下、CloudWatch) でNATゲートウェイの CloudWatchメトリクス を確認します。メトリクスの詳細は 公式ドキュメント に記載があります。 WEARでは、 BytesOutToDestination が BytesInFromDestination の2倍ほどの量でした。これは、NATゲートウェイを経由した外向きの通信量が、NATゲートウェイを経由した内向きの通信量の2倍あることを示します。 VPCフローログでの確認 次に、 VPCフローログ を用いて、より詳細な通信内容を確認します。VPCフローログにはVPC内部のネットワークインターフェイス間の通信内容が記録されています。 Amazon S3(以下、S3) バケットに出力されたVPCフローログを Amazon Athena(以下、Athena) でクエリして通信内容を確認します 2 。 VPCフローログのAthenaテーブルは以下の内容で作成したことを前提に説明します。 VPCフローログテーブル作成クエリ CREATE EXTERNAL TABLE `vpc_flow_logs_table`( `version` int COMMENT '' , `account_id` string COMMENT '' , `interface_id` string COMMENT '' , `srcaddr` string COMMENT '' , `dstaddr` string COMMENT '' , `srcport` int COMMENT '' , `dstport` int COMMENT '' , `protocol` bigint COMMENT '' , `packets` bigint COMMENT '' , `bytes` bigint COMMENT '' , ` start ` bigint COMMENT '' , ` end ` bigint COMMENT '' , `action` string COMMENT '' , `log_status` string COMMENT '' , `vpc_id` string COMMENT '' , `subnet_id` string COMMENT '' , `instance_id` string COMMENT '' , `tcp_flags` int COMMENT '' , ` type ` string COMMENT '' , `pkt_srcaddr` string COMMENT '' , `pkt_dstaddr` string COMMENT '' , `region` string COMMENT '' , `az_id` string COMMENT '' , `sublocation_type` string COMMENT '' , `sublocation_id` string COMMENT '' , `pkt_src_aws_service` string COMMENT '' , `pkt_dst_aws_service` string COMMENT '' , `flow_direction` string COMMENT '' , `traffic_path` int COMMENT '' ) PARTITIONED BY ( `logdate` string COMMENT '' ) ROW FORMAT DELIMITED FIELDS TERMINATED BY ' ' STORED AS INPUTFORMAT ' org.apache.hadoop.mapred.TextInputFormat ' OUTPUTFORMAT ' org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat ' LOCATION ' $LOCATION_OF_LOGS ' TBLPROPERTIES ( ' projection.enabled ' = ' true ' , ' projection.logdate.format ' = ' yyyy/MM/dd/HH ' , ' projection.logdate.interval ' = ' 1 ' , ' projection.logdate.interval.unit ' = ' HOURS ' , ' projection.logdate.range ' = ' 2022/01/01/00,NOW ' , ' projection.logdate.type ' = ' date ' , ' skip.header.line.count ' = ' 1 ' , ' storage.location.template ' = ' $LOCATION_OF_LOGS/${logdate} ' , ' typeOfData ' = ' file ' ) $LOCATION_OF_LOGS はVPCフローログの出力先S3パスに読み替えてください。 WEARではAthenaテーブルの作成を Terraform の aws_glue_catalog_table で行っています。上記のクエリは aws_glue_catalog_table から作成されたテーブルを SHOW CREATE TABLE で出力したクエリのため、公式ドキュメントとは表現が一部異なっています。 次のようなクエリ 3 でVPC内部からNATゲートウェイを経由した外向きの通信を確認します。SQL内にコメントしてある箇所は適宜自分の環境に読み替えてください。 SELECT pkt_dst_aws_service, SUM (bytes) AS bytesTransferred FROM " vpc_flow_logs_database " . " vpc_flow_logs_table " -- VPCフローログテーブルが存在するデータベース.VPCフローログテーブル WHERE srcaddr LIKE ' x.y.% ' -- VPC CIDRのネットワーク部分(例:VPC CIDRが`172.168.0.0/16`の時、`172.168.`) AND dstaddr in ( ' x.y.a.b ' , ' x.y.c.d ' , ' x.y.e.f ' ) -- NatGateway IP AND action = ' ACCEPT ' AND logdate BETWEEN ' YYYY/MM/dd/00 ' AND ' YYYY/MM/dd/23 ' -- 調査対象の日付(UTC) GROUP BY pkt_dst_aws_service ORDER BY bytesTransferred DESC このクエリでは、特定の日付でVPC内部からNATゲートウェイ経由で送信したトラフィック量をAWSサービス 4 ごとに確認しています。この例では1日で調査していますが、Athenaはスキャンしたデータ量に料金が比例します 5 。サービスの規模によってはまず時間単位でクエリし、スキャン量が許容できることを確認してください。 結果の例を以下の表に記載します。 pkt_dst_aws_service が - である箇所はAWSが管理していないIPに向けた通信です。表の数値はWEARの実際の値ではなくダミーデータです。今後出てくるAthenaのクエリ結果はすべて実際の値ではなく、ダミーデータを記載します。 pkt_dst_aws_service bytesTransferred AMAZON 106000 EC2 4500 - 2000 DYNAMODB 1000 CLOUDFRONT 3 GLOBALACCELERATOR 1 続いて、NATゲートウェイを経由してVPC内部で受信するトラフィックも確認します。 SELECT pkt_src_aws_service, SUM (bytes) AS bytesTransferred FROM " vpc_flow_logs_database " . " vpc_flow_logs_table " -- VPCフローログテーブルが存在するデータベース.VPCフローログテーブル WHERE srcaddr NOT LIKE ' x.y.% ' -- VPC CIDRのネットワーク部分(例:VPC CIDRが`172.168.0.0/16`の時、`172.168.`) AND dstaddr in ( ' x.y.a.b ' , ' x.y.c.d ' , ' x.y.e.f ' ) -- NatGateway IP AND action = ' ACCEPT ' AND logdate BETWEEN ' YYYY/MM/dd/00 ' AND ' YYYY/MM/dd/23 ' -- 調査対象の日付(UTC) GROUP BY pkt_src_aws_service ORDER BY bytesTransferred DESC pkt_src_aws_service bytesTransferred EC2 21000 AMAZON 6000 - 5000 CLOUDFRONT 1500 DYNAMODB 1000 GLOBALACCELERATOR 1 この時点で Amazon DynamoDB(以下、DynamoDB) の ゲートウェイエンドポイント が不足しており、NATゲートウェイを経由して通信してしまっていることが読み取れます。 Datadog のように、 IPアドレス範囲を公開 しているサービスを利用している場合、VPCフローログの pkt_dstaddr (送信先IPアドレス)だけで把握も可能です 6 。しかし、VPCフローログのみでは AMAZON の内訳など、具体的にどのエンドポイントに向けて通信しているかがわかりません。これらを把握するため、VPC内部の名前解決の際のクエリログを活用してより詳細に調査します。 リゾルバーでのクエリログでの確認 リゾルバーでのクエリのログ記録 を設定すると、VPC内部で行われた名前解決のクエリログをS3に保存できます。VPCフローログで確認できる送信先のIPアドレスとクエリログの名前解決の結果を突き合わせることで、NATゲートウェイを経由して行われた通信先のドメインが把握できます。 ドキュメント に従ってクエリログのAthenaテーブルを作成します。 リゾルバーのクエリログのAthenaテーブルは以下の内容で作成したことを前提に説明します。 リゾルバーのクエリログテーブル作成クエリ CREATE EXTERNAL TABLE `vpc_dns_query_logs_table`( `version` string COMMENT '' , `account_id` string COMMENT '' , `region` string COMMENT '' , `vpc_id` string COMMENT '' , `query_timestamp` string COMMENT '' , `query_name` string COMMENT '' , `query_type` string COMMENT '' , `query_class` string COMMENT '' , `rcode` string COMMENT '' , `answers` array<struct<rdata:string, type :string,class:string>> COMMENT '' , `srcaddr` string COMMENT '' , `srcport` int COMMENT '' , `transport` string COMMENT '' , `srcids` struct<instance:string,resolver_endpoint:string> COMMENT '' , `firewall_rule_action` string COMMENT '' , `firewall_rule_group_id` string COMMENT '' , `firewall_domain_list_id` string COMMENT '' ) PARTITIONED BY ( `logdate` string COMMENT '' ) ROW FORMAT SERDE ' org.openx.data.jsonserde.JsonSerDe ' STORED AS INPUTFORMAT ' org.apache.hadoop.mapred.TextInputFormat ' OUTPUTFORMAT ' org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat ' LOCATION ' $LOCATION_OF_LOGS ' TBLPROPERTIES ( ' projection.enabled ' = ' true ' , ' projection.logdate.format ' = ' yyyy/MM/dd ' , ' projection.logdate.interval ' = ' 1 ' , ' projection.logdate.interval.unit ' = ' DAYS ' , ' projection.logdate.range ' = ' 2022/01/01,NOW ' , ' projection.logdate.type ' = ' date ' , ' storage.location.template ' = ' $LOCATION_OF_LOGS/$VPC_ID/${logdate} ' , ' typeOfData ' = ' file ' ) $LOCATION_OF_LOGS はリゾルバのクエリログの出力先S3パス、 $VPC_ID はログを記録しているVPCのIDに読み替えてください。 VPCフローログテーブルと同様にAthenaテーブルの作成をTerraformで行っており、上記は SHOW CREATE TABLE で出力したクエリです。そのため、公式ドキュメントとは表現が一部異なっています。 次のようなクエリでVPCフローログとリゾルバーのクエリログを突き合わせます。 SELECT R.query_name, SUM (F.bytesTransferred) AS bytes_day, ROUND ( SUM (F.bytesTransferred) * 30.0 / ( 1000 * 1000 * 1000 ), 2 ) AS GB_months FROM ( SELECT pkt_dstaddr, SUM (bytes) AS bytesTransferred FROM " vpc_flow_logs_database " . " vpc_flow_logs_table " -- VPCフローログテーブルが存在するデータベース.VPCフローログテーブル WHERE srcaddr LIKE ' x.y.% ' -- VPC CIDRのネットワーク部分(例:VPC CIDRが`172.168.0.0/16`の時、`172.168.`) AND dstaddr in ( ' x.y.a.b ' , ' x.y.c.d ' , ' x.y.e.f ' ) -- NatGateway IP AND action = ' ACCEPT ' AND logdate BETWEEN ' YYYY/MM/dd/00 ' AND ' YYYY/MM/dd/23 ' -- 調査対象の日付(UTC) AND pkt_dst_aws_service = ' AMAZON ' GROUP BY pkt_dstaddr ) F LEFT JOIN ( SELECT DISTINCT query_name, answer.rdata FROM " vpc_dns_query_logs_database " . " vpc_dns_query_logs_table " -- リゾルバーのクエリログテーブルが存在するデータベース.リゾルバーのクエリログテーブル CROSS JOIN UNNEST(answers) as st(answer) WHERE answer. type = ' A ' AND logdate = ' YYYY/MM/dd ' -- 調査対象の日付(UTC) ) R ON F.pkt_dstaddr = R.rdata GROUP BY R.query_name ORDER BY bytes_day DESC このクエリでは、大きく分けて3つのことを行っています。 VPCフローログテーブルからNATゲートウェイを経由するトラフィックの送信先IPアドレスを取得(クエリの F 部分) リゾルバーのクエリログテーブルからドメインとIPアドレスの対応表を作成(クエリの R 部分) 1と2をIPアドレスで結合し、送信先ドメインごとのトラフィック量を取得 クエリの結果を確認すると、 firehose.ap-northeast-1.amazonaws.com. と sqs.ap-northeast-1.amazonaws.com. に対するトラフィック量が多いことを確認できました。WEARでは、アプリケーションのログを aws-for-fluent-bit を用いて Amazon Data Firehose(以下、Firehose) に送信しています。このサービスに対する送信がほとんどであり、 インターフェイスVPCエンドポイント が不足していることに気付けました。 Amazon Simple Queue Service(以下、SQS) も同様に、VPCエンドポイントが不足していることが判明しました。 他の箇所についても同様に調査すべく、 pkt_dst_aws_service = 'EC2' などに変更しながらトラフィック量を確認していきます 7 。 その結果、以下のドメインに対しての通信量が多いことを確認できました。 d5l0dvt14r5h8.cloudfront.net. に見覚えはありませんでしたが、AWSサポートに確認したところ、 Amazon ECR パブリックリポジトリ(以下、ECRパブリックリポジトリ) であることが判明しました(2024年7月現在)。 *.datadoghq.com WEARのAPI d5l0dvt14r5h8.cloudfront.net. これで通信内容が判明しました。NATゲートウェイ経由で大量に通信しており、削減効果が見込めそうな宛先は以下の通りです。 AWSサービス Datadog WEARのAPI ECRパブリックリポジトリ 次章からはこの結果を元に、可能な限りNATゲートウェイを経由せずに済むように対応します。 調査結果をもとにNATゲートウェイ経由での通信量を削減する AWSサービスとの通信 AWSサービスをNATゲートウェイ経由で通信させないためには、VPCエンドポイントが必要です。ただし、通信するすべてのAWSサービスに対してVPCエンドポイントを用意すればいいとは限りません。VPCエンドポイントにも起動時間による料金と、データ処理による料金が発生するためです。 インターフェイスエンドポイントの料金 を確認し、 NATゲートウェイで通信する場合の料金 との損益分岐点を確認します。 NATゲートウェイはすでに存在し、外部への通信に利用しています。NATゲートウェイは削除できないため、NATゲートウェイの起動時間に関するコストは考慮しないことにします。また、インターフェイスエンドポイントはAZごとに起動時間の料金がかかります。WEARでは3AZを利用しているため、3つとして計算します。 ap-northeast-1 リージョンの料金は以下のようになっています(2024年7月現在)。 NATゲートウェイの料金表 NAT ゲートウェイあたりの料金 (USD/時) 処理データ 1 GB あたりの料金 (USD) USD 0.062 USD 0.062 インターフェイスエンドポイントの料金表 各 AZ の VPC エンドポイント 1 つあたりの料金 (USD/時間) USD 0.014   AWS リージョンで 1 か月に処理されるデータ 処理データ 1 GB あたりの料金 (USD) 最初の 1 PB 0.01 USD 次の 4 PB 0.006 USD 5 PB以上のもの 0.004 USD 1か月あたりの通信量(GB)を とし、以下の式を満たす を計算します。VPCエンドポイントの起動時間は 24時間*30日*3AZ で計算しています。また、調査時に概算した結果、1ヶ月に1PB以上は使っていないため、最初の1PBの料金で計算します。 すると となるため、月に581GB以上通信するのであればNATゲートウェイ経由よりもVPCエンドポイント経由の方が安いということが導けます。 そのため、WEARではFirehoseとSQSのインターフェイスエンドポイントを追加で作成することにしました。ゲートウェイタイプのVPCエンドポイントの場合は追加料金なしで利用できる 8 ため、DynamoDBのVPCエンドポイントも作成します。 Datadogとの通信 Datadogは AWS PrivateLink(以下、PrivateLink) を経由して通信する方法を提供しています 9 。ただし、調査の過程でWEARではこの方法は断念しました。 Datadogには サイト という概念があります。各サイトは完全に独立しており、サイト間でデータの共有はできません。WEARでは Amazon Elastic Kubernetes Service(以下、EKS) のリージョンと使用しているDatadogサイトの場所が異なっていました。その場合、『VPCピアリングを使用した他のリージョンからの接続』ですが、リージョン間の通信は、2024年7月時点で 1GBあたり$0.09 かかってしまいます。そのため、WEARではDatadogへの通信に関してはNATゲートウェイを経由することを許容しました。ご利用中のDatadogサイトのPrivateLinkとデータ送信元が同一リージョンにある場合はPrivateLinkを用いる方法を検討してみてください。 WEARのAPIとの通信 調査結果から、WEARのWebアプリケーションからAPIへの通信がNATゲートウェイを経由して行われていることがわかりました 10 。通信経路の概略は以下の図の通りです。AWS間の通信のためインターネットには出ていませんが、NATゲートウェイを経由して通信してしまっています。 これらは同一VPCに存在しているため、VPC内部のみで通信を完結させたいところです。WEARのEKS内のPodは Application Load Balancer(以下、ALB) の配下に存在します。調査時点ではインターネット向けのALBのみ存在しましたが内部向けのALBも作成し、VPC内部からの通信は内部向けのALBに対して行うようにします。 WEARでは、ALBとALBのエイリアスレコードの作成を AWS Load Balancer Controller と ExternalDNS を用いてIngressに専用のアノテーションを付与することで行っています。 既存のIngressを踏襲し、新たに内部向けALB用のIngressを作成します。 alb.ingress.kubernetes.io/scheme アノテーションのデフォルト値は internal ですが、後述する理由により明示的に internal を設定しておきます。 以下に内部向けALB作成のサンプルIngressを記載します。また、内部向けALBを作成するプライベートサブネットに自動検出用のタグ 11 が付与されていることを確認してください。 apiVersion : networking.k8s.io/v1 kind : Ingress metadata : name : api-internal namespace : api annotations : kubernetes.io/ingress.class : alb alb.ingress.kubernetes.io/scheme : internal external-dns.alpha.kubernetes.io/hostname : api.wear.jp # インターネット向けIngressのものと同じ # (以下略) spec : rules : - http : paths : - path : / pathType : Prefix backend : service : name : api port : number : 80 次に、作成する内部向けALBのエイリアスレコードを登録するためのプライベートホストゾーンと、プライベートホストゾーン用のExternalDNSを新たに作成します。プライベートホストゾーン名はエイリアスレコードのレコード名と一致させます。 ここで1つ注意しなければならないことがあります。 すでに通信が行われているドメインに対して新たにプライベートホストゾーンを作成する場合は、エイリアスレコードを作成するまではEKSのVPCにプライベートホストゾーンを関連付けてはいけない ということです。 プライベートホストゾーンを作成した時点で、関連付けされているVPC内部の通信はそのプライベートホストゾーンで名前解決を試みます。しかし、プライベートホストゾーンの作成とエイリアスレコードの作成は同時にできないため、名前解決に失敗してしまいます。プライベートホストゾーンは作成時に必ず1つ以上のVPCを関連付けなければならないため、使用していないVPCのみを一時的に関連付けておきます。 以下に Terraform を用いたサンプルコードを記載します。ここでは、使用していないデフォルトのVPCを一時的にプライベートホストゾーンに関連付けしています。 resource " aws_route53_zone " " private_api " { name = " api.wear.jp " vpc { # 一時的にdefault VPCを指定。 vpc_id = data.aws_vpc.default.id vpc_region = " ap-northeast-1 " } force_destroy = false } # 一時的にdefault VPCを指定するためのデータソース data " aws_vpc " " default " { default = true } 同じドメインでパブリックホストゾーンとプライベートホストゾーンを出し分けるためにプライベートホストゾーン用のExternalDNSを新たに作成します 12 。以下のオプションで起動します。 --aws-zone-type=private --annotation-filter=alb.ingress.kubernetes.io/scheme=internal --domain-filter=${プライベートホストゾーン名} 内部向けALBのためのアノテーションがついているリソースのみを対象に設定しています。これが、デフォルト値にもかかわらず明示的にIngressにアノテーションを設定する理由です。 元々起動していたパブリックホストゾーン用のExternalDNSには --annotation-filter=alb.ingress.kubernetes.io/scheme=internet-facing を用いて再起動し、インターネット向けALB用のリソースのみを対象にします。 ExternalDNSの準備ができたら内部向け用IngressをEKS内に作成し、内部向けALBとALBのエイリアスレコードが作成されていることを確認します。一時的に関連付けておいたVPC内部からdigコマンド等で名前解決し、プライベートアドレスに解決されることも確認しておきます。確認後、プライベートホストゾーンをEKSのVPCに関連付けし、一時的なVPCの関連付けは解除します。 以下にサンプルコードを記載します。ここでは、プライベートホストゾーンに関連付けされているVPCを、使用していないVPCからvariablesに設定されたEKSのVPCに変更しています。 variable " vpc_id " { type = string description = " EKSのVPC ID " } resource " aws_route53_zone " " private_api " { name = " api.wear.jp " vpc { vpc_id = var.vpc_id vpc_region = " ap-northeast-1 " } force_destroy = false } 最終的な通信経路の概略は以下の図の通りです。これで、WEARのWebアプリケーションからAPIへの通信がVPC内部で完結するように設定できました。 ECRパブリックリポジトリとの通信 WEARでは、aws-for-fluent-bitを初めとする、複数のコンテナイメージでECRパブリックリポジトリのものを多用しています。 ドキュメント には以下のような記載があります。 現在、VPC エンドポイントは Amazon ECR パブリックリポジトリをサポートしていません。プルスルーキャッシュルールを使用して、VPC エンドポイントと同じリージョンにあるプライベートリポジトリでパブリックイメージをホストすることを検討してください。 上記の案内通り、プルスルーキャッシュルールを使用することにします。プルスルーキャッシュルールを使用すると、DockerHubやECRパブリックなどにあるリポジトリを自分のAWSアカウントのプライベートリポジトリにキャッシュしておくことができます。あらかじめ手動でイメージをプッシュしておく必要はなく、自分のリポジトリからプルしようとした際にイメージが存在しなければ、自動的に設定先のリポジトリからプルしてイメージを格納しておいてくれます。 詳細は ドキュメント をご参照ください。また、WEARでは Amazon Elastic Container Registry(以下、ECR) 用のVPCエンドポイントはすでに作成してあったため、新たに作成する必要はありませんでした。 以下にTerraformを用いたサンプルコードを記載します。 resource " aws_ecr_pull_through_cache_rule " " ecr_public " { ecr_repository_prefix = " ecr-public " upstream_registry_url = " public.ecr.aws " } 設定完了後、マニフェストのイメージを以下のように書き換えます。 $ACCOUNT_ID はプライベートリポジトリが存在するAWSアカウントのIDです。また、WEARでは元々DatadogのコンテナイメージをHelmのデフォルト値である gcr.io/datadoghq からプルしていましたが、このタイミングでECRに切り替えました 13 。 - image: public.ecr.aws/aws-observability/aws-for-fluent-bit + image: $ACCOUNT_ID.dkr.ecr.ap-northeast-1.amazonaws.com/ecr-public/aws-observability/aws-for-fluent-bit これで、初回プル時にはNATゲートウェイを経由してイメージがプルされますが、その後はVPCエンドポイント経由でプライベートリポジトリからプルされるようになりました。 結果 対応完了後、NATゲートウェイを経由する通信量がどのくらい減ったのかを確認します。まずは、以下のクエリでVPC内部からNATゲートウェイを経由した外向きの通信に対して対応前後の削減量を確認します 14 。 SELECT B.pkt_dst_aws_service AS pkt_dst_aws_service, ROUND ( CAST (B.bytes_day- COALESCE (A.bytes_day, 0 ) AS double)/B.bytes_day* 100 , 2 ) AS Reduction_percentage FROM ( SELECT pkt_dst_aws_service, SUM (bytes) AS bytes_day FROM " vpc_flow_logs_database " . " vpc_flow_logs_table " -- VPCフローログテーブルが存在するデータベース.VPCフローログテーブル WHERE srcaddr LIKE ' x.y.% ' -- VPC CIDRのネットワーク部分(例:VPC CIDRが`172.168.0.0/16`の時、`172.168.`) AND dstaddr in ( ' x.y.a.b ' , ' x.y.c.d ' , ' x.y.e.f ' ) -- NatGateway IP AND action = ' ACCEPT ' AND logdate BETWEEN ' YYYY/MM/dd/00 ' AND ' YYYY/MM/dd/23 ' -- 対応前の日付(UTC) GROUP BY pkt_dst_aws_service ) B LEFT JOIN ( SELECT pkt_dst_aws_service, SUM (bytes) AS bytes_day FROM " vpc_flow_logs_database " . " vpc_flow_logs_table " -- VPCフローログテーブルが存在するデータベース.VPCフローログテーブル WHERE srcaddr LIKE ' x.y.% ' -- VPC CIDRのネットワーク部分(例:VPC CIDRが`172.168.0.0/16`の時、`172.168.`) AND dstaddr in ( ' x.y.a.b ' , ' x.y.c.d ' , ' x.y.e.f ' ) -- NatGateway IP AND action = ' ACCEPT ' AND logdate BETWEEN ' YYYY/MM/dd/00 ' AND ' YYYY/MM/dd/23 ' -- 対応後の日付(UTC) GROUP BY pkt_dst_aws_service ) A ON B.pkt_dst_aws_service = A.pkt_dst_aws_service ORDER BY B.bytes_day DESC pkt_dst_aws_service Reduction_percentage AMAZON 99.91 EC2 -2.82 - -2.14 DYNAMODB 100.0 CLOUDFRONT 93.75 GLOBALACCELERATOR 100.0 結果を確認すると、 AMAZON への通信量が99.91%、 CLOUDFRONT が93.75%、 DYNAMODB への通信量が100.0%削減できていることがわかりました。VPCエンドポイントがうまく作用しているようです。増えている箇所もありますが、通信量は日によって誤差があるため対応によるものではありません。NATゲートウェイを経由してVPC内部に受信する通信に関しても確認します。 NATゲートウェイを経由する内向き通信の削減量確認クエリ SELECT B.pkt_src_aws_service AS pkt_src_aws_service, ROUND ( CAST (B.bytes_day- COALESCE (A.bytes_day, 0 ) AS double)/B.bytes_day* 100 , 2 ) AS Reduction_percentage FROM ( SELECT pkt_src_aws_service, SUM (bytes) AS bytes_day FROM " vpc_flow_logs_database " . " vpc_flow_logs_table " -- VPCフローログテーブルが存在するデータベース.VPCフローログテーブル WHERE srcaddr NOT LIKE ' x.y.% ' -- VPC CIDRのネットワーク部分(例:VPC CIDRが`172.168.0.0/16`の時、`172.168.`) AND dstaddr in ( ' x.y.a.b ' , ' x.y.c.d ' , ' x.y.e.f ' ) -- NatGateway IP AND action = ' ACCEPT ' AND logdate BETWEEN ' YYYY/MM/dd/00 ' AND ' YYYY/MM/dd/23 ' -- 対応前の日付(UTC) GROUP BY pkt_src_aws_service ) B LEFT JOIN ( SELECT pkt_src_aws_service, SUM (bytes) AS bytes_day FROM " vpc_flow_logs_database " . " vpc_flow_logs_table " -- VPCフローログテーブルが存在するデータベース.VPCフローログテーブル WHERE srcaddr NOT LIKE ' x.y.% ' -- VPC CIDRのネットワーク部分(例:VPC CIDRが`172.168.0.0/16`の時、`172.168.`) AND dstaddr in ( ' x.y.a.b ' , ' x.y.c.d ' , ' x.y.e.f ' ) -- NatGateway IP AND action = ' ACCEPT ' AND logdate BETWEEN ' YYYY/MM/dd/00 ' AND ' YYYY/MM/dd/23 ' -- 対応後の日付(UTC) GROUP BY pkt_src_aws_service ) A ON B.pkt_src_aws_service = A.pkt_src_aws_service ORDER BY B.bytes_day DESC 結果は以下の通りです。WEARのAPI( EC2 )への通信がVPC内部で完結したこと、プルスルーキャッシュルールによって CLOUDFRONT や外部サービスへの通信回数が減ったことで通信量が大幅に減っています。 pkt_src_aws_service Reduction_percentage EC2 89.15 AMAZON 98.22 - 63.65 CLOUDFRONT 99.14 DYNAMODB 100.0 GLOBALACCELERATOR 100.0 Cost Explorerで対応前後の1日毎のグラフを確認します。最終的に対応が完了したのは6/6頃です。グラフの通り、大幅にコストを減らせました。NATゲートウェイのコストだけで言うと、80%ほど削減できました。 しかし、NATゲートウェイを経由しなくなった分、VPCエンドポイントのコストが増えているはずです。そちらも確認します。APIオペレーションに『VpcEndpoint』も追加し、グラフを確認します。 VPCエンドポイントの通信コストを加味しても、コストを大幅に削減できています。対応前のNATゲートウェイとVPCエンドポイントの総額で計算すると、最終的には60%ほど削減できました。 まとめ 本記事ではNATゲートウェイの通信内容の調査と通信量の削減方法について紹介しました。VPCフローログとリゾルバーのクエリログを確認することで詳細な通信内容を把握できました。通信内容に応じて適切な対応をした結果、約60%のコストを削減できました。NATゲートウェイのコスト削減を検討している方がいれば、ぜひ参考にしてみてください。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com 結果画像のy軸はマスク処理を施してあります。今後出てくるCost Explorerの画像はすべてy軸がマスク処理済みのものです。 ↩ Amazon VPC フローログのクエリ ↩ 参考: サンプルクエリ - Amazon CloudWatch Logs ↩ ここでのAWSサービスはすべてのサービス名ではなく、VPCフローログの pkt-src-aws-service フィールドの値で表示されるもの(参考: VPC フローログを使用した IP トラフィックのログ記録 - Amazon Virtual Private Cloud ) ↩ Amazon Athena の料金 ↩ Datadogへの通信は、ほとんどが pkt_src_aws_service = 'EC2' に内包されています。 ↩ わかりやすさのため pkt_dst_aws_service ごとにクエリを実行していますが、このカラムにはパーティションが設定されていないため、この条件句によってスキャン量を減らすことはできません。Athenaのスキャン量による料金を減らしたい場合、 pkt_dst_aws_service にパーティションを設定することを検討するか、この条件を削除し、1回のクエリですべてを出力してください。 ↩ ゲートウェイエンドポイント - Amazon Virtual Private Cloud ↩ AWS PrivateLink を介して Datadog に接続する ↩ 背景: WEAR Webフロントエンドリプレイスのアーキテクチャ選定とNext.jsへの移行 ↩ キー名: kubernetes.io/role/internal-elb 、値:1のタグが必要(参考: Amazon EKS でのアプリケーション負荷分散 ) ↩ 参考: ExternalDNSでPrivate Hosted ZoneとPublic Hosted Zoneにレコードを出し分ける | DevelopersIO ↩ Docker 環境のコンテナイメージ ↩ わかりやすさのために1つのクエリにしていますが、対応前の結果は最初にクエリした際どこかにメモしておき、対応後の日付だけクエリして手動で比較する方がAthenaの料金上良いと思います。 ↩
アバター
こんにちは、ZOZOTOWN開発本部でZOZOTOWN iOSの開発を担当している らぷらぷ です。 今年のWWDCもワクワクする情報が目白押しでしたね。個人的にはApple Intelligenceが今後どんな進化を果たし、日常生活をどう変えていくのかが楽しみです。 本記事では、ZOZOのiOSエンジニアが注目したセッションや、参加したラボで得た知見を紹介します。 現地参加されたメンバーによるレポートもありますので、ぜひ御覧ください。Appleのスタッフや各国の開発者との交流や、現地に行く人向けのアドバイスなどまとまっております。 techblog.zozo.com オンライン組メンバーのキャッチアップ Sessions UI updates Translation API Apple Vision Pro Labs Xcodeによって実行されるResolve Packagesの時間について SPM移行で遭遇したバグや不明点を聞いてみた メモリ関連で気になることをひたすら聞いてみた まとめ オンライン組メンバーのキャッチアップ 現地に参加しなかったオンライン組メンバーは、各自キャッチアップした情報をMiroに集め、毎日30分から1時間ほどビデオ通話でシェアし合いました。このブログに書きたいセッション、ラボで聞きたいこと、社内の他のチームにも共有したいこと、いち開発者として気になることなど、思い思いに話しました。 特にセッションは「可能な限り全部見たい!」という気持ちになりますが、KeynoteとPlatforms State of the Union、Recapを除くと全117セッションあります。とはいえ、見るべきセッションを選ぶのはなかなか難しいです。最初の2、3分を見て判断しようかな…と見始めたら最後まで見てしまったこともあります。 セッションの要約があればじっくり見るべきか判断しやすいかも、ということで今年はAIチャットボットにセッションの字幕を要約してもらってから内容を深ぼるか判断しました。例として「 Bring your app to Siri 」を挙げましょう。概要は以下のように記載されています。 Learn how to use SiriKit and App Intents to expose your app's functionality to Siri and Apple Intelligence. Discover which intents are already available for your use, and how to adopt App Intent domains to integrate actions from your app into the system. Find out what metadata to provide, how to make your entities searchable with Spotlight, how to integrate your app with in-app search, and much more. 以下が、こちらのセッションの字幕をAIチャットボットに要約してもらった結果です。 .typesquare_tags ol {width: calc(100% - 1em) !important; margin-left:0 !important;} .typesquare_tags ul {margin-left:1em !important; width: calc(100% - 3em) !important;} Apple IntelligenceとAssistant Schemasの導入により、Siriとアプリケーションのインテグレーションがより強力かつ柔軟になりました。開発者は新しいAPIを活用することで、より自然で個人化されたユーザー体験を提供できるようになります。この新しい機能は、iOS 18以降で順次展開される予定です。 既存のフレームワーク: SiriKit: iOS 10で導入され、システムが提供するインテントを使用します。例えば、音楽の再生やテキストメッセージの送信などの一般的なアクションに適しています。 AppIntents: iOS 16で導入され、より柔軟なアプリ統合を可能にします。SiriKitのドメインと重複しない機能に適しています。 Apple Intelligenceによる改善: 新しい大規模言語モデルにより、以下の改善がなされています: より自然な会話 文脈理解の向上 個人化された体験 画面上の情報の理解と活用 新しいApp Intent domains: 12の新しいドメイン(メール、写真、カメラ、スプレッドシートなど)が導入され、100以上の新しいアクションをサポートします Assistant Schemas: 事前定義された「スキーマ」に基づいてインテントを実装します。これにより、コードが簡素化され、コンパイル時の検証が可能になります 個人コンテキスト機能: アプリ内検索: ShowInAppSearchResultsIntentを拡張し、Siriがアプリの検索機能を直接利用できるようになります。 セマンティック検索: 新しいIndexedEntityAPIを使用して、アプリのコンテンツをSiriの意味検索に利用可能にします。 テストと実行: ショートカットアプリを使用して、新しいインテントをテストできます。 将来的には、これらのインテントが自動的にSiriと連携するようになります。 これだけ情報が揃っていると、動画を見てさらに掘り下げるかどうかを判断しやすくなります。掘り下げる場合、このようにセッション動画の構成と詳細が分かっていると、集中して視聴すべきパートを絞ることができます。さらに、要約文内の気になるキーワードについてAIチャットボットに質問しておくことで理解を深められます。 今回この方法を試したことで、動画中のスライドによる解説を理解する時間をグッと短縮できました。 Sessions UI updates ZOZOTOWNのiOSアプリの開発を担当している荻野と、WEARのiOSアプリの開発を担当している山田です。 今回の発表でも、SwiftUIの幅広いアップデートがありました。これまでは、SwiftUIの機能が少ないため社内導入を見送っていた人もいると思います。しかし、SwiftUIはますます使いやすくなっており、もう見逃すことはできなくなってきているのではないでしょうか。まだセッションを見ていない方、まずは「 What’s new in SwiftUI 」からご覧ください。 特に注目すべきは、ズームトランジションのアニメーションが新たに加わったことです。ズームトランジションとは、別の画面へ遷移する際に画面がズームするようなアニメーションです。ZOZOTOWNやWEARでは以前から独自にズームトランジションを実装していましたが、今回のアップデートで標準のズームトランジションが利用できるようになったのは大きな進歩です。さらに、SwiftUIだけでなくUIKitでも対応しているのは非常に嬉しい発表でした。詳細は「 Enhance your UI animations and transitions 」のセッションで確認でき、ドキュメントベースでは「 Enhancing your app with fluid transitions 」で、Viewの状態変化を図を用いてわかりやすく説明しています。ZOZOTOWNのiOSアプリでもズームのアニメーションを行なっている部分があるので、今後の改善に活かしていきたいです。 また、「 Add personality to your app through UX writing 」で紹介されたアプリに個性を出すためのUXライティングにも注目しました。このセッションは、アプリ内の文章をデザインするときに考える声・トーンの一貫性、いわゆるトンマナをAppleのHuman Interface Designチームがどう合わせているのかを解説しています。 具体的には、アプリを人に見立てたとき、どんな性格になるか形容詞を考え、それらをグルーピングします。これがアプリの声の定義となり、声の定義を満たすように文章を作ります。例えば、お金を貯めることが目的のアプリでも、子供向けの貯金アプリか長期的な投資を支援するアプリかどうかで、表現するべき言葉はまったく異なります。 ZOZOTOWNやWEARでも、それぞれのアプリでお客様が洋服を買う・コーディネートを投稿するモチベーションを最大化できるような文章を作るように心がけています。セッションを通してまだまだ改善できる部分があると感じたので、デザイナーやビジネスサイドにもこのセッションを共有しようと思います。 Translation API こんにちは、FAANS部iOSチームの加藤です。今年のWWDCでは英語が苦手な私にとって、とても嬉しいAPIの紹介がありました。そうです! Translation APIです! Translation APIに関するセッションは「 Meet the Translation API 」です。ぜひご覧ください。 Translation APIはAppleの新しいAPIであり、Swiftで作られたアプリに強力な翻訳機能を簡単に組み込むことができます。Translation APIは、Appleの翻訳アプリにおいて翻訳対象である20言語に対応しており、入力された言語の種類を自動で認識して、任意の言語に翻訳できます。また、翻訳したい言語の翻訳モデルを事前にダウンロードしておくことで、オフラインの環境でも翻訳が行える点も非常に便利です。 ZOZOの開発するアプリでは、アイテムの詳細説明や、アイテムに対するレビューを閲覧できる機能が備わっています。日本語を読めないユーザーがZOZOのアプリを使用する場合には、アイテムの詳細説明やレビューを理解することが難しく、アプリに掲載されているアイテムの良さが伝わらないかもしれません。こういった問題も、Translation APIを用いた翻訳機能を導入することで解決でき、より多くのユーザーがZOZOのアプリを使ってくれるきっかけになるかもしれません。 私も英語が苦手で英語との間に分厚い壁を感じていたので、Translation APIを組み込んだアプリがこの壁を打ち破ってくれることをとても楽しみにしています。 Apple Vision Pro ARやVRといったXR領域のリサーチや検証などを担当している創造開発ブロックの @ikkou です。頭はひとつしかないのにVRヘッドマウントディスプレイやARグラスはたくさん持っています。昨年のWWDC23で発表されたApple Vision Proがいよいよ日本でも発売されました。 去年のWWDC23レポート記事 で私は『決してお安いお買い物ではありませんが、アーリーアダプター気質のある開発者は間違いなく買うでしょう。もちろん私も買います。』と書きましたが、予告通りApple Vision Proを買いました。当時は約50万円想定だったものが円安の影響で約60万円になったのは想定外の出費となりましたが、今も毎日使っています。家族の一瞬を立体的に残せる空間ビデオは最高ですし、この文章もApple Vision ProをMacの仮想ディスプレイとして利用して書いています。サンクコスト効果も働いていますが、少なくとも現時点では “装着する” というひと手間をかけるだけの価値があるデバイスだと感じています。 そんなApple Vision Proですが、Appleが力を入れていることはWWDC24のvisionOSに関連するセッション数の多さからも伝わってきます。非常に多くのvisionOS関連セッションが用意されていますが、中でも「 Design great visionOS apps 」は必見です。Appleのプラットフォームにとって初めての空間コンピューティングであるApple Vision Proに最適化したアプリを作るための重要なリファレンスとなるセッションです。 また、空間コンピューティングが当たり前になった世界では、ブラウザの世界も2Dから3Dに変容を遂げます。つまりvisionOS対応アプリをリリースせずとも、空間Webへの対応が当たり前になる世界線も十分に考えられます。そういった観点から「 Optimize for the spatial web 」セッションも非常に重要です。このセッションはiOSエンジニアだけでなくWebフロントエンドエンジニアも認識しておくべき内容に溢れています。 Apple Vision Proは非常に高価なデバイスであるため、キャズムを超えるまで時間がかかると考えています。そういった状況で今すぐvisionOS対応アプリを開発するべきか悩ましいかもしれませんが、開発者視点だけで言えば着手するべきだと考えています。盛り上がりを形作る今が特に楽しい時期です。もちろんビジネス的な側面で言えば、まだまだ母数が少ないであろうApple Vision Proに開発リソースを割くのであれば、別のところに割くべき、という考え方もあります。しかし、長らくXRを推している身としては、今こそ機運が高まって欲しいと願わずにはいられません。 Labs Xcodeによって実行されるResolve Packagesの時間について ZOZOTOWN開発本部のとしです。今回はXcodeによって実行されるSPMのResolve Packagesに毎回かなりの時間がかかっていることが、開発をしていく中でネックになっていたので、この問題について相談しました。 結論から言うとこのLabを通じて、クリティカルな解決方法が見つかったわけではありません。しかし、画面を共有することでAppleのエンジニアが意図していない挙動であること、ワークアラウンドを発見できたこと、逆に自分たちが考えていた手法は実行できないということなどが分かりました。このLabでの結果によって今後の調査のアプローチの方向性がわかったので意義のあるLabになりました。 やはり、Apple側としてもその不具合を再現するプロジェクトとFeedback Assistantを使用したBug Reportingがあるとかなり助かるようでした。 SPM移行で遭遇したバグや不明点を聞いてみた 計測プラットフォーム開発本部の中岡です。私は以前執筆した計測フレームワークをCocoaPodsからSPMに移行する作業の中で遭遇したバグや不明点について2つ質問しました。 techblog.zozo.com 1つ目がObjective-CのSwift Packageのtarget.nameに”-”が含まれている場合、リソースバンドルのアクセスに失敗する問題です。ラボの前に自分なりにOSSのSwift Package Managerを調査して原因となっているであろう箇所を 修正するPR を提出していました。そしてこれについて議論をしたのですが、結果としてSwift Pacakge ManagerではなくXcode側のバグだろうとのことでした。そのためLabsが終わった後にバグを再現させるプロジェクトを作りフィードバックを送りました。 2つ目がswiftSettingsについてです。計測フレームワークにはswiftSettingsを使った分岐処理があり「これを利用する側から設定を変更できないか」という相談をしました。結果として、現状Swift Packageにはそのような機能はありませんでした。しかし以下のforumsでPackage traitsという機能が議論されていることを教えていただきました。まさに求めていた機能なので今後に期待です。 forums.swift.org メモリ関連で気になることをひたすら聞いてみた ZOZOTOWN開発本部のらぷらぷです。最近メモリ効率を上げるための調査や対応に興味があります。Appleのエンジニアはどんな関心を持ってメモリ効率について学んでいるのかが気になり、Performance, power, and stability consultationにてお聞きしました。 お話ししたエンジニアの方曰く、まずはメモリを観察することを好きになり、毎日メモリ使用量を観察して妥当な使用量を見極め、異常値が発生してから分析することを続けましょうとのことでした。よく考えればiOSに限らないパフォーマンスチューニングの観点と一緒ですね。 その他、Memory graphの生成の詳細を解説してくれたり、「 Analyze heap memory 」を例にメモリー安全なコードを紹介してくれたりしました。メモリー安全の話からSwift Concurrencyの話に移り、InstrumentsでSwift Concurrencyをデバッグする方法も教えていただきました。 次々と会話が進み、30分では足りない時間を過ごしました。 具体的なコードの課題をもっていなくても、こうしてAppleのエンジニアの考えていることを聞けて、そこからセッションの視聴を提案されるのもラボの魅力のひとつです。 まとめ 本記事では、WWDC24における弊社iOSエンジニアの取り組み、注目したセッション、ラボから得た知見をお伝えしました。今年も数多くのセッションやラボから学びを得ることができました。これらをZOZOのサービスの進化にどう活かすか、今後の課題として取り組んでいきます。 ZOZOでは、一緒にサービスを作り上げてくれる仲間を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 hrmos.co corp.zozo.com
アバター
はじめに こんにちは、推薦基盤ブロック、新卒1年目の 住安宏介 です。普段は推薦システムの開発・運用を担当しています。 2024年6月に開催されたコンピュータビジョン・パターン認識分野において世界最高峰の国際会議の1つであるCVPR(Conference on Computer Vision and Pattern Recognition)2024に参加しました。参加レポートとして発表内容や参加した感想を紹介いたします。また、最後にZOZO NEXTが行っているワークショップのスポンサー活動についてZOZO Researchの清水から紹介いたします。 目次 はじめに 目次 CVPR とは 開催地のシアトルについて 学会のスケジュール 企業展示ブースの様子 ポスターセッションの雰囲気 採択数増加に伴うポスターセッションの懸念とその実際 特に、印象に残った研究発表 SLICE: Stabilized LIME for Consistent Explanations for Image Classification [Highlight] CAM Back Again: Large Kernel CNNs from a Weakly Supervised Object Localization Perspective Comparing the Decision-Making Mechanisms by Transformers and CNNs via Explanation Methods [Award Candidate] Learning to Rank Patches for Unbiased Image Redundancy Reduction Identifying Important Group of Pixels using Interactions CVFAD におけるスポンサーの取り組み さいごに CVPR とは 先述の通り、 CVPR はコンピュータビジョン・パターン認識分野における世界最高峰の国際会議の1つです。近年、CVPRはそのプレゼンスを大きく高めており、採択された論文の影響力も著しく向上しています。Google Scholarのh5-indexによる全分野の学術誌・国際会議のランキングで、Nature、New England Journal of Medicine、Scienceに次いで4位にランクインしています。このことは、CVPRにおける研究成果が、量だけでなく質においても非常に高く評価されていることを示しています。CVPRは、そのプレゼンスの高まりとともに、コンピュータビジョンとパターン認識の分野における最新の研究成果を発表する場として、世界中の研究者や開発者から高い注目を集めています。 開催地のシアトルについて 開催地となったシアトルは、美しい自然環境に恵まれた都市として有名です。例えば、日本で人気のコーヒーの名前の由来となったマウント・レーニアや、ユネスコ世界遺産に登録されているオリンピック国立公園があります。また、シアトルは世界的に有名なテック企業が集まる都市でもあります。そのため、CVPRに参加された研究者の間では、これらの企業のオフィスツアーに参加する動きも見られ、活発な交流が行われていました。 シアトルの街並み 会場となったシアトルコンベンションセンター 学会のスケジュール 学会の スケジュール は以下のように構成されています。 1日目から2日目 ワークショップ チュートリアル 3日目から5日目(本会議) キーノート パネルディスカッション オーラル発表 ポスター発表 企業展示 AIアートギャラリー この中で特に興味深かった企業展示ブースの様子、ポスターセッションの雰囲気、そして私が予期していた採択数増加に伴うポスターセッションの懸念とその実際について紹介します。 企業展示ブースの様子 企業展示ブースでは、各会社が最新の研究成果や実際の活用事例を紹介していました。50を超える企業が参加しており、どのブースも大変賑わっていました。また、展示内容も非常に興味深くワクワクするものばかりで、とても楽しい時間を過ごせました。特に興味深かったのは Zoox の無人運転車両です。この車両は運転席が不要であるため、興味深いことに座席が対面で配置されていました。私はペーパードライバーであるため、導入されることを楽しみに待っています。 企業デモの様子 Zooxの無人運転車両 ポスターセッションの雰囲気 ポスターセッションとは、発表者が研究内容をポスター形式で展示し、聴講者と直接対話しながら説明と議論をする発表形式です。最も権威のある国際会議の1つであるため、参加者の熱気がとても感じられました。また、発表された研究の多くが魅力的であり、一人の発表者に多くの聴講者が集まる場面も多々見られました。多数の聴講者に対応するため、チームで発表するケースも見られました。 国内の学会では最初から最後まで説明し終えた後に質問を受ける流れが多いですが、今回は概要を話してから適宜聴講者の質問を元に会話するという形が主流でした。私もこの形で大学院時代の研究内容を発表しました。 実際のポスターセッションの様子1 採択数増加に伴うポスターセッションの懸念とその実際 本会議での論文採択数は年々増加しており、すべての興味のある論文を把握しきれない、またはすべての興味のあるポスターを聴講できない可能性が懸念されました。今年のポスターセッションは各90分で、約450件のポスターが展示されていたため、単純計算で1つのポスターにかけられる時間は約12秒しかありません。このため、事前に聴講するポスターを決めておくなどの工夫が必要でした。 しかし、この懸念にもかかわらず、ポスターセッションは予想以上に効率よく回れました。その理由の1つに、CVPRが提供する採択論文の推薦システムを活用することで、事前に興味のある論文を特定できたことが挙げられます。このシステムにより、興味のある論文を事前にチェックするのが非常に容易になりました。 また、興味のあるポスターが会場内で分散していると移動や探索が大変になる可能性がありました。ですが、分野ごとにポスターが配置されていたため、関連する研究を効率的に回れ、思ったよりも負担ではありませんでした。実際に、私はセッション内で大体6から9件ほどの研究を聴講でき、満足感が高かったです。 実際のポスターセッションの様子2 www.scholar-inbox.com 特に、印象に残った研究発表 以上では、会議中の様子を紹介しました。ここからは、私が興味を持っているコンピュータビジョン分野の深層学習モデルの説明可能性について紹介いたします。 SLICE: Stabilized LIME for Consistent Explanations for Image Classification [Highlight] Revoti Prasad Bora、Kiran Raja、Philipp Terhörst、Raymond Veldhuis、Raghavendra Ramachandra 深層学習モデルの説明可能性を高めるための有名な手法の1つとして、LIME(Local Interpretable Model-agnostic Explanations)という手法があります。LIMEとは、Deep Neural Networkなどの複雑なモデルを線形モデルのような簡単なモデルで局所的に近似することによって、各特徴量の貢献度を測定します。しかし、LIMEの説明には一貫性がないという問題があります。例えば、同じ入力データに対してLIMEを複数回適用した場合でも、適用ごとに特徴量の貢献度のランキングが異なったり、貢献度の符号の正負が逆転したりすることがあります。 この論文では、LIMEの手法を改善し、一貫した説明を提供できるように、SLICE(Stabilized LIME for Consistent Explanations)という手法を提案しました。この手法では、LIMEの説明において貢献度が正と負に変動する特徴を除去することにより、説明の安定性を向上させています。具体的には、符号エントロピーに基づいて不安定な特徴を特定し、それらを除去することで、一貫性のある貢献度を求めています。 結果を見ると、このSLICEという手法は平均実行時間が通常のLIMEの約4倍かかります。LIMEより安定した説明ができるため、今後の説明手法の選択肢の1つになると考えています。 CAM Back Again: Large Kernel CNNs from a Weakly Supervised Object Localization Perspective Shunsuke Yasuki、Masato Taki 近年、CNN(Convolutional Neural Network)の性能向上を目指してカーネルサイズを拡大したラージカーネルCNNが注目されています。先行研究による分析では、性能向上の理由としてCNNのカーネルサイズの拡大により、CNNが広範囲の特徴から情報を集めるようになり、そのような有効受容野の拡大が原因だと述べられていました。 この研究では、実際にラージカーネルCNNの性能向上の理由が有効受容野の拡大であるのか検証し、その妥当性を弱教師ありオブジェクトローカリゼーション(WSOL)の観点から網羅的に評価しました。実験結果から、ラージカーネルCNNの精度向上の理由が有効受容野の拡大だけで説明することは難しいことがわかりました。代わりに、著者らはその理由として、特徴マップの改善が原因であることを示唆しました。具体的には、古典的なモデルの判断根拠の可視化手法のCAM(Class Activation Mapping)を使用して通常のCNNとラージカーネルCNNを比較していました。その結果、通常のCNNは局所領域のみを活性化するのに対し、ラージカーネルCNNはオブジェクト全体を活性化することがわかりました。さらに、この傾向を活かしラージカーネルCNNとCAMを単純に組み合わせた手法でWSOLの精度を検証した結果、複雑な構成を持つ最先端のCNNベースのWSOL手法に匹敵する性能を持つことを示しました。 個人的には、古典的な可視化手法であるCAMが、最新のCNNの性能向上の理由を説明していることから、同様に他の説明手法を最新のCNNやViT(Vision Transformer)に対して適用した際に新たな説明が得られるのかが気になります。 Comparing the Decision-Making Mechanisms by Transformers and CNNs via Explanation Methods [Award Candidate] Mingqi Jiang、Saeed Khorram、Li Fuxin 多くの研究で、TransformerとCNNの判断根拠の違いが議論されています。特に、TransformerはCNNよりも形状に着目して判断するという知見があります。この研究では、そのような新たな知見を得るために「sub-explanation counting」と「cross-testing」と呼ばれる評価指標を提案しました。 1つ目の指標のsub-explanation countingは、モデルが画像内の複数の画像パッチを構成的に見て判断しているのか、それとも一部の重要な画像パッチに強く依存しているのかを調べる手法です。まず、MSEs(Minimally Sufficient Explanations)と呼ばれる、完全な画像と同程度の予測確率を持つ最小限の画像パッチセットを取得します。次にそのMSEsの一部の画像パッチを削除して予測確率を計算し、完全な画像の予測確率との尤度比を求めます。もし、尤度比が高ければ、その画像パッチの組み合わせが重要であり、モデルは複数の画像パッチを同時に着目しているため「構成的」と判断されます。逆に、重要な画像パッチを削除すると予測結果が大きく変わるものを「分離的」と判断されます。この手法によると、Transformerは構成的であり、CNNは分離的であることが示されていました。 2つ目の指標のcross-testingは、モデル間の判断根拠の類似性を評価するための指標です。もし、2つのモデルが同様の視覚的特徴に依存している場合、cross-testingの高いスコアを獲得します。その結果、類似したタイプのアーキテクチャや学習法のモデルでは、予測に使用する特徴が似ていることが確かめられていました。 個人的には、実際にアプリケーションに応用する際には、どちらかのアーキテクチャのモデルがベースとなるのか、それともケースバイケースでアーキテクチャを使い分けるべきかが気になります。 Learning to Rank Patches for Unbiased Image Redundancy Reduction Yang Luo、Zhineng Chen、Peng Zhou、Zuxuan Wu、Xieping Gao、Yu-Gang Jiang 深層学習モデルの説明可能性の文脈ではないのですが、類似した研究の方向性として冗長な情報の削減というものがあり、興味深い考え方をしていたので紹介いたします。画像において、近くにあるピクセルがしばしば似た色や明るさを持つことがあり、このような類似した情報を冗長な情報といいます。推論の高速化や効果的な伝送のためには、この冗長性を削減することが重要です。 この研究は、冗長な情報を削減するためにMAE(Masked Auto Encoder)を用いた自己教師あり学習フレームワークLTRP(Learning to Rank Patches)を提案しました。MAEとは、画像パッチをマスクした入力に対して元の入力画像を出力するように再構成することで特徴表現を学習する手法です。この手法は、画像再構成の難易度に差を持つことを知られています。例えば、犬の胴体をマスクしても元々の犬とほとんど同様な再構成は容易ですが、尻尾をマスクすると再構成は難しくなることがあります。著者らはこのような難易度の差に目をつけ、その画像パッチの情報量を測定する指標として再構成の難易度を使用しました。つまり、再構成が難しい画像パッチは重要な情報を含むと考えられ、再構成が容易な画像パッチは冗長な情報と見なすということです。LTRPの具体的な手法は、まず事前学習されたMAEを利用して画像パッチの擬似スコア(再構成画像の崩れ具合のスコア)を生成します。そして、このスコアを疑似ラベルとした教師データにより、各パッチの重要度のランク付けを推論する代理モデルを学習します。この代理モデルにより、任意の入力画像に対し重要な画像パッチを特定でき、特定されなかった画像パッチは冗長な情報として削減できます。 再構成が難しいというネガティブな要素に直面した場合、それを改善する方向で研究が行われると思うのですが、それに情報量を結びつけることでポジティブな効果を生み出そうとした発想がとても興味深いと思いました。 Identifying Important Group of Pixels using Interactions Kosuke Sumiyasu、Kazuhiko Kawamoto、Hiroshi Kera 私が発表した論文を簡単に紹介したいと思います。 画像分類モデルの振る舞いを理解するために、画像内のピクセルが推論に与える影響を貢献度として測定し、可視化することがよく行われています。既存の手法であるGradCAMやAttention rolloutでは、画像内の各ピクセルが推論に与える影響を測定して可視化しています。しかし、貢献度の高いピクセルを集めたピクセル群が必ずしも高い貢献度を持たないことがあります。例えば、海辺にいる鴨の画像分類を考えると、単純には主に鴨の部分にフォーカスすれば良いと考えられます。ですが、鴨は海の上に生息する傾向があるため、背景に写っている海辺の情報を鳥の情報と一緒にモデルに入力した方が分類を補助するために役立つ可能性が高いことが考えられます。実際に従来手法では、鴨の体や嘴といった情報のみを集めていたのですが、確信度が低く、一方で鴨の体だけでなく海などの情報も含めた方がより高い確信度を持つことがわかりました。 この研究では、モデルにピクセル群を入力したときに推論へ大きく影響を与えるピクセル群を特定する手法MoXI(Model eXplanation by Interactions)を提案しています。具体的には、ゲーム理論で用いられるShapley Valueと相互作用値という2つの量を計算することで、個々のピクセルの貢献度に加えて、ピクセル間の協調による貢献度を考慮できる手法となっています。 CVFAD におけるスポンサーの取り組み ZOZO Researchの清水良太郎です。最後に、CVPRで毎年開催されているファッションやアートに関するワークショップ Workshop on Computer Vision for Fashion, Art, and Design (CVFAD)について紹介いたします。CVFADは今年で7回目の開催であり、毎年様々な大学や企業からファッション関連の最新の研究が発表されています。 ファッション関連のAI技術の研究者にとっては毎年欠かすことのできないイベントであり、ZOZO NEXTも今年はスポンサーとして関わりました。今年の採択率は38%と、ワークショップながら厳しい査読プロセスを経て、8件の研究が発表されました。なお、採択された論文は、毎年CVPR WorkshopsのProceedingsに掲載されています。 私個人としても、今年は「A Fashion Item Recommendation Model in Hyperbolic Space」が採択され、現地で発表してきました。この研究は、画像情報を用いた推薦モデルの学習に、双曲空間における距離尺度を導入し、優れた精度を達成したという内容です。双曲空間は、我々の生活しているユークリッド空間と呼ばれる平坦な空間と異なり、曲がった空間の一種です。原点から離れるほど空間が広くなるという特徴から、階層的な構造を有したデータの埋め込みと相性が良いとされています。推薦のデータにおいては、わかりやすい階層構造だけでも、例えばユーザとアイテム。また、アイテム間の人気度から生じる階層構造が存在します。 ファッション画像推薦モデルの学習によって得られる双曲空間のイメージ 本研究では、双曲空間においてファッション画像特徴量を用いた推薦モデルを学習し、その精度や学習されたユーザ・アイテム空間に関する多角的な考察をしました。詳しくは、 論文 をご一読いただけると幸いです。 CVPRはコンピュータビジョン・パターン認識分野において世界最高峰の国際会議であるため、そのワークショップに対する採択の難易度も年々向上しております。特に、一部のワークショップは、一流の国際会議やジャーナルに匹敵するような評価を受けています。実際にGoogle Scholarが発表しているh5-indexを基準にしたランキングでは、(2024年7月5日現在)Computer Vision & Pattern Recognition分野の全学術誌・国際会議を通して7番目の評価をされています。会社の企業理念である「世界中をカッコよく、世界中に笑顔を」を叶えるべく、世界中のファッションに関わるAI研究者や技術者から認知をしてもらうことはとても重要な一歩です。このような場で継続的に研究成果を発表するだけでなく、スポンサー企業として積極的に参加することにより、ZOZO/ZOZO NEXTのプレゼンスの向上に大きく貢献すると考えています。 さいごに 今回は、CVPRに参加した感想や内容の一片をお伝えしました。特に経験して良かったことは、トップ層の研究者や開発者たちが持つ知的好奇心の強さと行動力を直接感じることができたことです。このような熱意に触れられたことは、将来の糧になると感じています。ここで得られた知見や経験を今後の開発に取り入れ、より良いサービス開発をしていきたいと思っています。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com hrmos.co
アバター
はじめに こんにちは、CISO部の 兵藤 です。日々ZOZOの安全を守るためSOC業務に取り組んでいます。 本記事ではMicrosoftのDefender for Endpointを用いてAppleのmacOSに対してセキュリティ対策するTipsについて紹介します。 また、CISO部ではその他にもZOZOを守るための取り組みを行っています。詳細については以下の「OpenCTIをSentinelに食わせてみた」をご覧いただければと思います。 techblog.zozo.com 目次 はじめに 目次 背景と概要 前提知識 Microsoft Defender for Endpoint導入方法 macOSとWindowsでのDefender for Endpointの機能差分 macOSへのLive Response機能 Live ResponseのBashスクリプトの登録と実行 スクリプトのTips デバイス分離の際の通知スクリプト 子プロセスまで切るスクリプト フォレンジックアーティファクト取得スクリプト まとめ おわりに 背景と概要 ZOZOではmacOSのエンドポイントの保護にMicrosoft Defender for Endpointを利用しています。Microsoft Defender for Endpointは、macOSに対してもエンドポイント保護ができます。 ですが、Windows端末に対して行うことができるインシデント対応時のエンドポイトの機能とmacOSの機能には差分があるため、独自でmacOSに対して分析調査を行う必要があります。そのための簡易なスクリプトを用意し、インシデント対応に活用しています。その事例について本記事で紹介します。 前提知識 Microsoft Defender for Endpoint導入方法 ZOZOではユーザ端末を管理するために、Microsoft Intuneを利用しています。Microsoft Intuneは、Windows端末だけでなくmacOSにもFirewallやGateKeeperなどセキュリティの設定プロファイルやアプリを配ることができます。Microsoftのライセンスを購入している組織はこういった形でmacOSに対してもIntuneで管理することがあるかと思います。 Microsoft Defender for EndpointもIntuneによって配ることが可能です。詳しくは 公式ドキュメント をご覧ください。構成プロファイルが多いですが、必要な構成プロファイルとなるためIntuneで配布する必要があります。 macOSとWindowsでのDefender for Endpointの機能差分 Defender for Endpointを使ったインシデント対応では、UI上の右上の3点リーダーから表示される以下の機能群を使うことが多いと思います。 Windowsでの機能 macOSでの機能 左図はWindowsでのDefender for Endpointの機能群で、右図はmacOSでの機能群です。 大きな違いは「分離スクリプトから強制解放をダウンロード」と「自動調査の開始」です。「分離スクリプトから強制解放をダウンロード」についてはWindows用のバッチファイルなのでmacOSでは使えません。「 自動調査 の開始」についてはDefender for Endpointが、FileやProcess、Driver、通信先、永続化の有無(レジストリ)などを自動で調査してくれる機能です。普段はアラームトリガーで動くのですが、手動でも実行できます。Windows特有の調査項目もあるので、これもmacOSでは使えません。 macOSへのLive Response機能 Defender for Endpointは端末に対してリモートからコマンドベースの操作が可能です。リアルタイムで端末の情報を遠隔から取得できたり、ファイルの取得や削除が可能で、インシデント対応においては何かと便利です。 macOSでもこのLive Response機能を使うことができます。macOSで利用できるコマンドは 公式ドキュメント をご覧ください。Windowsと違ってmacOSでは分離機能、アンチウィルススキャンなどがコマンドで利用可能です。また、 run コマンドについてはmacOS上でbashスクリプトの起動が可能です。 Live ResponseのBashスクリプトの登録と実行 macOSではLive Response機能を利用し端末上でBashスクリプトを利用できます。この機能で用意しておいた便利スクリプトを起動し、SOC対応に活用しています。 このスクリプトはDefender for EndpointのLive Response起動時の画面から登録できます。まずは、右上の「ライブラリへのファイルのアップロード」を選択します。 次にアップロードするファイルを選択し、アップロードします。 アップロード完了後は、Live Responseの library コマンドでアップロードしたスクリプトを確認できます。このLlibraryに保存されているスクリプトは library delete コマンドで消さない限り残るので、一度登録しておけばSOC対応中に何度も呼び出すことが可能です。 この library に存在するスクリプトの起動は以下のコマンドで行います。 run < bash-file in library > ここまでのオペレーションで、macOS上でカスタムしたBashスクリプトを実行できます。 スクリプトのTips スクリプトを侵害された可能性のある端末で回すことになるので、侵害の度合いや対応フェーズによってスクリプトを回す、回さないの判断があると思います。それについてはアラームの出方によって都度判断することをお勧めします。 デバイス分離の際の通知スクリプト Defender for Endpointではデバイスのネットワーク(以降NW)上からの分離が可能です。この機能を使うとデバイスのユーザーはネットワーク通信を用いた操作ができなくなるので、業務が止まってしまいます。出社している場合は同フロアや同僚の助けを借りて対処が可能ですが、リモートワークが主体の弊社ではSOCの対応なのか、NWでの不具合なのか判断がつきません。 Windows環境の場合は、デバイス分離の際にユーザーへ通知する機能 1 が存在します。macOSではこのような機能はありません。 Windowsでのデバイス分離の通知 macOSでもユーザが判断するために、デバイス分離の際にカスタムスクリプトを回すことによって、端末のユーザに通知できます。以下にスクリプトの例を示します。 script_message = ' display dialog "コンピュータウィルスの感染の疑いがあるため、SOCにてPCを隔離いたしました。\n現状の詳細について、緊急のお問い合わせはこちら\n\n<電話番号を記載>" with title "SOC_Alert" with icon caution buttons {"OK"} ' /usr/bin/osascript -e " ${script_message} " 社内の緊急時の連絡先や対応フローに沿って手順を記載すると、ユーザも安心して今後の対応に進むことができると思います。 子プロセスまで切るスクリプト Defender for EndpointのLive Response機能では remediate と言ったコマンドがあります。このコマンドについては以下のように公式ドキュメント 2 に記載されています。 デバイス上のエンティティを修復します。 修復アクションは、エンティティの種類によって異なります。 ファイル: 削除 プロセス: イメージ ファイルを停止、削除する サービス: イメージ ファイルを停止、削除する レジストリ エントリ: 削除 スケジュールされたタスク: 削除 スタートアップ フォルダー項目: ファイルを削除する このコマンドによってプロセスを遠隔から切ることができるのですが、子プロセスまでまとめて切ることができません。 そこでmacOSに存在する便利なコマンド pkill を用いたスクリプトで、親から子プロセスまで切ることができます。 # pkill.bash /usr/bin/pkill -P " $1 " このスクリプトをLive Responseで実行する際には以下のようにします。 run pkill.bash < 親のPID > 不審なプロセスから作成される子プロセスをまとめて切ることができるので便利です。 フォレンジックアーティファクト取得スクリプト Defender for Endpointでは「調査パッケージの収集」の機能を利用すると、フォレンジックに役立つ情報をリアルタイムで収集できます。 lsof や kextstat コマンド出力、 .zsh_history ファイルなど多彩なログを収集可能です。収集できる情報については 公式ドキュメント を参照してください。 この「調査パッケージの収集」機能で目ぼしい情報は収集できます。ですが、各インシデントによっては別途ブラウザのログや、ASL(Apple System Log)、SFLファイルを確認したいことがあります。 これらのアーカイブファイルがどんなログなのか確認したい場合は、JSAC 2022で行われた An Introduction to macOS Forensics with Open Source Software のスライドを参考にしてみるとわかりやすいです。 どういったログなのか理解していないと思わぬ情報の見落としに繋がる可能性があります。そこで、事前に取得できるアーティファクトを確認しておくことが重要です。 例えば「 com.apple.LaunchServices.QuarantineEventsV2 のログはデフォルトでは wget などのダウンロードでは com.apple.quarantine 拡張属性がつかないので記録されない」などです。 これらのmacOSの各種アーティファクトを追加で取得する際に便利なツール AutoMacTC を用いてスクリプト作成しました。 AutoMacTCはセキュリティベンダーであるCrowdStrikeが公開しているmacOSのフォレンジックツールです。このツールを使うことで、macOSの各種ログや設定ファイルを取得できます。 その他のフォレンジックツールはmacOSの性質上、再起動が必要です。そこで、リアルタイムフォレンジックを行えるツールで、各種ログを取得できるAutoMacTCを選定しました。 また、AutoMacTCはmacOSにデフォルトであるPython 3で動作し、追加のパッケージも pip などでインストールしないため、迅速にアーティファクトを取得できます。最近はメンテナンスされていないのが難点ではあるので、その点は注意が必要です。こちらの Issue などのように収集対象が変わっている場合があります。 このツールをDefender for EndpointのLive Responseで使うためには、諸々の準備が必要です。 まずAutoMacTCで利用するmodulesをzip圧縮しておきます。 zip -r modules.zip modules この modules.zip ファイルと automactc.py を事前にLive Responseのlibraryに登録しておきます。そして以下のカスタムスクリプトもlibraryに登録しておきます。 # automactc.bash folder = " /Library/Application Support/Microsoft/Defender/response/ " #Live Responseがputfileコマンドでファイルを置くディレクトリ modulefile = " modules.zip " pyfile = " automactc.py " /usr/bin/unzip -o " ${folder}${modulefile} " -d " ${folder} " /usr/bin/python3 " ${folder}${pyfile} " -m all -o " ${folder} " 登録した後はzipファイルやPythonスクリプトを putfile コマンドで展開し、カスタムスクリプトを run するだけでAutoMacTCが動作します。動作後のアーティファクトは getfile コマンドで取得できます。 追加で様々なアーティファクトを取得でき便利です。 まとめ Defender for EndpointをMacで活用するためのTipsを紹介しました。macOSでのインシデント対応においては、Windowsとは異なる機能差分があるため、独自のスクリプトを用意しておくと便利です。 ZOZOではこれからもSOCの運用体制を整備し、ZOZOの安全性の向上を図っていきたいと考えています。 おわりに ZOZOでは、一緒に安全なサービスを作り上げてくれる仲間を募集中です。ご興味のある方は、以下のリンクから是非ご応募ください! corp.zozo.com デバイス分離 ↩ ライブ応答を使用してデバイス上のエンティティを調査する ↩
アバター
はじめに こんにちは。DevRelブロックの @ikkou です。6月26日に「 WWDC24 報告会 at LINEヤフー, ZOZO 」を開催しました。WWDCに参加したLINEヤフーとZOZOのエンジニアが新しく発表された技術や得た知見・情報などを共有するイベントです。今年はオフラインのみで開催しました。 登壇内容まとめ LINEヤフーとZOZOの社員によるLTとゲストを招いてのパネルディスカッションを行い、その後は交流会で盛り上がりました。 なお、本イベントはAppleがNDAを締結した開発者にのみ公表している情報を取り扱っており、参加はApple Developer Programに加入している方に限定して実施しました。本レポートもLTの詳細は割愛し、雰囲気をお伝えできればと思います。 コンテンツ 登壇者 Essential Highlights: SwiftUI updates たなたつ / LINEヤフー What’s new in controls Fuya / ZOZO AccessorySetupKit 鎌倉和弘 / LINEヤフー Swift Charts: Vectorized and function plots yamaken / LINEヤフー What's new in privacy 松原良和 / LINEヤフー Introduction to Swift Testing! shoma / ZOZO What's new in SwiftData 大岡湧汰 / LINEヤフー Panel Discussion freddi / LINEヤフー jollyjoester / メルカリ akkie76 / DeNA 交流会 発表風景 LINEヤフーのたなたつさんによるLT LINEヤフーの松原さんによるLT ZOZOのshomaさんによるLT 発表資料 一部の登壇者については、イベント当日に投影していた資料からNDAに抵触する箇所を省き、公開情報のみで構成した資料を公開しています。 speakerdeck.com Panel Discussion 乾杯で始まり和やかな感じで進行したパネルディスカッション 名物“乾杯er”である jollyjoester さんの「カー→ンパ↑ーイ!」とともにカジュアルな雰囲気でパネルディスカッションがはじまりました。 今回のパネルディスカッションは、LINEヤフーの freddi さんの他、グループ外からのゲストとしてメルカリの jollyjoester さんとDeNAの akkie76 さんの3名で進行しました。私たちのWWDC報告会として社外ゲストをお招きしての実施は初めての試みでしたが、それぞれがWWDC24の現地で感じたことを中心にWWDC24を振り返りました。 交流会 パネルディスカッションの後、交流会を実施しました。昨年に続きApple JapanからTechnology Evangelistの豊田さまにご参加いただき、参加者からのさまざまな質問に答えていただきました。 最後に みなさまご参加ありがとうございました。WWDCの報告会は毎年の恒例イベントです。また来年も開催できることを楽しみにしています! ZOZOでは一緒にサービスを作り上げてくれる仲間を募集中です。ご興味のある方は以下のリンクからぜひご応募ください。 hrmos.co corp.zozo.com
アバター
はじめに はじめまして。2024年に新卒として株式会社ZOZOに入社しました。 佐藤仁 と申します。 この記事は4か月間にわたる内定者アルバイトの体験記です。アルバイトの概要、チームの文化、実際に行ったタスク、反省点、フィードバックをご紹介します。 目次 はじめに 目次 内定者アルバイトについて 内定者アルバイトでの働き方 実際にアルバイトをした部署の概要 ZOZOTOWN開発1部フロントエンドブロックの取り組み 週一回の輪読会 Findy Team+による開発運用の改善 実際に行ったタスク 似合うアノテーション 背景 開発体制 実装箇所 得られた知見 フィードバックと反省点 最後に 内定者アルバイトについて 内定者アルバイトとは内定承諾から入社までの期間にアルバイトの雇用契約を結び、内定先で就業できるものです。 選考時に志望していた職種以外の求人に応募できます。同期を見ているとフロントエンドを志望したけど内定者アルバイトではSREに参画する人もいました。なので専門外の技術を学ぶには良い機会になりそうですね。 内定者アルバイトでの働き方 私は2023年1月末に内々定をいただき、同年12月から翌年3月の大学卒業までの4か月アルバイトを行いました。 ZOZOTOWN開発部門の社員にはフルフレックス制度が導入されていますが、内定者アルバイトはシフト制なので、私は10時から17時、1日6時間勤務を週3日、フルリモートで働きました。 メンターとは朝に相談会の時間を設け、業務での悩み事を共有しました。業務の事以外にも一人暮らしに向けた引っ越しの相談なども付き合ってもらえました。MTGの時間を定期的に用意してもらえたのでフルリモートでも問題なく働けました。 私は新潟にいたので、オフィスへ出勤するのが難しかったのですが、千葉にいる内定者アルバイトの同期は出社して働く人もいました。 実際にアルバイトをした部署の概要 私が参画した部署はZOZOTOWN開発本部ZOZOTOWN開発1部Webフロントエンドブロックです。 受け持つ案件は大規模・中規模案件がメインで、サービスに関わる新機能の実装を主に担当します。2023年12月時点ではスタッフは10名程度で構成されていました。 チームのコミュニケーションはSlackとGoogle Meet、デザインツールはFigma、仕様に関してはConfluenceとMiro、タスク管理にはGitHub Projectsを使っています。 チームで印象的だった取り組みを2つ紹介します。 ZOZOTOWN開発1部フロントエンドブロックの取り組み 週一回の輪読会 毎週金曜日に1時間読書する時間を設け、その後1時間読んだ内容について議論し、チームに導入できることはないか相談します。読む本は皆で相談しながら、今のチームにとって必要な本を選んでいます。 内定者アルバイトの期間では「 フロントエンド開発のためのテスト入門 今からでも知っておきたい自動テスト戦略の必須知識 」を読みました。開発1部では新規機能の開発に追われテストをしっかり書く機会がないこともありましたが、テスト文化を築く良いきっかけになりました。 本以外にも社員の方に翻訳してもらった Google Engineering Practices Documentation を読みました。PRの書き方や小さなPRを作る重要性、コメントをする上での心構えをチームで共有できました。 Findy Team+による開発運用の改善 Findy Team+は1PRあたりの平均マージ・クローズ時間や変更行数などの数値を分析できるツールです。開発1部ではこれを用いて週に1回、PRのオープンからマージまでのサイクルタイムやマージされたPRの数、1PRあたりの変更行数を振り返ります。 Google Engineering Practices Documentation を輪読会で読んでから、PRの粒度が小さくなり、PRをオープンしてからマージされるまでの平均時間が改善されました。 別の部署での取り組みがブログにあるので興味のある方は下記リンクからご覧下さい。 techblog.zozo.com 実際に行ったタスク 4か月の間、色々なタスクを担当しましたが、開発期間が長かったものを1つ紹介します。 似合うアノテーション ZOZOTOWN会員にどちらのコーデ画像の方が似合っているかアノテーションをしてもらう機能です。 背景 ZOZOTOWNは創業25年目を迎え、 経営戦略 として新たに「ワクワクできる『似合う』を届ける」が追加されました。 服と服、人と服の似合うデータをもとにモデルを開発したい。そのために「似合う」に関する大量のデータが必要だが現在大量にデータ取得できる経路がない。という経緯で、似合うに関するデータを大量に収集するためのアンケート機能を実装する必要がありました。 開発体制 部署内のテックリード、メンターと自分の3人でフロントの部分を実装しました。issueはGitHubのProjectsに案件ごとでまとめられており、issueと紐付けてPRを出していきました。 検証に上がった後は結合テストで開発者たちによる実機検証をしました。その後はQAエンジニアに動作確認をしてもらい、リリースという流れです。週に1回バックエンドのチームと共有会もあり、進捗や疑問点を共有しました。 実装箇所 APIの部分はメンターの方が既に実装していたので、私はそのAPIを使ったhooksやコンポーネントの実装を担当しました。 シンプルなUIで内定者アルバイトとして適切な難易度だったと思います。ただ、ZOZOTOWNは歴史あるプロダクトなだけあって普段のフロントエンド開発では見ないディレクトリ構成をしていたり、社内独自のライブラリがあったりしたので慣れるまで時間がかかりました。 一番難しかったのは途中離脱を防止するアラートの実装です。このページではアンケートを回答すると画面が変わりますが、URLのパスが変わるわけではありません。そのため、ブラウザバックをする時にアンケート開始前のホーム画面に戻ってしまうので、前の回答ページには戻れないと伝える必要がありました。 JavaScriptにはHistoryAPIがあります。このHistoryAPIはブラウザ上の閲覧履歴を操作できるものです。また、ブラウザバックのハンドリングには popstate イベントを使いました。 このAPIを利用して、アンケートに回答すると最初の1回だけ履歴を追加します。その後、ブラウザバックをした時にpopstateイベントを検知してアラートを表示していました。 しかし、iOSのsafariでは履歴の追加後にブラウザバックをするとアラートが出なくなる挙動を発見しました。この問題を解決できず、最終的にはアラート機能を外す結果となりました。 得られた知見 GitHub Projects内で管理されていたissueが細分化されていました。一見issueの量が多く見えて大変と感じるかもしれませんが、1つのissueが小さいことからPRの粒度も小さくなり、Reviewerの負担が減りました。このようなことからタスクをしっかり細分化することで開発サイクルを早められると気付けました。 ブラウザ・バージョンの互換性について意識するようになりました。開発者としては当たり前な部分だと思いますが、今までやってきた業務ではおざなりにしていたんだなと実感できました。エラーが起きた時、もしくはエラーはないが想定通りの動作が確認できない時、別のブラウザで同様のエラーが見られるか確認するようになりました。ブラウザ依存のAPIは互換性を意識する必要があると学べました。 セマンティックなマークアップ、アクセシビリティに対する意識も上がりました。私は内定者アルバイトを始める前、ある程度UIの実装ならできると慢心していましたが、レビューを受ける中で自分がいかに無知か再確認できました。小さなコンポーネント1つでもアクセシビリティのことを考慮すると改善の余地がたくさん出てきました。Reviewer方の知見の広さに感嘆するばかりでした。 フィードバックと反省点 内定者アルバイト最終日にMTGがあり、いくつかフィードバックをいただきました。 作業スピード、スキルは申し分ない 自分で仕事をガツガツ進めていく姿勢が良かった チームの輪読会・朝会で積極的に話せている などありがたいお言葉をいただきました。作業スピードが早いのは良いことですが、その反面PRの書き方が雑でレビュアに負荷を与えていたという反省点があります。動作確認の手順が書かれていない、概要の情報量が不足している、仕様の抜け漏れがある、メンバーが前提知識で知っていることをわざわざ書いていて冗長などの指摘をいただきました。 また、指摘箇所を修正した後連絡がないなどチーム開発をする中でもメンバーに寄り添った立ち回りができていなかったと思います。メンターからも言われたことですが多少時間がかかっても良いから抜け漏れのないPRを出して周囲に気遣いができるようになろうと思いました。 もう1点、レビューにもっと参加して欲しかったというフィードバックもいただきました。これは私も同じことを感じていて、せっかくZOZOのエンジニアたちのコードを読めるというのにコードリーディングにしっかり時間を割けず、勿体ない事をしたなと反省しています。配属後は1日の中で必ず一度は他の人のPRを見てレビューする習慣をつけようと考えています。 最後に ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com
アバター
はじめに 技術評論社様より発刊されている Software Design の2024年5月号より「レガシーシステム攻略のプロセス」と題した全8回の連載が始まりました。 本連載では、ZOZOTOWNリプレイスプロジェクトについて紹介します。2020年に再始動したZOZOTOWNリプレイスでは、「マイクロサービス化」が大きなカギとなりました。今回は、SRE部が行った、リプレイス方針の決定から導入ツールの選定、マイクロサービスのリリース方法の改善までを紹介していきます。 目次 はじめに 目次 ZOZOTOWNリプレイスにおけるSRE部の方針 IaCの導入 IaCとは プラットフォーム基盤におけるIaC CI/CDの導入 CI/CDとは GitHub Actions 変更のあるインフラリソースのみをCIの対象とする工夫 Canary Releaseの導入 Canary Releaseとは ZOZO API GatewayとALBによるCanary Release IstioによるCanary Release Progressive Deliveryの導入 Progressive Deliveryとは Progressive Deliveryツールの選定 Flagger GitOpsの導入 GitOpsとは GitOpsツールの選定 Flux2 マイクロサービスのリリース方法の改善 改善前の課題 課題を解決したCDパイプライン Required reviewers OCIRepositoryに変更 改善の結果 おわりに こんにちは。株式会社ZOZOの技術本部 SRE部の籏野( @gold_kou )と堀口( @makocchi6 )と申します。第2回は、ZOZOTOWNリプレイスにおける、SRE部によるIaC(Infrastructureas Code)やCI/CD関連の取り組みを中心にまとめます。 ZOZOTOWNリプレイスにおけるSRE部の方針 ZOZOTOWNの開発に従事するSite Reliability Engineerは、技術本部のSRE部という組織に所属しています。2020年にZOZOTOWNのリプレイスを本格的に再開した際に、SRE部としていくつかの方針を決定しました。 まずは、AWSの導入です。ZOZOTOWNはもともと、オンプレミスのインフラ構成になっており、スケールアウトに課題がありました。たとえば、大規模なセールでは平常時の3倍以上のリクエストを受けますが、そのピークトラフィックを捌さばくために相応のインフラ設備を購入する必要があります。これには数ヵ月といったリードタイムが発生するため、準備も入念に行う必要があるうえ、スケールアウトが不十分でシステムエラーが多発するケースもありました。AWSを導入することでその問題を解決することにしました。 また、マイクロサービスはコンテナで管理することにしました。コンテナ技術を利用することで、処理速度および起動速度が速い、Amazon ECR(以下、ECR)などのイメージレジストリを利用できる、などさまざまなメリットを享受できます。 そして、コンテナ化されたマイクロサービスはAmazon EKSのKubernetes(以下、K8s)クラスター上で稼働させることにしました。ZOZOでは、このマイクロサービスが稼働するK8sクラスターを「プラットフォーム基盤」と呼んでいます。プラットフォーム基盤は、マルチテナンシー方式で、複数のマイクロサービスをnamespace単位で区切り、単一のK8sクラスター上で稼働させています。マルチテナンシー方式を採用した理由は、管理のしやすさやリソース効率の良さなどに加えて、リプレイスの速度を上げるためです。当時のZOZOの状況では、マルチテナンシー方式を採用することで、マイクロサービスの構築をパターン化して量産する体制を整えやすいと判断しました。 ほかにもこの時点で、IaC、CI/CD、Canary Release、Progressive Deliveryの導入を決定しました。また、それらを導入する中で、GitOpsの導入やマイクロサービスのリリース方法を改善するようになりました。本記事では、これらの取り組みを紹介します。 IaCの導入 IaCとは IaCは、インフラ構成をコード化して、そのプロビジョニングを自動化する手法です。次の効果が期待できます。 Gitによるバージョン管理とレビュー環境の提供 構築作業におけるヒューマンエラーの削減 新規メンバーのキャッチアップが容易 自動化による開発効率とリリーススピードの向上 コードの再利用 インフラへのCI/CD導入 プラットフォーム基盤におけるIaC プラットフォーム基盤のインフラはほとんどがIaC化されています。具体的には、AWSリソースはAWS CloudFormation(以下、CloudFormation)、K8sリソースはK8sのマニフェスト、DatadogリソースとPagerDutyリソースとSentryリソースはTerraformによりIaC化されています。 CI/CDの導入 CI/CDとは CI/CDは、CI(継続的インテグレーション)とCD(継続的デリバリー)の組み合わせです。CIはコードの変更を起点にコードの静的分析、ビルド、テスト、成果物の生成などの実行を自動化する手法です。一方、CDはCIで検証・テストされたコードや成果物を目的の環境に自動でデプロイする手法です。CI/CDを導入することで、本来は人が行う必要のない作業が自動化され、結果的に空いた時間で本質的な作業に人が取り組めます。これは、メンバーのモチベーション向上と、組織全体の生産性向上につながります。 プラットフォーム基盤におけるインフラのCI/CDでは、AWSリソースとK8sリソース、Terraformリソースの変更を検知して、リソースを更新します(図1)。前提として、IaCが導入されている必要があります。 図1 プラットフォーム基盤におけるインフラのCI/CD GitHub Actions CI/CDのツールはGitHub Actions(GHA)を採用しました。2020年当時は、GHAがまだサービスとしてリリースされて間もなく、マトリックスビルドができるなどの利点はあるものの、機能的に他のツールと比べて大きな優位性はないという社内評価でした。しかしながら、GitHubとの統合性の高さや将来性をふまえGHAを採用しました。 変更のあるインフラリソースのみをCIの対象とする工夫 前提として、プラットフォーム基盤のインフラを管理するGitHubリポジトリは、直下に「cloudformation」「k8s」「terraform」というそれぞれのIaCのディレクトリが存在する構成になっています(図2)。そしてそれぞれの配下に、環境(dev、stg、prdなど)やマイクロサービスなどに応じたディレクトリが存在します。 図2 プラットフォーム基盤のインフラに関する GitHubリポジトリの中身 この工夫では、OSSの tj-actions/changedfiles を利用し、Pull Request(以下、PR)で変更のあったディレクトリ情報のみを取得して、その情報を後続のJobへ渡すようにしました。後続のJobでは、変更のあったディレクトリに関するインフラリソースに関してのみ処理されます。結果として、すべてのインフラリソースに対してCIを実行していたころよりも、大幅にCIの時間を短縮できました。また、マイクロサービスがどれだけ増えても実行時間が長期化することはなくなりました。 Canary Releaseの導入 Canary Releaseとは Canary Releaseは、新しいバージョンのアプリケーションを段階的にデプロイする手法です。段階的にリリースすることで、新しいアプリケーションにバグがあった場合でもユーザーへの影響を最小限に抑えられます。また、リリース作業をする人(以下、リリーサー)の心理的負担も軽減できます。 ZOZO API GatewayとALBによるCanary Release 最初に、内製のZOZO API GatewayとAWSのApplication Load Balancer(以下、ALB)によるCanary Releaseをプラットフォーム基盤に導入しました。 ZOZO API Gatewayの加重ルーティング機能を利用して、各マイクロサービスのCanary Releaseを実現しました *1 。 そして、ALBの加重ルーティング機能を利用して、ZOZO APIGateway自体のCanary Releaseを実現しました。ZOZO API Gatewayの前段にはIngressリソースであるALBが存在します。ZOZO API GatewayのPrimaryリソースとCanaryリソースをそれぞれ用意し、それらをターゲットグループとして任意の加重率を設定します。加重率の設定は、リスト1のようにIngressリソースのannotationで行います。この設定を適用すると、AWS Load BalancerControllerはIngressリソースの変更を検知し、ALBのListener Ruleを更新します。 alb.ingress.kubernetes.io/actions.forward-external-traffic : | { "Type" : "forward" , "ForwardConfig" :{ "TargetGroups" :[ { "ServiceName" : "zozo-api-gateway-primary" , "ServicePort" : "80" , "Weight" : 90 } , { "ServiceName" : "zozo-api-gateway-canary" , "ServicePort" : "80" "Weight" : 10 } ] } } リスト1 ALBの加重ルーティング設定例 IstioによるCanary Release 前述したZOZO API GatewayとALBによるCanary Releaseを導入した後に、プラットフォーム基盤にIstioを導入しました。マイクロサービス間通信およびプラットフォーム基盤外への通信において、一貫した通信制御を提供するサービスメッシュが必要だったためです。 Istioの導入に伴い、Canary Releaseの手法を、IstioによるCanary Releaseに変えました。リスト2は、Istioの DestinationRule と Virtual Serviceにより、ZOZO API GatewayをCanary Releaseする設定例です。DestinationRuleでは、hostやsubsetsのprimaryとcanaryを定義します。VirtualServiceでは、destination ごとにDestinationRuleで定義したhostとsubsetを指定し、weightで加重率を設定します。この設定を適用すると、istiodにより自動的にistioproxyのconfigが更新され、ZOZO API Gatewayへのトラフィック加重率が変更されます。 apiVersion : networking.istio.io/v1alpha3 kind : DestinationRule metadata : name : destinationrule spec : host : zozo-api-gateway.ns.svc.cluster.local subsets : - name : primary labels : version : zozo-api-gateway - name : canary labels : version : zozo-api-gateway-canary --- apiVersion : networking.istio.io/v1alpha3 kind : VirtualService metadata : name : virtualservice spec : hosts : - zozo-api-gateway.example.com // 省略 http : - route : - destination : host : zozo-api-gateway.ns.svc.cluster.local subset : primary weight : 90 - destination : host : zozo-api-gateway.ns.svc.cluster.local subset : canary weight : 10 // 省略 リスト2 IstioによるCanary Releaseの設定例 ZOZO API Gatewayだけでなく、ほかのマイクロサービスも同様の方法で Canary Releaseできます。 Progressive Deliveryの導入 Progressive Deliveryとは Progressive Deliveryは、Canary Releaseも含め、より広範に新しいバージョンのアプリケーションを安全にリリースするための概念です。そこには、Blue/GreenデプロイメントやA/Bテストなどに加えて、Canary Releaseの自動化も含まれます。 前述のとおり、プラットフォーム基盤には IstioによるCanary Releaseを導入しました。しかしながら、Canary Releaseの進行における判断コストや加重ルーティングの進行、切り戻しなどの作業に関して運用コストが高いという課題がありました。そこで、Progressive DeliveryによるCanary Releaseの自動化を導入しました。 Progressive Deliveryツールの選定 Progressive Deliveryツールの候補としてArgo Rollouts、Spinnakerなどがありましたが、ZOZOのプラットフォーム基盤では次の理由からFlaggerを採用しました。 Istio との連携をサポートしており、自動でVirtualServiceの加重を変更できるため Datadogのメトリクス取得をサポートしており、判断基準に使用できるため いずれも、もともとは人が実施していた作業を自動化するだけなので、導入イメージが湧きやすかったという点が大きかったです。 Flagger Flaggerは、Progressive Deliveryを実現するKubernetes Operatorです。Flaggerを導入する主なメリットは2つです。 1つ目は、Canary Release作業の工数削減です。Flaggerはメトリクスの取得・分析、判断、加重率の変更作業などをすべて自動化してくれます。 2つ目は、Canaryリソースのコスト削減です。K8sのHorizontalPodAutoscalerの仕様上、min Replicasを0にはできません。したがって、Canary Release時以外でもCanaryのPodを最低1つは常時起動しておく必要がありました。しかし、Flaggerを導入すればそれが必要なくなります。 なお、プラットフォーム基盤では、Flaggerは次のように動作します(図3) *2 。 図3 プラットフォーム基盤におけるFlaggerの動作 加重変更 : VirtualServiceのweightを変更 スケールアウト/イン : K8sの設定を変更 メトリクス取得 : Datadogにクエリを発行 通知・アラート : Slackに通知 GitOpsの導入 GitOpsとは GitOpsは、Gitリポジトリを唯一の真実の情報源(Single Source of Truth)とし、K8sクラスターが自身の状態をGitリポジトリと同期するCD方式です。いわゆるPull型と呼ばれ、定期的にGitリポジトリをチェックし、変更があった場合はその変更を自動的に反映します。 一方、これまでプラットフォーム基盤にはCIOpsと呼ばれる、Push型のCD方式を導入していました。GitHubのPRのマージをトリガーにCIが走り、K8sクラスターへGHAがapplyをしていました。つまり、GHAにはapplyを実行するための強い権限が付与されていました。 CIOpsからGitOpsにすることで、CIはGHAの責務、CDはGitOpsツールの責務として分離し、GHAから強い権限を剥がせます。また、ワークフローのシンプル化や高速化も期待できます。 GitOpsツールの選定 ZOZOのプラットフォーム基盤ではFlux2を採用しました。類似のOSSとしてArgoCDが挙げられます。Flux2を採用した理由は、すでにプラットフォーム基盤で利用している FlaggerがFlux2と同じFlux Projectに所属しており、親和性の高さを期待できたからです。 Flux2 Flux2は、GitOps Toolkitと呼ばれるいくつかのコンポーネントにより動作します。たとえばSource ControllerやKustomize Controllerです。 Source Controllerは、Gitリポジトリ、Helmリポジトリ、バケットなどからアーティファクトを取得します。プラットフォーム基盤では、GitRepositoryというカスタムリソースを使用して、プラットフォーム基盤のインフラを管理しているGitリポジトリからK8sマニフェストを取得しています。 Kustomize Controllerは、Source Controllerによって取得したマニフェストをfetchし、それらをクラスターに適用します。Kustomizeという機能を利用して、マニフェストのカスタマイズやパッチ適用も行えます。Kustomizationというカスタムリソースを管理します。 リスト3は zozo-web-gateway というマイクロサービスのGitRepositoryとKustomizationの例です。GitRepositoryは、プラットフォー ム基盤のインフラを管理するGitHubリポジトリのreleaseブランチに対して、secretに保存されているSSH鍵を使って1分間隔でfetchする設定です。Kustomizationは、そのGit Repositoryの k8s/prd/zozo-web-gateway というディレクトリに存在するK8sマニフェストを1分間隔でfetchして、クラスターに展開する設定です *3 。 apiVersion : source.toolkit.fluxcd.io/v1 kind : GitRepository metadata : name : zozo-web-gateway namespace : zozo-web-gateway spec : interval : 1m0s ref : branch : release secretRef : name : zozo-web-gateway-flux-secrets-202307120000 url : ssh://git@github.com/xxx --- apiVersion : kustomize.toolkit.fluxcd.io/v1 kind : Kustomization metadata : name : zozo-web-gateway namespace : zozo-web-gateway spec : interval : 1m0s path : ./k8s/prd/zozo-web-gateway prune : false sourceRef : kind : GitRepository name : zozo-web-gateway suspend : false リスト3 GitRepositoryとKustomizationのYAML設定例 マイクロサービスのリリース方法の改善 改善前の課題 プラットフォーム基盤の構築当初は、masterブランチからreleaseブランチ宛のPR(以下、Release PR)を作成すると本番環境のCIパイプラインが動作し、PRをマージするとCDパイプラインが動作するという方法でリリースしていました。この手法のメリットは、リリース前の動作確認が可能で、リリース手順も簡単であることでした。しかし、1つのRelease PRで複数のチームの複数のマイクロサービスのリリースを管理することになるため、リリース時のチーム間での調整コストが発生したり、リリーサーを制限できなかったりなどの課題がありました。 課題を解決したCDパイプライン Required reviewers GitHubのEnvironmentsに、Deployment protection rulesという機能が存在します。たとえば、Required reviewersというruleを使用すると、そのEnvironmentに関して特定のチームや個人による承認を必須とします。図4は、pf_infra_sreというEnvironmentの例です。Web UI上で設定と承認ができます。リスト4のように、Required reviewsが設定されたEnvironmentをGHAのjobで指定すると、そのjobは承認されるまで実行されなくなります。 図4 Required reviewersの設定例 release-approval : runs-on : ubuntu-latest environment : name : pf_infra_sre リスト4 Environmentを使用したjobの例 このjobをデプロイ関連のjobのneedsに指定すれば、結果的にリリーサーを特定のチームや個人に絞れます。 OCIRepositoryに変更 Flux2のSource ControllerをGitRepositoryからOCIRepositoryに変更しました。OCI Repositoryは、GitRepositoryと同じくSource Controllerが管理するカスタムリソースの1つです。OCI(Open Container Initiative)互換のレジストリを参照します。プラットフォーム基盤にFlux2を導入した当初はOCIRepositoryという選択肢がありませんでしたが、この時点では利用できるようになっていました。 GitRepositoryのままだと、PRをマージしたタイミングでGitHubリポジトリを参照してデプロイされてしまいます。これでは、そのGitHubリポジトリの権限を持つスタッフは誰でもPRをマージできてしまうので、Required reviewersでリリーサーを制限しようとしても意味がありません。 そこで、Required reviewersでの承認後にOCIRepositoryへKustomizeマニフェストをpushするGHAのjobを構築しました。OCI RepositoryにはECRを利用し、KustomizationがECR上で管理されているマニフェストを取得して、クラスターへ適用します。また、OCI Repositoryを採用することで、SSH鍵の管理が不要になりました。 改善の結果 以上の改善により、リリーサーを制限できるようになりました。また、Release PRを使う必要がなくなったので、マイクロサービスのリリースにおけるこれまで発生していたチーム間での調整作業が不要になりました *4 。 おわりに 第2回では、ZOZOTOWNリプレイスにおけるIaCやCI/CD関連の取り組みを中心に紹介しました。ZOZOTOWNのリプレイスにより、「柔軟なシステム」「技術のモダン化」「開発生産性の向上」「採用強化」という4つの効果が期待できます。今回の取り組みの紹介を例に挙げると、AWSやK8sなどのクラウド化により、スケーリングなどの観点で柔軟なシステムの構築が可能になりました。IaCやCI/CDに関する複数のモダンな技術を取り入れ、開発生産性が向上しました。そして、これまでの取り組みをテックブログで公開したり、イベントで発表したりしました。その結果、テック業界におけるZOZOの技術プレゼンスが向上し、採用強化につながりました。リプレイス前は10名以下だったインフラチームには、現在SRE部として30名以上が所属しています。 SRE部はZOZOTOWNの成長のため、今回紹介した事例に限らず、さまざまな課題の解決や取り組みをしてきました。今後もZOZOTOWNのリプレイスを進めていきます。 本記事は、技術本部 SRE部 ECプラットフォーム基盤SREブロックの籏野 光輝と、技術本部 SRE部 商品基盤SREブロック ブロック長の堀口 真によって執筆されました。 本記事の初出は、 Software Design 2024年6月号 連載「レガシーシステム攻略のプロセス」の第2回「ZOZOTOWNリプレイスにおけるIaCやCI/CD関連の取り組み」です。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com *1 : 【ZOZOTOWNマイクロサービス化】API Gatewayを自社開発したノウハウ大公開! *2 : カナリアリリースを自動化!Flaggerでプログレッシブデリバリーを実現した話 *3 : CIOpsからGitOpsへ。Flux2でマイクロサービスのデプロイを爆速にした話 *4 : ついに最強のCI/CDが完成した 〜巨大リポジトリで各チームが独立して・安全に・高速にリリースする〜
アバター
はじめに こんにちは。DevRelブロックの @wiroha です。6月25日に「 ZOZO物流システムリプレイスの旅〜序章〜これまでとこれから 」を開催しました。ZOZOのエンジニアが物流システムリプレイス開発事例を紹介するイベントです。 登壇内容まとめ 弊社から次の3名が登壇しました。各発表はYouTubeにアップロードしてありますので、見逃した方やもう一度見たい方はぜひご覧ください。 コンテンツ 登壇者 分散メッセージングシステムを用いた新発送サービスのアーキテクチャ 作田 航平 ここがつらいよ 物流マイクロサービス 真田 洋輝 リプレイスを安心安全に 〜段階的リプレイスと等価比較〜 上原 駿 分散メッセージングシステムを用いた新発送サービスのアーキテクチャ www.youtube.com speakerdeck.com 作田からは発送サービスのアーキテクチャについて、リプレイス前後の比較やメリット・課題などを発表しました。分散メッセージングシステムを使用することで、基幹システムで障害が発生しても発送業務が継続できるように疎結合な新システムが構築できました。一方すべての課題を解決できたわけではなく、今後の課題も紹介されました。 ここがつらいよ 物流マイクロサービス www.youtube.com speakerdeck.com 真田からは物流マイクロサービスのつらいポイントにフォーカスした発表を行いました。実際に遭遇した課題とその対策を紹介し、Xでも「ありそう」「リアル」といった反響をいただきました。イレギュラーフローも含めた現場運用の理解やバランス感覚が必要で、物流システムの特性を感じました。 当日はたくさんの質問をいただきました。その中で回答できなかったものについて、回答を掲載します。 Q:ダウンフラグONはサーキットブレイカーのようなものですか? A:ダウンフラグ(サーキットブレーカーでいう状態)を自動で更新するか手動で更新するかの違いはありますが、同じようなものです。障害発生時にサーキットブレーカーのように通信自体を遮断するかどうかについてはまだ検討中ですが、障害発生時用の挙動に切り替わる点は同じです。 リプレイスを安心安全に 〜段階的リプレイスと等価比較〜 www.youtube.com speakerdeck.com 上原からは入荷リプレイスの概要と進め方について発表しました。入荷はもし止まってしまうとさまざまな業務に影響が出る重要な部分です。既存課題の解決をしながら安全にリプレイスするため、フェーズを分けて段階的に進めることにしました。旧実装と新実装後で等価比較する仕組みにより、安全にリプレイスを進めることができていました。 こちらの発表に関しても、当日いただいた質問への補足回答をいたします。 Q:モジュラモノリスのアプリケーションを複数人で開発する中で苦労されたエピソードがあれば教えてください。 A:モジュラーモノリス内には、発送と入荷があり、テストデータの準備に苦労しました。というのも発送と入荷の開発は、それぞれのチームに分かれており、お互いの開発状況をあまり把握しておらず、入荷用にデータを修正したり、逆に発送用にデータを修正したりしなくてはならないなどがありました。 Q:具体的にどういう部分(テーブルなど)が原因で基幹DBから分離ができないのでしょうか? A:具体的には在庫などでしょうか。在庫増減などは複雑な部分(ZOZOTOWNや発送などが関わる)なので、分離は難しいと考えました。 最後に 今回はたくさんの方にご参加いただき、ありがとうございました。質問も多数寄せられ、関心の高さが伺えました。今後もさまざまな分野でイベントを開催していきますので、ぜひご参加ください。 ZOZOでは一緒にサービスを作り上げてくれる仲間を募集中です。ご興味のある方は以下のリンクからぜひご応募ください。 corp.zozo.com
アバター
目次 目次 はじめに 企画LPの儚さ 企画LPをアーカイブする! 仕様 開発について 技術選定 ちょっと技術の話 スクラム開発 ユーザーの反応 おわりに はじめに こんにちは! ZOZOTOWN企画開発部・企画フロントエンド1ブロックの秋山です。ZOZOTOWNトップでは、セール訴求や新作アイテム訴求、未出店ブランドの期間限定ポップアップ、著名人コラボなどの企画イベントが毎日何かしら打ち出されています。私はそのプラットフォームとなる企画LPをメインに実装するチームに在籍しています。 チーム特性としては以下のようなものがあります! クリエイティブコーディング寄りの実装をする機会に恵まれやすい 普段のLP案件内では、エンジニアよりも他職種(デザイナー、PM、ビジネス職)と連携することが多い 企画LPの儚さ 定常的なページと違って、開催期間が定まっている企画LPの命は儚いものです。月に20本ほどの企画LPがローンチされては、人知れずクローズしていきます。 制作陣一同、タイトなスケジュールのなか心を込めてつくりますが、目に見えやすいかたちで実績が残りません。もちろん、GitHubリポジトリ上のコードやデザインラフとしてはある程度残っていきますが、色々と容量を圧迫するものは削除していきたい所存です。そうすると、成果物は段階的に消滅することになります。 削除されないうちにも、企画量が膨大すぎて過去事例を掘り当てるのに苦労しがち、という問題もありました。「あの企画のとき、どう対応してたっけ?」と聞かれても、咄嗟に目当ての情報を見つけられなかったりします。 企画LPをアーカイブする! ちょうどデザイナーチームでも似たような課題感を抱えており、この機会にきちんとプロジェクト化して「ZOZOTOWN LP ARCHIVE」という社内ツールをつくることにしました! まず、普段から親交のあるデザイナーと2人で構想・仕様の大枠を練ったあと、PMにもジョインしてもらい、ビジネス側にもヒアリングしながらプロジェクトを進めました。この起案メンバーでは週次定例を行い、全体の仕様や運用方法を詰める作業や、各部の進捗確認をしていきました。 仕様 リアルタイム検索(フリーワード、日付、タグ検索) LP画面のキャプチャ閲覧・DL LP概要閲覧 各種ドキュメントへのリンク 上記の機能をミニマムで盛り込み、デザイナー・PM・事業部・エンジニア等、企画に関わる多方面のスタッフにとって嬉しいツールを目指します。 これはZOZOTOWNの企画コンテンツ全体としてのポートフォリオでもあります。前提として新規LPは全てアーカイブされますが、せっかくなので過去1年程度の企画はがんばってデータを集め、反映することにしました! これだけでも、200本ほどの量になります。 開発について さらに! 開発メンバーとして、元気な新卒2年生とフロントエンド道25年の大ベテランの方に入っていただけることとなり、プロジェクトが本格始動しました! メンバーそれぞれが事業案件を持ちながらの実装且つ、SRE等の他部署と連携が必要だったので、空白の期間も挟みつつ進みました。全ての工程を完了するには2か月くらいかかりました。 技術選定 社内ツールということもあり、個人的に試してみたいものを自由に使うことができました! 会社のプロダクトで技術選定を自由に行える機会はそう多くないので、わくわくします。 採用したのは、Next.js(App Router)、Panda CSSなどです。Recoil、dayjs、js-file-downloadなどのライブラリも、適宜相談・検討しながら導入しました。ヘッドレスCMSはmicroCMSを利用しました。 ちょっと技術の話 工夫した点などを開発メンバーに聞いてみましょう! こんにちは。大ベテランということで技術選定や困ったことがあったときなどのフォローとアドバイスを担当した竹口です。大ベテランということで紹介されましたが、ZOZOへは入社したてといった状態でした。ということでメンバーの技術レベルや社内の開発環境はどういったものかを把握するところからはじめました。 本記事を読んでいただくとわかると思いますが、意欲高めのメンバーが揃っていたということもありNext.jsのApp RouterやPanda CSSといったZOZOTOWN開発本部内で採用実績のない技術を使っています。社内ツールということもあって積極的にやりたいことを自分たちで選択していきました。 今までのNext.jsのルーティング方式であるPages Routerと、比較的新しいApp Routerになってから変更されたところを調べながら、メンバーでああでもない。こうでもない。といった感じで進めました。最終的には社内のホスティング環境がサーバーでレンダリングできないのがわかり、急遽SSG化してGitHub Actionsでビルドしてデプロイといった大きな変更も。柔軟に対応できるメンバーだったのは日頃から密に対話して進めていたからできたことかと思います。 工夫したのはNext.jsによる開発経験が高めのチームではなかったためPull Requestに対してファイルや変数の命名、コンポーネントの粒度など細かく事例を示したり代替案を出したりしながらレビューしたところです。 入社したてで良いメンバーと楽しく進めることができたのは良い経験となりました。 こんにちは。LPアーカイブのホーム画面実装を担当したゾイです。ホーム画面のメイン機能はリアルタイム検索でしたので、私からはリアルタイム検索の機能実装について工夫した点を話したいと思います! リアルタイム検索には企画タイトルや企画の開催期間のような基本的な情報はもちろん、技術・デザイン・企画種などのタグ検索機能を実装する必要もありました。 SSRで検索機能を実装することも検討しましたが、以下の3点を踏まえCSRで検索機能を実装することにしました。 ヘッドレスCMSにリクエストを送りすぎると負担が重い SSRの場合ロード時間が長くなりUX的に良くない LPアーカイブサイトはSPAっぽくサクサク遷移させたい そのため、状態を共有するコンポーネントが多くてもパフォーマンス的に問題ない設計を工夫する必要があり、状態管理ライブラリーの導入を検討しました。 結果から言うと、今後のメンテナンスしやすさを優先し可読性が良いと思われる Recoil を導入することになりました。 Recoilを導入した理由は次の通りです。 Recoilは atom が更新されたら該当のatomを利用するコンポーネントのみ再レンダリングするため、パフォーマンスが良い。 Recoilは Redux のような他の状態管理ライブラリーに比べて可読性が良く、今後のメンテナンスがしやすい。 新しい状態を追加することが必要になった場合Recoilだと RecoilRoot の配下に新しいatomを追加するだけで済むので、Contextのように親コンポーネントを再度ネストする必要がない。 RecoilはSSR時にメモリーリークが発生している問題があるようです。しかし、LPアーカイブでは以下の点を踏まえ、Recoilを利用しても問題ないと思いました。 LPアーカイブでは主にCSRを利用している 社内ツールなのでのスケールも比較的に小さい それでは、具体的にRecoilを利用してリアルタイム検索機能をどう実装したかを話したいと思います。 1. RecoilRootの配下に親コンポーネントを包む <RecoilRoot > < Home /> < / RecoilRoot> 2. SSR時にヘッドレスCMSから取得したデータをatomに保存する // 全データ保存用のatom const [ result , setResult ] = useRecoilState ( resultAtom ) // ヘッドレスCMSからのレスポンスが更新された場合のみ状態を更新する useEffect (() => { if ( result ! == data ) setResult ( data ) } , [ data , result , setResult ]) 3. 各々のフィルターコンポーネントの入力状態を(2)とは別のatomに保存 // 企画タイトルでフィルターするコンポーネントの例 export const TitleSearch = () => { const [ _ , setSearchInput ] = useRecoilState ( searchInputAtom ) const handleChange = ( e: React . ChangeEvent < HTMLInputElement > ) => { setSearchInput (( prev ) => ({ ... prev , title : e . target . value , })) } return ( < input type = "text" placeholder = "例)ZOZOWEEK" onChange = { handleChange } /> ) } 4. ユーザーのインプットによりフィルターコンポーネントの値が更新されたら(2)のデータをフィルターし、新しい結果を表示する import { useRecoilValue } from 'recoil' import { searchInputAtom } from '@/recoil/atom/searchInputAtom' import { resultAtom } from '@/recoil/atom/resultAtom' // (2)で取得したデータ const result = useRecoilValue ( resultAtom ) const [ filteredResult , setFilteredResult ] = useState ( result ) // (3)で更新したフィルターのデータ const searchInput = useRecoilValue ( searchInputAtom ) useMemo (() => { const filters = [ // LPのタイトルでフィルターする場合の例 const lpTitleRegex = new RegExp ( searchInput . title || '' , 'i' ) ( item: ZozoLpArchiveContent ) => lpTitleRegex . test ( item . lpTitle ) ,   // 開催期間など、その他のフィルター(省略) ] const filteredResult = result . filter (( item ) => filters . every (( filter ) => filter ( item )) ) setFilteredResult ( filteredResult ) } , [ result , searchInput ]) 秋山の方からは、CSS in JSライブラリPanda CSSの使用感の紹介です。 ZOZOTOWNのフロントエンドリプレイス環境ではNext.jsのPages Routerを採用しており、CSS in JSライブラリにはEmotionを利用しています。 今回はNext.jsのApp Routerを使いたかったので、React Server Componentがデフォルトということになります。Emotionは'use client'したコンポーネントでしか利用できない(実装を開始した2023年末時点)ため、App Routerのよさを生かすにはEmotion以外の選択肢を探す必要がありました。 今後他のメンバーが参画する可能性もあり、ZOZOTOWNフロントエンドリプレイス環境と似た書き方をできる方がいい 公式ドキュメントが丁寧 Tailwindなども検討してみたのですが、上記の理由から、Panda CSSを採用しました! Panda CSSにはいろいろな機能が搭載されています。コンポーネント内で何度も使う基本のスタイルと、マイナーチェンジしたスタイルを使い分けて書くことができる“レシピ”という機能がとても便利でした! 基本のスタイルはbaseの中に書いていきます。variants内にはbaseから変化をつけたいスタイルを書いていきます。 const Detail = styled ( 'div' , { base : { width : '620px' , padding : '20px 15px' , } , variants : { isCatch : { true : { padding : '10px' , } , } , } , }) < Detail isCatch = { true } > 略 < / Detail> 今回は利用しませんでしたが、“パターン”という機能もあり、Box、Flex、Wrap、Aspect Ratioなど便利なレイアウトパターンが用意されています。これらは適宜importして使います。 Panda CSSを使っていて個人的に感じたデメリットとしては、CSSプロパティをキャメルケースで書かないといけない&値を’ ’で囲わないといけないのがちょっと面倒なことくらいです。 const Capture = styled ( 'li' , { base : { marginRight : '20px' , } , }) 今回はEmotion、styled-componentsっぽい書き方をしてみました。Tailwind風に書くことも可能で、導入にあたって他のCSS in JSからPandaに変更する場合の学習コストはかなり低そうです。ただし自由に書ける反面、規約などで書き方をチームで統一するなどしないと破綻しそうにも思いました。 あとこれは余談ですが、⌘+Sするたび、ターミナルに🐼が出現して地味に癒されます。 スクラム開発 開発佳境の時期にはデイリースクラムを行い、毎日アイデアを出し合ったり、疑問点を都度解消したりしていました。 デイリースクラムにはGitHubのProject機能を利用しました。タスク管理はGitHubのIssueで行いました。Issue・PRは細かめに分けて、実装・レビュー共にさくさく進められたと思います。また、Slackで専用チャンネルを作り、スクラム以外の時間でも気軽に相談できる環境となっていました。 ユーザーの反応 当初想定していたユーザー層は企画LP関係のスタッフに限られており、使われ方としては以下のようなものがありました。 ビジネス職が企画立案時や営業時の参考にする PMが効果検証や要件策定時の参考にする デザイナーがアイデア帳、過去施策との比較、振り返り用として利用する フロントエンドエンジニアが過去の実装例を掘り当てる ZOZOTOWN全体の企画LPポートフォリオとして活用する いざローンチしてみると、以下のような予想外の恩恵もありました。 バックエンドエンジニアが検索リクエスト数を振り返るのにも使える 新メンバーなどは見てるだけでも勉強になりそう デザインかっこよ 眺めているだけでも楽しい UIがよくてわかりやすい ZOZOTOWNサイトと似たUIなので、馴染みのある操作方法で違和感なく説明なくてもすぐ利用できた といった、デザインを絶賛する声も。 多方面から嬉しいリアクションをもらえたのも、横のつながりを駆使して、職種の垣根を超えて力を合わせた成果です! おわりに 普段の業務での成果物はトップダウンの事業案件にまつわるものがほとんどです。しかし、今回の「ZOZOTOWN LP ARCHIVE」は現場メンバーの「こんなものを作って便利にしよう!」というアイデアから生まれました! いろいろな立場のスタッフの協力を得て、無事に成果物のローンチ・運用まで漕ぎ着けられました! それにしても、発案・構想・設計・実装・運用フロー作成と、最初から最後まで育てた成果物には愛着もひとしおです!「自分たちの城」感がすごいですね! 尚且つ、色々な部署にとって、小規模だけど有益なプロダクトである(自己満じゃない)というのがまた良かったです。 株式会社ZOZOでは、アイデア次第でこんなふうに自由度の高い開発経験をできる環境が整っています! ご興味のある方はぜひ、ご応募お待ちしております! corp.zozo.com
アバター