TECH PLAY

株式会社ZOZO

株式会社ZOZO の技術ブログ

938

はじめに こんにちは。データシステム部推薦基盤ブロックの新卒1年目の上國料( @Kamiko20174481 )と、5年目の宮本( @tm73rst )です。私たちのチームでは、ZOZOTOWNの推薦システムを開発しています。2024年7月のテックブログでは、ZOZOTOWNのホーム画面に表示される「 モジュール 」の並び順をパーソナライズする取り組みを紹介しました。 techblog.zozo.com モジュール とは、トレンドやキャンペーンなど特定のテーマに基づき商品群を表示する枠のことです。 モジュールの内容は企画チームの意図に基づいて設定されますが、ユーザーごとに関心や求めるコンセプトが異なるため、一律の表示ではなく最適な順序で並べることが重要です。 このように、ユーザーごとに適したモジュールを配置する仕組みを モジュールパーソナライズ と呼びます。本記事では、このモジュールパーソナライズの精度を向上させるために実施した「モジュールの多様性向上」と「受注系指標の改善」についてご紹介します。 パーソナライズ機能や推薦システムの開発に携わる方々の参考になれば幸いです。 目次 はじめに 目次 モジュールパーソナライズ Two-Towerモデルによる推薦 モジュールパーソナライズの仕組み モジュールパーソナライズをリリースしてわかった課題 1. 多様性の欠如 パーソナライズロジックの過度なクリック履歴依存 モジュール間での商品の重複表示 2. 購入促進の難しさ 改善のアプローチ パーソナライズの方針 Two-Towerモデルの精度向上 後処理の改善・導入 A/B テスト 概要 リリースを行う際の実験群の選定方針 結果 受注系指標 多様性に関する指標 Treatment1(クリック最適化モデル + 後処理)について Treatment2(カート投入最適化モデル)について Treatment3(カート投入最適化モデル + 後処理)について 今後の展望 パーソナライズのリアルタイム化 商品の並び順のパーソナライズ 推薦指標の定式化 最後に モジュールパーソナライズ ZOZOTOWNではユーザーの興味を引く商品を効果的に訴求するため、ホーム画面に多様なモジュールを表示しています。しかし、すべてのユーザーに同じモジュールを一律に表示すると、ユーザーによっては興味の薄いコンテンツが前面に出てしまい購買促進につながりにくいという課題がありました。 そこで、 ユーザーの嗜好に応じてモジュールの並び順を最適化する「モジュールパーソナライズ」 を導入しました。本手法の中核となるのが Two-Towerモデル です。 Two-Towerモデルによる推薦 モジュールパーソナライズは、以下の図に示す Two-Towerモデル を利用して構成されています。 Two-Towerモデルでは、 ユーザーの特徴 と 商品の特徴 をそれぞれ個別のニューラルネットワーク(タワー)で学習し、共通の埋め込み空間へマッピングします。類似した特徴を持つユーザーと商品が近い位置に配置されるよう学習されるのが特徴です。 具体的には、 ユーザーの属性 (例:年齢、性別、閲覧履歴など)を元にユーザー埋め込み(ユーザーの嗜好を表すベクトル)を計算し、 商品の属性 (例:カテゴリ、ブランド、価格帯など)を元に商品埋め込み(商品の特徴を表すベクトル)を計算します。 推薦する際は、ユーザー埋め込みと候補商品の埋め込みのコサイン類似度を算出し、ユーザーの嗜好に合った商品をランキング化します。 モジュールパーソナライズの仕組み ZOZOTOWNのホーム画面における推薦対象は 商品単体ではなく、複数の商品を含むモジュール です。そこで、以下の手順で モジュール単位のランキング を行います。 各商品の推薦スコアを算出(Two-Towerモデルによるスコアリング) モジュール内の商品のスコアを集約 統合スコアが高い順にモジュールを並び替え このフローにより、ユーザーの興味に合ったモジュールが上位に表示され、より最適なコンテンツを提供できるようになります。 モジュールパーソナライズをリリースしてわかった課題 モジュールパーソナライズの導入により判明した課題は、 多様性の欠如 と 購入促進の難しさ の2点です。 1. 多様性の欠如 多様性はやや抽象的な概念ですが、ここでは以下の2つの観点で課題を整理します。 パーソナライズロジックの過度なクリック履歴依存 ユーザーの商品クリック履歴は、特定のカテゴリや同一商品に偏る傾向があります。これは購入検討時に類似商品を比較したり、関心のあるカテゴリを集中的に探索するといった行動が要因です。しかし、従来のモデルは 直近のクリックデータに最適化 されているため、その履歴に強く依存してしまうリスクがあります。その結果、最近クリックしたカテゴリの商品ばかりが上位に表示され、ユーザーの探索の幅が狭まります。また、一時的に興味を持った商品や意図せずクリックした商品が学習へ影響を与え、ユーザーの本来の嗜好を正確に捉えにくくなる可能性もあります。 モジュール間での商品の重複表示 従来のZOZOTOWNのホーム画面では、同じ商品が複数のモジュールにわたって重複表示されるケースがありました。 特に、モジュールごとに横スクロールなしで表示される商品群は、サイト全体の印象やユーザーの探索行動に大きく影響を与えるため、重要な要素です。本稿では、これらの商品群を「ファーストビュー」と定義します。 なお、一般的に「ファーストビュー」は 縦スクロールなしで画面上部に表示される範囲 を指すことが多いですが、本稿では 各モジュール内で横スクロールなしに表示される商品群 を意味する点にご注意ください。 ファーストビューで同一商品が繰り返し表示されると、以下の問題が発生します。 商品の偏り :特定の商品やカテゴリへの露出が過剰になり、多様な商品が目に留まりにくくなる 新しい発見機会の減少 :同一商品の露出が増えることで、ユーザーが新しい商品に出会う機会が制限される 探索意欲の低下 :同じ商品が目立つことで新鮮さが薄れ、ユーザーの縦スクロールや横方向への探索意欲が低下しやすくなる このため、ファーストビューでの商品重複は、ユーザー体験の質を低下させる要因となっています。 2. 購入促進の難しさ 従来のロジックを用いたA/Bテストの結果、受注に関する指標のさらなる向上の可能性が明らかになりました。特に、パーソナライズロジックがクリック履歴に強く依存していることで、ユーザーの本来の嗜好を正確に捉えきれていない可能性があります。この影響により、推薦されたモジュール内の商品がユーザーの購入意欲を十分に喚起できず、購買へつながりにくい状況になっていると考えます。 改善のアプローチ パーソナライズの方針 課題を解決するために、図に示すように以下の対応をします。 Two-Towerモデルの精度向上 後処理の改善・導入 これにより、ユーザーの嗜好に即したモジュールのスコアリングを行い、後処理によってより多様な商品がユーザーに届くよう順序を最適化し、上述の課題を解決します。ここでいう「ユーザーの嗜好に即した」とは、 購入へつながる確度が高い ものを指します。 Two-Towerモデルの精度向上 今回モデルを改善する目的は、「ユーザーの短中期的な嗜好を学習し、それに即した多様な推薦をし最終的に購入に促す」ことです。主な改善ポイントは、以下の2点です。 最適化指標の変更 UserTowerの特徴量の変更 まず、 最適化指標 については、従来の「クリック」から「カート投入」に切り替えました。クリックは単なる興味関心の指標に過ぎないのに対し、「カート投入」はユーザーが実際に購入を検討した行動であり、より購買意向に近い嗜好を反映していると考えたためです。一方で、「購入」を最適化指標として採用しなかったのは、データの絶対量が少なく、学習が不安定になる懸念があったためです。そのため、 クリックよりも購買意向に近く、かつ十分なデータが確保できる「カート投入」 を最適化指標として選定しました。 次に、 UserTowerの特徴量 については、これまで「直近のクリック履歴(時系列データ)」のみを利用していましたが、 過去1か月のクリックログを活用する方針に変更 しました。中期的なログを学習することで、ユーザーの継続的な嗜好を捉え、より多様な商品を推薦しやすくなると考えています。具体的には、「商品」「ブランド」「カテゴリ」「カラー」などの複数の軸でユーザーの関心が高い要素を集計し、上位のものを抽出します。さらに、これらの情報に時系列の重み付けをし、直近の行動と過去の一貫性をバランスよく反映できるよう工夫しています。 細かな変更点としては、データ分割におけるトランザクション期間を1週間に設定し、訓練・検証・テストデータそれぞれで四分位範囲を用いたフィルタリングを行いました。これにより、学習から評価までの過程でデータ分布が大きく偏る(Skew)リスクを最小限に抑えつつ、分割の一貫性を高めることができます。 以下では、従来モデルを クリック最適化モデル 、改善後のモデルを カート投入最適化モデル と呼ぶことにします。 後処理の改善・導入 後処理を導入する目的は、「Two-Towerモデルでスコアリングされたモジュール群から、重複や偏りを抑えつつ多様な商品をユーザーに届け、体験価値を向上させる」ことです。主な改善ポイントは、以下の3点です。 モジュールのスコア計算手法を変更 類似モジュールの連続表示を抑制 関心度に応じたモジュールの再配置 まず、 モジュールのスコア計算手法の変更 についてです。従来は、モジュール全体の商品を対象にスコアを計算していました。今回はホーム画面の縦スクロールというUI特性に合わせ、 ファーストビューのみを対象にスコアを計算するよう変更 しました。これにより、ユーザーが実際に注目する領域に基づいた評価が可能となり、より適切なモジュールの順位付けが期待できます。 次に、 類似モジュールの連続表示の抑制 についてです。従来、同一商品を含むモジュールが連続して上位に表示されることがありました。これは、異なるモジュール間でスコアを独立に算出するため、高スコアの同一商品を含むモジュールが複数上位に配置されやすかったことが原因です。そこで、 各モジュールのファーストビューに同じ商品が多く含まれている場合、スコアが低いモジュールの順位を下げる仕組みを導入 しました。これにより、ホーム画面上での商品の重複表示を抑え、多様性の向上を図ります。 最後に、 関心度に応じたモジュールの再配置 についてです。埋め込みを用いたスコアリングには予測誤差が生じるため、ユーザーの本質的な嗜好を完全には捉えきれない可能性があります。そこで、 直近でユーザーが閲覧したもののクリックしなかったモジュールは迅速に下位へ移動させ、新しいモジュールを上位に推薦する仕組みを導入 しました。これにより、ユーザーの関心に即したモジュールを素早く提示し、飽きを防ぎつつ最適なモジュールを推薦できます。 A/B テスト 概要 新ロジックの効果を評価するために、ZOZOTOWN会員を対象として 5週間のA/Bテスト を実施しました。現行ロジックと前述のアプローチを組み合わせた 以下の4つのパターン を用意しました。また、テスト対象のユーザーが各実験群に均等に振り分けられるよう、4つのグループそれぞれに25%ずつ割り当てています。 実験群 説明 Control クリック最適化モデル Treatment1 クリック最適化モデル + 後処理 Treatment2 カート投入最適化モデル Treatment3 カート投入最適化モデル + 後処理 リリースを行う際の実験群の選定方針 A/Bテスト開始前に、プロジェクトメンバー間でリリース判断の基準を明確化しました。具体的には、受注系指標だけでなく、多様性に関する指標も考慮することを原則としました。ただし、多様性が向上してもKGIやKPIが悪化する場合はリリースを見送る方針とし、全メンバーで認識を統一しました。 結果 A/Bテスト結果のサマリを受注系の指標と多様性に関する指標に分けて以下に示します。 受注系指標 指標 備考 T1/C 比(%)  T2/C 比(%)  T3/C 比(%)  ホーム画面訪問者の受注金額 ホーム画面にランディングしたユーザーの合計受注金額 99.9 100.2 100.1 モジュール経由の受注金額 モジュールに表示された商品の合計受注金額 100.2 100.9 101.2 モジュール経由の受注商品点数 モジュールに表示された商品の合計受注商品点数 100.4 101.2 100.9 モジュール経由のカート投入数 モジュールに表示された商品の合計カート投入数 100.2 101.4 101.3 多様性に関する指標 指標 備考 T1/C 比(%)  T2/C 比(%)  T3/C 比(%)  モジュール掲載商品のユニーク閲覧数 表示商品の合計ユニーク閲覧数 101.8 99.8 101.4 モジュール掲載商品のユニーククリック数 表示商品の合計ユニーククリック数 100.5 100.0 100.4 モジュール間の閲覧商品の重複率 モジュール間で同じ商品を閲覧した割合 92.0 98.4 91.5 モジュールの重複率 前日と比較して同じモジュールが表示された割合 98.3 106.3 106.1 商品の多様性 ユーザーごとのユニーク閲覧商品数 / 全体の閲覧商品数 100.4 100.0 100.3 カテゴリの多様性 ユーザーごとのユニーク閲覧カテゴリ数 / 全体の閲覧商品数 100.3 101.3 101.6 ブランドの多様性 ユーザーごとのユニーク閲覧ブランド数 / 全体の閲覧商品数 99.8 99.0 99.2 Treatment1(クリック最適化モデル + 後処理)について 従来のロジックでは、特定の商品や子カテゴリに偏りが生じ、新しい商品の発見機会が制限されていました。しかし、商品の多様性を考慮した後処理を追加した結果、ユーザーが閲覧する商品のバリエーションが広がり、 多様性に関するほぼすべての指標が飛躍的に向上 しました。さらに、クリック数や閲覧ページ数の増加も確認され、ユーザーが新しい商品を発見しやすくなったことが示されました。このことから、後処理の導入が興味喚起の向上に寄与したことが実証されたと考えられます。 一方で、 受注系指標への大きな影響は見られませんでした 。その要因の1つとして、モジュールのスコアリングに使用した既存のクリック最適化モデルが、直近のクリックログに引っ張られ偏ったカテゴリや商品を推薦していた点が挙げられます。そのため、後処理を追加しても一部のケースではユーザーの嗜好を十分に反映できていなかった可能性があります。 Treatment2(カート投入最適化モデル)について カート投入最適化モデルは、「短中期的な嗜好を学習し、多様な推薦をすることで購入を促進する」という設計意図を実現し、 受注系指標が大幅に向上 しました。短中期的な嗜好の学習により、カテゴリや商品の偏りを抑えながら一貫性のある推薦をし、ユーザーに幅広い選択肢を提供した結果、売上向上に寄与したと考えられます。 一方で、「ブランドの多様性」は減少しましたが、定性評価ではユーザーの関心が高いブランドの露出が増加し、過去のクリック履歴に含まれていないブランドも、嗜好に基づく推薦が適切に機能していることが確認されました。 また、「モジュールの重複率」は増加しましたが、A/Bテスト結果を基に追加分析を行ったところ、重複したモジュールは売上と正の相関を持つことが判明しました。この結果から、モジュールの重複は必ずしもネガティブな影響を与えるとは言えず、むしろユーザーに適切な商品を継続的に提示することが有効な手段となり得ると考えられます。 しかし、 商品の多様性やユニーク商品に対する閲覧数・クリック数の向上は確認されませんでした 。この要因として、短中期的な嗜好を学習することで、ユーザーの過去の行動に基づいた推薦が強化され、一貫性のある提案が可能になったことが挙げられます。これにより、関連性の高い商品が表示されやすくなる一方で、ログの即時性が低下し、推薦内容の変化が抑制される傾向があると考えられます。 Treatment3(カート投入最適化モデル + 後処理)について カート投入最適化モデルと後処理を個別に適用した場合、それぞれ特定の指標には良い影響を与えたものの、一部の指標にはネガティブな影響も見られました。しかし、両者を統合したモデルでは互いの弱点を補完し合い、全体的なスコアの向上が確認され、各指標のバランスを取ることができました。 結論として、 カート投入最適化モデルによりユーザーの嗜好に即したスコアリングをするとともに、後処理でより多様な商品がユーザーに届くよう順序を最適化することで、受注系指標と多様性に関する指標の向上を両立させることができました 。 この結果を踏まえ、受注系指標および多様性に関する指標を総合的に評価した上で、Treatment3のリリースが決定しました。 今後の展望 モジュールパーソナライズのTwo-Towerモデルをカート追加最適化モデルに切り替え、後処理を追加することで、 多様性と受注系の指標の向上 を実現しました。今後は以下のような取り組みを行う予定です。 パーソナライズのリアルタイム化 商品の並び順のパーソナライズ 推薦指標の定式化 パーソナライズのリアルタイム化 カート投入最適化モデルへの切り替えにより、モジュール内の商品の重複率が増加し、ユニークな閲覧数やクリック数が減少しました。これは、短中期的な嗜好を学習した結果として生じたものであり、意図した効果ではあるものの、さらなる改善の余地があります。 現行のシステムでは、推論パイプラインが1時間に1回実行され、ユーザーのパーソナライズ情報が更新されます。従来のロジックでは、更新頻度を上げると直近の閲覧履歴に過度に影響され、ユーザーの短期的な嗜好が強く反映されるという課題がありました。しかし、カート投入最適化モデルでは、中期的な嗜好も考慮することで、短期的な変動に左右されにくい推薦が可能になっています。 この特性を活かし、リアルタイムでのモジュール更新を導入すれば、短期嗜好への過剰適応を抑えつつ、ユーザーの関心に応じた商品を最適なタイミングで推薦できると考えています。 商品の並び順のパーソナライズ 現在、一部のモジュールではユーザーごとに商品がパーソナライズされているものの、多くのモジュールでは共通の商品が表示されています。また、モジュールパーソナライズだけでは、ユーザーごとの微妙な嗜好の違いを十分に捉えきれない場合があります。例えば、同じカテゴリの商品を表示するモジュールであっても、ユーザーごとに好むブランド、価格帯、デザインの傾向は異なります。 現行のシステムでは、表示する商品の選定は行われているものの、ユーザーごとの関心度に応じた並び順の最適化は行われていません。そのため、より個々の嗜好に適した推薦を実現するには、商品の表示順をユーザーごとに最適化することが重要です。現在、このパーソナライズ機能の開発を進めています。 推薦指標の定式化 今回の取り組みでは多様性を重視しましたが、今後は新規性やセレンディピティなど、さらなる推薦指標の改善にも取り組む予定です。推薦指標は、企業やサービスごとに定義が異なるため、適切な指標の選定が重要になります。 そこで、私たちが目指すZOZOTOWNのホーム画面に最適な推薦指標を定義し、それをどのように改善につなげるかをチーム内で議論しながら具体化していきます。 最後に 本記事ではZOZOTOWNのホーム画面に表示するモジュールの並び順をパーソナライズするシステムとその効果について紹介しました。今回取り上げた部分以外にも改善すべき箇所が大量にあるので、これからも1つずつ改善していくことでユーザーにとってより良いZOZOTOWNを提供できるよう邁進していきます。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com
アバター
はじめに データシステム部検索技術ブロックの内田です。私たちはZOZOTOWNの検索精度改善や検索システムの運用効率化のためのメンテナンスなどに取り組んでいます。 これまでテックブログでご紹介してきた通り、ZOZOの検索改善チームではランキング学習(Learning to Rank)やクエリの意図解釈、ベクトル検索の導入など、比較的モダンなアプローチでZOZOTOWNの検索改善に努めてきました。先進的な技術を調査し、サービスの開発に応用することはサービスの品質改善において重要な取り組みです。 techblog.zozo.com しかし、モダンなアプローチをとる一方で、検索エンジンのベーシックな設定についてはメンテナンスする機会が徐々に減少していきました。設定内容や経緯を把握している開発メンバーの割合も減っていき、このままだと誰も触れない謎の設定になってしまうリスクがあったため、一度見直しを実施することにしました。 この記事では、全文検索エンジンの基礎的な設定について見直しを始める際に意識した内容を紹介します。これから検索システムを構築する方、全文検索エンジンの設定について学ぶ方の参考になれば幸いです。 全文検索システムの設定の基礎 全文検索とは、与えられたクエリにマッチするテキスト情報を持つ文書を発見する技術です。Luceneを代表とする全文検索エンジンは、文書に含まれるテキスト情報を解析して索引(インデックス)を構築することでクエリにマッチする文書の発見の高速化を実現しています。 ここで重要となるのが、 文書中のテキスト情報からインデックスに登録されるトークンをどのように抽出するか という問題です。トークン抽出の品質は、全文検索全体の精度に大きく影響を与えます。そのため、トークン抽出を担うアナライザー(解析器)の設定は非常に重要です。 Apache SolrやElasticsearchなどのLuceneベースの全文検索エンジンでは、アナライザーは以下のような三層構成をとります。入力が想定されるクエリや文書に含まれるテキスト情報に応じてそれぞれを設定します。クエリに対して適用するアナライザーと文書中のテキスト情報に適用するアナライザーは別々に設定できますが、出力されるトークンの一致が取れるよう設計する必要があります。 Character filter:入力されたテキストに対する加工処理 Tokenizer:トークンへの分割処理 Token filter:トークンに対する加工処理 テキスト情報に応じたアナライザー設定 N-Gramと形態素解析 先述した通り、検索エンジンに文書を登録する際にはテキスト情報をトークンに分割する必要があります。英語など西洋の多くの言語では空白文字で単語が区切られますが、日本語の文章は明確な区切り文字を持ちません。そのため、日本語で構成される文書に対しては、N-Gramと形態素解析に基づいたトークン分割が採用されることが多いです。ZOZOTOWNの検索システムでも検索対象フィールドに対しては、この2種のトークン化手法に基づいたアナライザーが設定されています。 N-Gramは文章をN文字ごとに区切ります。一方で形態素解析は辞書に基づき意味のある単位で文字列を区切ります。 利点 欠点 N-Gram ・検索漏れが少ない(再現率が高い) ・辞書が不要 ・検索ノイズが多い ・無差別に分割するため、インデックスのサイズが肥大化しがち ・N文字以下の文字列入力に対してトークンを出力できない 形態素解析 ・検索ノイズが少ない(適合率が高い) ・インデックスのサイズが小さめ ・品詞に基づくフィルタリングや変形が可能 ・辞書が必要 ・辞書に過不足があり単語を正しい位置で区切れなかった場合に検索精度が低下する (主に未知語による検索漏れが起こりやすい) この通り、N-Gramと形態素解析はそれぞれ得意とする領域が異なります。どちらを適用するかは取り扱うテキスト情報の特徴に合わせて検討する必要があります。例えば、ZOZOTOWNで取り扱う商品のテキスト情報には以下のような特徴があります。 商品名:主に名詞で構成されるため、品詞に基づく処理を必要としない。ファッション系の商品名はカタカナの連語で構成される固有名詞(未知語)であることが多く、形態素解析器では正しい位置で区切ることが難しい。 商品説明文:入力テキストが大きく、比較的多量のトークンが抽出される。日本語の文章で記述されていて、検索上意味を持ちにくい助詞などの形態素を数多く含むため品詞によるフィルタリングを行いたい。 これら2つの手法を適用したフィールドを横断的に検索するクエリを用いることで検索の精度を向上させることが出来ます。代償として検索処理の計算コストやインデックスのサイズおよび構築時間が膨らむため、それに見合った精度向上が期待できるかは検証するとよいでしょう。また、それぞれのフィールドに対するスコア重み付けの調整も重要です。 辞書 現在、ZOZOTOWNの検索システムに設定されている各種辞書の見直しに取り組んでいます。見直しを進める中で、逆に検索精度面に問題を発生させてしまっている設定が見受けられました。その一例を紹介します。 形態素解析器のユーザ辞書 ZOZOTOWNの検索システムは形態素解析器としてKuromojiを採用しています。Sudachiなど他の形態素解析器では挙動が異なる可能性があるためご注意ください。 形態素解析器の辞書は形態素解析処理の根幹となるため、検索の精度に大きく影響を及ぼします。文書中に出現する語句を多くカバーするためにユーザ辞書に単語を片っ端から追加してしまいがちです。ZOZOTOWNの検索システムの形態素解析ユーザ辞書にも過去に様々な語句が登録されていました。 ここで注意が必要なのが、ユーザ辞書に登録された語は優先的に区切られやすくなるというKuromoji tokenizerの挙動です。例えば、ZOZOTOWNではアクセサリーの略語である「アクセ」がユーザ辞書に登録されていました。これにより、「アクセ」を部分文字列に含む文章は概ね「アクセ」トークンを含むように分割されるようになります。結果として「アクセサリー」を意図して「アクセ」というクエリで検索したユーザに対して、全く関連のない商品を返してしまうことがありました。 GET _analyze { " tokenizer ": { " user_dictionary_rules ": [ " アクセ,アクセ,アクセ,カスタム名詞 " ] , " type ": " kuromoji_tokenizer " } , " text ": [ " アクセント " ] } // Output: { " tokens ": [ { " token ": " アクセ ", " start_offset ": 0 , " end_offset ": 3 , " type ": " word ", " position ": 0 } , { " token ": " ント ", " start_offset ": 3 , " end_offset ": 5 , " type ": " word ", " position ": 1 } ] } このような挙動は辞書に登録された短い語句で頻繁に発生していました。そのため、文書に登場する未知語を片っ端からユーザ辞書に登録するのではなく、以下のケースに絞って登録する方針で整理を進めています。 語句が意図せず区切られてしまっている場合、区切らせないためにその語句を登録する 途中で区切られてほしい語句(フレーズ)が区切られない場合、その語句の区切り方を登録する シノニムの辞書 検索の再現率を向上させるためにSynonym (graph) token filterを利用することがあります。辞書に語句の同義関係を記述することで、検索時にトークンの拡張を行えます。 Synonym token filterは、元のトークンとシノニム拡張で得られたトークンを同等の重みで扱う点に注意が必要です。スコア計算式によっては、拡張後のトークンにマッチした文書が元のトークンにマッチした文書よりも高いスコアを持つことがあります。ヒット件数を増やしたいという動機で軽率に同義関係を増やしてしまうと、検索の精度に悪影響を及ぼす危険性があります。 例えばZOZOTOWNでは、シノニム辞書に「リング」と「指輪」が同義語として登録されていました。「指輪」で検索した際に、ピンキーリングなどの指輪商品をヒットさせたかったものと思われます。これら2つの語は似た意味を持ちますが、厳密には同義ではなく「リング」は指輪に限らず輪形のもの全般を指す語です。結果として、指輪を探すユーザに対して、イヤリングやスマホリングなどを返してしまう不具合を発生させてしまっていました。 シノニム拡張は再現率を向上させるのに有用ですが、スコアの制御ができないため、同義ではなく類義の語まで適用範囲を拡張してしまうと思わぬ結果を招くリスクがあります。類義語でクエリを拡張したい場合は、アナライザー内で処理するのではなくクエリの構築時に重み付きのOR条件で記述する方が制御が容易になると思われます。ZOZOTOWNでもシノニム辞書で扱っている語句を整理して、一部はクエリ構築時に展開処理を施す方針を検討しています。 まとめ 本記事では、Elasticsearchのアナライザー設定について解説しました。また、ユーザ辞書の登録方針やシノニム拡張のリスクについて具体例を交えて説明しました。 ZOZOでは今後も引き続き、有益な検索結果を提供できるよう検索機能の改善に努めていきます。 おわりに ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com
アバター
はじめに こんにちは、計測システム部フロントエンドブロックの平田です。 私が所属する計測フロントエンドブロックでは ZOZOMETRY というスマートフォンを用いて身体計測し、計測結果を3Dモデルやデータとして可視化し、Web上で管理できるtoBサービスを開発しています。 このサービスのフロントエンドではReact(Next.js)を採用しています。更にそれらの知見を深めるために、NYで開催されたJSNation、React Summit US 2024、そしてWorkshopに参加してきました。 この記事では現地参加ならではの経験や、参加したセッションへの考察、Workshopで学んだ内容などを紹介していきます! はじめに JSNationとReact Summitとは? Day 1 - JSNation Day 2- React Summit After Party 気になったセッションについて Chrome DevTools 2024: Debugging and Performance Optimization for React Developers AIアシスタントによるデバッグの向上 React Developer Toolsによる拡張性と最適化 ローカルオーバーライド機能での実験と検証 パフォーマンス最適化ツールの活用 3Dレイヤーとアニメーションデバッグ まとめ Green Bytes: How Enhancing Web Vitals Contributes to Environmental Sustainability まとめ Day 3 - Workshop ワークショップ内容 Server Components Client Components Client Router Server Actions イベント全体を通じて感じたこと 最後に JSNationとReact Summitとは? JSNation と React Summit は、JavaScriptおよびReactに特化した国際的なカンファレンスで、 GitNation が主催しています。現地参加とオンライン参加のハイブリッド形式で開催され、それぞれJavaScript、React.js関連の様々なセッションが行われます。また、ネットワーキングやワークショップ、アフターパーティーなど、多彩なプログラムが提供されています。イベントでは最新の技術の動向を学び、世界中の開発者と交流する絶好の機会を体験できます。 様々な国や地域で開催されていますが、今回は2日間を通じてアメリカで開催されたJSNation、React Summitに参加してきました。ZOZOからは昨年オランダ・アムステルダムで開催された同イベントにもエンジニアが参加しており、今回も引き続き参加できることになりました。そして3日目は、1日通しで行われるワークショップに参加してきました。 日付 時間帯(EST) イベント 場所 2024/11/18 9:00 - 17:00 JSNation Liberty Science Center 2024/11/19 9:00 - 18:00 React Summit Liberty Science Center 2024/11/19 19:00 - 22:00 After Party Barcade & Hudson Hound 2024/11/20 9:00 - 18:00 Workshop Double Tree by Hilton それでは早速参加したそれぞれのイベントについて詳しく紹介していきたいと思います。 Day 1 - JSNation 公式サイト には「In New York」や「Manhattan views」と大きく書かれています。しかし実際の会場はNew York Cityの隣、Jersey CityにあるLiberty Science Centerです。間違えないようにしましょう。 会場に向かう途中で見た朝のマンハッタンの風景 最寄駅から会場に向かう途中の道 丁度紅葉シーズンだったので木々が秋色に色づいてました 会場のLiberty Science Center外観 会場に到着してチェックインすると、ステッカーやJSNationオリジナルロゴ入りのマグカップをもらいました。 階段もJS仕様になっています。 開場してすぐの8時過ぎに到着したのでまだ人がまばらでした。 Visitorバッジ。ラストネームのスペルを間違えて登録していました。 このQRコードには個人情報が紐づけられていて、イベント中のネットワーキングに大活躍します。このバッジは2日間を通して使用するため紛失しないようにしましょう。ちなみに私は2日目に新しいバッジが配布されると思い込みホテルに忘れてしまいました。 砂糖たっぷりの喉が焼けるほど甘いドーナツ。会場にはドーナツやペイストリー、フルーツなどが用意されており、朝ごはんを心配する必要はありませんでした。 参加者の中には会社のチーム単位で参加している人もいれば1人で参加している人もいました。私は近くにいたサウスカロライナ、ニューヨーク、ドイツから来た参加者と仲良くなり、そのまま一緒に行動しました。 軽く自己紹介をして、どんな仕事をしているか、どんな技術を使っているか、そしてどんなモチベーションでこのイベントに参加しているのかを話しました。自己紹介の際に自分が携わっているZOZOMETRYについて話したら、皆が口々に「クール」と言ってくれてちょっと嬉しかったです。 また、会場で出会ったほとんどの人が使っている技術はReactでしたが、ドイツ人エンジニアは出会った人の中で唯一Angularを使っていました。その人の会社は現在Angularを使って開発しているだけでなく、ドイツ国内でAngularの使い方を教えることもしているそうです。 そんなこんなで盛り上がっているうちにオープニングの時間となり会場に移動しました。 オープニングの様子。(写真提供:GitNation) 司会グループがラップを披露していました。(写真提供:GitNation) 最初のセッションの様子です。(写真提供:GitNation) 会場では、メイン会場ともう1つの会場で同時に2つのセッションが行われ、参加者は興味のあるセッションを選んで参加する形式でした。私は事前に参加するセッションをある程度決めておきました。 セッションの後には毎回質問ブースで個別質問できるSpeakers Q&A Roomもありました。(写真提供:GitNation) ランチメニューには多様性への配慮を感じさせるラインナップが揃っていました。(写真提供:GitNation) いくつかセッションに参加したり色々な人と話したりしていると、あっという間にランチタイムです。 ランチタイムには仲良くなった人々と合流して、のんびりと食事を楽しみました。直前のセッションがWebのパフォーマンスに関する内容だったため、どのようなツールを使っているかについて話が盛り上がりました。今のチームでDatadog RUMを使っていることを話すと「Synthetic Monitoring Tool」を勧められました。外部のエンジニアと気軽に相談できる環境はとても素敵ですよね。 技術的な話だけでなく、NYC観光情報についても盛り上がりました(残念ながら観光をする機会がなかったので、情報を活かす場はありませんでしたが)。また、最近の厳しい北米のジョブマーケットの話や、各国の休暇制度の違いについても話題になりました。特に、日本の企業では病気休暇が一般的には存在しないことに驚愕されました。様々な話題で盛り上がりとても楽しいひとときでした。前職まで西海岸の某所で働いていたこともあり、どこか懐かしい雰囲気を感じました。 またセッション以外には、スピーカーとカンファレンス参加者が意見を交わすディスカッションコーナーが設けられており、リスナーとしても参加可能でした。私はその時間を逃してしまったものの、果敢にディスカッションに参加した人から「周りのレベルが高すぎて、自信が無くなりそうになった」という感想を聞きました。 ディスカッションルームの風景(写真提供:GitNation) その他は企業ブースもたくさん来ておりノベルティハントしつつお話を聞いて回ったりしました。 「Storybook」ならぬ「Storyblok」という会社があり、CMSツールを提供している企業です。(写真提供:GitNation) セッション会場外の様子(写真提供:GitNation) 砂糖たっぷりのおやつ Day 2- React Summit 2日目はReact Summitです。この日もチェックイン後にマグカップとステッカーが貰えました。 React Summitの会場内観(写真提供:GitNation) 会場に入ると前日のJSNationと比べて参加者数や企業ブースも増え、より賑やかな印象を受けました。また、前日カジュアルな格好をしていた参加者がスーツ姿に変わっているなど、皆の気合いも一段と高まっているように感じました。 オープニング開始前に以前チームで導入を検討していたSentryのブースを訪れました。当時Sentryに対してほとんど知識がなく手探り状態だったので、導入コストやNext.jsとの相性など基本的なことから、使用方法、現在使っているDatadog RUMとの違いなどを質問しました。話を聞いた印象としてはDatadog RUMと似たような印象を受けたのでもう少し深く聞こうとしたところでタイムオーバーとなり少し残念でした。 企業ブース(写真提供:GitNation) React Summitのオープニング(写真提供:GitNation) React Futureのパネルディスカッション風景(写真提供:GitNation) 前日のJSNationではもちろんJavaScriptに関するセッションもありましたが、チーム内コミュニケーションに関するものなど、一般的な内容も多かった印象です。一方React Summitのこの日は、ほとんどのセッションがより技術的な内容にフォーカスしていたように感じました。 ランチメニュー。手前から3番目の豆腐を焼いた食べ物が美味しかったです デザート。イタリアンスイーツのカンノーリもあったのですが出遅れたせいで全て完売していました。(写真提供:GitNation) この日のランチタイムは、前日とは違うメンバーと一緒に過ごしました。セッションの感想や、どんな仕事をしているのか話しているうちに、気づけばピザ談義で大盛り上がりでした。 皆様によると、どうやらNYのピザやベーグルが世界一と評される理由は「水が綺麗だから」らしいのです。これを読んでいる皆さん同様私も最初は疑いましたが、後にChatGPT先生にも確認したところ、同じ説明をされてしまいました。ちなみにシカゴ出身の方曰く「シカゴピザこそが世界最高」だそうです。 こうしてピザ論争が続いたわけですが、実のところ私はピザが好きではないのでした。 企業ブースの中には高額商品が当たるイベントなども用意されていました。(残念ながら当選しなかったです。) イベントエンディングの様子(写真提供:GitNation) 気づけばあっという間に2日目も終了しました。この2日間はエネルギッシュな雰囲気に触れ、非常に刺激的で充実した時間を過ごすことができました。 After Party React Summitの後には、After Partyの場が用意されていました。2つのバーがイベント参加者専用に確保されており、1つは静かに会話を楽しみたい人向けの Hudson Hound 、もう1つは賑やかに過ごしたい人向けの Barcade でした。 私はゆっくりと会話を楽しみたかったのでHudson Houndへ行きました。イベント中に仲良くなった人や、その時初めて出会った人々とこの2日間のイベントはどうだったか、どのセッションが一番良かったかなど話しました。そこで聞いて驚いたことがあったのですが、中にはGitNationに招待されて参加費無料で来ている人もいるそうです。招待された人は、別のカンファレンスで登壇したことをLinkedInに投稿したところ、GitNationの目に留まり招待が来たそうです。興味がある方は試してみてもいいかもしれないですね。 Hudson Houndの内観。落ち着いた環境でゆったりと過ごせたので、非常にリラックスできました。 仲良くなった人々と。 Barcadeの内観 バー周辺の様子。綺麗で落ち着いていて路上バイオリニストなどもいて夜でも平和でした。 気になったセッションについて それでは、特に印象に残ったセッションを2つご紹介します。これらはGitNationのウェブサイトでも視聴可能ですので、ぜひチェックしてみてください。 gitnation.com Chrome DevTools 2024: Debugging and Performance Optimization for React Developers gitnation.com まず1点目はGoogle ChromeのエンジニアリングリーダーのAddy OsmaniさんのAIを活用したデバッグ支援やパフォーマンス最適化機能のセッションでした。 プレゼンテーションの様子(写真提供:GitNation) 以下、気になったポイントをまとめます。 AIアシスタントによるデバッグの向上 Chrome DevToolsの AIアシスタントパネル では、AIとチャット形式でトラブルシューティングが可能です。この機能により、UIコンポーネントのエラー分析やスタイル調整の提案を受けることができ、デバッグ作業が直感的に進められると感じました。 実際にReactアプリでエラーが発生した場合、AIアシスタントがエラー内容を要約し、解決策を提示する様子がデモで紹介されていました。このアプローチはデバッグ効率を大幅に向上させる可能性を感じました。 React Developer Toolsによる拡張性と最適化 React Developer Tools では、レンダー更新のハイライト表示機能が非常に有用です。不要な再レンダーを特定し、パフォーマンス向上につなげられる点が印象的でした。 さらに、Server Componentsのサポートが追加され、クライアントとサーバー側の処理を簡単に区別できるようになったことも、最適化やデバッグ作業の効率化に寄与していると感じました。 ローカルオーバーライド機能での実験と検証 Chrome DevToolsのローカルオーバーライド機能 では、元のコードを変更せずにスタイル調整やAPIレスポンスのモックが可能です。この機能を使うことで、バックエンドが未完成でもフロントエンドのデバッグや検証を進められる柔軟性が魅力的でした。 特にスタイル調整をローカルに保存し、セッションをまたいで変更内容を保持できる点は、デザインの検証作業を効率化できると感じました。 パフォーマンス最適化ツールの活用 Core Web Vitalsの分析機能 は、パフォーマンス改善の具体的な指標を提供し、ユーザー体験の向上に役立つと感じました。また、トレースアノテーション機能を使うことで、チーム内でデータ共有や注釈の付与ができ、パフォーマンス向上施策を共同で進められる点も印象的でした。 3Dレイヤーとアニメーションデバッグ DOM要素の階層構造を視覚的に確認できる 3Dビュー機能 は、インタラクションデザインやアニメーション調整に役立つと感じました。 特にアニメーションインスペクターを使用すると、リアルタイムでタイミングや効果を調整できます。この機能は触り心地の良いWebアプリケーション作成に貢献すると感じました。 まとめ このセッションを聞く前は、そもそもChrome DevToolsにAIアシスタント機能があることを知りませんでした。しかし、AIアシスタントや新しいデバッグ機能、パフォーマンス最適化ツールがWeb開発の効率を大きく向上させることを実感しました。 これらの機能を活用することで、より柔軟で視覚的にアプリケーションを構築できることがすぐに想像できました。計測フロントエンドブロックでの開発でもどんどん活かしていきたいです。 Green Bytes: How Enhancing Web Vitals Contributes to Environmental Sustainability gitnation.com もう一点気になったセッションはZEALのFull Stack Developerの Dimitris Kiriakakis さんのプレゼンテーションでした。 ウェブサイトの最適化は、ユーザー体験の向上だけでなく、環境負荷の軽減にもつながるという視点からの非常に興味深いものでした。このセッションでは、Web Vitalsの改善を通じてウェブアプリケーションのパフォーマンスを向上させ、CO2排出量を削減するための具体的な手法や実践例が紹介されました。 まず、インターネットのカーボンフットプリントについて取り上げられていました。インターネットは世界のCO2排出量の3.7%を占めており、これは航空業界に匹敵する規模だそうです。AI業界の成長に伴い、この数値は今後さらに増加する可能性が指摘されています。ウェブのカーボンフットプリントに影響を与える主な要因として、インフラストラクチャー、データ転送量、エンドユーザーのデバイス使用が挙げられていました。特にページ重量(Page Weight)が大きく影響し、ページサイズが大きいほどネットワーク使用量や電力消費量が増加するという内容は印象的でした。 次に、Core Web Vitalsに関する説明がありました。Googleは2020年にCore Web Vitalsを導入し、ユーザーエクスペリエンスを評価するための指標を標準化しました。具体的には以下の3つの指標が重視されています。 Largest Contentful Paint (LCP) :ページ読み込み速度を測定し、2.5秒以下が良好な体験とされる。 Interaction to Next Paint (INP) :インタラクティブ性を測定し、200ms以下が良好な体験とされる。 Cumulative Layout Shift (CLS) :視覚的な安定性を測定し、0.1以下が良好な体験とされる。 これらの指標を改善することで、パフォーマンスの向上と環境への負荷を減らすことの両方を実現できるという説明があったのですが、これは説得力がありますよね。 具体例として、意図的にパフォーマンスが悪化するように設計されたウェブアプリケーションのケースが紹介されました。Google Lighthouseによる評価では、LCPが13.2秒、CLSが0.367という非常に悪いスコアでした。しかし、以下の最適化を施すことで大幅に改善された事例が示されました。 プレゼンテーションの様子(写真提供:GitNation) 画像最適化 :モバイル向けに小型の画像を生成し、WebP形式に変換。 優先度の設定 :ビューポート内の主要要素を優先的に読み込み、他の要素は遅延ロード。 レイアウトシフトの排除 :安定したレイアウト構造を確保。 これにより、ページ重量は70%削減され、LCPは800ms、CLSは0.1以下という結果を達成したそうです。この事例を通して、具体的な改善策とその効果を明確に理解できました。 さらに、Chromeのパフォーマンスタブを用いたプロファイリングやインタラクションイベントの追跡を活用し、問題点を特定・改善する手法も紹介されました。特にINPスコアの最適化では、重いタスク処理や応答遅延を最小限に抑えることが重要であり、電力消費を抑えながらユーザーエクスペリエンスも向上させることができるとの説明が印象的でした。 このセッションでは、最適化によるCO2排出量削減の具体例も取り上げられていました。あるプラグインのサイズを1KB削減することで、アムステルダムからニューヨークへのフライト5回分のCO2排出を削減できたという報告は、最適化がもたらす環境への影響を強く実感させるものでした。また、ZEALのケースでは、LCPが13秒、CLSが1.775という非常に悪いスコアから、最適化によって劇的に改善された結果が示されていました。 加えて、エコフレンドリーなウェブ開発を支援するツールや手法についても紹介されました。 アセット最適化 :画像圧縮やコードのミニファイ化を通じてデータ量を削減。 グリーンホスティングプロバイダー :再生可能エネルギーを使用するホスティングサービスを選択。 コンテンツデリバリーネットワーク (CDN) :地理的に近いサーバーからコンテンツを配信し、データ転送量を削減。 まとめ これらのアプローチは、持続可能なソフトウェア開発を推進するうえで役立つものであり、企業や開発者にとって今後さらに注目される分野だと感じました。 また、Webのパフォーマンスを向上させることは、自分たちのプロダクトを技術的・ビジネス的に向上させるだけではありません。地球にも優しいという、普段あまり意識していなかった視点からもアプローチできることに気づきました。これにより、非常に良い学びを得ることができました。 今回紹介するのは以上になりますが、他にもアクセシビリティの話や最近フロントエンドフレームワーク界隈でじわじわと人気が上がっている Svelte の話など興味深いセッションがたくさんありました。また、セッションの後に他の参加者と感想を言い合ったり、どのように業務に活かせそうか話したりすることで、一層理解が深まりました。 Day 3 - Workshop 3日目はワークショップでした。私は Kent C. Dodds さんによる「React Future (Server Components and Actions)」のセッションに参加しました。このワークショップでは、まだ公式に安定リリースされていない Server Components と Server Actions についてハンズオン形式で学ぶことができました。Server ComponentsとServer Actionsがどのような役割を果たすのかを、実際にフレームワークを構築しながら理解を深めました。 ワークショップを選んだ理由は、現在計測チームの一部メンバーと共にZOZOMETRYの管理画面を開発しているからです( 関連記事 )。ZOZOMETRYの管理画面は社内メンバー専用のクローズドプロダクトであり、実験的な技術も採用しやすいため、フロントエンドで Next.jsのServer Actions を使用することに決めました。今回、このプロジェクトで初めてServer Actionsに触れたため、さらなる理解を深める目的でこのワークショップに参加しました。 インストラクターのラップトップとマスコットキャラクターのコアラちゃんです。 ワークショップ用のリポジトリをはじめ、オリジナルコアラステッカーなど、至る所がコアラちゃんまみれだったのです。なぜコアラちゃんなのか誰も突っ込んでいなかったため、その謎は解明しないまま今日に至ります。 ワークショップは、まず簡単なアイスブレークと自己紹介から始まりました。自己紹介の内容は、名前、出身地、仕事内容、好きなアイスクリームのフレーバーについてでした。最も人気があったフレーバーは、Cookie Dough(焼く前のCookie生地味)でした。参加者の内訳は、アメリカからが最も多く、次いでヨーロッパ、アジア圏からは私以外に韓国からの参加者がいました。ちなみに、ここでもZOZOMETRYのことを紹介したところ、「クールだ」と言ってもらえました。 ワークショップ内容 Server Components React Server Components(以下、RSC)は、従来のSPAアーキテクチャに代わる新しい手法として注目されています。クライアントとサーバーの役割を効率的に分担し、ストリーミング対応やインタラクティブなUI構築を容易にします。今後の発展に期待しつつ、まずは基本的な仕組みや実装を、作業を通して理解していきました。 Client Components RSCの革新の中心はClient Componentsにあり、これにより新しいアプローチが可能になります。サンプルアプリでは、ボタンをクリックしてテキストを編集し、Enterキーで送信する機能を実装しました。このプロセスを通じて、Client Componentsの重要性や役割について学びました。Client Componentsはインタラクティブな操作を処理するために欠かせない要素であり、ユーザーの操作に応じて即座に反応できる点が特長です。このアプローチにより、ユーザーエクスペリエンスの向上が期待でき、サーバーとクライアントの役割分担を明確にできます。今回のアプリ開発を通じて、Client Componentsが動的な機能やイベント処理を担当する重要な役割を果たすことを理解し、実際にその動作を確認することで、その有用性を実感しました。 Client Router アプリ開発において、スムーズで直感的なユーザーエクスペリエンスを提供するためには、クライアントサイドルーターが欠かせません。ここではClient Routerの役割とその利点について学びました。 Server Actions Server Actionsは、Client Componentsとサーバー間のデータやアクションを効率的にやり取りするための重要な要素です。ワークショップでは、フォームの操作におけるServer Actionsの活用方法を学びました。フォーム送信時に、クライアントからサーバーに対してアクションを呼び出し、その結果を動的に反映させる仕組みです。このように、Server Actionsを使うことで、サーバー側のデータ操作を簡潔に行い、クライアントとのやり取りがシンプルになります。 Server Actionsのポイントは、Client ComponentsからServer Actionsを直接呼び出すことができ、アクションの参照がクライアントに渡されることです。これにより、従来のフォーム送信のような間接的な手法を省き、より直感的で効率的なデータ操作が可能になります。 RSC図解(ワークショップより) 今回のワークショップでは、Server Components、Client Components、Client Router、Server Actionsについて学びました。これらの技術を活用することで、サーバーとクライアント間の役割分担が効率化され、パフォーマンスの向上やユーザーエクスペリエンスの改善が実現可能です。特に、Server Actionsを使ったデータのやり取りや、Client ComponentsによるインタラクティブなUIの実装は、今後のアプリケーション開発において非常に有用な技術です。これらの技術が今後どのように進化していくのか、非常に楽しみです。 本来数日間にわたって行う内容を1日で詰め込んだため非常にハードでしたが、周りの参加者と助け合いながら何とか乗り切ることができました。参加者の多くはフレンドリーで、協力して課題を解決していく様子は学生時代のようで楽しかったです。また、インストラクターとも距離が近く、カンファレンスの時よりもリラックスして話ができ、裏話なども聞くことができました(2024年は登壇しすぎてしんどかった等)。 ワークショップが終わった後は、他の参加者たちと「来年も会えるといいね」と言い合いながら、別れを惜しみました。 ワークショップの様子です。 少人数での開催だったため、非常にアットホームでフレンドリーな雰囲気でした。また、インストラクターは課題を完成させることよりも、他の参加者と多くの会話をすることを推奨していました。 余談ですが、写真を見ると分かるように半袖の参加者もいます。この日は最高気温が11度だったにもかかわらず室内は冷房が7度に設定されており、エアコンの風が髪をそよがせるほどでした。その結果、私は体調を崩しました。 お昼ご飯はアメリカンメキシカン、いわゆるTex-Mexでした。メキシカンが大好きなので嬉しかったです。 イベント全体を通じて感じたこと ゲットしたノベルティたち。 今回のカンファレンスは、国内外を通じて私にとって初めての参加となりました。これまで「ニューヨークの人々は西海岸の人々に比べて冷たい」と耳にしていたため、どのような雰囲気なのか少し緊張していました。しかし実際には、多くの参加者がとてもフレンドリーで交流を楽しむことができました。 また、最近ではアメリカの若者にとって日本が人気の旅行先となっているようで、日本を訪れた経験のある参加者にも多く出会いました。参加者層を見ていると、東海岸からのアメリカ人が最も多く、次いでカナダの東部からの参加者が目立ちました。一方で、西海岸からの参加者は意外に多くなく、ヨーロッパからの参加者も限定的でした。しかし、とあるイタリア人エンジニアによると、ヨーロッパからもそれなりに参加者がいたようです。アメリカやカナダ以外では、ESTAを利用して参加できる国からの参加者が多いように思いました。ちなみに、日本から片道13時間ほどかけてニューヨークまでやってきたと言うととても驚かれました。 セッションはオンラインでも視聴可能なため、「技術を学ぶ」という観点では動画配信でも十分に知識を得られるかもしれません。しかし、現地で世界中から集まったエンジニアたちと直接対話することで知識の吸収にとどまらず、彼らの働く環境やマインドセット、バックグラウンドについても知ることができました。自分の中の『当たり前』が、他の人にとっては異なることを実感できる貴重な体験は、視野を広げる素晴らしい機会となりました。 今回のカンファレンスを通じて著名なエンジニアや世界的企業に所属するエンジニアと交流できたことで、技術知識をアップデートしただけではなく、大きな刺激を受け日々の開発へのモチベーションも向上しました。そして何よりも非常に楽しい時間を過ごすことができました。今後も機会があれば今回のご縁を大切にし、現地で再会できることを楽しみにしています。 最後に 最後に、計測システム部フロントエンドブロックでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください! www.wantedly.com
アバター
ZOZO開発組織の2025年1月分の活動を振り返り、ZOZO TECH BLOGで公開した記事や登壇・掲載情報などをまとめたMonthly Tech Reportをお届けします。 ZOZO TECH BLOG 2025年1月には、前月分のMonthly Tech Reportを含め5本の記事を公開しました。その中でも特に注目度の高かった記事をピックアップしてご紹介します。 1月21日に公開した「 フロントエンドテストの正解って?FAANSにおけるテスト戦略の振り返りとこれから 」は、リアルな現場感を綴った記事です。2025年1月第4週のおすすめエントリーとして、 はてなブログのXアカウントにも取り上げてもらいました 。 Webフロントエンドにおけるテストには様々な論点がありますが、FAANSの事例を通じて、何か得られるものがあればうれしいですね。 techblog.zozo.com また、1月最終日に公開した「 BigQueryのアンチパターン認識ツールで独自のSQLリンターを開発しました 」では内定者アルバイトスタッフの成果物を紹介しています。 取り組む中で見つけたエラーに対してPull requestを送ってマージされることで、問題の発生を防げるようになりました。ZOZOではこのような取り組みを応援、そして歓迎しています! techblog.zozo.com ZOZO DEVELOPERS BLOG EC基盤開発本部 SRE部の三神と亀井による、ZOZOTOWNにおけるSREの魅力や日常を紹介する記事を公開しました。SRE部の組織構成から日々の業務、そして今後の展望についても触れています。ZOZOTOWNのSREに興味のある方は、ぜひご一読ください。 technote.zozo.com 登壇 LODGE XR Talk Vol.23 1月17日に開催された『 LODGE XR Talk Vol.23 』で、技術戦略部の諸星( @ikkou )が「CES 2025 Report」と題して1月上旬に参加したCES 2025でのXRの動向について登壇しました。 登壇後に公開したCES 2025のイベントレポート記事では、XRに限らずFashion TechやBeauty Techについても触れています。あわせてご覧ください。 techblog.zozo.com JR西日本・ファーストリテイリング・ZOZOが語るビジネス要件を踏まえたデータ基盤の構築 1月24日に開催された『 JR西日本・ファーストリテイリング・ZOZOが語るビジネス要件を踏まえたデータ基盤の構築 』で、データ基盤ブロック ブロック長の奥山( @pokoyakazan )が「 ZOZOを支えるリアルタイムデータ連携基盤の歴史とビジネス貢献 」というタイトルで登壇しました。 【ZOZOエンジニア登壇情報】 明日1/24(金) 12:00~13:15にオンラインで開催される『JR西日本・ファーストリテイリング・ZOZOが語るビジネス要件を踏まえたデータ基盤の構築』にデータシステム部の奥山 @pokoyakazan が登壇します🎙️ お気軽にご参加ください! https://t.co/OVMWx370aT #data_findytools — ZOZO Developers (@zozotech) 2025年1月23日 speakerdeck.com 掲載 教育新聞 昨年12月15日に開催した「 Girls Meet STEM〜ITのお仕事を体験しよう〜 」について、技術戦略部の長澤( @wiroha )が取材を受けた記事が、「 教育新聞 」に掲載されました。 www.kyobun.co.jp エンジニアtype エンジニアtypeが運営する音声コンテンツ『 聴くエンジニアtype 』の配信100回突破を記念した過去のおすすめ回を特集した記事に、CTOの瀬尾( @sonots )登場回が紹介されました。 type.jp 瀬尾が登場した第1回のテーマは「 エンジニアとして成長するために新人時代にするべきことは? 」でした。こちらは記事としても公開されています。未見の方はぜひチェックしてみてください。 type.jp AdverTimes. 昨年12月18日にAI・アナリティクス本部の川田が登壇した「 宣伝会議AI研究会 」の様子が、「 AdverTimes. 」に掲載されました。 www.advertimes.com その他 2025年3月期 第3四半期決算発表 1月31日に2025年3月期 第3四半期決算を開示しました。詳細は以下のリンクにある開示資料をご確認ください。 corp.zozo.com 以上、2025年1月のZOZOの活動をお届けしました! ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com
アバター
はじめに こんにちは、データシステム部データ基盤ブロックの奥山( @pokoyakazan )です。普段は全社データ基盤の開発・運用を担当しており、最近ではZOZO全体のデータガバナンス強化にも取り組んでおります。本記事ではCloud Composer上に構築しているデータマート集計基盤でdbtのモデル更新も行えるようにした事例についてご紹介します。 目次 はじめに 目次 背景 データマート集計基盤 dbt導入 データマートの使い分け dbt導入にあたっての課題 モデルごとに自動リトライができない 依存関係による待ち合わせ制御ができない データマート集計基盤へのdbt導入 Airflow Dagの設計 dbtデータマート更新処理の実装 データマートごとのタスクグループ作成 1. モデル情報を保持するクラスの定義 2. manifest.jsonの読み込みとモデル情報の取得 3. タスクグループの作成 dbtモデル間の依存関係を定義 dbtデータマート→dbtデータマート ソースシステム→dbtデータマート dbtデータマート→SQLデータマート Elementaryを使った実行履歴・テスト結果の可視化 まとめ 背景 データマート集計基盤 ZOZOでは、データ基盤利用者が作成したSQLファイルに記述されたクエリによって日々更新されるBigQueryのテーブルをデータマートとして管理しています。とても活発に利用されており、2025年2月現在1,100を超えるデータマートが存在します。そしてこれらのデータマートを更新するジョブを管理するシステムをデータマート集計基盤と呼んでおり、 Apache Airflow のマネージドサービスである Cloud Composer 上に構築しています。Cloud Composerの導入事例については以下の記事で紹介しているのでぜひご覧ください。 techblog.zozo.com dbt導入 データ利活用が進む中で、データ基盤におけるデータガバナンスを強化していこうという取り組みが始まりました。そこでより品質担保されたデータマートを提供することを目的として、複数のデータモデリングツールを比較検討した結果、 dbt を導入することにしました。dbtの選定理由・導入経緯については以下の記事で紹介しているのでぜひご覧ください。 techblog.zozo.com なお今回の記事はこちらの記事の続編となり、実際にdbtをシステムに組み込んでいくにあたっての過程や方法について紹介していきます。 データマートの使い分け 前提として、今回のdbt導入にあたってすでにデータマート集計基盤で日々更新されている1,100を超えるデータマートをすべてdbtでモデリングし直す方針は取っていません。SQLを書くだけで完結する既存の仕組みは利便性が高く、データ基盤の利用促進に繋がりますし、ビジネス部門の方々を含む全ての利用者に1からdbtを学習してもらうのは現実的でないと判断したためです。そこで、以下2つのデータマートを使い分けることにしました。 SQLデータマート: これまでのSQLファイルで更新されるデータマート レポーティング用途 dbtデータマート: dbtによって更新されるデータマート 集計定義を統制して品質担保 dbt導入にあたっての課題 dbtをそのまま単体で導入するだけでは、運用面で以下のような課題がありました。 モデルごとに自動リトライができない 依存関係による待ち合わせ制御ができない 1つずつ見ていきます。 モデルごとに自動リトライができない dbtでは dbt run コマンド1つで依存関係を考慮しながら全モデルを一括更新できます。そのためサーバ上のcronやGitHub Actionsから簡単に実行が可能です。ただしこの方法では、途中モデル更新が失敗した際に効率的な再実行ができないという課題がありました。 例えば、以下のような依存関係を持つ dbt_model1 から dbt_model5 を dbt run で一括更新するとします。 ここで、 dbt_model1 ~ dbt_model3 の更新は成功し、 dbt_model4 の更新で失敗した場合を考えます。 この時、GitHub Actionsジョブ内で dbt run を実行するstepを再実行する必要があります。ただし再実行は全モデルが対象となるため dbt_model1 ~ dbt_model3 の更新も最初からやり直しとなり、無駄な時間とリソースが発生してしまいます。 依存関係による待ち合わせ制御ができない dbtモデルにはソースシステムから連携される一次テーブルを参照するものもあり、そういったモデルは依存するソースシステムのデータ連携が完了するまで更新開始を待つ必要があります。例えばGitHub Actionsでこの処理を実現する際は待ち処理用のstepを用意することになるかと思います。この場合、全てのソースシステムからの連携完了を確認するまで dbt run は実行できず、結果として無駄な待ち時間が発生します。依存するソースシステムの連携が終わり次第、対象モデルを即時更新していく仕組みが理想です。 データマート集計基盤へのdbt導入 上記の「モデルごとの自動リトライ」や「依存関係による待ち合わせ制御」といった機構は、すでにデータマート集計基盤で実装済みです。そのためSQLデータマートと同様、Cloud Composerからdbtデータマートを更新する仕組みにしました。また、「SQLデータマートからdbtデータマートを参照したい」という要件も挙がっていたため、1つのAirflow DagでSQLデータマートとdbtデータマートの両方を更新できるよう設計しています。 補足となりますが、Airflow上からdbtを実行できる Cosmos というOSSツールがあります。しかし、上記のSQLデータマートからdbtデータマートへの依存に関する要件や、今後発生するビジネス要件に柔軟に対応する必要があることを考慮し、OSSではなく自分達で内製することにしました。 Airflow Dagの設計 Airflow Dagからdbtモデルを更新していくにあたってのポイントは「1つのAirflowタスクごとに1つのdbtモデルを更新する」という点です。dbtでは --select オプションを使うことで、特定のモデルやタグ、その他の条件(例えば、モデルの状態や依存関係など)によって更新対象のモデルを選択できます。そこでdbtモデルごとにAirflowタスクを作成し、 --select オプションを使って対象のモデルのみを更新するようにしました。 dbt run --select " ${ 対象dbtモデル名 } " 依存関係の解析や待ち合わせ制御をAirflowに任せることで、タスクの失敗時にはそのタスクのみを再実行でき、他のタスクに影響を与えずに処理を続けることができます。具体的には、データマート単位でタスクグループを作成し、それぞれのタスクグループ内でデータマートの更新処理とデータ品質チェックを行う2つのタスクを定義しています。 データマートの更新処理( update_datamart タスク): dbtデータマートの場合 dbt run を実行 データ品質チェック( data_quality_check タスク): dbtデータマートの場合 dbt test を実行 SQLデータマートの場合Dataplexを利用(本記事では割愛) また、全てのdbtデータマートタスクグループの処理完了後に dbt_test_warning タスクを実行します。 データ品質チェックについて詳しく見ていきます。データ品質チェックの方法は大きく分けて2種類あります。 Errorデータ品質チェック: 各dbtデータマートの更新直後に data_quality_check タスクで実行 Warningデータ品質チェック: 全てのdbtデータマートの更新完了後に dbt_test_warning タスクで実行 dbtでは severity という設定で、データ品質チェックの重要度を error または warn から設定できます。「Errorデータ品質チェック」では、致命的なデータ品質の問題を検出するため、 severity:error に設定したテストを以下のコマンドで実行します。 dbt test --select " ${ 対象dbtモデル名 } ,config.severity:error " このチェックはデータマートごとに実行され、品質に問題が見つかった場合、後続のタスクを停止します。一方、「Warningデータ品質チェック」では、 severity:warn に設定したテストを以下のコマンドで実行します。 dbt test --select " config.severity:warn " select でモデルを指定せず全てのdbtデータマートに対して一括でテストを行っており、Warningが発生しても後続のタスクはそのまま実行されます。また、品質に問題が見つかってもSlack通知のみ送るようにしています。このように、ErrorチェックとWarningチェックを適切に分けることで、重要なデータ品質問題は即時で対応し、Warningレベルの課題は効率的にモニタリングすることが可能となります。 dbtデータマート更新処理の実装 実際にCloud Composer(Airflow)からdbtモデルを更新するコードについて見ていきます。dbtデータマートの更新処理における実装のポイントは主に2つあります。 データマートごとのタスクグループ作成 dbtモデル間の依存関係を定義 これらの実装は、dbtコマンド実行後に生成される manifest.json を解析することで行っています。 manifest.json は、dbtプロジェクトのメタ情報を保持するファイルで、以下のような情報が記載されています。 各dbtモデルの詳細(モデル名、ファイルパスなど) モデル間の依存関係 dbtテストなどの情報 データマートごとのタスクグループ作成 1. モデル情報を保持するクラスの定義 manifest.json を読み込む前に、前準備として各dbtモデルの情報を保持するための DbtModel クラスを定義します。このクラスでは、テーブル名・依存先モデル・ユニークIDなど、 manifest.jsonに記載されているモデルのメタ情報 をプロパティとして管理します。 class DbtModel (): def __init__ (self, project_id, unique_id, dataset, table, depends_on_models): self._project_id = project_id self._unique_id = unique_id self._dataset = dataset self._table = table self._depends_on_models = depends_on_models @ property def project_id (self): return self._project_id @ property def unique_id (self): return self._unique_id @ property def dataset (self): return self._dataset @ property def table (self): return self._table @ property def depends_on_models (self): return self._depends_on_models def table_id (self): return f '{self._project_id}.{self._dataset}.{self._table}' 2. manifest.jsonの読み込みとモデル情報の取得 次に manifest.json をロードして各モデルの詳細情報を取得し、取得した情報から DbtModel をインスタンス化していきます。その後、インスタンス化した DbtModel オブジェクトを dbt_models リストに追加していきます。 with open ( 'target/manifest.json' ) as f: manifest_dict = json.load(f) dbt_models = [] for node in manifest_dict[ "nodes" ].keys(): if node.split( '.' )[ 0 ] == "model" : model_conf = manifest_dict[ "nodes" ][node] dbt_model = DbtModel( project_id=model_conf[ 'database' ], unique_id=model_conf[ 'unique_id' ], dataset=model_conf[ 'schema' ], table=model_conf[ 'name' ], depends_on_models=model_conf[ 'depends_on' ][ 'nodes' ], ) dbt_models.append(dbt_model) 3. タスクグループの作成 dbt_models リストをループし、各モデルに対応するタスクグループを作成していきます。タスクグループ内には、先述の update_datamart タスクと data_quality_check タスクを定義しています。 # タスクを格納する辞書 task_dict = {} for dbt_model in dbt_models: model = dbt_model.table with TaskGroup(group_id=model) as task_dict[model]: dbt_run_command = f 'dbt run --select "{model}"' update_datamart = BashOperator( task_id= 'update_datamart' , bash_command=dbt_run_command, ) dbt_test_command = f 'dbt test --select "{model},config.severity:error"' data_quality_check = BashOperator( task_id= 'data_quality_check' , bash_command=dbt_test_command, ) update_datamart >> data_quality_check dbtモデル間の依存関係を定義 続いて、タスク間の依存関係をどのように定義していくかを紹介します。依存関係は以下の3つに分けて考えます。 dbtデータマート→dbtデータマート ソースシステム→dbtデータマート dbtデータマート→SQLデータマート これらの依存関係の定義方法について、それぞれ詳しく見ていきます。 dbtデータマート→dbtデータマート まず、dbtデータマート同士の依存関係について紹介します。 manifest.json 内の各モデル情報には depends_on_models というリストが含まれており、このリストには依存先となるモデルが格納されています。この情報を元にAirflowでタスク間の依存関係を定義していきます。 # 先ほど作成したdbt_modelsリストをループ for dbt_model in dbt_models: # dbtモデルが依存するnodeのunique_idでループ for depends_on_node_unique_id in dbt_model.depends_on_models: # 依存先nodeがmodelの場合依存関係定義 if depends_on_node_unique_id.split( '.' )[ 0 ] == "model" : depends_on_model = depends_on_node_unique_id.split( '.' )[- 1 ] # task_dict: タスクを格納する辞書 task_dict[depends_on_model] >> task_dict[dbt_model.table] depends_on_models リストをループし、各モデルの依存先を確認していきます。依存先がdbtモデル( unique_id の先頭が model )の場合に、Airflowの >> 演算子を用いて依存関係を定義しています。 ソースシステム→dbtデータマート 次に、ソースシステムからdbtデータマートへの依存関係について紹介します。まず、source情報を保持するために DbtModel と同様 DbtSource クラスを用意します。そして各dbtモデルの depends_on_models を調べていきます。依存先がdbtのsource( unique_id の先頭が source )である場合、そのsourceの情報を取得し、取得した情報から DbtSource をインスタンス化します。その後、インスタンス化した DbtSource オブジェクトを sources リストに追加していきます。 # 待ち処理を行うSourceのリスト sources = [] # 依存関係リスト dependencies = [] # dbtモデルごとのループ for dbt_model in dbt_models: # dbtモデルのunique_idを取得 dbt_model_unique_id = dbt_model.unique_id # dbtモデルが依存するモデルのunique_idでループ for depends_on_node_unique_id in dbt_model.depends_on_models: # 依存するモデルがSourceの場合、情報を取得 if depends_on_node_unique_id.split( '.' )[ 0 ] == 'source' : source_conf = manifest_dict[ "sources" ][depends_on_node_unique_id] unique_id = source_conf[ 'unique_id' ] project_id = source_conf[ 'database' ] dataset = source_conf[ 'schema' ] table = source_conf[ 'name' ] # DbtModelクラス同様、Source用クラスで情報を保持 dbt_source = DbtSource(project_id, unique_id, dataset, table) # DbtSourceインスタンスごとにリストに追加 sources.append(dbt_source) # 依存関係リストに追加 dependencies.append({ 'before' : dbt_source.table, 'after' : dbt_model.table}) そして DbtSource オブジェクトごとに待ち処理用のタスクを定義し、最後に依存関係を貼っていきます。 # Sourceのリストから待ち処理を行うタスクを生成 for source in sources: task_dict[source.table] = PythonOperator( task_id=f 'wait_{source.table}' , # ソースシステムの待ち処理を行うタスク python_callable=_wait_source_created, ) # 依存関係リストから依存関係を定義 for dependency in dependencies: before = dependency[ 'before' ] after = dependency[ 'after' ] task_dict[before] >> task_dict[after] dbtデータマート→SQLデータマート 最後に、dbtデータマートからSQLデータマートへの依存関係について紹介します。SQLファイルを解析し、 FROM 句や JOIN 句の後に記載されているテーブルIDを取得する処理は既にデータマート集計基盤で実装済みです 1 。そのため、この処理で得られた参照先のテーブルIDとdbtデータマートのテーブルIDを比較し、一致した場合に依存関係を定義していきます。 # SQLデータマートごとのループ for datamart in sql_datamarts: """ SQLを解析してFROM, JOINの後にくるテーブルIDを取得し、 取得したテーブルIDをdepends_on_table_idsリストに格納する処理 datamart: DbtModelクラス同様、SQLデータマート用クラスで情報を保持している """ # FROM, JOINの後にくるテーブルIDでループ for depends_on_table_id in depends_on_table_ids: # dbtモデルごとのループ for dbt_model in dbt_models: # FROM, JOINの後にくるテーブルIDとdbtモデルのテーブルIDを比較 # 一致した場合、依存関係を定義 if depends_on_table_id == dbt_model.table_id(): task_dict[dbt_model.table] >> task_dict[datamart.table] Elementaryを使った実行履歴・テスト結果の可視化 最後にdbtの運用におけるTipsとして、実行履歴やテスト結果の可視化について紹介します。dbtでの問題発生時のSlack通知や dbt test の履歴管理のために、dbtのオブザーバビリティツールである Elementary を使っています。 Elementary には edr というCLIコマンドがあり、主に2つのサブコマンド monitor と send-report が重要です。 edr monitor はdbtコマンドで発生したErrorやWarningをSlackなどに通知するコマンドで、dbtデータマートのタスクグループ内のタスクが失敗した際に実行されます。具体的には、Airflowの on_failure_callback 機能を使ってタスク失敗時にのみ実行される関数内で edr monitor を実行します。また、 dbt_test_warning タスクの後にも edr monitor を実行することで、データ品質チェックのWarning通知も飛ばしています。一方、 edr send-report は、これまでの実行履歴を元にモニタリング用ダッシュボードを作成するコマンドで、全てのdbt関連タスクの完了後、最後に実行してダッシュボードを更新します。 Elementary を活用することで、実行履歴やデータ品質チェックの結果を可視化し、ErrorやWarningに迅速に対応できるようになります。 まとめ 本記事では、データマート集計基盤でdbtのモデル更新も行えるようにした事例について紹介しました。利用者がSQLファイルを書くだけでデータマートを更新できる既存の仕組みを残しつつ、集計定義を統制して品質担保したいデータマートはdbtでモデリングしていく方針を取りました。今後もデータの品質の向上やガバナンス強化のためにデータマート集計基盤を改善していく予定です。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com 「Cloud Composerにデータマート集計基盤を移行しました」の「各マートのSQLファイルからマート間の依存関係グラフの作成」を参照ください ↩
アバター
はじめに こんにちは、データサイエンス部データサイエンス2ブロックの Nishiyama です。我々のチームでは、AIやデータサイエンスを活用したプロダクトの開発ために、研究開発に取り組んでいます。我々のチームの具体的な業務については、以下の記事を参考にしてください。 techblog.zozo.com 本記事では、レビューパトロールの業務時間を67.7%削減したガイドライン違反検出ツールの開発について述べます。社内で特定の部署が抱える課題を解決し、業務効率を上げるツールを開発する方の一助になると幸いです。 目次 はじめに 目次 ガイドライン違反検出ツール 背景 作成した理由 課題の原因 課題の特定方法 課題の解決方法 技術選定 LLMを用いたガイドライン違反検出ツール開発 実験 評価 コスト 開発で徹底したこと さいごに ガイドライン違反検出ツール 開発したガイドライン違反検出ツールは、LLMを用いてZOZOTOWN上のレビューをパトロールし、違反を検出します。具体的には、以下に示すパイプラインを開発しました。 ガイドライン違反検出ツールのパイプライン図 パイプラインは、バッチ処理として実行されます。以下は詳細なステップです。 Cloud SchedulerはCloud Functionsをトリガーする Cloud FunctionsはVertex AI Pipelinesをキックする BigQueryから対象期間のレビューを抽出する Cloud Storageからガイドラインを取得する 3と4で得られたレビューとガイドラインをガイドライン違反検出ロジックへ入力する 違反検出ロジックは、違反可能性が高いレビューと違反理由を出力する 違反可能性が高いレビューと違反レビューをGoogle スプレッドシート(以下、シートと呼ぶ)に書き出す シートのURLを取得し、Slackへ通知する 以降は、ガイドライン違反検出ツールを作成した理由と、技術選定について述べていきます。 背景 2023年にZOZOTOWNは レビュー機能 を実装し、ユーザーは、ZOZOTOWNで購入した商品へレビューを投稿することやレビューを閲覧できるようになりました。また、健全なサイト運営のためのレビュー投稿ルールとして レビューガイドライン を導入しパトロール業務が発生しました。パトロール業務は、投稿されたレビューに対して、レビューガイドライン違反の有無を判定する業務です。パトロールでレビューガイドライン違反と判断されたレビューは、ガイドラインに従ってZOZOTOWN上から取り下げる対応をします。 ZOZOTOWN上のレビュー例 作成した理由 ガイドライン違反検出ツールは、パトロール業務を担当する部署が抱えていた、次の2つの課題を解決する価値が高いため開発しました。 現在、パトロール業務にかける時間や担当者が多い 将来、パトロール業務にかける時間や担当者が増加する 上記の課題を解決することで、パトロール担当部署は、これまでパトロール業務にかけていた時間や担当者を将来的にも別の業務に割り当てることができます。よって、解決する価値が高いと判断しました。 課題の原因 まず、パトロール業務にかける時間や担当者が多い理由は、一件ずつ目視で確認していたからです。以下にパトロール業務のフローを示します。 対象の期間のレビューを集計しシートに書き出す レビューのタイトルと内容がガイドラインに違反しているか目視で照らし合わせて確認する ガイドライン違反の有無と理由をシートに記載する 特に、パトロール業務のフロー2で照らし合わせるガイドラインは、30項目以上あります。そのため、パトロール業務の時間や担当者数の増加につながっています。 次に、将来パトロール業務にかける時間と人数が増える理由は、投稿されるレビュー数の増加が考えられるからです。要因として以下が考えられます。 レビュー機能の認知向上 商品数の増加 キャンペーンの実施 パトロール業務フローを見ると、投稿されるレビュー数の増加は、そのままパトロール対象のレビュー数の増加につながることがわかります。 課題の特定方法 上記の課題と原因を特定した方法は、関係者へのヒアリングです。以下のようなヒアリングをして、パトロール担当者の業務を理解し課題の深掘りをしました。 パトロール業務の目的と内容 パトロール業務に時間と人がかかる理由 ヒアリング内容から、課題の解決された状態を想定して、リリース基準を作成しました。 課題の解決方法 パトロール業務の品質を担保した上で課題を解決するために、半自動化の運用を採りました。半自動化は、ガイドライン違反検出ツールの出力を、パトロール担当者が目視確認する運用です。半自動化の運用にした理由は、ガイドライン違反検出ツールのみで、違反を確定させることが難しかったからです。具体的には、商品画像を参照しなければ分からない商品不良や人間の判断に委ねるべき曖昧なレビューが該当します。下記にガイドライン違反ツールを用いたパトロール業務のフローを示します。 ガイドライン違反検出ツールが対象の期間のレビューを入力として違反可能性が高いレビューと違反理由をシートに書き出す パトロール担当者は違反可能性の高いレビューがガイドラインに違反しているか目視で照らし合わせて確認する ガイドライン違反の有無と理由をシートに記載する ガイドライン違反検出ツールは、対象期間のレビューを入力として、違反可能性が高いレビューを出力します。違反可能性が高いレビュー数は、対象期間のレビュー数と比較して、68.5%少ない量になります。したがって、パトロール業務を担当する部署が抱えていた課題を以下の2点において解決していると言えます。 現在、対象期間のすべてのレビューと比較して68.5%少ない違反可能性が高いレビューの目視確認で済むため、パトロールにかける時間や担当者を削減できる 将来、投稿されるレビューが増加した際も、違反可能性が高いレビューのみ目視確認するため、時間と担当者の増加を抑えることができる 技術選定 ガイドライン違反可能性の高いレビューを検出するために、LLMのモデルとしてOpenAI APIのgpt-4-1106-previewを採用しました。OpenAIのGPT-4 APIを採用した理由は2点あります。1点目は、少量のデータセットに対してIn-Context Learningすることである程度の検出力が見込めたからです。2点目は、In-Contex Learningによりガイドライン違反基準を素早く柔軟に変更できるためです。素早く柔軟な違反基準の変更がメリットになる理由は、違反基準に主観を含む言語化の難しい曖昧なレビューが存在しているためです。技術選定時に比較・検討した手法は以下です。 LLM ナイーブなテキストフィルタリング モデルのフルスクラッチ・ファインチューニング 比較・検討結果について述べます。ナイーブなテキストフィルタリングは、違反可能性の高いレビュー検出の精度が低くなりました。なぜなら、同様のテキストが含まれる文章であっても、使用される文脈によって、ガイドライン違反の有無が異なるためです。次に、モデルのフルスクラッチ・ファインチューニングは、大きなデータセットをアノテーション段階から構築する必要があったため見送りました。 LLMを用いたガイドライン違反検出ツール開発 LLMを用いたツール開発の戦略は、 Optimizing LLM Accuracy に従いました。promptの書き方については、 Best practices for prompt engineering with the OpenAI API を参考にしました。特に参考にした点は以下です。 stepを明示的に示す Few-shot promptingを与える 期待する出力を明示する 各項目について、具体例を交えて、説明します。 1点目の「stepを明示的に示す」では、細かくstepを分け明示的に推論過程を与えました。加えて、Zero-shot Chain-of-Thoughtも使用しました。具体的な例を以下に示します。 prompt: str = f """ step1: ガイドライン項目をよく読んで、理解します。 step2: レビュー内容をガイドラインと1つ1つ照らし合わせて違反を検出します。 step3: 違反の有無と理由をあなたの考えも合わせて出力してください。 Let's think step by step """ 2点目の「Few-shot promptingを与える」では、ガイドライン違反検出の精度が低い項目に絞って使用しました。Few-shot promptingは、LLMに少数の例を明示的に与える方法です。具体的な例を以下に示します。 prompt: str = f """ 違反の有無と理由をあなたの考えも合わせて出力してください。 {guidelines[i]}の違反例: {violation_examples} """ # guideline[i]: 違反検出精度が低いガイドラインの本文 # violation_example: 違反例 3点目の「期待する出力を明示する」では、JSON formatを明示的に与えました。理由は2点あります。1点目は、出力を通し番号にすることで、input tokensより3倍高いoutput tokensの料金を抑える狙いがあったからです。2点目は、 response_format={"type": "json_object"} とした場合でも期待するJSON formatではない場合があったからです。 prompt: str = f """ あなたの出力は以下の出力フォーマット例に従ったjson formatです。 出力フォーマット例: 1. 違反がある場合: {{"違反あり" : "通し番号"}} 2. 違反が複数ある場合: {{"違反あり" : "通し番号,通し番号")}} 3. 違反が無い場合: {{"違反なし" : ""}} """ 実験 定量評価のための実験は、学習データセットに対してPrompt Engineeringを行いprecision, recall, f1-scoreを確認し、エラー分析をしました。データセットは、パトロール業務担当者にアノテーションを依頼し少量ずつ構築しました。 定性評価のための実験は、2つあります。1つ目は下記のようにグループを分け、ガイドライン違反を目視確認する実験です。この実験の目的は、ガイドライン違反ツールの出力が、目視確認とどの程度異なるか検証することです。2つ目は、False Negativeの質に対する実験です。この実験の目的は、ガイドライン違反検出ツールが許容できない見逃しをしていないかの確認です。なぜなら、半自動化の運用上、False Negativeのレビューは、担当者による目視確認がされないままZOZOTOWN上に掲載されるためです。 グループ タスク Aグループ 全てのレビューを目視確認する Bグループ ガイドライン違反可能性の高いレビューのみ目視確認する 評価 リリース基準を超えた時点での定量評価と定性評価について述べます。定量的な評価は以下のようになりました。precisionがやや低いため、目視確認する量が増えています。一方recallは高いツールとなっていることが分かります。 手法 / 評価指標 precision recall f1-score ガイドライン違反ツール 0.75 0.934 0.8319 定性的な評価は、AグループとBグループで出力の差異がほとんどない結果になりました。また、False Negativeの質についても、問題がない範囲であることを確認しました。 コスト ガイドライン違反検出ツールにかかる費用は、2つあります。1つ目がgpt-4-1106-previewの料金で $ 0.022/1レビューです。2つ目がGoogle Cloudの料金で、dev, stg, prd環境を合わせて $ 4.11/月です。 開発で徹底したこと ガイドライン違反検出ツールの開発で徹底したことは、パトロール担当部署の課題を解決することです。そのために、パトロール担当部署と連携を取りました。具体的には、定例ミーティングへの参加と定性評価と実験です。定例ミーティングへ参加することで、以下のメリットがありました。 開発着手時から後のユーザーとなる現場の声をツールに反映できる 現場の課題の優先度を鑑みて、開発する機能の優先度を揃えたスケジュール管理ができる 開発者がパトロール担当者から直接良い/悪いフィードバックを受けられる 認識の齟齬によるプロジェクトの手戻りが無い 定性評価と実験では、実験の設計段階から双方向に会話をして取り決めることができました。加えて、普段からコミュニケーションを取っていたため、実験が終わり次第評価・フィードバックというサイクルを早めることができました。私とパトロール担当部署を含めた関係者の間で課題を解決することを共有し、同じ方向を向けていたことは、ガイドライン違反検出ツールの質を高めたと感じます。 さいごに 本記事では、ガイドライン違反検出ツールの開発を紹介しました。ガイドライン違反検出ツールの導入により、業務時間を67.7%削減、チェック件数を68.5%削減しました。課題解決のためのツール作成を検討している方がいれば、ぜひ参考にしてみてください。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com
アバター
こんにちは、株式会社ZOZOで25卒の内定者アルバイトをしている村井です。この記事では業務で取り組んでいる、BigQueryで使うSQLのリンターの作成方法について紹介します。 目次 目次 課題と解決策 課題 解決策 BigQueryのアンチパターン認識ツール ミニマムな使い方 日本語がSQL内に含まれている際の問題 アンチパターンを定義する リンターとしてBigQueryのアンチパターン認識ツールを使用する際に生じる課題と解決策 構成 APIサーバ化 Chrome拡張 動作例 まとめ 課題と解決策 課題 社内では様々なチームがSQLを書いており、動作はするものの良くない書き方をしている場合があります。そういった構文を検知して、前もって修正する必要があります。 解決策 BigQueryのコンソールで入力されたSQLの不正構文を検知、修正案を提示できるようにしました。 BigQueryのアンチパターン認識ツール BigQueryのアンチパターン認識ツール とはGoogleが作成しているBigQueryのアンチパターンを教えてくれるツールです。 自分でアンチパターンを定義せずに使用した場合、以下のアンチパターンを検知して教えてくれます。 Selecting all columns 以下のように、アスタリスクによって全てのカラムが選択されている際のアンチパターンです。 SELECT * FROM `project.dataset.users`; SEMI-JOIN without aggregation DISTINCTのないサブクエリでINを使用しているときに発生します。 SELECT u.name FROM `project.dataset.users` u WHERE u.id IN ( SELECT id FROM `project.dataset.orders` o WHERE o.status = ' shipped ' ); Multiple CTEs referenced more than twice CTEが複数参照されているときに発生します。 WITH a AS (SELECT col1 FROM `project.dataset.table1`), b AS (SELECT col2 FROM a), c AS (SELECT col1 FROM a) SELECT a.col1, b.col2, c.col1 FROM a, b, c; Using ORDER BY without LIMIT ORDER BY句をLIMIT無しで使用したときに発生します。 SELECT name, age FROM `project.dataset.employees` ORDER BY age DESC ; Using REGEXP_CONTAINS when LIKE is an option LIKE句で十分表現でき、正規表現を使う必要がない場合に発生します。 SELECT username FROM `project.dataset.users` WHERE REGEXP_CONTAINS(username, r ' .*admin.* ' ); Using an analytic functions to determine latest record 分析関数を用いて最新のレコードを特定するときに発生します。以下のコードはrow_numberを使用していますが、ORDER BYとLIMITを使って書き直すことができます。 SELECT id, fare FROM ( SELECT id, fare, row_number() over(partition by id order by fare desc ) rn FROM `project.dataset.table1` ) WHERE rn = 1 ; Convert Dynamic Predicates into Static Dynamic PredicateをStatic Predicateに変えるとパフォーマンスが向上するかもしれないときに発生します。以下のコードは、WHERE句に含まれるサブクエリが動的に条件を生成します。 SELECT * FROM `project.dataset.users` u WHERE u.id IN ( SELECT id FROM `project.dataset.customers` WHERE region = ' US ' ); Where order, apply most selective expression first WHERE句の中のフィルタ条件が不適切な順序であるときに発生します。以下のコードには price > 1000 と category = 'electronics' のフィルタがあります。この場合選択性が高いのは category = 'electronics' なので price > 1000 より前に書くべきであるということです。 SELECT id, name FROM `project.dataset.products` WHERE price > 1000 AND category = ' electronics ' ; Missing DROP Statement TEMP TABLEをDROPしないときに発生します。 CREATE TEMP TABLE `project.dataset.temp_table` (id INT64, name STRING); Dropped Persistent Table TEMP TABLEで事足りる際に、永続的なテーブルをCREATEして最後にDROPしているときに発生します。 CREATE TABLE `project.dataset.temp_table` (id INT64, name STRING); SELECT * FROM `project.dataset.temp_table`; DROP TABLE `project.dataset.temp_table`; 今回はこれに加えて自ら定義したアンチパターンを検知したいので、のちに追加します。 ミニマムな使い方 まず、ローカルでBigQueryのアンチパターン認識ツールを使う方法をご紹介します。 最初に、 BigQueryのアンチパターン認識ツールのリポジトリ をcloneします。このシステムはJavaで作られており、以下のコマンドでjibを使ってDockerコンテナイメージをビルドします。 mvn clean package jib:dockerBuild -DskipTests ビルドができたら以下でクエリの解析結果が返ってきます。 docker run \ -i bigquery-antipattern-recognition \ --query " SELECT * FROM \` project.dataset.table1 \` " また、特定のSQLファイルを解析するには以下のようなコマンドを入力します。 export INPUT_FOLDER = $( pwd ) /samples/queries/input export INPUT_FILE_NAME =multipleCTEs.sql docker run \ -v $INPUT_FOLDER : $INPUT_FOLDER \ -i bigquery-antipattern-recognition \ --input_file_path $INPUT_FOLDER / $INPUT_FILE_NAME 日本語がSQL内に含まれている際の問題 今回BigQueryのアンチパターン認識ツールを使用するにあたって、以下のようにSQLに日本語が入るとエラーが発生するという問題が発生しました。 --日本語ああああああああああああああああ SELECT title, language FROM `bigquery- public -data.samples.wikipedia` WHERE REGEXP_CONTAINS(title, ' .*aaaaa.* ' ) エラー内容は以下の通りです。 ERROR com.google.zetasql.toolkit.antipattern.util.AntiPatternHelper - index 138,length 138 java.lang.StringIndexOutOfBoundsException: index 138,length 138 at java.base/java.lang.String.checkIndex(String.java:3278) at java.base/java.lang.StringUTF16.checkIndex(StringUTF16.java:1470) at java.base/java.lang.StringUTF16.charAt(StringUTF16.java:1267) at java.base/java.lang.String.charAt(String.java:695) at com.google.zetasql.toolkit.antipattern.util.ZetaSQLStringParsingHelper.countLine(ZetaSQLStringParsingHelper.java:67) at com.google.zetasql.toolkit.antipattern.parser.visitors.IdentifyRegexpContainsVisitor.visit(IdentifyRegexpContainsVisitor.java:63) at com.google.zetasql.parser.ASTNodes$ASTFunctionCall.accept(ASTNodes.java:3592) at com.google.zetasql.parser.ParseTreeVisitor.descend(ParseTreeVisitor.java:45) (略) Javaの例外であるStringIndexOutOfBoundsExceptionが出ています。要約すると、文字列の指定されたインデックスが文字列長を超えているというエラーです。 このエラーの原因は、BigQueryのアンチパターン認識ツールがバイト長で文字列長をカウントしていることでした。アンチパターンが起こっている行を示すためにBigQueryのアンチパターン認識ツールは文字列長をカウントします。その手法こそがバイト長を文字列長として扱うというものでした。SQLがアルファベットや数字など1バイト文字だけで構成されている場合、バイト長と文字列長が一致するので問題ありません。しかし、SQLに日本語などのマルチバイト文字が入っている場合、バイト長>文字列長となってしまいエラーが発生します。 この解決手段として、バイト長を文字列長と一致させるようBigQueryのアンチパターン認識ツールのコードを改変しました。 改変内容は、BigQueryのアンチパターン認識ツールに PR を出しマージされたので、現在この問題は発生しません。 アンチパターンを定義する 今回は定義済みのアンチパターンに加えて不正なテーブル名をアンチパターンとして検出するという要件がありました。具体的には、以下のようにSQL内に出現するテーブル名の中にプロジェクト名が入っていない場合、プロジェクト名まで含めるよう促すというものです。 OK FROM `project_name.dataset_name.table_name` NG FROM `dataset_name.table_name` -- プロジェクト名が省略されている このように、自分で定義したアンチパターンを追加する方法をご紹介します。 前提として、BigQueryのアンチパターン認識ツールはSQLをAST(抽象構文木)に変換して、そのASTをトラバースして構文解析します。このときVisitorパターンを用います。したがって、新たなアンチパターンを定義するときには該当ノードをトラバースするVisitorを作成する必要があります。AntiPatternVisitorを実装する形でParseTreeVisitorを継承した新しいVisitorクラスを定義していきます。 // このソースコードは `src/main/java/com/google/zetasql/toolkit/antipattern/parser/visitors` フォルダに配置してください。既存のVisitorが置かれています。 public class IdentifyTableVisitor extends ParseTreeVisitor implements AntiPatternVisitor { public static final String NAME = "Table" ; private Set<String> tableNames = new HashSet<>(); private Set<String> withNames = new HashSet<>(); private ArrayList<String> result = new ArrayList<String>(); private final String SUGGESTION_MESSAGE = "テーブル名が不正です。プロジェクト名をテーブル名に追加してください %s." ; public IdentifyTableVisitor(String query) { this .query = query; } public void visit(ASTNodes.ASTTableExpression tableExpression) { if (tableExpression instanceof ASTNodes.ASTTablePathExpression) { visit((ASTTablePathExpression) tableExpression); } else if (tableExpression instanceof ASTNodes.ASTJoin) { visit(((ASTNodes.ASTJoin) tableExpression).getLhs()); visit(((ASTNodes.ASTJoin) tableExpression).getRhs()); } else if (tableExpression instanceof ASTNodes.ASTTableSubquery) { ASTNodes.ASTQueryExpression queryExpression = ((ASTNodes.ASTTableSubquery) tableExpression).getSubquery().getQueryExpr(); if (queryExpression instanceof ASTNodes.ASTSelect) { ASTNodes.ASTTableExpression tableExpression1 = ((ASTSelect) queryExpression).getFromClause().getTableExpression(); visit(tableExpression1); } } } @Override public void visit(ASTTablePathExpression tablePathExpression) { if (tablePathExpression.getPathExpr() != null ) { List<String> namePaths = tablePathExpression.getPathExpr().getNames().stream() .map(ASTIdentifier::getIdString).collect(Collectors.toList()); tableNames.addAll(namePaths); } if (tablePathExpression.getUnnestExpr() != null ) { String unNestExpressions = tablePathExpression.getUnnestExpr().getExpression().toString(); withNames.add(unNestExpressions); } } // ここでWITH句で定義されたテーブル名を抽出 @Override public void visit(ASTWithClause withClause) { List<ASTAliasedQuery> namePaths = withClause.getWith().stream().collect(Collectors.toList()); for (ASTAliasedQuery value : namePaths) { value.accept( this ); withNames.add(value.getAlias().getIdString()); } } private int countDot(String str) { int count = 0 ; for ( int i = 0 ; i < str.length(); i++) { if (str.charAt(i) == '.' ) { count++; } } return count; } public String getResult() { for (String tableName : tableNames) { int count = countDot(tableName); if (count != 2 && !withNames.stream().anyMatch(set -> set.contains(tableName))) { result.add(String.format(SUGGESTION_MESSAGE, tableName)); } } return result.stream().distinct().collect(Collectors.joining( " \n " )); } @Override public String getName() { return NAME; } } このコードで、テーブル名に当たるASTのノードを訪問し、ドットの数でテーブル名にプロジェクト名が含まれているかどうかを判断します。ドットが2個未満の場合はプロジェクト名が含まれていないという判定をします。しかし、WITH句で定義されたテーブル名に関してはこの限りではないので除外できるようにします。 そして以下のファイルのgetParserVisitorListに、定義したAntiPatternVisitorのインスタンスを追加します。 public List<AntiPatternVisitor> getParserVisitorList(String query) { return new ArrayList<>(Arrays.asList( new IdentifySimpleSelectStarVisitor(), new IdentifyInSubqueryWithoutAggVisitor(query), new IdentifyDynamicPredicateVisitor(query), new IdentifyOrderByWithoutLimitVisitor(query), new IdentifyRegexpContainsVisitor(query), new IdentifyCTEsEvalMultipleTimesVisitor(query), new IdentifyLatestRecordVisitor(query), new IdentifyWhereOrderVisitor(query), new IdentifyMissingDropStatementVisitor(query), new IdentifyDroppedPersistentTableVisitor(query), new IdentifyTableVisitor(query) // 追加 )); } これで不正なテーブル名をアンチパターンとして警告できるようになりました。 リンターとしてBigQueryのアンチパターン認識ツールを使用する際に生じる課題と解決策 BigQueryのアンチパターン認識ツールをそのまま使用する場合、Dockerコンテナを建てるかビルド済みのjarファイルをコマンドライン上で動作させます。使ってもらう際、各々の環境の違いもある中で動作環境を整え、解析対象のSQLを参照しコマンドを実行してもらう方法では手間がかかりすぎます。さらにエンジニア以外の使用も想定されるため、現実的ではありません。 そこで、Chromeの拡張機能として、BigQueryのコンソールで入力されたSQLをボタン1つで解析できるようにしました。SQLを投げると解析結果が返ってくるAPIを作成し、そのAPIをユーザが呼び出すという構成です。これによりコンソールにSQLを入力するだけで誰でも解析を掛けられるようになりました。 構成 作成したリンターを実際にChromeの拡張機能として使用する方法を紹介していきます。 APIサーバ化 BigQueryのアンチパターン認識ツールは、Spring Bootを使ってWebサービスとして使う環境が最初から整っています。それを利用してSQLをリクエストとして解析結果を返すAPIサーバを作成します。Spring Bootをセットアップする際のおおまかな手順は以下の通りです。 mainメソッドの変更 Spring BootのControllerを記述 出力メッセージのクラスを作成 まず、mainメソッドでSpring Bootを起動させられるようにします。 @SpringBootApplication public class Main { public static void main(String[] args) { SpringApplication.run(Main. class , args); } } 続いて、実際に構文解析するコードをSpring BootのControllerとして書き直します。基本的に改変する前のmainメソッドと同様ですが、1つのSQLを受け取り1つの解析結果を返せるようにします。 public class QueryRequest { private String query; public String getQuery() { return query; } } @RestController public class MainController { private static int countQueriesWithAntipattern = 0 ; @PostMapping ( "/" ) public Map<String, Object> processQuery( @RequestBody QueryRequest queryRequest) { try { String query = queryRequest.getQuery(); String replies[] = new String[ 1 ]; AntiPatternCommandParser cmdParser = new AntiPatternCommandParser( new String[] {}); AntiPatternHelper antiPatternHelper = new AntiPatternHelper( cmdParser.getProcessingProject(), cmdParser.useAnalyzer()); OutputWriterForResponse outputWriter = new LogOutputWriterForResponse(); Boolean rewriteSQL = cmdParser.rewriteSQL(); outputWriter.setRewriteSQL(rewriteSQL); InputQuery inputQuery = new InputQuery(query, "query provided by param:" ); StringBuilder result = executeAntiPatternsInQuery(inputQuery, outputWriter, cmdParser, antiPatternHelper); result.append(logResultStats()); outputWriter.close(); replies[ 0 ] = result.toString(); return Map.of( "replies" , replies); } catch (Exception e) { return Map.of( "errorMessage" , e.toString()); } } private StringBuilder executeAntiPatternsInQuery(InputQuery inputQuery, OutputWriterForResponse outputWriter, AntiPatternCommandParser cmdParser, AntiPatternHelper antiPatternHelper) { StringBuilder stringBuilder = new StringBuilder(); try { List<AntiPatternVisitor> visitorsThatFoundAntiPatterns = new ArrayList<>(); // parser visitors antiPatternHelper.checkForAntiPatternsInQueryWithParserVisitors(inputQuery, visitorsThatFoundAntiPatterns); // analyzer visitor if (antiPatternHelper.getUseAnalizer()) { antiPatternHelper.checkForAntiPatternsInQueryWithAnalyzerVisitors(inputQuery, visitorsThatFoundAntiPatterns); } // rewrite if (cmdParser.rewriteSQL()) { GeminiRewriter.rewriteSQL(inputQuery, visitorsThatFoundAntiPatterns, antiPatternHelper, cmdParser.getLlmRetriesSQL(), cmdParser.getLlmStrictValidation()); } // write output if (!visitorsThatFoundAntiPatterns.isEmpty()) { return outputWriter.writeRecForQuery(inputQuery, visitorsThatFoundAntiPatterns, cmdParser); } return stringBuilder; } catch (Exception e) { System.out.println(e); return stringBuilder; } } private static String logResultStats() { StringBuilder statsString = new StringBuilder(); statsString.append( " \n * Queries with anti patterns: " + countQueriesWithAntipattern); return statsString.toString(); } } そして、レスポンスで使う出力メッセージ作成クラスを追加します。 // このソースコードは `src/main/java/com/google/zetasql/toolkit/antipattern/output` フォルダに配置してください。 public abstract class OutputWriterForResponse { private boolean rewriteSQL = false ; public abstract StringBuilder writeRecForQuery(InputQuery inputQuery, List<AntiPatternVisitor> visitorsThatFoundPatterns, AntiPatternCommandParser cmdParser) throws IOException; public void close() throws IOException {}; public void setRewriteSQL( boolean rewriteSQL) { this .rewriteSQL = rewriteSQL; } } // このソースコードは `src/main/java/com/google/zetasql/toolkit/antipattern/output` フォルダに配置してください。 public class LogOutputWriterForResponse extends OutputWriterForResponse { private static final Logger logger = LoggerFactory.getLogger(LogOutputWriter. class ); public StringBuilder writeRecForQuery(InputQuery inputQuery, List<AntiPatternVisitor> visitorsThatFoundPatterns, AntiPatternCommandParser cmdParser) { StringBuilder outputStrBuilder = new StringBuilder(); outputStrBuilder.append( " \n " + "-" .repeat( 50 )); outputStrBuilder.append( " \n Recommendations for query: " + inputQuery.getQueryId()); for (AntiPatternVisitor visitor: visitorsThatFoundPatterns) { outputStrBuilder.append( " \n * " + visitor.getName() + ": " + visitor.getResult()); } if (cmdParser.rewriteSQL() && inputQuery.getOptimizedQuery() != null ) { outputStrBuilder.append( " \n * Optimized query: \n " ); outputStrBuilder.append(inputQuery.getOptimizedQuery()); } outputStrBuilder.append( " \n " + "-" .repeat( 50 )); outputStrBuilder.append( " \n\n " ); return outputStrBuilder; } } これで構文解析の機能をAPIリクエストでSQLを投げることで使用できるようになりました。 次に作成したAPIサーバをデプロイします。Cloud Runにデプロイするまでの流れは以下の通りです。 Artifact Registryにリポジトリを作成 Docker imageをビルド、タグ付け Docker imageのpush Cloud Runにデプロイ まず、Artifact Registryにリポジトリを作成します。形式はDockerを選択します。 次に、実際にpushします。 以下でDocker imageをビルドします。 mvn clean package jib:dockerBuild -DskipTests Docker imageにタグ付けします。 docker tag bigquery-antipattern-recognition [ REGION ] -docker.pkg.dev/ [ PROJECT_ID ] / [ REPOSITORY_NAME ] / [ IMAGE ] そして、pushします。 docker push [ REGION ] -docker.pkg.dev/ [ PROJECT_ID ] / [ REPOSITORY_NAME ] / [ IMAGE ] 次にイメージをCloud Runにデプロイします。 gcloud run deploy --image [ REGION ] -docker.pkg.dev/ [ PROJECT_ID ] / [ REPOSITORY_NAME ] / [ IMAGE ] : [ TAG ] --platform = managed --project =[ PROJECT_ID ] これでCloud Run上に、POSTリクエストでSQLをbodyに含めれば解析結果が返ってくるAPIサーバをデプロイできました。次に社員のみがこのAPIを使用できるようにするため、IAPによる認証をつけます。IAPはロードバランサ上で動作するので、まずロードバランサを作成します。そしてCloud Runサービスをバックエンドサービスとしてロードバランサに紐づける作業を先にします。具体的な流れは以下の通りです。 外部静的アドレスを取得 DNSの設定 ロードバランサを作成 作成したCloud Runサービスをロードバランサに紐づける ロードバランサのフロントエンドの設定 IAPの設定 まず、ロードバランサに接続するための外部静的アドレスを「VPCネットワーク - IPアドレス」から予約します。 次にCloud DNSからレコードセットを作成します。リソースレコードのタイプはAとし、IPv4アドレスには予約したIPアドレスを紐づけます。IPアドレスの設定ができたらロードバランサを作成します。 「ロードバランサの作成」を選択し、外部のアプリケーションロードバランサを作成します。 次にバックエンドの構成を設定します。「バックエンドサービスとバックエンドバケット」からバックエンドサービスを作成します。バックエンドタイプを「サーバーレスネットワークエンドポイントグループ」に設定し、新しいバックエンドとして先ほど作成したCloud Runサービスを指定します。 これでロードバランサとCloud Runサービスが紐づきました。 フロントエンドの構成では、プロトコルをHTTPSにします。IPアドレスに先ほど設定したものを指定します。証明書は、「新しい証明書を作成」で先ほど作成したドメインを指定し、Googleマネージドの証明書を作成します。 ロードバランサを作成できました。次に、このロードバランサに対してIAPで認証をかけます。Identity-Aware Proxyのコンソール画面から設定します。 バックエンドサービスの中からIAPの認証をかけたいものを選び、IAPのトグルをオンにします。そして、アクセス権の設定をします。プリンシパルを追加し、「IAP-secured Web App User」ロールを割り当てます。割り当てられたプリンシパルは今回作成したAPIサーバへアクセスできるようになります。 最後に当該バックエンドサービスの設定画面で、最下部の「HTTPオプションを有効にする」にチェックを入れておきます。こちらについては後述します。 Chrome拡張 次に、クライアントサイドの作成方法を紹介します。Chrome拡張を用いてBigQueryのコンソール画面に解析ボタンを設置しました。ユーザが解析ボタンを押すと、エディタに入力したSQLをリクエストボディとして先ほど作成した解析用のAPIリクエストを送信できるようにしました。解析結果はモーダルで表示します。 Chrome拡張を作成する際、以下のようなディレクトリ構造になります。 linter-extension ├── content.js ├── manifest.json └── styles.css manifest.jsonは、拡張の構成や権限、動作方法を定義します。host_permissionsにAPIサーバのURLを記述、content_scriptsのmatchesにDOMを操作するサイトのURLを記述しておきます。 { " manifest_version ": 3 , " name ": " linter ", " version ": " 1.0 ", " permissions ": [ " cookies ", " activeTab " , ] , " action ": { " default_popup ": " popup.html " } , " content_scripts ": [ { " matches ": [ " https://console.cloud.google.com/bigquery* " ] , " js ": [ " content.js " ] , " css ": [ " styles.css " ] } ] , " host_permissions ": [ " https://[作成したAPIサーバのドメイン]/* " ] } content.jsには、実際にページのDOMを操作して要素を追加、削除、変更するJavaScriptを書きます。今回の場合は以下のような内容を書きます。 コンソール画面へのボタンの追加 エディタに書かれたSQLの読み取り APIをリクエストする 初回の認証を通すコードは IAP セッションの管理 を参照しました。 // エディタからSQLを取得するコード function getCombinedText () { const formParent = document . querySelector ( '[エディタのセレクタ]' ) ; if ( formParent ) { console . log ( formParent ) const content = formParent . innerText || formParent . textContent ; return content . replace (/ [\r\n \ \ ] + / g , '' ) ; } else { return "" ; } } // 初回の認証を通すコード var iapSessionRefreshWindow = null ; function sessionRefreshClicked () { if ( iapSessionRefreshWindow == null ) { iapSessionRefreshWindow = window . open ( "/?gcp-iap-mode=DO_SESSION_REFRESH" ) ; window . setTimeout ( checkSessionRefresh , 500 ) ; } return false ; } function checkSessionRefresh () { if ( iapSessionRefreshWindow ! = null && ! iapSessionRefreshWindow . closed ) { fetch ( "/" , { method : 'POST' , headers : { 'Content-Type' : 'application/json' , } , credentials : 'include' , }) . then ( function ( response ) { if ( response . status === 401 ) { window . setTimeout ( checkSessionRefresh , 500 ) ; } else { iapSessionRefreshWindow . close () ; iapSessionRefreshWindow = null ; } }) ; } else { iapSessionRefreshWindow = null ; } } // APIを叩くボタンを設置する function addButton () { const linterUrl = '[APIサーバのURL]' ; const modalHTML = ` <div id="modal" class="modal"> <div class="modal-content"> <span class="close">×</span> <div id="modalText"></div> </div> </div> ` ; document . body . insertAdjacentHTML ( 'beforeend' , modalHTML ) ; const modal = document . getElementById ( "modal" ) ; const modalText = document . getElementById ( "modalText" ) ; const close = document . getElementsByClassName ( "close" )[ 0 ] ; function openModal ( message ) { modalText . textContent = message ; modal . style . display = "block" ; } close . onclick = function () { modal . style . display = "none" ; } window . onclick = function ( event ) { if ( event . target === modal ) { modal . style . display = "none" ; } } function addButtonToActionBars () { const parentDiv = document . querySelectorAll ( "[ボタンを追加したい親要素]" ) ; const newDiv = document . createElement ( 'div' ) ; const button = document . createElement ( 'button' ) ; newDiv . appendChild ( button ) ; parentDiv . appendChild ( newDiv ) ; button . addEventListener ( 'click' , async () => { const query = getCombinedText () ; try { const response = await fetch ( linterUrl , { method : 'POST' , headers : { 'Content-Type' : 'application/json' , } , body : JSON . stringify ({ "query" : query }) , credentials : 'include' , }) ; if ( response . status === 401 ) { button . onclick = sessionRefreshClicked () ; } else if ( ! response . ok ) { console . log ( response ) ; throw new Error ( `HTTP error! Status: ${ response . status } ` ) ; } else { const data = await response . json () ; openModal ( data . replies . join ( '\n' )) ; } } catch ( err ) { openModal ( err ) ; } }) ; } ; addButtonToActionBars () ; } window .onload = addButton ; 今回はBigQueryのコンソールからAPIサーバにクロスオリジンでリクエストを送っています。 かつ、Content-Typeヘッダにapplication/jsonを指定してPOSTリクエストをしているため、リクエストの前にプリフライトリクエストが発生します。先述した「HTTPオプションを有効にする」をチェックしない場合プリフライトリクエストが正常にサーバ側に届かないので注意してください。 動作例 最後に作成したChrome拡張を有効化します。Chromeでchrome://extensions/にアクセスして拡張機能ページを開き、画面右上のデベロッパーモードをオンにします。 そして画面左上の「パッケージ化されていない拡張機能を読み込む」をクリックします。すると拡張機能のディレクトリを選択できるようになるので、作成した拡張機能のディレクトリを選択します。すべての拡張機能の欄に作成したものが追加されたことを確認してください。 拡張機能を有効にすると以下のような解析ボタンが現れます。 クエリを入力し、解析ボタンを押すと解析されます。 まとめ 今回はChrome拡張としてBigQueryのアンチパターン認識ツールを利用して独自のSQLリンターを作成できました。ぜひ参考にしていただけると幸いです。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com
アバター
こんにちは、XR × Fashion TechやXR × Beauty Techといった領域を推進している創造開発ブロックの @ikkou です。 2025年1月7日から10日の4日間にかけてラスベガスで開催された「 CES 2025 」に一般参加者として現地参加してきました。なぜZOZOがCESに参加するのか、疑問に思われる方もいるでしょう。私自身が注力しているXR領域に関する最新動向の調査と、ZOZOとしても親和性の高いFashion Tech・Beauty Techのトレンドを一次情報として得るためです。現地で最新技術を直接体験することで、記事やニュースでは得られない深いインサイトを得られます。 私個人としては通算で6度目、ZOZO所属としては2020年、2023年、2024年に続き4度目の参加となります。継続して参加することで定点観測の意味もあります。 CES 2024 参加レポート - コロナ禍以前の活況を取り戻した CES CES 2023 参加レポート - 3年ぶりの現地参加 CES 2020参加レポート: 現地参加3年目の目線で視た #CES2020 前半はCESの概要と関連する情報のアップデートを、後半は特に私が注目したトピックについてお伝えします。 CES 2025全体のトレンドについては、Day 2から会場で配布されているCES Daily Showのデジタル版 Day 1 ・ Day 2 ・ Day 3 などをご覧ください。 CESとは CES 2025の6つのトレンドと49個のカテゴリー CES 2025のメインテーマは「DIVE IN」 出展社数と参加者数の推移 チケットの価格 会場の概要 会場間の移動 Vegas Loop Official CES Store XR Tech XR関連企業の出展動向 CES Innovation Awardsから見るXRデバイスのトレンド 出展ブースのXRデバイスから見るXRデバイスのトレンド ARグラスとスマートグラス 補助デバイスとしてのスマートグラス エルシオによる「オートフォーカスグラス」 EssilorLuxotticaによる「Nuance Audio」 Gentex Corporationによる「eSight Go」 要素技術 Cellidによる「最新のウェイブガイド」 DUAL MOVEによる「tXR display」 AGCによる「AR/MRグラス向け高屈折率ガラス基板」 Fashion TechとBeauty Tech コーセーによる「Mixed Reality Makeup」 アシックスとダッソー・システムズによる「パーソナライズドフットウェア」 Prinkerによる「Prinker POP」 その他の気になったFashion TechとBeauty Tech ロレアルグループによる「ロレアル セル バイオプリント」 Withingsによるスマートミラー「OMNIA」 おわりに CESとは Venetian Expoの1Fと2Fをつなぐエスカレーター付近に設置されているCESの看板 CES はCTA(Consumer Technology Association)が主催する、毎年1月にラスベガスで開催される世界最大級を誇る「 テクノロジーのショーケース 」です。 読み方は「せす」と呼ぶ方もいますが、正しくは「 シーイーエス 」です。CESはかつて『Consumer Electronics Show』と呼ばれていましたが、現在この表記は使用されていません。CESは現在、何かの略称ではなく、単独の名称として使用されています。 CES 2025からCESのロゴが刷新された 昨年、CESを主催しているCTAは記念すべき100周年を迎えました。そしてCES 2025にあわせてCTAとCESのロゴがリニューアルされました。 CES 2025の6つのトレンドと49個のカテゴリー CESには毎年CTAの打ち出すトレンドがあり、 2025年1月5日に発表されたCTAのプレスリリース によると、2025年のトップトレンドとして、以下の6つが発表されました。 AI / Artificial Intelligence デジタルヘルス / Digital Health エネルギートランジション / Energy Transition モビリティ / Mobility クオンタム / Quantum サステナビリティ / Sustainability 2024年はAI・万人のための人間の安全保障・モビリティ・サステナビリティの4つでした。2025年は万人のための人間の安全保障の代わりに、デジタルヘルス・エネルギートランジション・クオンタムが加わった形になります。 トップトレンドとは別に、次に挙げる49の技術領域が公式カテゴリーとして設定されています。 3D Printing, 5G Technologies, Accessibility, Accessories, AgTech, AR/VR/XR , Artificial Intelligence, Audio, Beauty Tech , Blockchain, Cloud Computing, Construction Tech, Content and EntertaInment, Cryptocurrency, Cybersecurity, Digital Health, Drones, Education Tech, Energy Transition, Energy/Power, Enterprise, Fashion Tech , Fintech, Fitness, Food Tech, Gaming and Esports , Home Entertainment and Office Hardware, Imaging, Investing, IoT/Sensors, Lifestyle, Marketing and Advertising, Metaverse, NFT, Quantum Computing, Retail/E-Commerce , Robotics, Smart Cities, Smart Home and Appliances, Sourcing and Manufacturing, Space Tech, Sports, Startups, Streaming, Supply and Logistics, Sustainability, Travel and Tourism, Vehicle Tech and Advanced Mobility, Video かつて『家電見本市』と形容されていたCESですが、現在は家電だけに留まらず、非常に幅広い技術領域を網羅していることが印象的です。私が注目しているAR/VR/XRをはじめとする技術領域は太字で示しています。 CES 2025のメインテーマは「DIVE IN」 LVCC West Hallに設置されている#CES 2025のモニュメント CES 2024のメインテーマ「All ON」に続くCES 2025のメインテーマは「DIVE IN」でした。直訳すると「飛び込む」ですが、物事に対して積極的に取り組むという意味もあります。未来への飛躍を象徴するテーマと言えるでしょう。 出展社数と参加者数の推移 コロナ禍以降の出展社数は減少しましたが、CES 2025では過去最高の4,500社以上が出展していました。参加者数は最盛期ほどではありませんが、昨年・一昨年に比べると少しずつ戻ってきています。コロナ禍前後の出展社数と参加者数の推移は以下の通りです。 年度 出展社数 参加者数 CES 2018 3,900社以上 182,198人 CES 2019 4,500社以上 175,212人 CES 2020 約4,500社 171,268人 CES 2021 約2,000社 約80,000人 CES 2022 2,300社以上 44,000人以上 CES 2023 3,200社以上 117,841人 CES 2024 4,300社以上 138,789人 CES 2025 4,500社以上 141,000人以上 なお、CESの参加者数は1桁単位で精密な数字が公表されていますが、これは参加バッジの発行数をもとにした重複カウントなしの純粋な参加者数です。CES 2025の正確な参加者数は後日公開される見込みです。 チケットの価格 LVCC West Hallのエントランスを仰ぐ CES 2025の参加登録は2025年9月11日に開始され、チケットの価格は同12月5日までの早期登録が149 USD、以降は350 USDでした。私は昨年に続き「貴重なCESの卒業生(英語表記は“valued CES alum”)」向けの特典により無料でした。 直近半年間でドル円の為替相場が大きく変動したため、チケットの取得タイミングによっては多少の差が生じました。例えば早期登録の期間中にチケットを購入していた場合は149 USDで約21,200〜22,400円の変動幅に、通常登録の期間中にチケットを購入していた場合は350 USDで約52,500〜55,200円の変動幅になります。 昨年はCES公式のカンファレンスプログラミングパスを追加しましたが、今年は追加せずにブースのみを巡りました。これは単純に、カンファレンスに参加するとブースを巡るための時間を削減せざるを得ないこともありますが、 セッション動画は後日公開される ため、そこに金銭的・時間的コストをかける必要はないと判断しました。これは参加する目的によっても異なると思いますが、私にとっては、ブースを巡ることが主目的のため、今回はカンファレンスプログラミングパスを追加しませんでした。 会場の概要 Image Source. https://www.ces.tech/explore-ces/maps-and-locations/ CESの展示会場はこれまでTech East・Tech West・Tech Southという3つのエリアに大別されていましたが、CES 2025ではTech Eastが LVCC Campus に、Tech Westが Venetian Campus に、そしてTech Southが C Space Campus にリネームされました。 工事が完了し再び利用されるようになったLVCC South CES 2024ではTech Eastに Westgate と Renaissance が含まれていましたが、CES 2025ではLVCCのみになりました。また、工事の完了したLVCC Southが再び利用されるようになりました。結果的にLVCCのみとなったことがリネームの理由と考えられます。同様にTech WestもVenetian Expoに統合されました。Tech SouthはC Spaceのため会場だったため、よりわかりやすい会場名になったと言えるでしょう。 一般の来場者向けの会期は4日間ありますが、1人で全ての会場・全てのブースを巡るのは非現実的です。私は例年通り主にマーケター向けのC Space Campusには行かず、以下の工程で会場を巡りました。 Day 1:LVCC Campus(WestとNorth) Day 2:Venetian Campus全体 Day 3:LVCC Campus(Central) Day 4:LVCC Campus(South) 4日間とも、とにかく歩き回りました。歩きやすい履き慣れた靴の準備が欠かせません。事前にある程度は計画して効率的に巡っているとはいえ、4日間の平均歩数は1日平均20,000歩を超えていました。 会場間の移動 とにかく会場間の移動には時間がかかります。今回、会場間の移動には徒歩、Vegas Loop、そしてLyftを利用しました。徒歩とLyftについては昨年からの特筆すべきアップデートはありませんでした。 Vegas Loop Vegas Loopで見かけたTesla Model X 昨年、一昨年と毎回お世話になっている Vegas Loop ですが、今年もLVCC各ホール間の移動に何度か利用しました。 Image source. https://www.lvcva.com/vegas-loop/ Vegas LoopはLVCCのWest Hall・Central Hall・South Hall間を結ぶ無償のLVCCラインと、RESORT WORLDとLVCCの間を結ぶ有料のRESORT WORLDラインがあります。RESORT WORLDラインは開通当初、1日乗り放題で2.5 USDでしたが、CES 2023時は1日乗り放題で4.5 USDに値上がりしました。そしてCES 2024では5 USDに、今年のCES 2025では10 USDと大幅に値上がりしていました。 WESTGATE RESORT STATIONが延伸される予定 各ステーションに掲示された路線図には、LVCC RIVIERA STATIONからWESTGATE RESORTへの延伸予定が示されていました。これは WESTGATE RESORTのページでも示されています 。 The new Vegas Loop will connect to the existing station, making it easy for guests of Westgate Resorts to get around town. そして、この路線は1月18日に開通したことが、 The Boring CompanyのXアカウントで投稿されています 。 CES 2025ではWESTGATEが会場として利用されることはありませんでしたが、過去には利用されていたので、再び会場として利用される可能性も考えられます。また、会期中はビジネスミーティングのためのプライベートブースとして利用されているので、この延伸はビジネスミーティングの利便性向上にも寄与するでしょう。 Official CES Store Official CES Store @ LVCC North Lobby CES 2025ではCES公式のOfficial CES Storeが会場内に4ヵ所設置されていました。名称は異なりますが、CES 2024ではOfficial Show Storeとして同様のストアが会場内に5箇所設置されていました。 Official CES Storeで販売されていた$40の公式Tシャツ 様々なグッズが販売されていましたが、昨年同様、Tシャツやフーディーは生成AIで作成されたと思われるビジュアルが多く感じました。特にモナリザがVR HMDを装着しているユニークなデザインに惹かれました。 XR Tech 前述の通り、CESのカテゴリーのひとつに「 AR/VR/XR 」が設けられています。このパートでは、それらをXR Techとしてまとめてお伝えします。 XR関連企業の出展動向 Image source. https://exhibitors.ces.tech/8_0/floorplan/?hallID=B CES 2025では、LVCC Central Hallに「 GAMING | XR 」とカテゴライズされた一画が設けられていました。「 AR/VR/XR 」と「 Gaming and Esports 」が統合された形です。 CES 2020では「 AR/VR & Gaming 」、CES 2023、2024では「 GAMING | METAVERSE | XR 」でした。私は昨年のレポート記事で「 個人の感覚として、2024年においてもメタバースムーブメントが去年同様盛り上がっているかどうかについて疑義があります 」と述べました。その通り、いよいよ「メタバース」というキーワードがカテゴリー名から消えました。事実、メタバースを全面に押し出したブースはほとんど見られませんでした。 Image source. https://exhibitors.ces.tech/8_0/floorplan/?hallID=B このLVCC Centralの「GAMING | XR」エリアには62ブースが出展していました。VR HMDの「Pimax」、アクションカメラの「Insta360」、そしてARグラスの「XREAL」など中国企業が目立っていました。 LVCC Central Hallに単独初出展した日本発XR企業のShiftall(左)とDiver-X(右) 一方で、これまでPanasonicのブース内に出展していた「Shiftall」は独立してブースを初出展しました。また、CES 2023でJ-Startup枠に出展していた「Diver-X」も単独でブースを初出展するといった日本発XR企業が躍進している様子が見受けられました。 また、例年はスタートアップ企業が集まるVenetian CampusのEureka ParkにもXR関連エリアが設けられていましたが、CES 2025では見当たりませんでした。 CES 2023では「Gaming/Metaverse/XR」エリアとして16社が出展、CES 2024では「Gaming/XR」エリアとして7社が出展していました。このように、近年では関連するブース数が減少傾向にありましたが、CES 2025では、いよいよエリア自体が消失してしまいました。 ただし、XR関連ブースが必ずしもこれらのエリアに出展しているとは限りません。LVCC Campus・Venetian Campusともに、このエリアに限らずXR関連のブースは多くあり、 「AR/VR/XR」カテゴリーとして登録されているブース 数は355にも及びました。 CES Innovation Awardsから見るXRデバイスのトレンド CESでは毎年、デザインやエンジニアリングにおいて優れた製品を表彰する CES Innovation Awards を実施しています。XR関連の製品は XR Technologies & Accessories としてカテゴライズされ、いわゆるHMD型・眼鏡型のXRデバイスは次の4製品が受賞しました。 Rokid Cupcake AR Glasses Sony XR Head-Mounted Display SRH-S1 Ultra Lightweight Polychromatic AR+AI Glasses XREAL One and XREAL One Pro 3/4が眼鏡型のXRデバイスでした。これは、HMD型のXRデバイスが一般的になりつつある中で、眼鏡型のXRデバイスが注目されていることを示していると言えるでしょう。 ただし、CES Innovation Awardsは自発的な応募が必要なため、すべてのXRデバイスの中から選出されているわけではありません。そこで、次に出展ブースで実際に試したXRデバイスからトレンドを見ていきます。 出展ブースのXRデバイスから見るXRデバイスのトレンド CES 2025で体験したXRデバイス(HMD型・眼鏡型)の一部 例年通り、CES 2025でもたくさんのXRデバイスを試してきました。この写真はHMD型・眼鏡型のXRデバイスの一部です。この他にも、例えば「 Mudra Band 」のような手首に装着する筋電デバイスもあります。 写真から一目瞭然ですが、非常に多くの眼鏡型デバイスが出展されていました。これは、HMD型デバイスが普及する中、眼鏡型デバイスへの関心が高まっていることを示していると考えています。 ARグラスとスマートグラス 一口に眼鏡型デバイスといっても、その機能や性質から「ARグラス」と「スマートグラス」に大別できます。 ARグラスは、現実世界にデジタル情報を重ね合わせることができるデバイスです。CES 2025への出展はありませんでしたが、SnapchatのSnapがクリエイター向けに展開している「 Spectacles 」は代表的なARグラスです。ARグラスは原則としてディスプレイを搭載し、スマートグラスと比較してトラッキング性能が優れていることが特徴です。 ARグラスに対してAR機能を持たない眼鏡型デバイスがスマートグラスです。市販されているMetaとRay-Banによる「 Ray-Ban Meta 」やAmazonの「 echo frames 」、そしてCES 2025の会場で試したほとんどの眼鏡型デバイスはスマートグラスです。スマートグラスはディスプレイを搭載しているものと搭載していないものに細分化できます。CES 2025では特にAI機能を搭載し、リアルタイムで会話を翻訳するスマートグラスを多数見かけました。 ここでスマートグラスの例として挙げている「Ray-Ban Meta」はまだ日本で販売されていませんが、USではある程度普及しているように感じました。ラスベガス市内で「Ray-Ban Meta」を取り扱っている店舗で聞いた話では、継続的に品薄または売り切れ状態が続いているとのことでした。 補助デバイスとしてのスマートグラス XR Techというと五感の内、視覚に焦点が当てられがちですが、これは「見える」ことを前提としています。しかし、加齢や健康状態の影響で視覚を含む五感の機能が低下することもあります。CESでは、こうした視覚や聴覚の補助を目的としたデバイスもいくつか出展されていました。 エルシオによる「オートフォーカスグラス」 大阪大学発スタートアップのエルシオによる「オートフォーカスグラス」 出典: https://elcyo.com/ 大阪大学発スタートアップのエルシオは、自動でピントが合うメガネ「オートフォーカスグラス」を出展していました。見る距離や目の状態に合わせてフレネル液晶レンズが変化し、近視・遠視に問わずひとつの眼鏡で快適な視界を提供するものです。ジャンルとしてはスマートグラスに分類されますが、視覚の補助デバイスの一例と言えます。 技術的なアプローチは異なりますが、CES 2024に出展していたViXionも「 ViXion01S 」というオートフォーカスアイウェアを2025年5月に発売予定です。 EssilorLuxotticaによる「Nuance Audio」 EssilorLuxotticaによる「Nuance Audio」 出典: https://www.nuanceaudio.com/en-us/c/hearing-glasses EssilorLuxottica はCES 2024に続き、CES 2025でも聴覚を補助する機能を持つヒアリンググラス「Nuance Audio」を出展していました。眼鏡型デバイスなので、視覚にあわせて聴覚も補助できるヒアラブルデバイスとも言えます。 EssilorLuxotticaは Ray-Ban Metaも手掛ける 世界最大手の眼鏡ブランドで、和真眼鏡の和真や福井めがね工業など、複数の日本企業も傘下に持っています。 USでは補聴器に分類されるため、現在はFDAの承認待ちとのことです。2024年にはAppleのワイヤレスイヤホン「AirPods Pro 2」も補聴器としてFDAが承認し、日本でも同年11月から ヒアリング補助プログラムが提供されています 。 私は以前患った突発性難聴が完治せず、聴覚に対して課題を抱えているため、このようなヒアラブルデバイスに対しては特に興味を持っています。普段使いしやすい見た目にも惹かれます。 Gentex Corporationによる「eSight Go」 Gentex Corporationによる「eSight」 出典: https://www.nuanceaudio.com/en-us/c/hearing-glasses 「 eSight Go 」は加齢に伴う黄斑変性などを始めとする課題を、カメラを通した映像で自然に補える視覚障がい者向けのウェアラブル補助デバイスです。ズーム機能により、遠方の対象を拡大して見ることもできます。 Gentex Corporationは例年CESでeSightを含む各種製品を出展しています。もともとはeSightと協力して開発していたデバイスですが、2024年の買収により、Gentex Corporationによって買収されています。 要素技術 CESでは完成している製品の他、製品を構成する要素技術となるものも多数出展されていました。その中で日本発の気になるものをいくつか紹介します。 Cellidによる「最新のウェイブガイド」 CellidによるメガネタイプARグラスと最新のウェイブガイド 出典: https://cellid.com/news/20241216 世界最大級の視野角を持つガラス製ウェイブガイドをはじめとするARグラス用ディスプレイを開発するCellidは、 メガネタイプARグラスのリファレンスデザイン の他、 最新のウェイブガイド を出展していました。 CellidはCES 2022以降、毎年出展している日本発のスタートアップで、これまではウェイブガイドを中心に出展していましたが、いよいよ自社開発のARグラスを開発しました。毎年確実に進化を遂げていて、将来的には数多の眼鏡型デバイスに使用されると考えています。 DUAL MOVEによる「tXR display」 JAPAN TECH 出展企業のひとつであるDUAL MOVEは、透過型の裸眼立体視ディスプレイ「 tXR display 」を出展していました。 これは、眼鏡型デバイス向けのものではなく、自動車のフロントガラスなどに搭載しているもので、いわゆる「車窓XR」として利用されることを想定しています。一般的なディスプレイと比較すると解像度は高くありませんが、透過で利用できることと、裸眼で立体視可能であることが大きな特徴です。 CESではモビリティも人気の技術領域で、特に自動運転は注目されています。tXR displayのような透過型の裸眼立体視ディスプレイはナビゲーション用途だけではなく、自動運転中のエンターテイメントにも活用できると考えられています。日本でも複数の「XRバス」が既に運用されていますが、その多くは平面なので、立体視ディスプレイを搭載することで、よりリアルな体験が可能になるかもしれません。 AGCによる「AR/MRグラス向け高屈折率ガラス基板」 ガラスをはじめとする製品を取り扱うAGCは、AR/MRグラス向けの 高屈折率ガラス基板 「 M100/200シリーズ 」を出展していました。これは眼鏡型デバイス向けのディスプレイに使用することを想定したもので、視野角の拡大や画像の鮮明化に繋がるとされています。 Fashion TechとBeauty Tech CES 2024でBeauty Techの集まる一画が設けられたものの、カテゴリーとしては存在していませんでした。しかし、CES 2025では注目の高まりに伴い、 Fashion TechとBeauty Techがそれぞれカテゴリーとして設けられました 。また、CES Innovation Awardsのカテゴリーとしても設けられています。 「Fashion Tech」カテゴリーとして登録されているブース 数は97、そして 「Beauty Tech」カテゴリーとして登録されているブース 数は130でした。ただし、カテゴリーは出展企業が複数指定できますが、これまではDigital Healthカテゴリーなどを設定していた企業も多く、必ずしもFashion TechやBeauty Techとして登録されているわけではありませんでした。個人的に興味を惹かれたブースをいくつか紹介します。 コーセーによる「Mixed Reality Makeup」 コーセーによる「Mixed Reality Makeup」 CES 2023に初出展したコーセーが、当時出展した『デジタルパーソナルカラー体験「COLOR MACHINE」』を『 Mixed Reality Makeup 0 min try-on studio 』として進化させて出展していました。先に紹介したCES Innovation AwardsのXR Technologies & Accessories部門にも選出されています。 これはいわゆる「ARメイク」や「Virtual Try-on」と呼ばれる取り組みで、プロジェクターで顔にメイクのテクスチャを投影することで、実際にメイクをしているかのような体験ができます。化粧品の色味を正確に再現することと、1000fpsの超高速プロジェクションマッピングにより顔の動きに追随することが特徴です。 体験している様子を私のXアカウントに投稿している ので、興味がある方はぜひご笑覧ください。 Mixed Reality Makeupの体験後、実際にタッチアップしてもらえた この取り組みは実店舗に設置することを想定しています。この記事を読んでいる皆さんも コーセーの旗艦店である「Maison KOSE銀座店」で「COLOR MACHINE」を体験できます 。 私たちも「 ZOZOCOSME 」の「 ARメイク 」や「 WEAR by ZOZO 」の「 WEARお試しメイク 」を提供していますが、オンラインでのEコマースとは異なる、オフラインの実店舗ならではの取り組みとして注目しています。 アシックスとダッソー・システムズによる「パーソナライズドフットウェア」 アシックスとダッソー・システムズによる「パーソナライズドフットウェア」 日本発のアシックスは、2024年7月にフランス発のダッソー・システムズと提携し、パーソナライズドフットウェアを開発すると 発表しました 。ダッソー・システムズのブースではこの取り組みの一環として、既に発売されている3Dプリント製のサンダル「 ACTIBREEZE 3D SANDAL 」の他、スニーカーのプロトタイプも展示されていました。このスニーカーを試し履きしましたが、中空構造の独特な歩き心地がとても印象的でした。 私たちも計測技術のひとつとして足の形をミリメートル単位で3D計測できる「 ZOZOSHOES 」を提供しているので、ある側面では競合にあたるかもしれません。しかし、こうした取り組みは、計測技術を用いたパーソナライズドフットウェアの普及に向けた一歩と言えるでしょう。 ブースの様子は ダッソーシステムズのFacebookページで確認できます 。映像中にも登場しますが、Meta Quest 3を用いたXRコンテンツも体験できました。 Prinkerによる「Prinker POP」 Prinkerの新製品「Prinker POP」 例年CESにテンポラリータトゥーデバイスを出展していたPrinkerは、CES 2025にあわせて「 Prinker POP 」を発表、出展しました。Prinker POPは、従来のPrinkerとは異なり、キオスク端末でユーザーがカスタムしたメイクアップパレットを作成できるパーソナライズドサービスです。 実際にキオスク端末で作成したカラーパレット これまでのPrinkerは個人が所有するtoC向けのデバイスでしたが、Prinker POPは店舗などに導入することを想定しているtoB向けのデバイスです。カメラを通して自身の顔でシミュレーションした色味をメイクアップパレットに出力するものなので、ARメイクの一種と捉えることもできます。 テンポラリータトゥーのPrinkerは日本でも購入できます が、このPrinker POPが日本に上陸するかどうかは未定です。 その他の気になったFashion TechとBeauty Tech ロレアルグループによる「ロレアル セル バイオプリント」 ロレアルグループによるパーソナライズされた肌分析を提供する卓上型ハードウェアデバイス「セル バイオプリント」 出典: https://www.loreal.com/ja-jp/japan/press-releases/group/j-ces-2025/ CES 2024でBeauty系企業として初めてキーノートを開催したロレアルグループは、CES 2025にあわせてパーソナライズされた肌分析を提供する卓上型ハードウェアデバイスの「 ロレアル セル バイオプリント 」を 発表しました 。 このデバイスは、2025年後半にアジア圏で試験的に導入される予定とされています。初出がアジア圏ということには驚きましたが、これは韓国のスタートアップ企業とのパートナーシップによって実現している背景からだと考えられます。近隣の日本でも導入される可能性があるため、注目していきたいと思います。 Withingsによるスマートミラー「OMNIA」 www.youtube.com スマート体組成計をはじめとする健康に結びつくスマートデバイスを提供するWithingsは、CES 2025にあわせてスマートミラー業界に参入して「 OMNIA 」を発表しました。 OMNIAは、体組成はもちろん、あらゆるデータをAIとともに可視化してくれるデバイスです。ブースではまだデモ展示のみでしたが、どんなことができるかはYouTubeに公開されているティザー動画で、どんなことができるかがわかります。 CES 2025ではSamsungも同様にスマートミラー業界に参入して「 MICRO LED Beauty Mirror 」を 発表しました 。以前からスマートミラーは出展されていましたが、2社の参入により今まで以上にBeauty Techとして注目される分野になると考えています。 おわりに ラスベガスの玄関Harry Reid Airport 例によって今回のCES視察は開発部門の福利厚生である「 セミナー・カンファレンス参加支援制度 」を利用しての参加となります。 今回は直行便を選択したためフライトのコストはCES 2024当時よりも高くつきましたが、乗り継ぎがない分、時間を有効に使えたと考えています。CES 2026の開催日程は、すでに2026年1月6日から9日の4日間と発表されています。参加意向のある方は、できるだけ早く手配することをおすすめします。 例年通りのことですが、フライトとホテル以外にも一定の金銭的コストが発生しています。CESに限らず、海外で開催されるカンファレンスにおいては、そのコストに対して得られる成果に対するコストの正当性を説明するのは難しいかもしれません。しかし、XR領域は「百聞は一見ならぬ“一体験”にしかず」です。CESに関するニュース記事はCESの会期中から多く目にしますが、現地に足を運び、自らの目と手で体験し、一次情報を得る重要性を再認識しました。 そして、CESはビジネスショーという性質上、個別に会話するプライベートブースが用意されています。いくつか参加しましたが、こういったオンサイトならではの対面コミュニケーションも、インターネットメディアの記事等からは得られない大きなメリットだと考えています。せっかく参加するのであれば、あらかじめそういった場をセッティングしておくことを強くおすすめします。 最後までご覧いただきありがとうございました。来年もまた、CES 2026のレポートをお届けできるように努めてまいります。 ZOZOでは、各種エンジニアを採用中です。ご興味のある方は以下のリンクからご応募ください。 corp.zozo.com 現場からは以上です!
アバター
はじめに こんにちは、FAANS部フロントエンドブロックでWeb開発をしている 平舘 です。 Webフロントエンドのテスト戦略って、結局どうすればいいのか、よくわからなくないですか? この記事では、FAANS Webアプリケーション開発におけるテスト実装の歴史を「リリース期」「急成長期」「現在」という3つの開発フェーズに分けて振り返ります。プロダクト立ち上げからのリアルな現場感とともに振り返りつつ、主にテスト配分についてチームで議論しながらプロダクトへ反映していった歴史のレポートになっています。みなさんのテスト戦略の見直しや実践のヒントになれば幸いです。 目次 はじめに 目次 背景・課題 この記事で語らないこと 前提:FAANSについて 開発の歴史とテスト戦略の変遷 フェーズ1. 怒涛のリリース期 起きたこと (1) 関心の中心は、「何をつくるか」 (2) こなれない実装 (3) jest-dom による差分チェック (4) テストの目的 (5) QAチームの存在 E2Eテストにおける手動/自動のメリット・デメリット リリース期のまとめ フェーズ2. 疾風怒濤の急成長期 起きたこと (1) 頻発する大規模なリファクタ (2) 統一性のないコードベース (3) 自動テストへの関心 (4) Storybookやるやん (5) Chromaticもええやん (6) 信頼に足るテスト (7) 設計視点の改善 急成長期のまとめ フェーズ3. 現在地とこれから 起きていること (1) 実行コストと信頼性の安定に向けて (2) 俺たちのトロフィー策定 (3)ユニットテストの立ち位置を確認 (4)アプリケーション品質と開発品質を分けて考える 今後の課題 (1) 測定・振り返りへの試み (2) その他 まとめ 背景・課題 現代のWebフロントエンド開発において、テスト手法 1 やツールは多様化しています。ユニットテスト、E2Eテスト、ビジュアルリグレッションテスト(VRT)など、それぞれの用途や目的が異なるため、適切な組み合わせが求められます。しかし、多様な選択肢がある一方で、次のような課題が生じがちです。 フロントエンドのテスト戦略に自信がない テストを書くのがストレス メンバー間でテストへの向き合い方が違う テストの効果を実感しにくい FAANSでも開発フェーズに応じてこのような課題が生じ、向き合ってきました。 この記事で語らないこと こうすればうまくいくという万能な正解や方法論。 テストツールの具体的な使い方。 前提:FAANSについて 内容を理解しやすくするために、まずFAANSのプロダクト背景を簡単に説明します。 サービス特性 : Webは主にバックオフィス機能を提供し、ユーザーの入力・操作が多く発生する。 開発体制 : ウォーターフォール開発。 独立したQAチームが存在。 アーキテクチャ構成 : React SPA : 業務利用ツールのため採用。 OpenAPIによるAPI開発 : YAMLベースでAPIスキーマを定義し、クライアントコードを自動生成。 より詳細なプロダクトの説明は弊バックエンドブロック田島による SLOの導入は早ければ早いほどよい 〜FAANSの事例とその効果〜 - ZOZO TECH BLOG に書かれています。ぜひ併せてお読みください。 開発の歴史とテスト戦略の変遷 以下はざっくりと各フェーズの開発状況を俯瞰した表です。 フェーズ 新規実装 変更・リファクタ QAバグ検出 フェーズ1 リリース期 ↑最大 →少ない ↑最大 フェーズ2 急成長期 ↗︎多い ↑爆増 ↗︎健在 フェーズ3 現在 ↗︎安定傾向 ↗︎多い →安定傾向 これをふまえて、各開発フェーズで起きたことを振り返っていきます。 フェーズ1. 怒涛のリリース期 まずは立ち上げからリリースまでの、プロジェクト最初期のフェーズです。 起きたこと (1) 関心の中心は、「何をつくるか」 どんなプロダクトでもそうだと思いますが、FAANSも立ち上げ当初は、事業価値の心臓となる部分をいかにスピーディに実現するかに集中していました。 将来的な展開を見据えながら、「誰に」「どんな価値を」「どれくらい」「いつまでに」「どうやって」提供するのか。不確実なものばかりのなかで、迅速な意思決定が求められました。 フロントエンドだけに閉じた世界で見ても、フレームワークやライブラリ選定、CI構築、実装指針のすり合わせなどを決める必要がありました。特に機能実装に直結する部分において、アーキテクチャ選定、実装などが山積みでした。 (2) こなれない実装 FAANSのテストの歴史について語るためには、この時期に実装されてしまった こなれない実装 たちについて語らなければなりません。 useEffect() を望ましくない場面で多用し、副作用が複雑に絡み合うことで、とてもテストを書けないようなロジックが量産されていました。 また、TypeScript本来の静的型チェックの効果も十分に発揮できていませんでした。特にnullableな値の扱いが未熟で、冗長なnullチェックによる認知不可が増加していました。 簡単な例としてアカウントの権限管理が挙げられます。FAANSでショップスタッフは必ずどこかのショップに所属しますが、上位権限である管理者アカウントの場合ショップ所属は任意です。これを表現する場合、所属先IDの有無は型で保証されている方が扱いやすいですが、未熟な型表現になってしまっていました。 // 型チェックが効果を発揮できていない type Account = { role : 'staff' | 'manager' ; companyId : number ; // 企業には必ず所属する shopId ?: number ; // ショップは staff のみ所属する } // こっちの方が堅牢で扱いやすい type Account = { companyId : number ; } & { role : 'staff' ; shopId : number ; } & { role : 'manager' ; shopId : number | undefined ; } ※実際の権限はもう少し複雑ですが、わかりやすくするため抽象化しています。 (3) jest-dom による差分チェック 仮想domスナップショットのリグレッションテストも試してみましたが、早々に断念しました。 デザインの方向性を試行錯誤しながら実装を進めている段階だったので、得られるメリットが小さく維持コストのほうが高かったためです。 (4) テストの目的 おそらくこれが最も本質的かつよくある問題ですが、メンバー間で共通認識を揃えられないまま、「ないよりあったほうがいいよね」ぐらいの認識でテストを実装していました。 結果として、 目的地としてどの程度の範囲をどうやってカバーしたいのか 意図がわからない、粒度のバラバラなテスト実装が散在してしまいました。特に、詳細度の高すぎるユニットテストが多く作られてしまいました。変更の影響を受けやすく、この時点で継続的に担保したいアプリケーションレベルの保証材料にならないテストです。これらは完全に無駄なわけではなく目的次第では有効ともとれるものであるため、特に指摘もなく実装され続けていきました。 しかしプロダクトがローンチされていないこの時点では、そもそもデザインや仕様の根幹的な価値自体が担保されていない状況です。末端の入出力、つまり外部仕様の不確実性が大きいなかで、より内部のテストは資産価値の薄いものでした。これらは、のちにメンテナンスコストをかける価値を見出せず削除することになりました。 (5) QAチームの存在 それでも大きな問題なくスケジュール通りにリリースできたのは、QAチームの働きによるものです。頻繁な変更にもかかわらず手動テストでしぶとくテストしてくれました。その作業負荷は相当なものだったと思います、当時のメンバーには頭が上がりません。 この時点でE2Eテストの自動化案も出ましたが、以下のようなメリット・デメリットの観点のうち、特に初期コストを割くのが難しかったため手動テストのみで運用しました。 E2Eテストにおける手動/自動のメリット・デメリット 項目 手動テスト 自動テスト 初期コスト ◯:手動テスト用の環境を用意するのは必須。 ×:環境や外部依存システムに応じた構築が必要。FAANSの場合、外部提供される認証システムとAPIが大きな障壁。 柔軟さ ◯:テストの詳細度や観点を都度コントロール可能。UXの直感的な評価が可能。 ×:静的なテスト詳細が必須。 探索的テスト ◯:得意。 ×:不得意。 再利用性・繰り返し △:テスト対象が増えるほど工数が増えていく。繰り返すことでテスターの心理負荷は増える。 ◯:実装されたテストは資産として反復して利用可能。結果としてカバレッジを大きくしやすい。 開発サイクルへの組み込み ×:開発サイクルへのフィードバックは最後になる。 ◯:CIで開発サイクルに組み込むことが可能。 リリース期のまとめ 目的面・方法面の両方で不確実性に向き合う日々のなか、自動テストに意識を割き構築していくのは、今から考えてもやはり難しく価値が薄かったと思います。思い切って自動テストを「積極的に書かない」判断が必要でしたが、その決定に至る基礎知識がない状態でした。 また、開発健全性という側面で、第三者的な目線から一定期間、開発状況についてのフィードバックをもらう動きをとれていればよかったと思います。現在ZOZOではWebフロントエンドで事業部を横断して情報共有していく動きが増えてきており、こういった問題は改善傾向にあります。 総じて、チームの共通認識に昇華できていない点において、 テストをよく書くメンバー/そうでないメンバー、どちらも同質にテストへの解像度が低かった です。当時を絵にすると、こんな感じでしょうか。 フェーズ2. 疾風怒濤の急成長期 無事ローンチ完了。基幹機能の価値が証明され、さらに機能拡充が進みます。一方で、コードベースの複雑化や、既存コードの大規模な変更が頻発しました。 起きたこと (1) 頻発する大規模なリファクタ 実装指針が徐々にアップデートされ、機能変更が入るタイミングで、設計方針に沿うよう既存実装を大きくリファクタする場面が増えました。 (2) 統一性のないコードベース 人的リソースの都合上、スポットで領域外のエンジニアが参加することも多く人の出入りが多くなり、コード量が増えるとともに複雑さも急増していた時期です。 テスト設計指針が浸透しないまま作成されたコードは、必要機能を満たしていながらも、俯瞰するとテスタビリティを欠いたり認知コストが高かったりしました。テスト指針という観点が抜けていることによって、設計議論の観点が暗黙的に低かったとも見ることができます。 (3) 自動テストへの関心 テスティングトロフィーという概念が話題になり始めたこともあり、この頃ようやくテスト実装指針の明確化に積極的に意識を割き始めました。 (1)、(2)のような状況で、安心して積極的にリファクタしていける環境を整えることが急務でした。 (4) Storybookやるやん そんな折、Storybookに play() 機能 2 が実装されました。 FAANSでもUIカタログとしてStorybookは導入済みであったため、使い慣れたツールでテスト実装できるのは願ってもないことでした。また、UIを通してモック化したAPI通信をテストできるため、信頼性の高い結果を期待できました。 (5) Chromaticもええやん Chromatic 3 を活用して、 play() の実行結果をスナップショットとして保存し、API疎通を含めたUIの整合性を担保しました。これによって、静的なビジュアル差分の確認にとどまらず、インタラクティブな動作を含めた総合的なテスト 4 となりました。 Chromaticは金額によってスナップショットできる上限が決まる料金体系のため、FAANSではPullRequestにラベルを付与することによってトリガーするようにしています。 過去のブログ記事 で実装例も載せておりますので、ご参照ください。 (6) 信頼に足るテスト ツールの学習コスト、MSWによるAPIのモック、CIの整備など、それなりの初期コストはかかりました。しかし、コストに見合うだけの信頼に足るテストをできている手応えがありました。 手応えの理由の1つは、開発サイクルにおいて素早くテスト結果を確認できることです。これまで自分たちの手動テストでしか担保できなかったE2Eに近いレイヤーでの動作保証を、CIでリグレッション検知できるようになりました。 もう1つは、検出した不具合や込み入った仕様を、素早く自動テストに反映できることです。Storybookの play() では実装したインタラクションテストをステップバイステップで簡単にデバッグ実行できます。手元で画面操作を確認しながら書けるテストは、思い描く仕様をスムーズに実装できる優れた機能です。 (7) 設計視点の改善 テストを書く文化が根付いてくると、「テストしやすい実装」も実感を伴ってわかってきます。「これはテストしにくいよね」という観点でスムーズに会話ができるようになり、より疎結合で責務が明確な、よい設計を議論できる土台になりました。 優れた設計はテストしやすい実装となり、信頼に足るテストは自信につながります。リリース期には苦痛ですらあったテストのメンテナンスが、開発体験において欠かせない存在となりました。 急成長期のまとめ StorybookとChromaticという強力なテストツールを使うことにより、自動テストの恩恵を受けることができるようになりました。 早い段階でリグレッションによる不具合を検知することにより、バグを減らすだけでなく、設計の改善や開発体験の向上につながりました。 フェーズ3. 現在地とこれから テスト基盤も少しずつ安定してきました。より効果的なテスト戦略の確立に向け、取り組むべき課題も明確化してきています。 起きていること (1) 実行コストと信頼性の安定に向けて テスト対象が増えるにつれて、1)実行時間の増加と2)Flakyなテストが問題になってきました。様々な改善対策をしましたが、長くなるため、ここではその項目を列挙するだけにとどめます。 実行時間の削減 Storybook shardオプションによる並列ジョブ実行 Chromatic turboSnapオプションによる差分検出 Flakyなテストの安定化 Storybookのバージョンを上げるタイミングで、記法が古いテストとFlakyなテストは一旦すべてコメントアウトした ボーイスカウト精神で直していくようにした asyncUtilTimeout オプションにより findBy のデフォルトタイムアウト時間を延長 これらの取り組みによって、現状ではStorybookとchromaticの実行時間とFlakyさは改善し、ほぼネックになっていないため、信頼性の高いテストを十分な速さで実行できている安心感があります。 (2) 俺たちのトロフィー策定 play() とChromaticによって結合テストあたりのレイヤーを厚くする方針は決まっています。しかしテストの目的・達成したい状態をより明確にチームで共有したいところです。 そこで、チーム全員で 「自分たちのテスト配分」 を描くことで、テストの目的や役割について議論を深めました。これはどのツールでどの層を、そしてどんな品質を担保するのか、解像度を上げるきっかけとして非常に有効でした。 左が初期案、右が完成系です。細かいですが配置の調整や、ツールについて明示しました。 完成に至るまでのプロセスでは、以下のような重要な議論が行われました。 (3)ユニットテストの立ち位置を確認 議論の結果、ユニットテストは以下のような範囲に限定する方針を採用しました。 外部依存のない静的ロジックを説明・定義すること。 たとえば、計算やフォーマット変換のようなロジック。 ドメインロジックやUIに密接な部分は結合テストに委ねること。 Storybookの play() 機能やChromaticでカバーすることで、手動操作に近いレベルでの信頼性を確保します。 コードベースは、テスタブルなコードとそうでないものが混じった状態です。理想は、テストしやすいロジックに設計を見直すことですが、リソースが限られている現状では現実的ではありません。まずは機能レベルで壊れにくい状態を目指すことが最優先と判断しました。 (4)アプリケーション品質と開発品質を分けて考える CIで自動テストを回すことで、漠然と「ある程度の品質保証はできているはず」という感覚に頼っていましたが、そもそもここでいう「品質保証」とは何を指しているのかをチーム内で議論しました。 この議論の中で、 テスティングトロフィー を意識しすぎた結果、固定観念に囚われてしまいがちだったことが明らかになりました。具体的には、ユーザに届けられる「アプリケーション品質」と、QAテストに引き渡す段階やリリースサイクル全般で必要とされる「開発品質」を分けて考える視点が不足していたのです。この視点によって、品質保証におけるチームのアプローチを整理できました。 FAANSではアジャイルな手法を取り入れることで、各工程においてバグ検知や仕様変更などのフィードバックを柔軟に実装に反映させることができる開発サイクルになっています。しかし、基本的には静的な仕様からQAテストを経てリリースするウォーターフォール方式であるため、この 品質の境界を明確に認識することが重要である という結論に至りました。 現状、開発チームのテストが直接的に保証できるのは主に開発品質です。一方で、最終的なアプリケーション品質を担保するにはQAテストが欠かせません。この事実整理によって、現状のフロント開発が担保しているテストだけでは不十分だということを再確認しました。ただし、もちろんこれは開発チームがアプリケーション品質を負わないということは意味しません。この区別の明確化により、 テスト実装の目的とその効果の測定範囲 を整理し、より適切なテスト戦略を構築できるようになります。そして、次のステップとして「開発品質」と「アプリケーション品質」をそれぞれ強化するためのアプローチを模索しています。 今後の課題 (1) 測定・振り返りへの試み さて、テスト結果(主にカバレッジ)を測定・振り返りすることで開発品質を上げ、より「根拠のある自信」 5 をもった開発体験にしたいところです。 しかし、フロント開発に閉じた世界で 定量的に品質を振り返る指標 を定めるのが難しいため、測定の設計が難航しています。以下のような方針で測定の準備を進めています。もしもよりよい方法があればぜひ教えてください。 機能軸で優先度づけし、ページ単位で観測。 事業重要度やユーザ利用頻度に基づいて優先度を定義。ページコンポーネント単位でカバレッジを観測。 優先度が高いページのカバレッジが落ちないようにする。 優先度が高いのにカバレッジの低い箇所を改善していく。 SonarQube Cloudの活用 静的観測軸として、コードベースの健康状態を監視。 複雑さの増大を監視し、テストしやすさの低下を未然に防ぐ指標として活用。 モジュール軸でのカバレッジ測定対象の定義 複雑性や変更頻度を考慮した重み付けし、効果的な測定対象を絞り込む。 今後また、結果を公開できればと考えています。 (2) その他 冒頭でご紹介した通り、FAANSではSLOにより品質を監視しています。組織全体でアプリケーション品質を維持するため、フロントエンドとしてどのようにこの指標を活用・コミットできるか模索しています。 QAチームにより本質的なテストに注力してもらうためには、E2Eを一部自動化し効率化したいところです。開発・QAで連携し、より良い開発サイクルを生むための施策を協議していきます。 まとめ フロントエンド開発における自動テストのあり方は、チームの成長やツールの進化とともに動的に変化していきます。今後生成AIの進化も大きく影響していくことでしょう。ベストプラクティスを静的な理想形として求めるのではなく、自分たちに合った戦略を築き上げる意識を持ち、動的で可塑性のある開発プロセスが重要だとわかりました。 今回の振り返りを通じて得られた学びは以下の通りです。 チーム全体での目的共有 テストの役割や目的を明確にし、同じ方向に向かって改善していくことで、安心感・納得感が高まること。 開発フェーズに応じた柔軟な戦略 特に黎明期は避けられない障害が多くある一方で、基礎知識と経験によって、ツールや環境の特性に則した戦略をとれること。 信頼性の可視化と測定の取り組み 現在は主要機能やロジックの重み付けを通じて適切な指標を模索しています。取り組み中ではありますが、信頼性向上の大きな一歩になると考えています。 現在進行形の取り組みが多い中ですが、このプロセス自体がプロダクトやチームの成長につながると信じています。今回の記事が同じ課題に向き合うみなさんの一助となれば幸いです。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com 特に前置きなく「テスト」と指す際は、TDDなどの設計・実装手法ではなく自動テストの文脈で語っています。 ↩ StorybookのPlay-functionは、ユーザーの操作をシミュレートし、コンポーネントのインタラクションを自動的にテストするための機能です。 Docs | Storybook ↩ Chromaticは、Storybookと統合されたビジュアルリグレッションテスト(VRT)ツールで、UIの見た目の変更をスナップショット比較で検出します。 Visual testing & review for web user interfaces • Chromatic ↩ 関連記事 Visual E2E Testing with Chromatic and Playwright ↩ 関連記事 ピラミッド、アイスクリームコーン、SMURF: 自動テストの最適バランスを求めて / Pyramid Ice-Cream-Cone and SMURF - Speaker Deck ↩
アバター
ZOZO開発組織の1か月の活動をまとめたMonthly Tech Reportをお届けします。2024年12月はアドベントカレンダーやGirls Meet STEMなどのイベントの他、様々な場で登壇しました。そして、2024年12月をもって、ZOZOTOWNは20周年を迎えました。そんな12月の出来事をご紹介します。 ZOZO TECH BLOG 2024年12月には、前月分のMonthly Tech Reportを含め15本の記事を公開しました。その中でも特に注目度の高かった記事をピックアップしてご紹介します。 12月20日に公開した「AWS re:Invent 2024参加レポート」は、AWS re:Invent 2024に参加したZOZOのエンジニアが、その概要や注目ポイントをまとめた記事です。例年、多くの新サービスが発表されるAWS re:Invent。今年も、その様子を詳しくお伝えしています。 techblog.zozo.com また、ZOZO発のOSSとしてリリースした「universal-links-test」に関する記事を公開しています。「universal-links-test」は swcutil コマンドをラップした関数を提供し、 apple-app-site-association ファイルの挙動確認をサポートするものです。開発にあたってのモチベーションや利用方法などを紹介しています。 techblog.zozo.com 登壇 SIGGRAPH Asia 2024 Tokyo 12月3日から6日の4日間に渡って開催された『 SIGGRAPH Asia 2024 』に、ZOZO 生産研究開発部 シミュレーションブロック ブロック長の安東と、ZOZO NEXT ZOZO Research Dept Applied ML Teamの平川の論文がそれぞれ採択されました。 コンピュータグラフィックス分野のトップカンファレンス 「SIGGRAPH Asia 2024」にて論文採択 ZOZO研究所、コンピュータグラフィックス分野のトップカンファレンス「SIGGRAPH Asia 2024」にて論文採択 会期中、安東はTechnical Papers Fast Forward、Technical Papers、Digging into the Technical Papersの各セッションで、平川はTechnical Communicationsで登壇しました。 ZOZO 安東 「 A Cubic Barrier with Elasticity-Inclusive Dynamic Stiffness 」 「 Digging into the Technical Papers 」 ZOZO NEXT 平川 「 An Empirical Analysis of GPT-4V’s Performance on Fashion Aesthetic Evaluation 」 Miroマスターズ 2024 12月5日に開催された『 Miroマスターズ 2024 』で、技術戦略部 テックリードの堀江( @Horie1024 )が「 生産性を倍増せよ! 11人のMiro達人たちの仕事活用術 」の枠において「 Miro × ZOZO ZOZOのMiro活用事例紹介 」というタイトルで登壇しました。 speakerdeck.com AI Leaders Connect #2 12月5日に開催された『 AI Leaders Connect #2 』で、AI・アナリティクス本部の川田が「 ZOZOの生成AI業務活用事例 」というタイトルで登壇しました。 01(zeroONE)2024 12月10日に開催された『 01(zeroONE)2024 』で、データシステム部の奥山が「 【クリエイティブサーベイ / ZOZO】今年のデータ基盤振り返り大会 」の枠において「 ZOZOにおけるデータマート集計基盤の成長と反省 」というタイトルで登壇しました。 📰ZOZOエンジニア登壇情報 本日、東京ミッドタウン・ホールで開催中の 01(zeroONE)2024 にデータシステム部 データ基盤ブロックのデータエンジニア 奥山が登壇します🎙️ 🖥️今年のデータ基盤振り返り大会 📅 2024/12/10 13:40 - 14:10 EXPO Theater A https://t.co/A3gxAOp4gY #01pN — ZOZO Developers (@zozotech) 2024年12月10日 speakerdeck.com 奥山は社会人漫才師「下町モルモット」のぽこやかざん( @pokoyakazan )としても活動しています。過去にはType転職の「 聴くエンジニアtype 」にも出演しています。興味のある方はぜひチェックしてみてください。 #075 ZOZOのデータエンジニア 兼 お笑いやってます/ZOZO奥山さん① #076 仕事の辛い・苦しいから解放してくれるのは“メタ反省会”/ZOZO奥山さん② #077 1日1%の成長を目指す。愚直にやれば、いつか何かしらの成果に繋がる/ZOZO奥山さん③ 株式会社ユーザベース×株式会社ZOZO×株式会社PR TIMES 3社合同フロントエンド勉強会 12月10日に開催された『 株式会社ユーザベース×株式会社ZOZO×株式会社PR TIMES 3社合同フロントエンド勉強会 』で、WEARフロントエンド部 テックリードの冨川( @ssssotaro )が「 useSyncExternalStoreを使いまくる 」というタイトルで、ZOZOTOWN開発3部の田中( @nayuta999999 )が「 MSW 2.xにあげた話 」というタイトルで登壇しました。 本日 12/10 (火) 開催!『株式会社ユーザベース×株式会社ZOZO×株式会社PR TIMES 3社合同フロントエンド勉強会』にZOZOエンジニアが2名登壇します🎙️ 🗣️ ZOZOTOWN開発3部 田中 @nayuta999999 🗣️ WEARフロントエンド部 テックリード 冨川 @ssssotaro https://t.co/afhkAscy6N #zup_frontend — ZOZO Developers (@zozotech) 2024年12月10日 speakerdeck.com speakerdeck.com ZOZO TECH BLOGとPR TIMES 開発者ブログにイベントレポートが掲載されています。こちらもぜひご覧ください。 techblog.zozo.com developers.prtimes.jp そのリプレイスは最適解? -コストから見るプロダクト開発Tips 12月11日に開催された『 そのリプレイスは最適解? -コストから見るプロダクト開発Tips 』で、物流開発部の上原が「 本番環境での等価比較がコスト削減に繋がった話 」というタイトルで登壇しました。 【ZOZOエンジニア登壇情報】 12/11(水) 12:00~13:00 にオンラインで開催される『そのリプレイスは最適解? -コストから見るプロダクト開発Tips』に物流開発部で基幹リプレイスに携わっている上原が登壇します🎙️ ぜひお気軽にご参加ください! https://t.co/ybVHmC8ipg #コストリプレイス_findy — ZOZO Developers (@zozotech) 2024年11月21日 speakerdeck.com GitHub Universe 2024 Recap in ZOZO 12月16日に開催された『 GitHub Universe 2024 Recap in ZOZO 』で、WEARフロントエンド部の山田( @gamegamega_329 )が「 iOS開発におけるCopilot For XcodeとCode Completion 」というタイトルで、データシステム部の佐藤( @rayuron )が「 GitHub Copilot のテクニック集 」というタイトルで登壇しました。 先日のイベントで @rayuron さんが発表されていたGitHub Copilotのテクニック集スライド! 🏃‍♀️ショートカット 🔃プロンプティングの技 💡仕様に基づいた知恵 ✍そして「少し書き始める」 実用的で分かりやすくまとめられています。 是非チェックしてみてください👇 https://t.co/RzYWQbmfje — GitHub Japan (@GitHubJapan) 2024年12月18日 先日のイベントからもう1つ、発表スライドの紹介です! iOS開発におけるGitHub Copilot For XcodeとCode Completion! iOS開発で2つのツールのコード補完について試してみた 🔰新規でコードを追加 ✅テストコードを追加 🍎AppleのSDKを扱う ヒント:適材適所 結果は👇 https://t.co/82DtXhiEGK — GitHub Japan (@GitHubJapan) 2024年12月18日 イベントレポートもあわせてご覧ください。 techblog.zozo.com AWS re:Invent 2024 Recap in ZOZO 12月17日に開催された『 AWS re:Invent 2024 Recap in ZOZO 』に、AWS re:Invent 2024に現地参加した4名のZOZOエンジニアが登壇しました。 📰ZOZOエンジニア登壇情報 明日 12/17 (火) 夜開催の自社イベント『AWS re:Invent 2024 Recap in ZOZO』に * 計測システム部から纐纈と土田 ( @andex_tokyo ) * 技術本部 SRE部から佐藤 ( @taquaki_satwo ) と江島 ( @sejima1105 ) 以上4名が登壇します🎙️ https://t.co/m27BlytrTp #reInvent24Recap — ZOZO Developers (@zozotech) 2024年12月16日 纐纈: EKSとAmazon Qのアップデート 土田: Amazon Novaのすゝめ基盤モデルの性能比較を添えて 佐藤: 英語が苦手でも学びが得られるWorkshopについて 江島: ガバナンスを支える新サービス イベントレポートには、ゲストスピーカーとしてご登壇いただいたアマゾンウェブサービスジャパン合同会社3名の方の登壇資料も掲載しています。あわせてご覧ください。 techblog.zozo.com 宣伝会議AI研究会 12月18日に開催された『 第1回 宣伝会議AI研究会 』で、AI・アナリティクス本部の川田が「 生成AIが実現するZOZOの業務効率化 ー 実例に見る活用の可能性 」というタイトルで登壇しました。 第47回 MLOps 勉強会 12月18日に開催された『 第47回 MLOps 勉強会 』で、データシステム部の佐藤( @rayuron )が「 ZOZOTOWN の推薦における KPI モニタリング 」というタイトルで登壇しました。 本日 12/18(水) 19~20 時にオンラインで開催される『第47回 MLOps 勉強会』にデータシステム部 推薦基盤ブロックの佐藤 @rayuron が『ZOZOTOWNの推薦のKPIモニタリング』というタイトルで登壇します🎙️ ご興味をお持ちの方はぜひご参加ください! https://t.co/VrFlFt0qvB #mlopsコミュニティ — ZOZO Developers (@zozotech) 2024年12月18日 speakerdeck.com 掲載 流通ニュース ZOZOの生成AI活用事例に関する記事が、小売・通販・中間流通・メーカーの最新ビジネスニュースを発信する「 流通ニュース 」に掲載されました。 www.ryutsuu.biz これらの活用事例は「“ビジネスAI元年” の2024年・ZOZOの生成AI活用事例」としてZOZO DEVELOPERS BLOGにもまとまっています。こちらにはAI・アナリティクス本部 本部長 牧野のコメントも掲載されています。あわせてご覧ください。 technote.zozo.com 日本ネット経済新聞 11月22日から12月13日にかけて実施していた「GitHub × ZOZOTOWN コラボアイテム販売」の取り組みについて、技術戦略部 ディレクターの諸星( @ikkou )が取材を受けた記事が、EC&流通のデジタル化をリードする専門紙「 日本ネット経済新聞 」に掲載されました。 netkeizai.com MONOist ZOZOMATやZOZOMETRYなどの計測技術に関する取り組みについて、計測プラットフォーム開発本部 本部長の山田が取材を受けた記事が、モノづくりスペシャリストのための情報ポータル「 MONOist 」に掲載されました。 monoist.itmedia.co.jp FASHIONSNAP ファッションコーディネートアプリ「WEAR by ZOZO」による「 WEAR Coordinate Awards 2024 」の取り組みについて、ファッションに関連する事象を中心に様々なトピックを取り上げる情報サイト「 FASHIONSNAP 」に掲載されました。 www.fashionsnap.com 「WEAR Coordinate Awards 2024」の特設サイトもあわせてご覧ください。 wear.jp その他 ZOZO Advent Calendar 2024 実施 2024年のアドベントカレンダーは、過去最多となる全11シリーズ、275記事を公開しました。 techblog.zozo.com ZOZO NEXT が XR Kaigi 2024 に初出展 12月12日、13日の2日間に渡って催された XR Kaigi 2024 のエキスポエリアにZOZO NEXTが初出展しました。 【 #XRKaigi 2024に出展】 東京ポートシティ竹芝で開催されるXR・メタバースの国内最大級のカンファレンス「XR Kaigi」に、ZOZO NEXTが2024年12月12日(木)~13日(金)に出展します。 https://t.co/vTlptizXsh https://t.co/z1l9fSVYbf — 株式会社ZOZO NEXT (@ZOZONEXTInc) 2024年12月11日 Girls Meet STEM 開催 12月15日(日)に、ZOZOにて中高生女子を対象とした体験イベント「 Girls Meet STEM〜ITのお仕事を体験しよう〜 」を開催しました。 techblog.zozo.com 以上、2024年12月のZOZOの活動をお届けしました! ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com
アバター
はじめに 技術評論社様より発刊されている Software Design の2024年5月号より「レガシーシステム攻略のプロセス」と題した全8回の連載が始まりました。 これまでの連載で、ZOZOTOWNリプレイスプロジェクトの始まりから各部門の取り組みなどを紹介してきました。最終回となる今回は、フロントエンドの取り組みを取り上げ、これまでのまとめを行います。 目次 はじめに 目次 はじめに フロントエンドエンジニアの責務 フロントエンドリプレイス前 リプレイス後 フロントエンドリプレイスプロジェクトの進め方 調査 設計 テスト Next.jsとの向き合い方 Custom Server 1. ロギング 2. リダイレクト 3. 既存システムからのForm POSTリクエストを受けるエンドポイント 4. マルチプロセスでの起動 next/link カナリアリリースと_next/data/*/jsonの関係 Next.jsでリプレイスしていくうえでの課題 ソフト/ハードナビゲーションのHTTPリファラの違い Shift_JISの取り扱い 振り返り リプレイス後のフレームワークとしてのNext.js リプレイス専任チームにした話 不要機能の削除・調整 リリース まとめ・今後の展望 はじめに ZOZOTOWNのWebフロントエンドは約3年前からリプレイスを実施してきました。連載最終回となる今回はZOZOTOWNのWebフロントエンドリプレイスプロジェクトの進め方や、その過程で得られた技術・組織に関する知見について紹介します。全8回にわたる連載のまとめとして、リプレイスプロジェクトの今後の展望についてもお伝えします。 フロントエンドエンジニアの責務 当社におけるフロントエンドエンジニアの役割について、リプレイス前後のアーキテクチャを比較しながら紹介します(図1)。 図1 フロントエンドリプレイスのアーキテクチャ遷移 フロントエンドリプレイス前 Windowsサーバ上で動作するIIS(Internet Information Services)でClassic ASP(VBScript)を利用して動的にHTMLを生成するサーバレンダリングを行っています。 HTMLの生成にはClassic ASP(ロジック)とHTML(テンプレート)の分離を可能にするテンプレートエンジンを利用しています。また、ブラウザ上ではJavaScriptライブラリのjQueryと一部React(TypeScript)を利用してインタラクティブなコンテンツの実装を行っています。 フロントエンドエンジニアの役割はリスト1に示されるように、Classic ASP(VBScript)で書かれたコード以外のHTML、CSS、JavaScriptを担当することです。 ▼リスト1 テンプレートファイル <!DOCTYPE html> < html > < head > < meta charset = "Shift_JIS" > < link rel = "stylesheet" href = "/assets/style/index.css" > </ head > < body > < header > (#%NoticeExists| < div class = "badge" > < div class = "badge-circle--count" > (#*UnreadNoticeCount#) </ div > </ div > #|# #) </ header > < div id = 'react-app' ></ div > < script src = "/assets/script/index.js" charset = "utf-8" ></ script > </ body > </ html > リプレイス後 IISとClassic ASPで実装していた部分は、Next.js(Pages Router)とUIに必要なデータをマイクロサービスなどから集めて整形するBFF(Backend for Frontend)に分解されました。その結果、フロントエンドエンジニアはNext.js、バックエンドエンジニアはBFFと、管理する役割がサーバ単位で分割されました。 Next.jsを導入したことで、フロントエンドエンジニアの役割にいくつかの変化が生じました。Next.jsはサーバサイドレンダリング(SSR)や静的サイト生成(SSG)をはじめWebアプリケーションに必要な機能を提供します。そのためフロントエンドエンジニアとしてページルーティング、HTMLのキャッシュ管理、機能要件・SEOを考慮したレンダリングパターンの選定などの役割が増えました。また、機能要件・SEOを考慮してSSRを利用するためサーバのパフォーマンスやエラーなどのメトリクスを監視し、サーバの運用を行うことも求められるようになりました。リプレイス前と比較して、フロントエンドエンジニアはサーバを含めた技術をより一層駆使してユーザーに快適なWeb体験を提供できるようになりました。 フロントエンドリプレイスプロジェクトの進め方 ZOZOTOWNは2004年のサービス開始から複数の技術で構成され、多くの開発者が機能改修を行ってきたことで、機能同士の依存関係が複雑になっていました。その中で開発当時の設計意図を直接的には知らないリプレイス専任のチームがどのようにZOZOTOWNのWebフロントエンドをリプレイスするプロジェクトを進めていったかを紹介します。 リプレイスプロジェクトはページや機能ごとにいくつかのフェーズに分けて進行します。各フェーズは通常の開発工程(調査・設計・開発・テスト・リリース)に従って進行しますが、リプレイスプロジェクト特有の課題が多くあります。とくに注意が必要な工程について説明します。 調査 長く運用され変遷を遂げてきたZOZOTOWNには機能要件仕様書が存在しないため、稼働しているコードに記載されているものが仕様であり、要件でもあります。リプレイスプロジェクトの基本的な要件は既存システムの要件を漏れなくリプレイスすることです。そのため、要件をコードから読み解く調査がとても大事な工程となります。 調査工程では、既存システムの機能開発・保守をしているチームではないため機能の理解に時間がかかるという課題があります。また、IISとClassic ASPで実装されている部分をどのようにフロントエンド/バックエンドで分けてリプレイスを行うかの判断も必要です。そしてフロントエンドエンジニアがバックエンド技術で実装されている機能についても理解する必要があることが課題となります。 これらの課題に対して、IISとClassic ASPで実装されている機能の一覧、通信シーケンス図、画面遷移図を作成し、開発者が既存機能の要件・機能を理解できるようにしています。また、機能を一覧にすることでフロントエンド/バックエンドエンジニアどちらが実装するかを漏れなく判断し、設計後のフェーズでの実装漏れによる後戻りを防ぐようにしています。 設計 調査で作成した機能一覧を元に設計していきます。リプレイス後はモノリスではなくNext.jsとBFFのため、OpenAPIを使ったスキーマ設計、通信シーケンス図を作成することを行いフロントエンド/バックエンドそれぞれが独立して開発を進めていけるようにします。 レンダリングパターンについては「SEO観点で劣化しないことを確約できる変更以外はしない」というプロジェクトポリシーに沿って基本的に既存と同様にします。また、リプレイス対象ページによっては現在のアーキテクチャ設計時点でのコアの実装を行い、レイテンシーやファーストビューなどのパフォーマンス劣化を起こさずにリプレイスできるかどうか先行して検証するためにProof of Concept(PoC)を行うことがあります。 テスト リプレイスは不具合が発生すると多くのユーザーに影響が出てしまいます。そのリスクを最小限に抑えるために、一部のユーザーだけにリプレイス後のシステムを提供するAkamai Application Load Balancerを利用したカナリアリリースを実施しています。提供する割合を徐々に増やしていき、全ユーザーに提供するまでに不具合が見つかった場合は提供割合を0%に戻して不具合を修正します。そのためリリース時のユーザー体験だけでなく、リリースを戻す際のユーザー体験に影響がないかもテストする必要があります。 Next.jsとの向き合い方 Custom Server ZOZOTOWNではCustom ServerにWebフレームワークのFastifyを利用してNext.jsを起動しています。Fastifyは一般的に使われるNode.jsフレームワークのExpressよりも高い処理速度を持ち、Hooks APIにより複数用意されているライフサイクルイベントをフックして処理を簡単に実行できます。Custom Serverで行っている処理は次の4つです。 1. ロギング FastifyのonResponseイベントをフックにしてサーバのアクセスログを出力しています。出力にはライブラリpinoを利用してJSON Lines形式で標準出力しています。 2. リダイレクト ZOZOTOWNはデスクトップ向けとモバイルデバイス向けで別々のURLが存在します。そのため、モバイルデバイスでデスクトップ向けURLにアクセスがあった場合はモバイルデバイス向けのURLにリダイレクトする仕様があります。また、既存システムではURLにソース情報である.htmlが含まれており、リプレイスで.htmlなしのURLに変更するためリダイレクトを行います。 Next.jsの機能としてのRedirect、Middlewareを利用することも検討しました。しかしRedirectは柔軟な条件設定が難しく、Middlewareはリプレイス当初のNext.jsバージョンではExperimentalな機能であったため、Custom Serverでリダイレクトを行っています。 3. 既存システムからのForm POSTリクエストを受けるエンドポイント リリーススコープを限定してリスクを最小限に抑えるため、既存システムでForm POSTリクエストを送っている箇所とリプレイス後のシステムの連携が必要なことがあります。ただし、Next.js(Pages Router)の getServerSideProps ではForm POSTのbodyをパースするしくみがないため、Fastifyにリクエストを受けるエンドポイントを作成することでパースされたPOST bodyの取り扱い処理を行っています。 4. マルチプロセスでの起動 とあるページのリプレイスでPoCを行った際に現在のサーバ性能・台数ではリクエストをさばききれないことがわかりました。単純に台数を増やすだけではかなりのコストがかかるため、サーバのCPUリソースをできる限り使ってさばけるリソースを増やすためにマルチプロセスで起動する処理を実装しています。 next/link next/linkはNext.jsでクライアントサイドのナビゲーションを実現するためのコンポーネントです。next/linkはページ遷移を行う際、サーバからHTMLではなくページを構成するデータ(json)を取得し、クライアントサイドでページを構築します。そのためページ全体を再読み込みするのではなく必要な部分だけを更新できるので、ユーザーにとってストレスのないページ遷移を実現できます。 リプレイスプロジェクト開始当初は、リプレイスされるページ間の遷移をクライアントトランジションでシームレスにすることを目指していました。ZOZOTOWNではバックエンドでURLを決定するロジックが多く、動的にURLが変わることがよくあります。そのため、Next.jsでリプレイス済みのURLの場合はnext/linkを使い、リプレイス前のURLの場合はaタグを使うAnchorコンポーネントを作成しました。コンポーネント化することで開発者がnext/linkを意識せずにできる限りクライアントサイドでの遷移になるようにしています。 Next.jsでリプレイス済みのURLか否かは、/pagesに存在するページのパスを生成してくれるpathpidaを利用してpropsのhrefと比較することで判定しています(リスト2)。 ▼リスト2 Anchor コンポーネント import { ReactNode } from 'react' import Link from 'next/link' const Anchor = ( { href } : { href : string , children : ReactNode } ) => { const isDefaultAnchor = isNextApplicationPath(href) if (isDefaultAnchor) { return < a href = { href } > { children } </ a > } return ( < Link href = { href } passHref > < a href = { href } > { children } </ a > </ Link > ) } カナリアリリースと_next/data/*/jsonの関係 ZOZOTOWNリプレイス後の環境では、新バージョンのリリースに伴うリスクを低減するために、新・旧バージョンを段階的に切り替えるカナリアリリース(エラー件数が多い場合は自動で0%にロールバック)が採用されています *1 。このため、リリース中はバージョンスキューのため新・旧の通信が入り混じることで _next/data/*/json が404エラーになることがあります(図2)。また、リプレイス後の環境でブラウザを開いたままにしていて新バージョンのリリース後にクライアントサイドトランジションを行った場合も _next/data/*/json が404エラーになります。 図2 Version Skewでの通信 404エラーになることによるユーザー影響を懸念しましたが、Next.js側で別のバージョンの不一致が発生した場合はアプリケーションを再読み込みするハードナビゲーションを行うしくみとなっているため、ユーザーには影響がないことが確認されました。これによって無事、リプレイス後の環境からリリースのリスク低減のためのカナリアリリースを導入することができました。 Next.jsでリプレイスしていくうえでの課題 ソフト/ハードナビゲーションのHTTPリファラの違い ソフトナビゲーションはhistoryを使用してURLを変更後にページに必要なデータ(json)を取得するため、ブラウザバックを行うと戻ったURLがリファラとなります。一方、ハードナビゲーションはページ全体が再読み込みされるため、戻る前の現在のページのURLがリファラとなります。 ZOZOTOWNでは、流入経路によって特殊なUIを表示する仕様や、前の選択状態を維持するためにリファラを利用していました。この状態でhistoryを使用したソフトナビゲーションにすることでブラウザバック時に問題が発生しました。 リファラは状況によってHTTPに乗らないこともあるため、依存しない実装に変更することも検討しました。しかし、この問題が発覚したタイミングがリリース直前で利用箇所も多く要件をまとめて設計することが難しかったため、問題が発生する特定のページからの遷移と特定ページへの遷移をハードナビゲーションに変更する対応を選択しました。 Shift_JISの取り扱い ZOZOTOWNのサービス開始以降、現在もWindows Server上でIISとClassic ASP(VBScript)が稼働しています。その結果、システムには文字コードShift_JISが残っており、キーワード検索のURLクエリにもShift_JISでエンコードされた値が使用されています。リプレイス後も裏側のシステムは引き続きShift_JISでの処理を行うので、互換性維持のためShift_JISを扱う必要があります。しかし、Shift_JISでエンコードされたマルチバイト文字を含むURLに対してNext.js(JavaScript)でURLSearchParams APIを使用すると、 application/x-www-form-urlencoded 形式でパースされてしまい文字化けしてしまうため、Shift_JISのまま扱うことができません。これは2つのユースケースで問題が発生しました。 1つ目はページネーションやソート順の変更など現在のURLに対して特定のクエリパラメータを変更したい場合です。Shift_JISでエンコードされたマルチバイト文字を含むパターンに対しては、URLSearchParams APIが登場する前のやり方と同じようにURL文字列を操作することで対応しました。 2つ目は getServerSideProps でリクエストURLを参照したい場合です。リプレイス後は基本的にソフトナビゲーションで実装しているため、リクエストオブジェクトのURLではなくクライアントサイドナビゲーションの _next/data を正規化した GetServerSidePropsContext のresolvedUrlを参照する必要があります。しかし、resolvedUrlはNext.js内部でURLSearchParamsを利用しているため文字化けしてしまいました。この問題に対してはNext.js内部での処理によって文字化けが発生してしまうことがわかりハードナビゲーションに変更することを検討しました。検討した結果、問題が発生するケースの中に既存システムからソフトナビゲーションでCSRを行っている箇所があったため、このケースのみresolvedUrlを利用せずほかのケースはリクエストオブジェクトのURLを利用することで対応しました。 また、HTMLの文字コードをShift_JISからUTF-8に変更したことでも問題が発生しました。FormデータのエンコーディングはHTMLの文字エンコーディングに依存するため、リプレイス後も送信先が既存システムの箇所でUTF-8がShift_JISとして扱われることで文字化けが発生しました。Formはaccept-charset属性を指定することでエンコーディングを指定できるため、Shift_JISを指定することで文字化けの問題を解決して新・旧システムを連携できました。 振り返り リプレイス後のフレームワークとしてのNext.js 1ページをピックアップし、Core Web Vitalsを使ってリプレイス前・後のシステムのパフォーマンス特性を比較しました。リプレイス後はTime to First Byte(TTFB)、First Contentful Paint(FCP)が改善されたことで、Largest Contentful Paint(LCP)までの時間短縮やTime to Interactive(TTI)が全体的に向上しました。一方でFCPとLCPの差が広がりページのレンダリングプロセスが遅くなっているため、今後の改善課題であることがわかりました。 開発者体験としては環境構築が簡単になったことや、JavaScriptのエコシステムを利用できることで開発効率が向上したことが挙げられます。また、表示ロジックがすべてJavaScript(TypeScript)で記載されることでテストがしやすくなったことも成果です。 一方でリプレイスならではの課題として既存システムとの共存があります。基本的に既存システムの機能仕様を変更する判断は行わずにリプレイスするため、現在のベストプラクティスと異なりフレームワークでサポートされていないことが数多くあります。そういった場合にフレームワークの制限の中で再現する必要性が挙げられます。 リプレイス専任チームにした話 プロジェクトが始まったころはチーム内で既存システムでの開発とフロントエンドリプレイスを並行して行っていました。プロジェクトとシステムを行き来しコンテキストスイッチを繰り返す必要があり、よりスムーズな進行を目指して専任チームで進めることとしました。そうすることで、スイッチする機会を減らしリプレイスプロジェクトに完全に集中できる環境を整えられました。 リプレイスを進め環境がモダンになっていく中で開発効率も上がり新しい人材も増え、現在ではフロントエンドリプレイスプロジェクトに3チームで並行して取り組めるようになりました。 不要機能の削除・調整 長く運用されてきたこともあり、既存システムにはデッドコードになっているものや古い機能のまま更新されずにいるものが数多くありました。リプレイス後のシステムになるべく負債を残さないように、また本来達成したいシステム入れ替えに大きく影響を与えないように、UIの刷新や機能の削除も積極的に関係者と調整して実施しました。 大きく複雑なシステムのため削除の判断がつかないものや、既存システムと並行して運用した際に問題がある場合など、その時点での判断を見送ったものも多くありますが、既存システムよりだいぶシェイプアップできました。 リリース 現在ZOZOTOWNのフロントエンドでは、ページや機能単位でリプレイスを行っています。既存システムと並行して開発していく関係で二重開発になってしまうケースや、リプレイスが完了すれば不要になる既存システムと整合性を保つための処理の開発などコストがかかっている場面もありますが、ビッグバンリリースによるリスクと天秤にかけて選択しています。 アプリケーションまるごとのリプレイスはしていないものの、ページによっては非常に複雑で大規模なリプレイスになってしまい、結果として非常に苦労することもありました。機能ではなくURL単位でリプレイスするなど小さくリリースしていく手段を複数持ち、適切に提案・判断できる状態にある必要性を感じました。 まとめ・今後の展望 これまで全8回にわたって、ZOZOTOWNリプレイスプロジェクトにおける取り組みや学びをさまざまな切り口で、紹介しました。 第1回:ZOZOTOWNリプレイスプロジェクトの全体アーキテクチャと組織設計 第2回:ZOZOTOWNリプレイスにおけるIaCやCI/CD関連の取り組み 第3回:API Gatewayとサービスメッシュによるリクエスト制御 第4回:ZOZOTOWNリプレイスにおけるマスタDBの移行 第5回:キャパシティコントロール可能なカートシステム 第6回:ZOZOTOWNにおけるBFFアーキテクチャ実装 第7回:検索機能リプレイスの裏側 第8回:フロントエンドエンジニアから見るZOZOTOWNリプレイスとまとめ・今後の展望 これらは、壮大なZOZOTOWNリプレイスプロジェクトの一部です。筆者たちは日々、試行錯誤を繰り返し、ZOZOTOWNという巨大なサービスのリプレイスに取り組んでいます。 ZOZOTOWNは、2004年12月のサービス開始から、基本的なアーキテクチャを変えずに成長してきました。そのアーキテクチャはきっと正解だったのだと思いますし、リプレイスに至るまで、開発や運用を続けてきたZOZOのエンジニアをリスペクトしつつ、これから先の未来におけるZOZOTOWNの成長のために、今考えられる最適なアーキテクチャを検討し、引き続きリプレイスを進めていきます。現在、アプリのAPIサーバのリプレイスや、基幹システムのリプレイスも進めていますので、今後またどこかで紹介できたらと思います。 最後になりますが、全8回にわたり、お読みいただきありがとうございました。読者のみなさんにとって、少しでも有益な情報になっていたらうれしいです。 本記事は、執行役 兼 CTOの瀬尾 直利、EC基盤開発本部 本部長の高橋 智也、ZOZOTOWN開発本部 ZOZOTOWN開発3部 フロントエンドリプレイスブロック ブロック長の新家 弘久、そして同 フロントエンドリプレイスブロックの森 泰樹によって執筆されました。 本記事の初出は、 Software Design 2024年12月号 連載「レガシーシステム攻略のプロセス」の最終回「フロントエンドエンジニアから見るZOZOTOWNリプレイスとまとめ・今後の展望」です。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com *1 : 前述の「テスト」項目で記載したカナリアリリースとは目的が異なるものです。
アバター
はじめに こんにちは。Developer Engagementブロックの @wiroha です。12月17日に「 AWS re:Invent 2024 Recap in ZOZO 」を開催しました。12/2〜6日の5日間に渡ってラスベガスで開催されたAWS re:Invent 2024を振り返るRecapイベントです。 登壇内容まとめ はじめに、アマゾンウェブサービスジャパン合同会社でソリューションアーキテクトを務める3名より、Ad & Marketing、Serverless、そしてEKS Auto Modeについて発表していただきました。その後、re:Invent 2024に現地参加したZOZOのエンジニア4名が、それぞれの視点で発表しました。 発表タイトル 登壇者 Ad & Marketing 関連 re:cap –頭出し- アマゾンウェブサービスジャパン合同会社 関藤 様 AWS re:cap Serverless アマゾンウェブサービスジャパン合同会社 朴 様 EKS Auto Mode アマゾンウェブサービスジャパン合同会社 堀内 様 EKSとAmazon Qのアップデート ZOZO 纐纈 Amazon Novaのすゝめ基盤モデルの性能比較を添えて ZOZO 土田 ( @andex_tokyo ) 英語が苦手でも学びが得られるWorkshopについて ZOZO 佐藤 ( @taquaki_satwo ) ガバナンスを支える新サービス ZOZO 江島 ( @sejima1105 ) Ad & Marketing 関連 re:cap –頭出し- アマゾンウェブサービスジャパン合同会社 関藤 様による発表 speakerdeck.com AWS re:cap Serverless アマゾンウェブサービスジャパン合同会社 朴 様による発表 speakerdeck.com EKS Auto Mode アマゾンウェブサービスジャパン合同会社 堀内 様による発表 speakerdeck.com EKSとAmazon Qのアップデート ZOZO 纐纈による発表 speakerdeck.com Amazon Novaのすゝめ基盤モデルの性能比較を添えて ZOZO 土田による発表 speakerdeck.com 英語が苦手でも学びが得られるWorkshopについて ZOZO 佐藤による発表 speakerdeck.com ガバナンスを支える新サービス ZOZO 江島による発表 speakerdeck.com 最後に 発表後にはAWSに関するクイズ大会を実施し、参加者同士で盛り上がりました。みなさまの正解率が高く、流石詳しい方が多いなと感じました。また、参加者にTシャツやポーチ、エコバッグなどのノベルティをプレゼントしました。 終了後にも登壇者やre:Invent現地参加者への質問などそれぞれ情報交換が行われ、有意義な時間を過ごすことができました。 時間の都合上、今回のRecapイベントでのZOZOからの登壇者は4名でしたが、AWS re:Invent 2024には総勢13名が現地参加していました。現地の様子やセッションの詳細に触れている参加レポート記事もあわせてご覧ください。 techblog.zozo.com ZOZOではAWSを活用しながら働くエンジニアを募集中です。ご興味のある方は以下のリンクからぜひご応募ください。 hrmos.co corp.zozo.com
アバター
はじめに こんにちは。Developer Engagementブロックの @wiroha です。12月16日に「 GitHub Universe 2024 Recap in ZOZO 」を開催しました。10/29-30日の2日間に渡ってサンフランシスコで開催されたGitHub Universe 2024を振り返るRecapイベントです。 登壇内容まとめ 各社から次の4名が登壇しました。 発表タイトル 登壇者 GitHub Universe 2024 Recap GitHub 服部 佑樹 様 iOS開発におけるCopilot For XcodeとCode Completion 株式会社ZOZO 山田 楓也 GitHub Copilot のテクニック集 株式会社ZOZO 佐藤 優羽 GitHubで育つインナーソース文化 : ニフティでの挑戦事例 ニフティ株式会社 芦川 亮 様 GitHub Universe 2024 Recap GitHub 服部 佑樹さまによる発表 服部さまからは、GitHub Universe 2024で発表された最新情報についてお話しいただきました。GitHub Copilotで使用できるAIモデルの選択肢が増える機能は、特に注目を集めているそうです。 iOS開発におけるCopilot For XcodeとCode Completion 株式会社ZOZO 山田 楓也による発表 speakerdeck.com 山田からはCopilot For XcodeとCode Completionの補完力の比較について発表しました。シチュエーションやバージョンによって異なるという前提がありつつ、今回の3番勝負ではGitHub Copilotが優位という結果になったそうです。 GitHub Copilot のテクニック集 株式会社ZOZO 佐藤 優羽による発表 speakerdeck.com 佐藤からはGitHub Copilotを効果的に活用するためのテクニック集を紹介しました。参加したみなさまの知らないテクニックも多く、役に立っていたようです。 GitHubで育つインナーソース文化 : ニフティでの挑戦事例 ニフティ株式会社 芦川 亮さまによる発表 speakerdeck.com ニフティ株式会社の芦川さまからは、インナーソースの挑戦事例をお話しいただきました。内製の便利ツールに対して、エンジニアがOSSのようにコントリビュートするパターンが成功例として印象的でした。どのように導入し、その結果がどうなっているのか、実践的なお話を聞くことができました。 プレゼント企画 プレゼント企画 すべての発表が終わった後、GitHubに関するクイズに答えてノベルティをプレゼントする企画を実施しました。GitHubを日常的に使用していればわかるような問題からファン向けのコアな問題まで、幅広い出題内容で盛り上がりました。 最後に 話題になったロゴも含むGitHubステッカー 本イベントを通じてGitHubの最新情報や、実際の活用事例を知ることができました。みなさまありがとうございました。今後もさまざまなイベントを企画していく予定ですので、ぜひご期待ください。 ZOZOでは一緒に働く仲間を募集中です。ご興味のある方は以下のリンクからぜひご応募ください。 corp.zozo.com
アバター
Developer Engagementブロックの @ikkou です。2024年もいよいよ終わりに近づいてきました。この季節の風物詩、「アドベントカレンダー」には皆さんも参加されましたか? ZOZOは例年アドベントカレンダーに参加し、2020年以降、記事数を100本、125本、175本、225本と増加。そして今年は過去最高の計275本の記事を公開しました! 本記事ではその概要をお伝えします。 ZOZO Advent Calendar 2024 今年は合計11個のカレンダーを完走し、12月1日から25日の間に275本の記事を公開しました! ZOZO ADVENT CALENDAR 2024、無事完走しました 🎉 今回はなんとシリーズ11まで、合計275件の記事を公開しました! 読んでいただいた皆さん、ありがとうございました! https://t.co/bwNIOJf5Ip #zozo_engineer #Qiitaアドカレ #Qiita pic.twitter.com/IWyhUwsolM — ZOZO Developers (@zozotech) 2024年12月26日 qiita.com 実施概要 ZOZOのアドベントカレンダーは以下の形式で運用しています。 形式 : 任意参加 運用方法 : Slackチャンネルで実施と参加を呼びかけ、各自が空いている日に登録 公開先 : ZOZO TECH BLOG、Qiita、Zenn、note、個人ブログなど 参加人数 : 140名(昨年より17名増) 公開記事数 : 合計275本 もっとも多くの記事を書いたのは昨年に続き@shiozakiさんで、今年は計29本の記事を公開しています。特に「シリーズ 3」は「JSON以外の◯SON」シリーズとして、ひとりで25記事を完走しています。 また、今年はチーム単位でひとつのカレンダーを自主的に担当する動きも見られました。 シリーズ 2: 推薦基盤 シリーズ 4: データSRE シリーズ 5: カート決済SRE 特定の技術領域に興味がある方は、ぜひ各シリーズの記事をチェックしてみてください。 アドベントカレンダーは、アウトプットの練習や執筆スキルを高める絶好の機会です。ZOZOではテックブログをアウトプットの主軸に置いていますが、「まだテックブログを書く自信が無い」「テックブログに書くにはネタが小粒」のような場合に、アドベントカレンダーは良い機会です。 運営目線での取り組みについてはDay 1の記事として公開しています。この記事中でも触れていますが、アドベントカレンダーを「お祭り感覚」で楽しむ文化が、ZOZOの開発組織には根付いています。これはZOZOの開発組織の特徴のひとつと言えるでしょう。 zenn.dev 2024年の振り返り ZOZOのアドベントカレンダーでは例年その年を振り返る記事を公開しています。 開発組織の振り返り ZOZOの開発組織については、昨年同様に執行役員 兼 CTOの @sonots が「振り返りと現状」を記事にまとめています。 qiita.com 推薦基盤チームの取り組み 推薦基盤チームに特化した「振り返りと現状」も記事にまとまっています。 qiita.com 生成AI活用事例の特集 2021年から2023年まではコーポレート広報チームによる「ファッションテックハイライト」を公開していましたが、今年は切り口を変えてZOZOの生成AI活用事例を紹介する特集記事を公開しています。こちらもあわせてご覧ください。 technote.zozo.com 過去のアドベントカレンダー ZOZOでは2018年から毎年アドベントカレンダーに参加しています。過去の取り組みは以下をご覧ください。 年 カレンダー 2023年 ZOZO Advent Calendar 2023 2022年 ZOZO Advent Calendar 2022 2021年 ZOZO Advent Calendar 2021 2020年 ZOZOテクノロジーズ Advent Calendar 2020 シリーズ 1 ZOZOテクノロジーズ Advent Calendar 2020 シリーズ 2 ZOZOテクノロジーズ Advent Calendar 2020 シリーズ 3 ZOZOテクノロジーズ Advent Calendar 2020 シリーズ 4 2019年 ZOZOテクノロジーズ Advent Calendar 2019 シリーズ 1 ZOZOテクノロジーズ Advent Calendar 2019 シリーズ 2 ZOZOテクノロジーズ Advent Calendar 2019 シリーズ 3 ZOZOテクノロジーズ Advent Calendar 2019 シリーズ 4 ZOZOテクノロジーズ Advent Calendar 2019 シリーズ 5 2018年 ZOZOテクノロジーズ Advent Calendar 2018 シリーズ 1 ZOZOテクノロジーズ Advent Calendar 2018 シリーズ 2 ZOZOテクノロジーズ Advent Calendar 2018 シリーズ 3 最後に ZOZOでは、プロダクト開発以外にも、アドベントカレンダーのような外部への発信も積極的に取り組んでいます。 一緒にサービスを作り上げる仲間をはじめ、エンジニアとしての技術力向上や外部発信に意欲的な方を積極的に募集しています。ご興味のある方は、以下のリンクからぜひご応募ください! corp.zozo.com
アバター
はじめに こんにちは、ブランドソリューション開発本部FAANS部の加藤です。私の開発しているショップスタッフの販売サポートツールFAANSでは、この度、コーディネート動画の投稿機能が実装されました。動画の投稿機能は、動画のトリミングや音声の編集ができ、投稿された動画はアプリ上で閲覧できます。 この記事では、動画の投稿機能を開発する上で直面した問題と、その解決方法をお伝えします。 目次 はじめに 目次 動画の投稿機能の流れ トリミング画面を1から作成する サムネイル画像の作成方法 トリミングコントローラからトリミング時刻の算出 動画のループ再生方法 エンコードされた動画が再生できるまでにラグが発生する問題への対処法 まとめ さいごに 動画の投稿機能の流れ まず、動画の投稿機能の流れを以下の図で説明します。ユーザーは最初に投稿したい動画を選択して、選択した動画に対してトリミング範囲、動画に付与する音楽、音楽の再生範囲を決定します。その後、動画に関する情報(動画内のモデルが使用しているアイテムの情報・動画の説明など)を動画情報の入力画面で入力して、投稿ボタンを押すことで動画のエンコードとアップロードが行われます。動画の投稿後は、一覧画面から投稿された動画とその詳細情報を閲覧できます。 今回、これらの動画投稿の機能を実装する上で、以下の3つの問題に直面しました。 トリミング画面を1から作成する トリミング区間でループ再生する エンコードされた動画が再生できるまでにラグが発生する トリミング画面を1から作成する iOSでは動画のトリミング機能を実装する方法の1つとして、UIKitのUIImagePickerControllerを用いた実装方法が挙げられます。UIImagePickerControllerを用いると下記、画像左側のように画面上部のトリミングコントローラーでトリミング範囲を指定できます。一方で、FAANSのトリミング機能は、トリミング後の動画長を1分以下に収める制限があります。そのため、トリミング後の動画長が1分を超える場合には、即時、画面上にアラートを出すことが求められるのですが、UIImagePickerControllerでは実現できません。そこで、複数のViewを組み合わせて、画像右側のようなオリジナルのトリミング画面を作成しました。 FAANSのトリミングコントローラーは、以下の画像のような5種類のViewで作成されています。View1は動画の各時刻におけるサムネイル画像が並べられたViewです。また、View2はView1に重ねられており、トリミング範囲外のView1に影をつけるためのViewです。ユーザーはView1を見ながら、どの部分をトリミング時刻にするかを伸縮可能なView3の両端をドラッグすることで指定します。具体的には、View3の両端に重ねられている透明なView5に触れており、ドラッグで動くView5の位置から動画のトリミング時刻を算出します。View2、3の内側は、View4を用いてくり抜かれており、下側のView1が見える状態になっています。次節では、トリミングコントローラのView1に配置するサムネイル画像を作成する方法と、View5の位置からトリミング後の動画の再生時刻を算出する方法を紹介します。 サムネイル画像の作成方法 本節では、トリミングコントローラー(View1)内に表示する動画の各時刻のサムネイル画像の作成方法について述べます。以下のようなプログラムでサムネイル画像を生成しました。 // トリミングビューに表示するサムネイルの数を計算する関数 private func thumbnailCount () -> Int { let thumbnailWidth : CGFloat = 30 // 各サムネイルの幅を固定値で設定 return Int(trimmingViewWidth / thumbnailWidth) // トリミングコントローラーの幅に基づいてサムネイル数を計算 } // 動画のサムネイルを生成し、トリミングコントローラーに追加する関数 private func setupThumbnails () { guard let videoURL = videoState?.path else { return } let asset = AVAsset(url : videoURL ) let imageGenerator = AVAssetImageGenerator(asset : asset ) // 動画の画像を生成するジェネレーターを設定 imageGenerator.appliesPreferredTrackTransform = true let duration = CMTimeGetSeconds(asset.duration) // 動画の全体時間を取得 let interval = duration / Double(thumbnailCount()) // サムネイル間の時間間隔を計算 // サムネイルの数だけループ for i in 0 ..< thumbnailCount() { // 各サムネイルの生成時間を設定 let cmTime = CMTime(seconds : interval * Double(i), preferredTimescale : 600 ) let timeValue = NSValue(time : cmTime ) // timeValueを参照して、サムネイル画像を生成 imageGenerator.generateCGImagesAsynchronously(forTimes : [ timeValue ] ) { [ weak self ] _, cgImage, _, _, _ in guard let self = self else { return } if let cgImage = cgImage { DispatchQueue.main.async { let imageView = UIImageView(image : UIImage (cgImage : cgImage )) // i番目のサムネイル画像の表示位置を設定 imageView.frame = CGRect( x : CGFloat (i) * ( self .trimmingViewWidth / CGFloat( self .thumbnailCount())), y : 0 , width : self.trimmingViewWidth / CGFloat( self .thumbnailCount()), height : self.trimmingViewHeight ) self .trimmingView.addSubview(imageView) self .trimmingView.sendSubviewToBack(imageView) } } } } } 上記のプログラムでは、thumbnailCountでサムネイル画像の幅を定義して、何枚のサムネイル画像をトリミングコントローラー内に配置できるかを算出します。算出された値で動画内の時刻を等間隔で指定して、指定した時刻のサムネイル画像をAVAssetImageGeneratorのgenerateCGImagesAsynchronouslyで生成します。あとは、生成された画像をtrimmingView(土台となるView)上に配置して完成です。 トリミングコントローラからトリミング時刻の算出 本節では、トリミングコントローラーからトリミング時刻を算出する方法について述べます。画像のように、黄色のView(以下、trimmingRangeView)とサムネイル画像が設置されたView(以下、trimmingView)の境界を基準として、トリミング時刻を算出します。 トリミング時刻算出のプログラムは以下の通りです。 private let handleWidth : CGFloat = 17 // trimmingViewの両端の幅 // トリミング範囲の開始時刻と終了時刻を計算する関数 private func calculateTrimmedTimeRange () -> ClosedRange < Double > ? { guard let videoURL = videoState?.path else { return nil } let asset = AVAsset(url : videoURL ) let videoDuration = CMTimeGetSeconds(asset.duration) // trimmingViewの長さに対する時刻の算出基準位置の割合を算出 let leftHandlePosition = (trimmingRangeView.frame.minX + handleWidth - trimmingView.frame.minX) / trimmingViewWidth let rightHandlePosition = (trimmingRangeView.frame.maxX - handleWidth - trimmingView.frame.minX) / trimmingViewWidth // 算出された割合×動画長でトリミング範囲後の時刻を算出する let trimmedStartTime = max( 0.0 , leftHandlePosition * videoDuration) let trimmedEndTime = min(rightHandlePosition * videoDuration, videoDuration) return trimmedStartTime ... trimmedEndTime } トリミング時刻を算出するために、AVAssetを用いて動画長( )を取得します。つぎに、trimmingRangeViewの時刻算出の基準点とtrimmingViewの長さの割合 を算出します。そして、 を計算することでトリミング時刻を算出できます。これを左右の基準点で行い、トリミングの開始時刻と終了時刻を算出できます。以下にtrimmingRangeViewの位置に応じて、トリミング時刻を更新しているgifを示します。gifのように算出されたトリミング時刻が1分を超える場合には、アラートを出すようにすることで、トリミング後の動画長を制限するFAANSオリジナルのトリミング画面を作成できました。 以上がトリミング画面を1から実装する方法の紹介です。 動画のループ再生方法 FAANSには2種類の動画再生が存在します。指定したトリミング区間に基づいてループ再生する場合(以下、エンコード前)と、動画の初めから終わりまでをループ再生する場合(以下、エンコード後)です。まず、比較的シンプルなエンコード後の動画の再生方法について述べます。プログラムは以下の通りです。 // 動画ファイルのURLから、AVPlayerを使用してプレイヤーを初期化 let player = AVPlayer(playerItem : . init (url : videoURL )) player.play() // 動画の再生 // 以下は表示のロジック let playerLayer = AVPlayerLayer() playerLayer.player = player playerLayer.videoGravity = .resizeAspect playerLayer.frame = bounds layer.addSublayer(playerLayer) // 動画の再生終了を監視するオブザーバーを追加 NotificationCenter. default .addObserver( self , selector : #selector(playerDidFinishPlaying(_ : )), name : .AVPlayerItemDidPlayToEndTime, object : player.currentItem ) // 動画再生終了時に呼び出されるメソッド @objc private func playerDidFinishPlaying (_ notification : Notification ) { guard let playerItem = notification.object as? AVPlayerItem , playerItem == playerLayer.player?.currentItem else { return } // 再生位置を最初に戻す playerLayer.player?.seek(to : .zero) { [ weak self ] _ in self ?.playerLayer.player?.play() } } 上記のプログラムでは、オブザーバー(.AVPlayerItemDidPlayToEnd)で動画の終了を監視しており、動画の終了時にplayerDidFinishPlayingが呼び出されます。playerDidFinishPlayingが呼び出されたとき、動画が初期の状態、すなわち0:00に戻されます。このプログラムでエンコード後の動画であれば、ループ再生できます。 一方でエンコード前の動画は、指定されたトリミング区間に基づいて、映像の途中でループする必要があるため、AVPlayerItemDidPlayToEndは利用できません。そこで、エンコード前の動画のループ再生を以下のように実装しました。 // 動画ファイルのURLから、AVPlayerを使用してプレイヤーを初期化 let player = AVPlayer(playerItem : . init (url : videoURL )) player.play() // 動画の再生 // AVPlayerの動画を表示するためのAVPlayerLayerを設定 let playerLayer = AVPlayerLayer() playerLayer.player = player playerLayer.videoGravity = .resizeAspect playerLayer.frame = bounds layer.addSublayer(playerLayer) // 再生中の時刻を定期的に監視するためのオブザーバーを設定 var timeObserverToken : Any? let timeInterval = CMTime(seconds : 0.01 , preferredTimescale : CMTimeScale (NSEC_PER_SEC)) // 0.01秒間隔で監視 timeObserverToken = player.addPeriodicTimeObserver(forInterval : timeInterval , queue : .main) { [ weak self ] _ in guard let self = self , let currentItem = playerLayer.player?.currentItem else { return } let currentItemTimeSeconds = CMTimeGetSeconds(currentItem.currentTime()) // 再生時間の取得 // プレイヤーが再生可能な状態(readyToPlay)である場合に処理を続行 if currentItem.status == .readyToPlay { playbackTimeDidChange(currentTime : currentItemTimeSeconds ) } } // 再生時刻が変更された時に呼び出される関数 func playbackTimeDidChange (currentTime : Double ) { guard let range = videoState.value?.video.timeRange else { return } // トリミング区間が代入されている変数 let startTime = range.lowerBound // トリミング区間の開始時刻 let endTime = range.upperBound // トリミング区間の終了時刻 if currentTime >= endTime { // トリミング区間の終了時刻を再生時刻が超えたときにトリミング区間の開始時刻に戻す player.seek(to : startTime , toleranceBefore : .zero, toleranceAfter : .zero) { [ weak player, weak self ] completion in guard completion else { return } player?.play() // シーク後に再生を再開 } } } 上記のプログラムでは、AVPlayerの再生時刻をオブザーバー(addPeriodicTimeObserver)を用いて0.01秒間隔で監視しています。AVPlayerの再生時刻がトリミング区間の終了時刻を超えた場合、AVPlayerの再生時刻をトリミング区間の開始時刻にシークすることで、トリミング区間におけるループ再生を実現しています。 エンコードされた動画が再生できるまでにラグが発生する問題への対処法 FAANSでは、エンコードした動画をアップロードした際に、エンコードされた動画のURLが発行されます。発行されたURLを参照して動画を再生しますが、S3に動画がコピーされるまでの間、動画を再生できません。そのため、動画が再生できるようになるまでの間はインジケータを表示して、再生可能になった時点でインジケータを非表示にして動画を表示する必要があります。下記の図は動画が再生できないケースの模式図です。本節では、動画が再生可能かどうかの監視方法について述べます。プログラムは以下の通りです。 private func configureVideoPlayer () { guard let videoURL = self .videoURL else { return } self .videoPlayer = AVPlayer(playerItem : . init (url : videoURL )) self .cancellable?.cancel() // 前回の監視がある場合はキャンセル(メモリリークを防止) // AVPlayerItemのステータスを監視 self .cancellable = videoPlayer?.currentItem?.publisher( for : \.status) .sink { [ weak self ] status in self ?.handleStatus(status : status ) // ステータスに応じて処理を実行 } } private func handleStatus (status : AVPlayerItem.Status ) { switch status { case .readyToPlay : // 動画が再生可能な状態になった場合 self .isLoadingVideo.value = false // インジケータを非表示に設定 case .failed : // 再生に失敗した場合 // エラーがファイルの未存在である場合にリトライ処理を実行 guard let error = videoPlayer?.currentItem?.error as NSError? , error.code == NSURLErrorFileDoesNotExist else { self .isLoadingVideo.value = false return } // 2秒後に再試行を行う(非同期で再生設定を再実行) DispatchQueue.main.asyncAfter(deadline : .now() + 2 ) { [ weak self ] in guard let self = self else { return } self .configureVideoPlayer() } default : break } } このプログラムでは、AVPlayerItemのステータスをCombineのpublisherを用いて監視し、ステータスが変化した際に対応します。ステータスがreadyToPlayの場合には再生可能であるため、インジケータを非表示にします。また、ステータスがfailedでエラーコードがNSURLErrorFileDoesNotExistの場合は、動画をS3にコピーしている最中と判断し、インジケータを表示したまま2秒後に再試行します。それ以外のエラーの場合には、S3のコピー以外のエラーとして処理を中断します。この実装で、S3への動画コピーが完了して再生可能になるまでインジケータを表示できます。 まとめ 今回は、FAANSにおける動画投稿に関する機能の実装方法について紹介しました。トリミング機能の実装方法の紹介では、トリミング画面を1から作る方法を解説し、サムネイル画像を作成する方法やトリミングコントローラーからトリミング時刻を算出する方法について述べました。また、動画をループ再生する方法と、エンコードされた動画が再生できるまでにラグが発生する問題の対処法についても紹介しました。この記事が同じような問題に遭遇した方や、これから動画に関する機能を開発しようとしている方の参考になれば幸いです。 さいごに ZOZOでは、一緒にサービスを作り上げてくれる仲間を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com
アバター
はじめまして! 25卒内定者のだーはまです。11月26日から27日にかけて湯河原で開催された内定者向け開発合宿に参加しました。この合宿では、みんなでハッカソンに取り組むだけでなく、温泉や足湯、美味しい食事を堪能しながら内定者同士の親睦を深めました。 合宿の概要 この合宿には参加を希望する25卒の内定者が集まり、2日間でハッカソンを行います。さらに先輩のエンジニアや役員がサポート役で参加します。 ハッカソンのルールは以下の通りです。 形式:個人開発 テーマ:日常で感じている課題を解決するアイデアを考えてみよう 集合 1日目は朝10時に湯河原駅へ集合するところから始まります。遅刻しないか心配でしたが、心配性な性格が功を奏し30分前に到着しました。ちなみに、僕のほかに心配性な同期4名ほどが30分前に集合していました。早く到着したメンバーで湯河原駅近くの喫茶店へ行きました。開発や大学などの話で盛り上がり、楽しい時間を過ごせました。 宿について 合宿場所は湯河原駅からバスで10分ほどの距離にある旅館「おんやど惠」です。 おんやど惠は、開発合宿に最適な施設と設備が揃った場所でした。露天風呂付きの温泉や足湯は心身ともにリラックスできます。ネットワーク環境も非常に安定しており、30人程度で同じWi-Fiを使用しても快適に開発を進められました。さらに、徒歩30秒のコンビニで気軽に買い出しができ、作業に集中できます。 自己紹介タイム 今回の開発合宿に参加した内定者とサポートしてくれる社員の自己紹介がありました。みんなそれぞれ特徴的な自己紹介をしてくれました。 昼食 自己紹介が終わると、昼食の時間です。3種類の美味しいお弁当が用意されていました。お弁当を選ぶ順番はくじ引きで決まります。僕は9番目というなんとも言えない順番だったのですが、一番人気のカルビ弁当を選べました。開発合宿が最高の形でスタートしました。 開発 こんにちは! 25卒内定者のてぃーてぃーです。さて、昼食後すぐに開発が始まりました。開発で行き詰まったところがあれば先輩社員の方たちにアドバイスをもらい、時には他の内定者の様子が気になりどんなものを作っているのかチラチラ見ながら、翌日の成果発表会に向けて開発を進めていきました! 今回の開発合宿で、みんなの開発に対する真剣な眼差しやエンジニアとしての姿を初めて見ることができました。 夕食 1日目の開発時間が終了し、夕食の時間がやってきました。食事はコース形式で、お刺身など様々な和食料理が出てきました。とても美味しかったです! 内定者や社員にかかわらず自由に着席し、食事とお酒を味わいました。入社歴や職種に関わらず、社員同士の距離が近いということがZOZOの魅力だと改めて感じました! 自由時間 夕食後は就寝まで自由時間です。立派な温泉に入ったり、再度集まって開発を続けたりしました。みんな寝る時間ギリギリまで開発や発表準備を続けており、とても熱意を感じました! 部屋は4人部屋でしたので、男性陣は職種ごと(モバイル、バックエンド、SRE、機械学習など)に分かれて寝泊まりしました。 成果発表会 こんにちは。ここからは25卒内定者のじゅんじゅんがレポートします! 2日目になり、いよいよ合宿の集大成を発表する時間になりました。発表順はくじ引きで決めます。 発表は8分と質疑応答の形式で行われました。質疑応答では、技術的な深掘りからフリートークまで、和気藹々とした発表会になりました! 表彰式 休憩後、表彰式が行われました。CPO賞1名と、先輩スタッフ賞2名、内定者賞1名の発表が行われました。それぞれ豪華な景品があったので、ドキドキしながら発表を聞きました。 懇親会 最後は旅館から近くのお店に移動し、開発合宿を振り返りながら先輩との仲を深めるために懇親会を行いました。この2日間ですっかり打ち解けていたので、年齢や職種などは関係なく、みんなでとても盛り上がりました! 僕はZOZOTOWN CPOの橋本さんと同じテーブルで、ZOZOの話からプライベートの話まで色々と聞くことができ、人生の勉強になりました。 受賞作品の紹介 ここからは、表彰された4つの作品を紹介します。 CPO賞「MOMENT CAPSULE」 プロダクト紹介 このアプリは、アナログの魅力を残しつつ、デジタルでつながる新しい形の「写真タイムカプセル」を実現するアプリです。指定した開封日時になるまで中身を閲覧できません。 開発経緯 このアプリは、タイムカプセルの魅力を手軽に楽しめる形で提供したいという思いから生まれました。きっかけは、妹が10年後の自分に向けた手紙を受け取ったエピソードで、「タイムカプセルってエモいな」と感じたことです。しかし、従来のタイムカプセルには以下のような課題があり、実現のハードルが高いものでした。 埋める場所の確保が必要 掘り返すためにみんなで調整しないといけない 何を入れたらいいか迷ってしまう 時間が経つと忘れてしまう可能性がある そこで、「もっと手軽で簡単なタイムカプセルを作れないか」と思い開発しました。 先輩スタッフ賞「まだ飲む?」 プロダクト紹介 飲酒者に数学の問題を解かせ、正答率に応じて泥酔度を算出し、泥酔度に応じてまだ飲んでも大丈夫かどうかをLINEグループにpush通知するアプリです。 開発経緯 年末になると飲み会が増え、それに伴いお酒による失敗も増加します。その原因を分析したところ、泥酔しているかどうかが自分でわからなくなり、まだ飲めると思い続けることで飲みすぎてしまい、結果として論理的思考力が低下し失敗につながるという流れが明らかになりました。そこで、論理的思考力をテストして酔っ払っているかどうかを周囲に認知させることで、この失敗を回避できるのではないかと思いました。 先輩スタッフ賞「メイクレシピ保存アプリ」 プロダクト紹介 メイクレシピの画像を読み込んで文字列化し、画像とともに保存できるアプリです。将来的には、保存したデータをもとに共通のコスメなどをレコメンドする機能を実装したいと考えています。 開発経緯 SNSで流行したメイクレシピの有益な情報が、画像のままではデータとして活用できず、埋もれてしまっている現状に課題を感じました。また、多くの人が他者と共有するために作成したメイクレシピがカメラロールの中で眠ったままになってしまう状況をもったいないと感じたことが、開発のきっかけです。 内定者賞「Decision Coin」 プロダクト紹介 Decision Coinはグループ内において、コインをアプリ内通貨として取引をしながら、ゲーム感覚で意思決定を行うアプリです。 開発経緯 日常生活における意思決定の難しさに着目して開発しました。家族、恋人、友達、研究室、サークルなど、ほぼ全てのコミュニティで意思決定が必要となりますが、価値観のすり合わせは言葉だけでは難しいことがあります。そのため、価値観の定量化を行う術がないか模索していました。また、最近自分が仮想通貨にハマっていることから、この意思決定を取引としてゲーム感覚で行うことで、より円滑なコミュニケーションと合意形成を実現できるだろうと考えました。 おわりに 2日間の開発合宿では、設備が整った旅館で温泉に入り放題という最高の環境の中、体を癒やしながら開発に集中できました。また、同期や先輩と開発や技術、ZOZOについて語り合う中で、社内の雰囲気の良さや高い技術力を実感しました。そして、これまで泊まりがけで過ごす機会のなかった、これから一緒に働く同期の人となりについても知ることができました。この合宿で得た知識と経験を活かし、さらに成長していきたいと思います。ありがとうございました!
アバター
はじめに こんにちは、WEAR Webフロントエンドチームでテックリードをしている冨川( @ssssota )です。業務でUniversal Linksのテストを効率化するために、独自のパッケージを開発し、 GitHub および npm で公開しました。本記事ではそのモチベーションと利用方法などを紹介します。 目次 はじめに 目次 背景・課題 apple-app-site-association ファイル 挙動確認の課題 解決の取り組み パッケージの機能 root権限とmacOS依存 デモ おわりに 背景・課題 まず、Universal Linksについて紹介します。Universal Linksは、ブラウザなどからiOSアプリを開くためのディープリンク技術の一種です。 apple-app-site-association というファイルをWebサーバに配置することで、WebページとiOSアプリの関連付けを行います。 apple-app-site-association ファイル apple-app-site-association ファイルは、Webサーバに配置するJSONファイルです。このファイルには、Universal Linksの挙動を制御するための情報を記述します。以下に例を示します。 { " applinks ": { " details ": [ { " appID ": " HOGE1.com.example.app ", " components ": [ { " # ": " nondeeplinking ", " exclude ": true } , { " / ": " /search/ ", " ? ": { " q ": " * " } } ] } , { " appIDs ": [ " HOGE2.com.example.app ", " HOGE2.com.example.app " ] , " components ": [{ " / ": " /*/posts/* " }] } ] } } このように、 apple-app-site-association ファイルには、アプリIDと対象URLのマッピングが記述されています。このファイルをWebサーバに配置することで、対象URLに対してiOSアプリが開かれるか否かを制御できます。一見するとglobのような記述と、パス、クエリ、フラグメントを設定でき柔軟に見えます。しかし、挙動が予想しにくい面もあります。 例えば、 * はワイルドカードで / という文字にもマッチします。上記の例では /*/posts/* というパス設定を記述していますが、 /foo/posts/ や /foo/bar/posts/baz/qux なども該当します。これはglobに慣れているエンジニアにとっては違和感を感じるかもしれません。 挙動確認の課題 Webサーバにファイルを配置する関係上、アプリエンジニアではなくWebエンジニアがファイルを管理することがあります。これはWebアプリのパスを管理しているエンジニアが管理するという意味では非常に合理的ではあるものの、挙動確認はやや困難です。設定のミスによって、意図せずアプリが起動するようになるなどのリスクもあります。 apple-app-site-association ファイルの挙動確認は、 swcutil というmacOSにプリインストールされたコマンドを用いることで行うことができます。しかし、このコマンドはドキュメントが少ない他、Universal Linksの挙動確認においては使い勝手が悪いです。以下にいくつかの使用例を示します。 $ swcutil verify -j ./apple-app-site-association -u '/' swcutil must be run as root $ sudo swcutil verify -j ./apple-app-site-association -u '/' { s = applinks, a = HOGE.com.example.app, d = www.example.com }: Pattern "/" matched. $ sudo swcutil verify -j '{"applinks":{"details":[{"appIDs":["HOGE1.com.example.app","HOGE2.com.example.app"],"components":[{"#":"nondeeplinking","exclude":true},{"/":"/search/"}]}]}}' -u '/search/' { s = applinks, a = HOGE1.com.example.app, d = www.example.com }: Pattern "/search/" matched. { s = applinks, a = HOGE2.com.example.app, d = www.example.com }: Pattern "/search/" matched. まず、 sudo コマンドでroot権限を使用していることに気づきます。非root権限で実行すると swcutil must be run as root というメッセージが標準エラー出力に出力されます。また、規模の大きなWebアプリケーションでは多数のURLパスのテストが必要となるケースも考えられます。このような場合シェルスクリプトなどを用い反復することになります。しかしながら出力はプログラムから扱いやすい形式とはいえません。 解決の取り組み 先のような課題を解決するため、 universal-links-test というNPMパッケージを開発しました。このパッケージは、 swcutil コマンドをラップした関数を提供し、 apple-app-site-association ファイルの挙動確認をサポートします。 NPMパッケージとして提供することで、Web開発者が容易に導入し、テストと共に apple-app-site-association ファイルを管理できるようになります。 パッケージの機能 現在、 universal-links-test は主に verify 関数を提供します 1 。この関数は apple-app-site-association ファイルのファイルパス(もしくはJSON)とURLを受けとり、指定されたURLによってどのアプリが開かれるかをMapで返します。以下に例を示します。 import { verify, type AppleAppSiteAssociation } from "universal-links-test"; const aasa: AppleAppSiteAssociation = { applinks : { details : [{ appIDs : [ "HOGE.com.example.app" ] , components : [ { "#" : "nondeeplinking" , exclude : true } , { "/" : "/search/" } ] , }] , } , } ; const result: Map < string , "match" | "block" > = await verify(aasa, "/search/" ); console .log(result. get ( "HOGE.com.example.app" )); // => "match" root権限とmacOS依存 verify 関数は swcutil コマンドのラッパーであるため、macOSかつroot権限が必要です。 universal-links-test はこの課題に対し完全な解決策は提供できないものの、緩和策を提供します。 それが universal-links-test/sim というモジュールです。これは universal-links-test 同様、 verify 関数を提供しています。 swcutil の挙動をシミュレートしているため、macOSやroot権限でなくとも利用できます。開発時には universal-links-test/sim を、CI環境では universal-links-test を利用することで、Universal Linksの挙動確認を確実に行えます。 GitHub Actionsでの利用例を以下に示します。 universal-links-test はmacOSかつroot権限を必要とするため、CIはmacOSランナーを利用する他、 sudo コマンドを用いてroot権限を取得する必要があります。すべてのテストをroot権限で実行するのはセキュリティ上望ましくないため、VitestやJestなどのテストランナーを使っている場合でも、対象ファイルのみ sudo で実行するなど配慮が必要です。以下の例では node --test コマンドを使うことで隔離しています。 // universal-links.test.js import { verify } from "universal-links-test" ; import { test } from "node:test" ; import * as assert from "node:assert" ; const appleAppSiteAssociationPath = "path/to/apple-app-site-association" ; test ( "Universal Links test" , async () => { const cases = [ [ "HOGE.com.example.app" , "/search/" , "match" ] , [ "HOGE.com.example.app" , "/search/#nondeeplinking" , "block" ] , [ "HOGE.com.example.app" , "/" , undefined ] , ] ; for ( const [ appID , path , expected ] of cases ) { const result = await verify ( appleAppSiteAssociationPath , path ) ; assert . strictEqual ( result . get ( appID ) , expected ) ; } }) ; # .github/workflows/test.yml # ... jobs : test : runs-on : macos-latest # macOSランナーを利用 steps : - uses : actions/checkout@v4 - uses : actions/setup-node@v4 with : node-version : 22 - run : npm ci - run : sudo node --test universal-links.test.js # root権限が必要 デモ デモページ をGitHub Pagesで公開しています。このページは universal-links-test/sim を利用しているため、ブラウザから環境問わずUniversal Linksの挙動確認できます。 おわりに universal-links-test は、Universal Linksの挙動確認をサポートするためのNPMパッケージです。Web開発者が容易に導入し、 apple-app-site-association ファイルの管理と挙動確認ができます。また、 universal-links-test/sim を利用することでmacOSやroot権限がなくともUniversal Linksの挙動確認ができます。 universal-links-test では、今後も機能追加やバグ修正していく予定です。ぜひご利用いただき、フィードバックをお寄せいただければ幸いです。 私たちZOZOでは一緒にサービスを作り上げてくれる方を募集しています。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com v0.1.0リリース時点での機能です。今後のバージョンで機能変更される可能性があります。 ↩
アバター
はじめに こんにちは。Developer Engagementブロックの @wiroha です。12月10日に「 株式会社ユーザベース×株式会社ZOZO×株式会社PR TIMES 3社合同フロントエンド勉強会 」を開催しました。3社のエンジニアが集まり、フロントエンドに関する取り組みを発信するイベントです。 登壇内容まとめ 各社から次の7名が登壇しました。 発表タイトル 登壇者 PR TIMESにおけるNext.jsとキャッシュの付き合い方 株式会社PR TIMES 柳 龍哉 Next.jsのアップデートに伴い、mswを2系に上げた話 株式会社ZOZO 田中勇太 こつこつ育てるSLO 株式会社ユーザベース ニッシー☆ 三年間の関わりから見る PR TIMES エンジニアリングの変化 Shogo SENSUI (@1000ch) useSyncExternalStoreを使いまくる 株式会社ZOZO 冨川宗太郎 あの日見たUIの名前を俺たちはまだ知らない 〜サイドピーク、はじめました〜 株式会社ユーザベース 田端 樹人 Recoilを剥がしている話 株式会社PR TIMES 桐澤 康平 PR TIMESにおけるNext.jsとキャッシュの付き合い方 株式会社PR TIMES 柳 龍哉さまによる発表 speakerdeck.com Next.jsの運用構成、キャッシュ戦略、Next.jsとの付き合い方についてお話ししました。キャッシュ戦略はページの特性によって異なる工夫をしていました。 Next.jsのアップデートに伴い、mswを2系に上げた話 株式会社ZOZO 田中勇太による発表 speakerdeck.com MSW 1.xから2.xへ上げた際に苦労したことや、2.xのメリットついて共有しました。メリットはスライド内でコードを比較して示しているため、そちらをご覧ください。 こつこつ育てるSLO 株式会社ユーザベース ニッシー☆さまによる発表 speakerdeck.com SLOの設定とその運用について、具体的な事例を交えて説明しました。「SLOの名前を具体的にする」「CUJベースでもっと具体的な命名にする」「指標をアップデートする」といったノウハウが共有されました。 三年間の関わりから見る PR TIMES エンジニアリングの変化 Shogo SENSUI (@1000ch)さまによる発表 speakerdeck.com PR TIMESにアドバイザーとして入り、積み重ねてきた改善についてお話ししました。課題解決の思考フレームワークを導入することで議論の質が向上したそうです。Backendと同居していたFrontend実装の別リポジトリ化は大きな成果だと感じました。 useSyncExternalStoreを使いまくる 株式会社ZOZO 冨川宗太郎による発表 speakerdeck.com useSyncExternalStoreの活用方法とその効果について説明しました。ブラウザ側でしか使えない関数やリソースを必要なときだけ利用できるようになり、Hydration Errorの回避などのメリットが得られるそうです。 あの日見たUIの名前を俺たちはまだ知らない 〜サイドピーク、はじめました〜 株式会社ユーザベース 田端 樹人さまによる発表 speakerdeck.com 画面右側からページがせり出してくる表示方法「サイドピーク」の実装方法を発表しました。現在はNotionで使用されていますが、まだ珍しいUIです。多くの段階を経て実現できており、同じUIを実装したい方にとって参考になる内容でした。 Recoilを剥がしている話 株式会社PR TIMES 桐澤 康平による発表 speakerdeck.com RecoilがReact 19で動かず対応される見込みがないことから、脱却するために取り組んでいることを共有しました。複数のパターンがあり一律に置き換えられるものではなく、影響範囲が広い場合もあり、戦略立てて進める必要がありそうでした。 最後に 本イベントは3社からエンジニアが集まることで、自社内だけでは得られない情報や知見を共有できる機会となりました。今後もこういったイベントを開催していきますので、ぜひご参加ください! ZOZOでは一緒に働く仲間を募集中です。ご興味のある方は以下のリンクからぜひご応募ください。 corp.zozo.com
アバター
はじめに こんにちは。SRE部プラットフォームSREブロックの松石です。 12月2日〜12月6日にラスベガスで開催された AWS re:Invent 2024 に、弊社から13名のエンジニアが現地参加しました。この記事では熱気あふれる会場の様子と現地参加したメンバーのそれぞれが印象に残ったセッションについてご紹介します。 目次 AWS re:Inventとは 現地の様子 セッションレポート おわりに AWS re:Inventとは re:InventはAmazon Web Services(AWS)が主催するAWS最大のカンファレンスです。このイベントでは、AWSの様々なサービスのアップデートや新サービスが発表されます。今年は世界中から約60,000人、日本から約1,700人の参加者がラスベガスに集まりました。今年のre:Inventでは、昨年から盛り上がりを見せる生成AIブームの影響が新サービスの発表や各セッション、EXPOでの企業ブースに多く見られました。 現地の様子 私は本イベントへの参加が今回初めてだったのですが、会場の熱気や日本では味わえない国際的な雰囲気とスケールの大きさに終始圧倒されました! 会場の入口 会場の入口にはAWS re:Invent 2024と書かれたWelcomeボードがありました。 EXPO こちらは会場内のEXPOエリアです。今年は国内外の総勢数百社の企業がブースを出しており、各社のプロダクトのデモを直接見たり触れたりできます。また、EXPOでは各プロダクトに精通するエンジニアやその他のスタッフに気軽に質問や相談したり、議論を交わしたりもできます。 さらに、EXPOでは恒例のSWAG集めも活発に行われており、現地参加したメンバーがたくさんのSWAGを集めていました! 基調講演 基調講演では生成AI関連やクラウド運用の効率化などの新サービスや新機能が発表されました。 以下の画像は新しいNewSQLデータベースであるAmazon Aurora DSQLが発表された瞬間です。 朝食や昼食 イベント開催期間中は、朝食や昼食が無料で提供されます。さらには、会場内の各所でドリンクや軽食が提供され、Workshopに持ち込むこともできました。 AWS認定者ラウンジ 会場内には、AWSの認定資格の保有者のみが使える休憩スペースがありました。ここでも参加者同士の交流や認定資格の保有者限定のSWAGなどが配布されていました。 その他 イベントの初日には、 APJ on Tour ’24 というAWS主催のパーティが開催されました。このイベントは、アジア太平洋地域のイベント参加者が交流できるコミュニティイベントです。 3日目には参加したメンバーのうち3名が毎年恒例の5Kマラソンに参加しました。定員は先着2,000名とのことでした。朝6時とかなり早かったですが、昇ってくる朝日を横目に走り、ゴール後には参加者に限定SWAGと記念メダルが渡されました。このイベントでは、ラスベガスの街中を走るというとても貴重な体験ができました! re:Invent 4日目には「re:Play」というパーティが開催され、ライブステージや巨大滑り台など音楽やアクティビティを楽しむことができました! セッションレポート ここからは現地参加メンバーが気になったセッションを紹介します。 Breakout Session Multi-Region strong consistency with Amazon DynamoDB global tables (DAT425-NEW) Understanding security & privacy on Amazon Bedrock, featuring Remitly (AIM360) Achieving scale with Amazon Aurora PostgreSQL Limitless Database (DAT420) The Future of Kubernetes on AWS (KUB201) Deep dive into Amazon Aurora DSQL and its architecture (DAT427) Chalk Talk IT transformation for nonprofits: Maximize impact with modernization (IMP205) Next-generation SaaS tenant isolation strategies (SAS312) Build resilient, high-performance applications with Amazon Aurora DSQL (DAT334) Code Talk Streamline Amazon EKS operations with generative AI (KUB321) Innovation Talk Security insights and innovation from AWS (SEC203-INT) Workshop Increase your database agility with Amazon FSX (STG404-R) Gen AI incident detection & response systems with Aurora & Amazon RDS (DAT307) Multi-Region strong consistency with Amazon DynamoDB global tables (DAT425-NEW) SRE部カート決済SREブロックの飯島です。 KeynoteではAmazon DynamoDB global tablesがマルチリージョンで強い整合性をサポートすることが発表されました。現在はプレビューであり、利用可能なリージョンはus-east-1(バージニア北部)とus-east-2(オハイオ)、us-west-2(オレゴン)の3つです。 この発表後に新たに追加されたBreakoutセッションに参加してきました。このようにre:InventではKeynoteで発表された新機能に関するセッションが追加されます。注目度が高いものだとすぐに予約が埋まってしまうため要チェックです。 Amazon DynamoDB global tablesとは選択したマルチリージョン間でDynamoDBテーブルを自動的にレプリケーションしてくれるフルマネージドな機能です。この機能を利用することで以下のメリットが得られます。 低遅延:ユーザは最も近いリージョンでデータにアクセスできる。 高可用性・対障害性:単一リージョン障害が発生しても他のリージョンからデータにアクセスし続けられる。 今回globale tablesのオプションで結果整合性(最終的な一貫性)と強い整合性(強固な一貫性)が選択できるようになりました。 これまでデフォルトの設定だった結果整合性では、タイミングによっては古いデータへアクセスすることもあります。例えば以下キャプチャのus-east-1のテーブルで更新があった場合、もし更新がレプリケーションされる前にus-west-2へアクセスすると古いデータが返ってきます。 画像引用元: https://youtu.be/R-nTs8ZD8mA?feature=shared&t=285 一方で、強い整合性ではいつどのリージョンのテーブルにアクセスしても最新のデータが返ってきます。 画像引用元: https://youtu.be/R-nTs8ZD8mA?feature=shared&t=368 以下の表はマルチリージョンにおける結果整合性と強い整合性の比較です。 マルチリージョンの結果整合性 マルチリージョンの強い整合性 強い整合性のある読み取り (ConsistentRead: true) は古いデータを返すことがある 強い整合性のある読み取り (ConsistentRead: true) は古いデータを返さない 書き込みと強い整合性のある読み取り (ConsistentRead: true) のレイテンシが低い 書き込みと強い整合性のある読み取り (ConsistentRead: true) のレイテンシが高い 競合は最後の書き込みを優先して解決される 競合が発生すると ReplicatedWriteConflictException が返される RPO (Recovery Point Objective) は 1 桁秒 RPO (Recovery Point Objective) はゼロ 先述した通りマルチリージョンの強い整合性は古いデータを返しませんが、結果整合性よりも「書き込みと強い整合性のある読み取り」のレイテンシが高く、整合性とレイテンシの間にはトレードオフがあります。異なるリージョンにおいて同じデータへの書き込みにより競合が発生した際の挙動や、RPO(Recovery Point Objective)にも違いがあります。 以下は強い整合性を使用する際の注意点です。 プレビュー機能のため本番環境での使用には適さない 強い整合性はすべてのレプリカテーブルに適用される global table作成後は整合性の切り替えができない 利用可能な3つのリージョンに展開が必要 例:us-east-1にDynamoDB tableを作成した場合、us-east-2とus-west-2にレプリカテーブルが作られる 強い整合性でglobal tableを作成すると結果整合性に変更はできません。逆も然りです。一度すべてのレプリカテーブルを削除してからglobal tableを再度作成する必要があります。 本セッションではデモや強い整合性をどのように実現しているか詳細な説明も行われました。興味のある方はリンクからぜひご覧ください。 AWS re:Invent 2024 - Multi-Region strong consistency with Amazon DynamoDB global tables (DAT425-NEW) 画像引用元: https://youtu.be/R-nTs8ZD8mA?feature=shared&t=1123 Increase your database agility with Amazon FSX (STG404-R) SRE部基幹プラットフォームSREブロックの斉藤です。普段はZOZOの倉庫システムやブランド様向けの管理ページなどのサービスのオンプレミスとクラウドの構築・運用に携わっています。またDBREとしてZOZOTOWNのデータベース全般の運用・保守も兼務しています。 ZOZO Advent Calendar 2024にて、技術セッション以外の場面でのre:Inventの魅力について紹介していますので併せてご覧ください。 qiita.com 目的と概要 Amazon FSx for NetApp ONTAPを活用し、データベースのクローンを作成する手法を学ぶための2時間のWorkshopに参加しました。事前準備されたEC2インスタンスやAmazon FSx環境を使用するため、ラップトップを持参し、会場内のWi-Fiを利用して操作します。 主な内容 Amazon FSxを用いたAmazon EC2上でのセルフマネージドデータベース「SQL Server、PostgreSQL、MySQL」の中から選択して操作する(SQL Serverを選択)。 高度な機能「スナップショット、クローン作成、バックアップ、レプリケーション」の中でもクローン作成に焦点を当て、データベースの迅速なコピーについて学習する。 画像引用元: Workshopの公開資料 Amazon FSx for NetApp ONTAPとは NetAppのデータ管理ソフトウェアであるONTAPをクラウド上で利用できるようにした、AWSのマネージドサービスです。エンタープライズ向けのデータ管理やストレージニーズに対応しており、オンプレミス環境やハイブリッドクラウド環境で広く使われているNetApp ONTAPの機能をそのまま活用できるというものです。 画像引用元: Workshopの公開資料 SnapCenterのインストールとFSx for ONTAP ファイルシステムの確認 用意されたEC2インスタンスにリモートデスクトップで接続できる状態にしてWindows Serverにログインします。 PowerShellを起動してFSx for ONTAPファイルシステムにSSHで接続し、ボリュームの状態を確認しておきます。さらに、snapcenter.exeを起動して、NetApp SnapCenterをインストールします。 NetApp SnapCenterとは データ保護・管理ソリューションで、データベース、仮想マシン、アプリケーション、ファイルシステムのバックアップ、リストア、クローン作成を一元管理できるツールです。 SnapCenterへストレージシステムの追加 SnapCenterにクローン元とクローン先のストレージシステムを追加します。 SnapCenterのナビゲーションペインから「Storage Systems」を選択します。 「New」をクリックします。 ストレージシステムの情報を入力していきます。 Storage System:データベースの管理IPアドレスを入力します。 Username:ストレージにアクセスするためのユーザーで、操作可能な権限が割り当てられます。 Password:ユーザーのパスワードを入力します。 SnapCenterへホストの追加 SnapCenterにクローン元とクローン先のホストを追加します。 SnapCenterのナビゲーションペインから「Hosts」を選択します。 「Managed Hosts」タブから「Add」をクリックします。 表示されたポップアップにデータベースのホスト情報を入力します。 Host Name:データベースのホスト名を入力します。 Credentials:実行に使用する認証情報を選択します。 Select Plug-ins to Install:「Microsoft Windows」と「Microsoft SQL Server」を選択します。SQL Serverホストを追加すると、必要なプラグインをデータベースホストに適用し、自動検出ができるようになります。 「Submit」をクリックして完了です。 クローンの作成 クローンを作成して別のインスタンスにデータベースのコピーを作成します。 SnapCenterのナビゲーションペインから「Resources」を選択し、さらにユーザーデータベースを選択します。 クローンを作成する対象となるバックアップを選択し、「Clone」を選択します。 画面が切り替わったら、「Clone settings」配下の「Clone Options」を入力します。 Clone server:クローン先のデータベースホスト名を入力します。 Clone instance:クローン先のデータベースインスタンス名を入力します。 Clone name:インスタンス内にクローンされるデータベース名を入力します(一意である必要があります)。 Choose mount option:「Auto assign volume mount point under path」を選択します。さらに、FSx for ONTAPのディスク上のディレクトリを指定します。このディレクトリは、クローン作成時に新しいボリュームとしてマウントされるルートロケーションに使用されます。 「Next」をクリックして完了です。 ※「Logs」から「Summary」は、今回特に設定せずにデフォルト値で進行していました。 クローンの確認 SQL Server Management Studioから確認するとクローンしたデータベースがDEVSQLインスタンス上に作成されました。 PowerShellを起動してFSx for ONTAPファイルシステムに再度SSHで接続し、ボリュームの状態を確認してみるとクローンしたボリュームが追加されていることが確認できました。 まとめ re:Inventで初めて海外のWorkshopに参加しました。不安や緊張を感じていましたが、スタッフが親切でサポートも手厚く、ブラウザベースで進行する形式だったため、手順を翻訳して学習を進められました。 今回は、Amazon FSx for NetApp ONTAPの一部機能について学びましたが、他にも多くの高度な機能が提供されています。これを機にさらなる理解を深め、弊社のEC2インスタンス上で稼働するSQL Serverの運用改善やコスト効率化に活用できる可能性を検討したいと思います。 Understanding security & privacy on Amazon Bedrock, featuring Remitly (AIM360) ブランドソリューション開発本部FAANS部バックエンドブロックの田島です。今年のAWS re:Inventでは、生成AIに関するセッションが昨年以上に強い存在感を放っている印象でした。その中でも、個人的に印象に残った「生成AIアプリケーションにおけるセキュリティ」に関するセッションについて紹介します。生成AIアプリケーションの開発においても、セキュリティやプライバシーの配慮は欠かせません。それに加え、「責任あるAI(Responsible AI)」というキーワードに示されるように、生成AI特有の考慮事項に対応する仕組みが求められます。 このセッションでは、まず生成AIアプリケーションのセキュリティについて、包括的な考え方が紹介されました。具体的には、セキュリティを3つのステージに分けて整理し、それぞれの要件が説明されました。 生成AIモデルの安全性の確保 安全な訓練データの使用や、事後学習フェーズでモデルの振る舞いを調整するアラインメントなど、生成AIモデル自体に関わるセキュリティ要件です。 モデルのアクセス制御と管理 モデルへの安全なアクセス制御や、運用中の監視・管理に関する要件です。 アプリケーション全体のセキュリティ モデルにアクセスする前のデータフロー管理や、ハルシネーションの防止、出力がポリシーに準拠しているかの確認などが含まれます。 さらに、多層的なセキュリティアプローチとして以下の決定論的制御と確率論的制御を組み合わせる重要性が強調されていました。 決定論的制御(Deterministic Controls) 再現性や説明可能性が求められる制御方法です。例えば、IAMを用いたアクセス制御やKMSによる暗号化などが該当します。 確率論的制御(Probabilistic Controls) 入力が意図したユースケースに沿っているかの確認や、出力結果の有害性をチェックするなど、生成AIモデル特有のアプローチです。例えば、機械学習モデルを用いて出力内容を評価する方法が該当します。この制御は柔軟性が高い反面、完全な再現性は保証できないため、一定のリスクを伴います。 続いて、これらの概念がAmazon BedrockというAWSの生成AIサービスと関連付けて解説されました。Bedrockはさまざまな生成AIモデルが利用でき、また、それを使ったアプリケーションの構築を容易に実現できるフルマネージドサービスです。責任あるAIを実現する仕組みとして「Guardrails for Amazon Bedrock」という機能が昨年発表されました。今回個人的に特に気になったのはGuardrailsの新機能である「Automated Reasoning checks」です。 Automated Reasoning checksは、前述の3つステージのうち3つ目の「アプリケーション全体のセキュリティ」に該当します。そして、この機能は決定論的制御と確率論的制御を組み合わせたアプローチを採用し、具体的には以下のような仕組みで動作します。 画像引用元: https://youtu.be/3Sxw6IIYhdE?feature=shared&t=2495 まず、ソースドキュメントとその内容をもとに回答を生成する目的をAutomated Reasoningサービスに送信し、自動推論ポリシーが生成されます。このポリシーは「以上」「以下」といった論理式を含む決定論的なルールとして構築されます。続いて、ユーザーからの質問やそれに対する回答が、このポリシーに準拠しているかどうかが判断されます。この検証部分は決定論的であるため、結果に再現性があります。一方で、ポリシー自体を生成するプロセスは確率論的です。ただし、この生成されたポリシーは人の手で修正可能なため、柔軟性と信頼性の両立を図りやすい点が特徴的です。 本セッションを通じて生成AIアプリケーション構築時のセキュリティに関する包括的な考え方を頭の中で整理できたのがまず大きな学びでした。また、Automated Reasoning checksは、論理式を使った決定論的なアプローチで生成AIアプリケーションの信頼性と説明可能性を高めるための画期的で興味深い仕組みだと感じました。実用的な生成AIアプリケーションの構築において、信頼性や説明可能性が障壁になりやすいと考えています。今後もその課題のための仕組みが強化されていくことで、生成AIアプリケーションの実用性がさらに広がることを期待しています! Streamline Amazon EKS operations with generative AI (KUB321) 計測システム部SREの土田です。 ZOZOMAT や ZOZOMETRY 等の計測技術のSRE業務を担当しております。計測システム部ではAIを活用した業務効率化に力を入れており、re:inventではAIを用いた様々なソリューションを紹介するセッションがあり、それらに参加しました。 本パートではEKSの障害時のオペレーションをAI活用して迅速に行えるようにするCodeTalkのセッション内容を紹介します。 目的と概要 CodeTalkとは、ライブコーディングやコードサンプルを使用して、AWSのエンジニアが実装したソリューションを詳細に解説するセッションです。他のセッションと異なり、POCレベルで作ってみたようなものも紹介されていて、新しいことがたくさん学べたセッションタイプでした。 このセッションでは、EKSの障害発生時にアラートとともに改善策を提案するSlack通知およびWebコンソールの実装を行なっていました。 実装されたもの Slack通知ではCloudWatchアラームの内容に加えて、具体的に問題がありそうなリソースについても記載を加えています。 エラーログや対処をまとめたRunbookを元に推奨されるActionが提案されるWebコンソール 構成 CodeTalkで解説されたソリューションの構成は以下の通りです。 画像引用元: セッションの公開資料 P.6 このセッションではデータのembeddingおよびsemantic searchはfaissを使用して行っていましたが、フルマネージドの Bedrock Knowledge Base でも実現可能と思っています。 github.com 処理の流れは以下の通りです。 1. KubernetesのログやRunbookのベクトル化 KubernetesのログやRunbookをチャンクに分割し、埋め込みモデル(Titan Text Embeddings V2)でembeddingを行い、ベクトルストアに保存します。 ユーザーの質問処理 ユーザーが「なぜ私のアプリの応答時間が長いのか?」といった質問を入力します。この質問も埋め込みモデルによってembeddingを行い、ベクトル化されます。 セマンティック検索 ユーザーの質問のベクトルデータをもとに、ベクトルストアでベクトルの近傍検索が行われ、質問に関連するKubernetesのログやRunbookが取得されます。 コンテキストの構築 セマンティック検索によって得られた関連情報がユーザーの質問と組み合わされ、コンテキストが構築されます。 プロンプト拡張 構築されたコンテキストと元のユーザーの入力が結合され、プロンプトが拡張されます。 大規模言語モデルでの応答生成 拡張されたプロンプトがAmazon Bedrockの基盤モデル(Amazon NovaやClaude3.5等)に送られ、対応策を含む応答が生成されます。 画像引用元: セッションの公開資料 P.8 このセッションで作られたソースコードおよび資料は以下のリポジトリに格納される予定です。 github.com まとめ 実際に実装されたコードを見ながら、ソリューションの解説を受けると、具体的な手順が分かりやすく、すぐに手を動かしたくなりました。 EKSの運用はハイコンテキストで、CloudWatchやDatadogと睨めっこすることが多いですが、生成AIを活用してこれらの作業を少しでも効率化できないか模索していきたいと思います! Gen AI incident detection & response systems with Aurora & Amazon RDS (DAT307) EC基盤開発本部SRE部商品基盤SREブロックの佐藤です。私は生成AIを活用したインシデント検知および対応(IDR)を行うシステムのワークショップに参加しました。 ワークショップの概要 AWSには以前より、 AWS Incident Detection and Response というエンタープライズサポート向けの有人インシデント対応サービスが提供されています。今回のワークショップは人間ではなく生成AIを活用し、インシデントの検知および対応を自動化するシステムをハンズオン形式で構築しました。 Amazon Bedrockについて Amazon Bedrockがどのようなサービスか整理します。 Amazon Bedrock :主要なAI企業やAWSが提供する生成AIの基盤モデル(Foundationモデル・FM)を簡単に統合・利用できるサービス。インフラ管理不要で機械学習に関する専門知識がなくても利用可能。 Amazon Bedrock Agents :生成AIエージェントを簡単に作成できるBedrockの機能。ユーザーのリクエストに対して基盤モデルを拡張して追加情報を収集し、複数のステップに分解したアクションを実行する。 Amazon Bedrock Knowledge Bases :基盤モデルの検索拡張生成(RAG)を行うために、様々なデータソースと接続させるサービス。 関連資料 aws-samples/aurora-postgresql-pgvector: Leverage pgvector and Amazon Aurora PostgreSQL for Natural Language Processing, Chatbots and Sentiment Analysis 用語 ワークショップの冒頭では前提となる用語の説明がありました。 基盤モデル(FM):事前トレーニングされたディープラーニングモデル。知識が不十分な場合、FMはハルシネーションと呼ばれる誤った出力を生成する傾向がある。 ベクトルの埋め込み:データを数値表現に変換しベクトルとしてマッピングしたもの。データ間の固有の特性と関係性を捉える数学的表現。 検索拡張生成(RAG):ハルシネーションの対策として新しいデータソースから情報を取得するための情報検索コンポーネントを導入すること。 エージェントワークフロー:データを収集し、そのデータを使用して自己決定したタスクを実行し、定められた目標を達成できるプログラム。目標は人間が設定するが、AIエージェントはその目標を達成するために必要なアクションを独自に選択する。 システム概要 使用するサービス Amazon CloudWatch Amazon DynamoDB Amazon Bedrock Amazon Aurora PostgreSQL Amazon API Gateway AWS Lambda 処理の流れ AuroraとRDSをCloudWatchが監視し、インシデントが発生するとその情報をDynamoDBに書き込む。 事前にインシデントに対するRunbooksをS3にアップロードしBedrock Knowledge Basesと同期し基盤モデルの拡張(RAG)を行う。 ユーザーはWebアプリケーションを操作してインシデントとRunbookの確認、AuroraとRDSの復旧をする。 ユーザーからの指示によって、Bedrock AgentsがBedrock Knowledge BasesからRunbookの取得とAuroraとRDSの設定変更を行う。 ワークショップの内容 ワークショップは以下の順番で進みました。 セットアップ 基盤モデルの拡張 エージェントの設定 インシデントのシミュレート Webアプリケーションをデプロイし復旧する 1. セットアップ このワークショップでは基盤モデルとしてAnthropicのClaude3とAmazon Titanを利用しました。 なお、Bedrock Knowledge Bases、Bedrock Agentsは事前に用意されたものを使用しました。 2. 基盤モデルの拡張 Runbookと呼ばれるインシデントの対応手順が書かれたMarkdownファイルをBedrock Knowledge BasesにアタッチしたS3へアップロードしました。以下は、CPUインシデントに対するRunbookの例です( 引用元 )。 # Title Runbook to remediate RDS CPU Utilization alert ## Issue PostgreSQL database instance is running out of high CPU utilization. ## Description This run book provides the step by step instructions to address the high CPU Utilization in the RDS instance. Follow the instructions in this run book to remediate the high CPU utilization incident. ## Steps 1. Check if the RDS instance is in available state. If the status is available, continue otherwise abort the process. 2. Get the current CPU utilization metrics for the last 1 hour for the RDS instance. . 3. Check if the maximum CPU utilization from the CPU metrics is above 80% , then scale up the RDS instance to the next availabe instance type. アップロードしたRunbookは、Bedrock Knowledge Basesのコンソール上で「同期」を実行することで、埋め込まれた基盤モデルによって解析されます。 解析されたデータは、小さなブロック(チャンク)に分割され、PostgreSQLの拡張機能である pgvector を利用してベクトルデータとして保存されます。これだけでRAGによる基盤モデルの拡張とベクトル埋め込みが簡単に実現できました。 3. エージェントの設定 Bedrock Knowledge BasesをBedrock Agentsにアタッチし、アクショングループと呼ばれるAIエージェントが実行するLambda関数を確認しました。下記はアクショングループに定義されたDBインスタンスの状態を確認する関数です( 引用元 )。 def check_rds_state (db_instance_identifier): """ Function to check the current state of the RDS cluster """ try : response = get_instance_details_helper(db_instance_identifier) status = response[ 'DBInstanceStatus' ] message = f "The RDS status is {status}." if status != 'available' : message += " It is unavailable state, please try again later." lambda_logger.info(message) return message except Exception as e: lambda_logger.error(f "Error checking status: {str(e)}" ) lambda_logger.error(traceback.format_exc()) return f "Error: {str(e)}" 4. インシデントのシミュレート pgbench を使用してデータベースのCPUまたはI/Oに高負荷のワークロードを発生させ、CloudWatchアラームをトリガーします。そのインシデント情報がDynamoDBに正しく保存されることを確認しました。 5. Webアプリケーションをデプロイし復旧する Streamlit で構築したWebアプリケーションをデプロイし、以下の動作を確認しました。 DynamoDBから取得したインシデント情報を画面に表示する。 UIを操作してBedrock Knowledge Basesにインシデントを照会し、関連するRunbookを取得する。 UIを操作してBedrock Agentsを呼び出す。Bedrock AgentsはBedrock Knowledge BasesからRunbookを取得し、ステップに基づいて、対応するアクショングループの関数を呼び出して実行する。 さらに、時間が足りず完成には至りませんでしたが、DBのインフラストラクチャに関する質問に答えるエージェントを作成する項目もありました。これはBedrock AgentsからDynamoDBやAurora、RDSなどのデータベースにリアルタイムでクエリを送信し、情報を取得することで実現する内容でした。 感想 初めて生成AIを活用したシステムを作成しましたが、作業自体は非常に簡単で、Amazon Bedrockの使いやすさに感心しました。生成AIの複雑さをうまく隠して、多くの人に普及させようとするAWSの意図が伝わってきた気がします。今までは、業務にどのように利用すべきか具体的なアイデアが浮かんでいませんでしたが、このワークショップを通じて、普段の運用にも取り入れられる可能性があると感じました。今後はこの経験をもとに、業務で生成AIを活用する方法を検討していきたいと思います。 Achieving scale with Amazon Aurora PostgreSQL Limitless Database (DAT420) ZOZOMO部SREブロックの蔭山です。普段は Fulfillment by ZOZO や ZOZOMO のSREを担当しています。 今回は興味を持っていたサービスの1つであるAmazon Aurora PostgreSQL Limitless Databaseがどのように実現されているかが紹介されたセッションに参加しました。その内容について簡単ではありますがご紹介します。 内容 今回のセッションはBreakoutセッション形式で実施され、以下の内容が話されました。 画像引用元: セッションの公開資料 P.3 まずデータベースでのスケーリングの問題が取り上げられました。データベースでスケーラビリティを確保するにシャーディングが必要となる、シャーディングを実現しようにも以下の4つの大きな問題があると述べられました。 Querying(クエリの実行) Consistency(一貫性の維持) Re-sharding(再シャーディング) Database capacity management(キャパシティ管理) 画像引用元: セッションの公開資料 P.7 それらの問題を解決するためにAmazon Aurora PostgreSQL Limitless Databaseが誕生したとのことです。ちなみにAuroraが誕生してちょうど10周年とのことでした。 画像引用元: セッションの公開資料 P.12 Aurora PostgreSQL Limitless Databaseの特徴について、サンプルとしてCustomer(会員)、Order(注文)、Tax Rate(税率)を使って説明されました。 画像引用元: セッションの公開資料 P.15 画像引用元: セッションの公開資料 P.16 このあとは実際のAurora PostgreSQL Limitless Databaseのアーキテクチャについて詳しく説明がありました。 シャードグループは通常のAuroraクラスタと同じ操作が可能となっており、それぞれ以下の責務を担っているとのことでした。 Distributed transaction routers(Routers): スキーマ構成やシャーディングされたデータの位置情報を保持 Data access shards(Shards): シャーディングされた各データを保持 画像引用元: セッションの公開資料 P.23 PostgreSQLと同じトランザクションレベルを獲得するため、各ノードで正確に時間を同期できるクロックサーバーを実現し、時間をベースにトランザクションを実現しているとのことでした。 画像引用元: セッションの公開資料 P.41 クエリ実行に関してはPostgreSQL互換を保ちつつ、独自にカスタマイズされたDBエンジンが実行計画を作ってクエリの実行を担っているとのことでした。 画像引用元: セッションの公開資料 P.47 実際に測定されたパフォーマンスについても語られており、複数のアプリケーションから1秒あたり250万回以上の更新処理を行ってもレイテンシは数ミリ秒程度とのことでした。 画像引用元: セッションの公開資料 P.53 まとめ 今回深くまで説明いただいたことでAurora PostgreSQL Limitless Databaseがどれほど工夫されて実現されているかを身近に感じることができました。 将来使いたいと思っていたデータベースサービスの1つだったので、ユースケースを想定しやすくなったのも良かったなと感じました。 IT transformation for nonprofits: Maximize impact with modernization (IMP205) YSHP部YSHPブロックの川俣です。YSHP部では主にZOZOとYahoo!ショッピング間のデータ連携を担当しております。今後システムリプレイスを控えており、そのタイミングでモダン化も予定しております。今回はモダン化の流れや手法を学習するChalk Talkに参加いたしましたので紹介させていただきます。 まず今回のChalk Talkの大きな要素としては下記5つで構成されていました。 画像引用元: セッションの公開資料 P.3 まずは「Why modernize?(なぜモダン化するのか?)」という目的とメリットの確認です。 「機敏性、スピード、革新性」「パフォーマンスと復元力」「効率性とコストの最適化」 これらは「モダン化」自体が目的とならないように事あるごとに立ち返りたい観点です。YSHP部としては効率性を重視して運用改善を図りたいところです。 画像引用元: セッションの公開資料 P.6 「Strategy(戦略)」の中ではマネージドサービスを利用することで「機敏性」を確保しつつトータルの運用コストを下げるという大まかな戦略について話がありました。その中で述べられていた一文を紹介させてください。 Migration and modernization is a journey 私が参加したその他のセッションでも共通してモダン化を「旅」と表現していました。「決断疲れ」や「スキルギャップ」などシステム面以外の課題も解決していき目的に辿り着く様はまさに「旅」と言っても過言ではないと感じます。 画像引用元: セッションの公開資料 P.8 次に「Modernization pathways(モダン化の道筋)」です。段階的に何から始めれば良いのか悩みがちな部分なので体系的なアプローチを示して貰えるのは非常に助かります。この部分はモダン化を推進していく際のステップになるため重要なポイントです。 画像引用元: セッションの公開資料 P.10 「Whiteboarding」のセクションでは講師の方がその場で参加者の方のシステム構成をヒアリングし、ホワイトボードにモダン化したパターンを示していきます。左側がオンプレミス環境の構成図ですが、YSHP部の構成と近しいものがあり、既存構成は世界共通なんだなと非常に親近感を覚えました。 右側のモダン化後の構成図は複数AZ、なおかつAmazon ECSを想定した構成です。AWS A2C(App2Container)を利用しながらアプリケーション部分をコンテナ化していき運用していくケースです。A2Cを利用してコンテナをECRに登録し、コントロールプレーンはECS、データプレーンはFargateという構成です。Fargateを利用する事で運用管理のコストを少なくしていく狙いがあります。 既存アプリケーションをいきなりAWS Lambda等の複数サービスに分解せず、まずはコンテナ化していく手法は先述の「Modernization pathways(モダン化の道筋)」に沿った形です。 こちらは別のモダン化パターンです。オンプレのDBをVIEWを介し、AWS Schema Conversion ToolやAWS Database Migration Serviceを利用してDynamoDBに移行していくケースです。当該Chalk Talkでは触れられませんでしたがAWS DMSはスキーマの変換に生成AIを用いる機能が追加されました。この機能の詳細はAWS Database Blogの記事「 New – Accelerate database modernization with generative AI using AWS Database Migration Service Schema Conversion 」をご覧ください。 「Wrap-up」でまとめられた要点はこちらです。 モダン化は価値実現のために複数の経路を採用しながら進める漸進的なプロセスです。 「どこから始めるべきか、何を使うべきか」という疑問を解決するために、「モダン化の道筋」を活用します。 「モダン化の道筋」に沿ったAWSサービスとパートナーツールをモダナイゼーションプラクティスに組み込みます。 画像引用元: セッションの公開資料 P.11 AWSには、それぞれの状況や段階に応じて最適なサービスがあると改めて実感しました。今回具体的な手法やサービスを知れたことで今後のモダン化に対する熱量がぐっと上がりました! 「Modernization pathways(モダン化の道筋)」に沿って段階的なおかつ確実にモダン化を推進していこうと思います。 Security insights and innovation from AWS (SEC203-INT) フロントSREブロックの江島です。ZOZOTOWNのエンドユーザーに近い部分(フロントエンド、BFF等)を担当領域としています。また、全社のAWS管理者としての役割も担っています。私からは「Security, compliance & identity」の分野におけるInnovation talkの内容を紹介します。 Innovation talkとは、AWSのシニアリーダーが登壇するトーク型セッションです。各分野の重要課題に関してAWSにおける最新の取り組みが紹介されます。BreakoutセッションやWorkshop等よりもやや抽象度が高めな内容かなと思います。セッションはAWSのCISOであるChris Betz氏を中心に進められました。内容としては、大きく3つのトークテーマに沿った話がありました。 1つ目のトークテーマは「セキュリティに関するオーナーシップの重要性」についてです。このトークテーマでは、どんなに優れたツールがあってもオーナーシップ無しではセキュリティは成立しないということが述べられ、それを達成するための手段として「ガーディアンプログラム」が取り上げられました。これは、各開発チームの一部メンバーをセキュリティ有識者に育て上げ、開発チームの内側からセキュリティ推進を図っていくというものです。 2つ目のトークテーマは「最新サービス紹介」でした。Vice President & Distinguished EngineerのBecky Weiss氏より、考え方の根底にあるメッセージと共に最新のセキュリティサービスの紹介がありました。 特に非常に興味深かったのがDeclarative Policies(宣言型ポリシー)です。 これは以下のような特徴を持ちます。 AWSサービスの設定状態を定義するポリシー機能 宣言されたポリシーに反する設定は拒否される(強制される) メンバーアカウントから設定変更を行おうとした場合にはカスタムエラーメッセージを返せる 現時点ではEC2の一部ユースケースのみが対象(サポート範囲は今後拡大される予定) 個人的にはAWS Security Hubのコントロールに対応するポリシーが増えてくればコンプライアンス遵守を効率よく進められそうな気がしており、今後のサポート範囲拡大に期待しています。 3つ目のトークテーマは「量子コンピューティングや生成AIといった最新技術に対するセキュリティについて」でした。量子コンピュータによる攻撃にも耐えることができる暗号アルゴリズムの開発、生成AIの時代においてモデルに対するセキュリティを確保するための仕組みが紹介されました。 最後は挑戦的なメッセージで締めくくられ、非常に刺激を受けました。 全体感を掴むためにも自身の興味がある分野についてはInnovation talkを聴講して頂くことをお勧めします。 Next-generation SaaS tenant isolation strategies (SAS312) こんにちは。計測プラットフォーム開発本部バックエンドブロックの髙橋です。普段はZOZOMAT、ZOZOGLASS、ZOZOMETRYなどの計測プロダクトのバックエンドの開発・運用をしています。 直近リリースしたZOZOMETRYはSaaS型のサービスです。そのため、よりSaaSプロダクトの設計について知見を深めたく、「Next-generation SaaS tenant isolation strategies」というセッションに参加しました。本セッションではまず、「SaaSでのテナント分離とは?」という基本的な部分から始まりました。 その後、次の内容を講師が説明しました。 テナント分離をどのようにして行うか? サイロモデル ブリッジモデル プールモデル なぜやるか? プライバシー、セキュリティのため コンプライアンス強化 スケーラビリティ、コストコントロールを含めたプロダクトのアジリティを上げるため 一般的にテナントを分離する際はJWTの中にテナント情報を入れ込み、その情報からどのテナントのリソースにアクセスするかを判別するような設計が多いかと思います。 ZOZOMETRYでは、JWT内のCognitoのアカウントIDを使ってテナントの照合をしていました。 本セッションでは、STSとJWTを用いた次世代のテナント制御について説明がありました。具体的には、JWTをSTSのAssumeRoleWithWebIdentityと組み合わせて使用し、一時的な認証情報を取得する方法が紹介されました。これにより、JWT検証の責務をSTSに移譲することが可能になり、アプリケーション側でのJWT検証の実装負荷やバグによるセキュリティ上の脆弱性のリスクを軽減できるというメリットがあります。ご興味のある方はセッション中に紹介された以下のGitHubリポジトリに関連した要素を持ったコンテンツがありますので、触れてみることをおすすめします。 https://github.com/aws-samples/data-for-saas-patterns/tree/main/samples/tenant-isolation-patterns https://github.com/aws-samples/aws-saas-tenant-usage-and-cost-attribution https://github.com/aws-samples/aws-saas-operations-workshop/tree/v2 https://github.com/aws-samples/aws-saas-cell-based-architecture 大きめのセッションだとどうしても登壇者→聴衆の一方向になりがちですが、Chalk Talkでは講師が参加者に話を振ったり、参加者側がセッション中に質問出来たりしたことはとても新鮮でした。 Build resilient, high-performance applications with Amazon Aurora DSQL (DAT334) ブランドソリューション開発本部WEARバックエンド部SREブロックの小林です。ここからは新規発表されたDSQLに関連するセッションを2本ご紹介します。まずは私からDSQL概要や従来のAuroraデータベースとの違いに関するセッションを、続いてDSQLアーキテクチャにDeep diveしたセッションとWorkshopについて遠藤より紹介いたします。 このセッションはChalk Talk形式でAmazon Auroraの歴史やAurora DSQLの特徴について解説されました。スライドタイトルとセッション名が不一致ですが、セッション内容に違いはありませんのでご安心ください。 まずはAuroraの歴史について言及されました。Amazon Auroraは2014年にMySQL互換としてリリースされ、2017年にPostgreSQL互換にも対応しました。その後もAurora Serverless、Limitless Databaseなど様々なサービスを発表しています。 続いてAurora DSQLの紹介です。DSQLはPostgreSQL互換の分散データベースです。2024/12/4セッション当日の時点ではPostgreSQL 16互換としてプレビューされています。クエリの実行結果や実行計画にほとんど違いはないとされ、JOINやAGGREGATE、DML、DDLもサポートされています。また、Ruby on Railsをはじめとしたほとんどのドライバも標準ドキュメントの通りに動くと説明されていました。 しかし、一部未対応の機能も存在します。外部キー制約やPL/pgSQL、地理情報で使用するPostGISなどはサポートしていないようです。特に外部キー制約は分散システム上でオーバーヘッドになりうるため、初期リリースでは対応が見送られるとのことでした。ロードマップ自体には含まれているそうなので、今後のリリースに期待です。 この中でAuroraとDSQLの違いについても取り上げられました。Aurora自体はPostgreSQLやMySQLのオープンソースバージョンを基盤にして作成されています。しかし、DSQLはPostgreSQLのコードを「約半分」利用しつつ、独自に構築した箇所が存在するという説明がありました。 具体的にはFrontend、Query executionはPostgreSQL由来ですが、Transaction control、User storageはDSQL独自のものです。User storageには「ジャーナル」という機能があり、これはDSQLの根底にある技術だと説明されていました。ジャーナルはデータの永続化を確保するための中心要素であり、ストレージへの書き込みとは異なり、書き込みが確認された時点で「永続化」とみなされるとのことでした。この仕組みにより、ストレージ層へのアクセスが大幅に最適化され、システム全体のスループットが向上します。 続く大きなトピックはトランザクション管理の同時実行制御に関する説明です。DSQLでは同時実行制御に楽観的同時実行制御(OCC:Optimistic Concurrency Control)を採用しています。ロックを使用しないOCCを採用することで、クライアントの遅延や長時間実行されるクエリによる他トランザクションへの影響を最小限に抑えるとのことです。 個人的にはこの同時実行制御部分に注意が必要だと感じました。たしかにOCCではPCC(悲観的同時実行制御:Pessimistic Concurrency Control)のように行やテーブルに対してロックを取得しません。しかし、コミット段階には競合のチェックはもちろん行われるため、競合が発生した場合は何らかの対策が必要です。スピーカーもこの点には触れており、アプリケーション側でリトライやアボートのロジックを組み込む必要があります。 その他マルチリージョンにおけるActive/Active構成の仕組みや、分散データベースのコア部分である時刻同期についても解説されました。特に自動復旧メカニズムへの信頼性が強調され、他リージョンとのデータ整合性を確実に維持する仕組みが解説されました。 まとめ DSQLはAuroraと名前を冠しているものの、ある意味Auroraとは別のデータベースとして位置づけられるべきものであると感じました。同時実行制御や各種機能のサポート状況など、まだまだ検証が必要な箇所は多くありますが、今後のリリースに期待が持てる内容でした。どこかで実際の検証を通したアウトプットができればと思います。 Deep dive into Amazon Aurora DSQL and its architecture (DAT427) はじめに EC基盤開発本部SRE部カート決済SREの遠藤です。普段はカート・注文関連のマイクロサービス構築と、DBREとしてデータベースの保守・改善に携わっています。 今回のre:Inventの新発表の1つであるAurora DSQLについて、Deep diveセッションとWorkshopに参加しました。 Aurora DSQLのアーキテクチャ 前述の通りTime Sync Serviceによって正確な時刻同期が可能になりました。これにより分散DBの課題であるノード間のレイテンシの問題がある程度解消されています。その上で非常に興味深い技術選定とアーキテクチャ選定によって、強い整合性と短いレイテンシを実現しています。 Deep diveセッションでは、Aurora DSQLのアーキテクチャについて解説されました。特徴はレイヤー間の独立性です。各レイヤーは特定のワークロードの要求に基づいて、水平方向に独立してスケール可能です。 読み取りが多いワークロードではStorageレプリカが増加し、書き込みが多い場合はJournalというコンポーネントが分割され水平にスケールします。 以下が各コンポーネントの概要です。 1.Router Transaction and Session Routerと紹介されていました。PostgreSQLプロトコルを受け取りPG Bouncerのように接続プーリングを提供します。トランザクションの開始時に各接続を適切な場所にルーティングします。 2.Query Processor リクエストはRouterを通してQuery Processorへ流れます。Query ProcessorはPostgreSQLのデータベースエンジンにあたり、AWS Lambdaの高速なスケーラビリティを可能にしているFirecracker上で稼働しています。 書き込みはQuery Processor内に留まり直接Storageへはアクセスしません。Commit処理は後続のAdjudicatorが担い、Query Processor間の通信も不要なためスケーラビリティが確保されています。 3.Adjudicator Adjudicatorは、書き込み時に複数のトランザクション間の競合を検証し処理の分離性を確保します。Query Processorは複数のAdjudicatorに対して「このトランザクションをコミットしても良いか?」と問い合わせます。問題なければAdjudicatorがJournalへログを書き込みトランザクションを終了します。 分散DBのため、複数のAdjudicatorが協調して動作し、分散合意アルゴリズムを使用してデータの一貫性を保ちます。従来、NewSQLと呼ばれる他の分散DBではPaxos、Raftといったアルゴリズムが主流ですが、DSQLはよりシンプルでパフォーマンスの優れた異なるアルゴリズムを採用しているようです。 ここについてはさらなる情報公開を期待したいです。 4.Journal Journalはデータの永続性と原子性を保証します。ログをStorageへ書き込みます。このJournalは、S3、DynamoDB、Kinesis、Lambdaなど多くのAWSサービスを支えるインフラの一部として10年以上かけて構築されたもののようです。必ず1つのJournalがStorageへ書き込むことで原子性が保たれています。 5.Storage 効率的なデータ検索に特化したストレージエンジンです。役割が分離されているため、永続性や同時実行制御の責任は負いません。 パフォーマンス最適化策の1つとして、本来Query Processor上で行われる一部の処理をStorage側にプッシュダウンできます。WHERE句のフィルター操作などをストレージノードで行うことにより、通信にかかるラウンドトリップとデータ量の削減に繋がりレイテンシが向上します。 Workshop 最終日にはDSQLワークショップが開催されたので参加しました。大変人気で予約できなかったのですが、1時間並んで当日参加できました。 実際にAurora DSQLを構築して以下のようなことを確認しました。 マルチリージョンでの書き込みとOCCによる楽観ロックの挙動 データモデリングのベストプラクティス アプリケーション側のリトライを考慮した実装 DSQLはPostgreSQL互換ですが、FK制約が作成できなかったり、ビューや一時テーブルが使えないなど、制約も大きいです。今後のアップデートに期待したいです。 まとめ Deep diveセッションとWorkshopに参加して、分散DBそのものにとても興味が湧きました。正確な時刻同期など技術の発展に伴い少しずつCAP定理の壁を突破していると実感しました。新しい概念や用語も多く理解するまで苦労しましたが、腰を据えて分散DBのアーキテクチャを学ぶ良い機会になりました。 The Future of Kubernetes on AWS (KUB201) 計測システム部SREの纐纈です。今回2回目のre:Invent参加となりました。私からはAmazon EKS周りのアップデートや今後の開発方針についてのセッションについてまとめたいと思います。 こちらのセッションでは、AWSのKubernetesプロダクト部門統括であるNathan Taber氏が、Kubernetesの最新動向とAmazon EKSの進化について解説しました。 Kubernetesは多くの企業で本番環境に導入されている一方で、その運用は特に大規模環境や分散環境で複雑化しています。AWSはこれに対応するため、Amazon EKSの機能強化を継続的に行っています。 今回のセッションでは以下の新機能や改善点などが紹介されました。 Extended Version Support: Kubernetesバージョンのサポート期間を延長。 Upgrade Insights: アップグレード時に互換性や影響を事前に把握できるツール。 Enhanced Control Plane Observability: コントロールプレーンの状態を可視化し、トラブルシューティングを容易化。 サービス開始以後7年が経過しようとしていますが、より使いやすいプラットフォームになるよう様々な改善が施されていることも示されていました。 画像引用元: セッションの公開資料 P.12 さらに、EKS Auto ModeやEKS Hybrid Nodesといった新機能により、Kubernetes運用の自動化やハイブリッド環境の対応が一層強化されました。 特にEKS Auto Modeに関しては、クラスタのノード管理が不要になり、適切なリソースをAWS側で選択してくれるのでコスト最適化にも繋がります。 画像引用元: セッションの公開資料 P.50 最後に、AWSの今後の方針としては、マルチクラスター管理や開発者体験の向上に注力し、Kubernetesの運用をよりシンプルで効率的にする方針を示しています。Kubernetesの専門知識を持ったチームでなくともEKSを運用できるようにすることで、より多くのユーザーに使ってもらえるプラットフォームにすることを目指しているようです。 画像引用元: セッションの公開資料 P.86 おわりに AWS re:Invent 2024に参加し、Keynoteやセッション、EXPOで多くのことを学べました。また、国内外のエンジニアの方々と交流し、日本では味わえないことを体験できるのが現地参加の醍醐味です! 今回得た知見を社内外に共有しながら、これからもAWSを活用してプロダクトとビジネスの成長に貢献していきます。 ZOZOではAWSが大好きなエンジニアを募集しています。奮ってご応募ください! corp.zozo.com corp.zozo.com
アバター
はじめに こんにちは、ZOZOTOWN企画開発部・企画フロントエンド1ブロックの ゾイ です。 ZOZOTOWNトップページでは、セール訴求や新作アイテム訴求、未出店ブランドの期間限定ポップアップ、著名人コラボなどの企画イベントが毎日何かしら打ち出されています。私はそのプラットフォームとなる企画LPをメインに実装するチームに在籍しています。 本記事では、Figmaのコメントに関する課題を解決するために開発したZOZO専用Figmaウィジェットの実装方法と、それによる業務効率化の成果をご紹介します! 目次 はじめに 目次 背景・課題 事前準備 1. Figmaコンポーネントを作る 2. Figmaのフックを利用する 3. メニューをカスタマイズする 活用ケース 1. 修正依頼 2. アニメーションの指示 3. 仕様確認 ユーザーの反応 終わりに 背景・課題 ZOZOTOWNではFigmaを利用して開発を行なっています。特に企画LPではアニメーションの指示など、デザインデータだけでは伝えられない情報を共有する必要があります。しかし、Slackだと後でスレッドを探すことに時間がかかってしまう、Figmaのコメント機能だとホバーしないと内容を読めないといった問題があったため、確認に時間がかかってしまう課題がありました。そのため、直近では Comment Note というFigmaのウィジェットを用いて共有することが増えましたが、以下の課題が残っていました。 返信するたびにカードを樹形図のように増やす必要があった為、確認に時間がかかる 対応有無が分かりづらい FigmaのMarketplaceでZOZOのデザイナーが必要とする機能を全て持っていて、上記の問題も解決するウィジェットを探したのですが、なかなかありませんでした。調べてみたらFigmaのウィジェットはReactで開発できるらしく、簡単に開発できそうだったので自分で開発することにしました。 今回の記事ではFigmaのウィジェットの開発方法と、デザイナーとエンジニア間の業務効率化のため取り込んだことについて話したいと思います。 ※ 今回の記事で紹介しているFigmaのウィジェットは組織限定で公開しています。 事前準備 基本的にはFigma公式のドキュメントを参考にしながら開発を進めました! www.figma.com FigmaウィジェットはReactを元に作られているため、Reactに慣れているならほぼ学習コストなしで実装できると思います。少し違和感がある点としては、HTML要素ではなくFigmaの要素を作るという概念でしょうか。 UIは「 Comment Note 」というウィジェットを参考にし、ZOZOのデザイナーが求めている機能を追加する形で開発を進めました。 1. Figmaコンポーネントを作る 添付の「修正」ラベルを例として、基本的な要素の作り方から説明したいと思います。 上記をFigma上で作るためのコードは以下のとおりです。 AutoLayout というFigmaのコンポーネント を利用したらFigmaのフレームを作ることができ、各Propsでスタイルを指定できます。例えるとしたらFigmaではコンポーネントがHTMLになり、propsがCSSになる感じでしょうか。 cornerRadius のような例外もありますが、概ねCSSプロパティーの名前が似てるのでなんとなく推測して開発できました。 export const Label = ({ color , text } : Props ) => { return ( < AutoLayout direction = "horizontal" horizontalAlignItems = "center" verticalAlignItems = "center" padding = {{ horizontal : 8 }} height = { 20 } fill = { color } cornerRadius = { 2 } > < Text fill = "#fff" fontSize = { 11 } fontWeight = "bold" > { text } </ Text > </ AutoLayout > ) } FigmaのGUIでも指定している内容が反映されていることが確認できます。実はFigmaにはあまり詳しくなかったので、今回の開発を通して逆に色々学ぶことができてよかったと思います。 2. Figmaのフックを利用する 1でUIを作ることができたので、次は機能の開発に進みたいと思います。今回はウィジェット内のチェックボックスを例として説明したいと思います。こちらのチェックボックスは依頼を受けている人が依頼者に対応有無を伝えるために利用することが多いです。 ReactのuseStateがFigmaでは useSyncedState になるなど、若干命名には違いがありますが、基本的にはReactと同じ概念になります。クリックイベントを通してテキストなどを変えたい場合は、以下のように実装できます。 const { useSyncedState } = widget const [ completed , setCompleted ] = useSyncedState ( 'completed' , false ) export const Checkbox = () => { return ( < AutoLayout // 省略 onClick = {() => setIsCompleted ( ! isCompleted )} > { isCompleted && ( < SVG width = { 15 } height = { 15 } src = { < FilledCheckboxIcon /> } /> )} </ AutoLayout > ) } 3. メニューをカスタマイズする Figmaウィジェットの上に表示されるメニュー(添付)は usePropertyMenu のフックを使って開発できます。1で紹介したラベルのテキストを、「修正」以外にも変更できるようにしたいので、メニューで変えられるようにしたいと思います。 下記のコードはドロップダウンメニューを追加する場合の例です。itemTypeには dropdown 以外にも、 action などにも変更できます。usePropertyMenuの1つ目の引数にはメニューに入れたい配列を、2つ目の引数には各メニューと連携する機能を渡すことができます。 const { usePropertyMenu } = widget usePropertyMenu ( [ { itemType : 'dropdown' , propertyName : 'labelSelector' , tooltip : 'Label selector' , selectedOption : label , options : LABEL_LIST , } , // 省略 ] , ({ propertyName , propertyValue }) => { case 'labelSelector' : setLabel ( propertyValue as string ) break // 省略 default : break } ) 活用ケース 他にも機能はありますが、上記の3つを組み合わせると大体の機能を開発できると思います。開発したウィジェットは実際以下のように活用しています! 1. 修正依頼 デザイナー:修正が発生したらFigmaの該当部分にウィジェットを追加してエンジニアに依頼する エンジニア:対応が終わったらチェックマークをクリックしてデザイナーさんに対応完了のことをお知らせする 2. アニメーションの指示 デザイナー:アニメーションの仕様や参考資料をウィジェットに貼る エンジニア:上記を参考にアニメーションを実装する デザイナー:アニメーションのスピードを調整したい場合、ウィジェットに貼る エンジニア:上記を参考にアニメーションを調整する 3. 仕様確認 デザイナー:デザインデータを作成する エンジニア:気になる部分があったらウィジェットに貼る デザイナー:ウィジェットにて返信する ユーザーの反応 ウィジェットを利用しているデザイナーの方々から以下のようなご意見をいただきました! コメントの返信をカードで一括確認できるようになったので、一目で見てやりとりが分かりやすくなった。 ラベルがZOZOのロゴの色になっていて、こだわりを感じた。 チェックを入れたらコメントノートがフェイドアウトされ、対応有無がわかりやすくなった。 終わりに 本記事ではFigmaウィジェットを紹介しました。ZOZO専用のFigmaウィジェットの導入によって開発フローやコミュニケーションコストを改善できて良かったと思います! 株式会社ZOZOでは、アイデア次第でこんなふうに自由度の高い開発を経験できる環境が整っています! ご興味のある方はぜひ、ご応募お待ちしております! corp.zozo.com
アバター