TECH PLAY

株式会社ZOZO

株式会社ZOZO の技術ブログ

974

はじめに こんにちは、データシステム部データ基盤ブロックの奥山( @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
アバター
はじめに こんにちは。Developer Engagementブロックの @wiroha です。12月15日(日)に、ZOZOにて中高生女子を対象とした体験イベント「 Girls Meet STEM〜ITのお仕事を体験しよう〜 」を開催しました。これは 公益財団法人山田進太郎D&I財団 が実施する「Girls Meet STEM」プログラムの一環です。中高生女子にIT業界の仕事を体験できる実践的な機会を提供し、将来の進路やキャリア選択の幅を広げるための後押しを行うことを目的としています。 当日は約20名の参加者が集まり、プログラミング体験、女性エンジニアとの交流、オフィスツアーを通じて、ITの仕事の魅力を体感しました。本記事では、当日の様子をご紹介します。 イベント概要 日時:2024年12月15日(日)13:00~16:00 会場:ZOZO西千葉本社 対象:中学1年生~高校3年生までの戸籍上または性自認が女性の方 定員:20名 www.shinfdn.org オープニング 今回、ZOZOやZOZOTOWNに興味を持って参加してくださった方が多く、イベントのオープニングでZOZOの事業や働く環境について紹介しました。自分のスマートフォンにZOZOTOWNアプリを入れているという参加者もいて嬉しく思います。 プログラミング体験会 プログラミング体験会では、プログラミングとは何かといった簡単な講義の後、PCを使って実際にプログラミングに取り組みました。パズルゲーム感覚でプログラミングを学べるiPad・Mac用アプリ「 Swift Playgrounds 」を使って、目標までのクリアを目指して挑戦しました。 順番にステージをクリアしながら、条件分岐や繰り返しといったプログラミングの基礎を学びました。サポートするのは4名の女性社員・内定者アルバイトです。密にコミュニケーションを取りながら、真剣にプログラミングに取り組む参加者の姿が印象的でした。 今日学んだ条件分岐や繰り返しがZOZOTOWNアプリ内のどの部分で使われているのかも解説しました。実際の仕事やプログラミングの活用方法についても理解が深まっていればと思います。 トークセッション 次に行われたのは、ZOZOで働く女性エンジニアによるトークセッションです。学生時代の経験やエンジニアになろうと思ったきっかけ、仕事の面白いところや進路選択などについて語りました。 モチベーションは周りの人に喜んでもらえることであったり、自分自身の楽しさであったり、それぞれの意見が出ました。進路選択についても途中で変えた方や文系からエンジニアに進んだ方など、実際の例を聞くことで自分の進路について考えるきっかけになったのではないでしょうか。 質問会 その後はグループに分かれてZOZOスタッフに質問する時間を設けました。参加者からは「エンジニアになるためにできていた方がいい教科は何ですか?」「文系だからIT分野に行けるか不安です」「ZOZOを選んだ理由は何ですか?」といった質問・相談が寄せられました。参加者からは「いろんな選択肢を知ることができて、不安が晴れました」といった声がありました。 オフィスツアー イベントの最後には、西千葉本社のオフィスツアーを実施しました。メッセージが込められたアートや遊び心のある会議室、絨毯の模様や色使いの工夫など、ZOZOらしいデザインが施されたオフィス内を案内しました。クイズなども挟みながら楽しく見学しました。普段はなかなか入ることのできないIT企業の職場環境を直接見ることで、働くイメージを膨らませる機会になったのではないでしょうか。 最後に 参加したみなさんに感想を伺うと、「ゲームのキャラクターが可愛くてまた遊びたい」「エンジニアの仕事はかっこいいなと思った」「周りが女の人ばかりで安心できて良かった」といった声がありました。今回初の試みではありましたが、イベントの目的を達成できていたようで何よりです。 また、サポートした社員たちからは「昔の自分と同じようなことで悩んでいる学生にアドバイスできて良かった」「イベントを通して何か踏み出すきっかけになれてたら嬉しい」といった感想が寄せられました。 ZOZOはこれまでもさまざまな女性活躍推進のための活動に取り組んできており、今後もこうした機会を提供していきたいと考えています。イベントを通じて、少しでも多くの女性がIT業界に興味を持ち、挑戦するきっかけになれば幸いです。
アバター
はじめに こんにちは、EC基盤開発本部SRE部カート決済SREブロックの金田・小松です。普段はSREとしてZOZOTOWNのカート決済機能のリプレイスや運用を担当し、AWSやAkamaiの管理者としても活動しています。 前編では、手動リリース作業が抱える課題を解決するために、GitHub Actionsを活用したリリースプロセス自動化の概要について解説しました。GitHub Actionsによる変更検知、ジョブ制御、結果通知の仕組みを構築することで、手作業の工数削減と安定性向上を実現する一連のフローをご紹介しました。 techblog.zozo.com 後編では、GitHub Actionsと連携してリリース作業を具体的に遂行するためのツールであるAWX Operatorについて、導入の背景や構成、実装の詳細に焦点を当ててご紹介します。AWX Operatorを活用することで、いかに効率的なリリース自動化を実現したのかをお伝えします(図における赤枠部分を中心に説明します)。 目次 はじめに 目次 リリース自動化の中核となるAWX Operator AWXとは? AWX Operatorとは? Playbookとは? ジョブテンプレートとは? AWX Operatorの選定理由 AWX Operatorの導入と構成 オンプレミス環境に配置しない場合の選択肢 今回の構成のメリット 事前準備 構築手順 1. AWX Operatorのインスタンスグループ作成とインスタンス追加 2. インスタンスの関連付け 3. Execution Nodeのバンドルインストール Receptorネットワーク構成について 接続状況の確認 デバッグとトラブルシューティングのTips ジョブテンプレートのコード管理 使用例:ジョブテンプレートの同期プロセス(GitHub Actionsを使用) AWX APIを利用したGitHub Actionsとの連携 APIリクエストの基本構造 ジョブの実行リクエスト ジョブの監視 まとめ 主要なポイント 期待される成果 さいごに リリース自動化の中核となるAWX Operator リリース自動化の話に入る前に、まずAWX Operatorに関連する基本用語について整理します。 AWXとは? AWXは、AnsibleのWebベースのUIやAPIを提供するオープンソースプロジェクトで、Ansibleの自動化タスクの管理、スケジュール、実行が可能です。 github.com 既存のZOZOTOWNオンプレミス環境でもAWXサーバが存在しており、オンプレミス環境における運用作業を自動化しています。例えば、以下のような様々な運用タスクの自動化に活用されています。 数百台のクライアントやサーバーへのエージェント一括インストール 定期バッチ処理の自動停止・再開 Windows Updateの自動適用 ネットワーク疎通確認 IISアプリケーションプールのリサイクルによるWebサービスの可用性維持など これらの作業を自動化することで、人的エラーを最小限に抑えながら、安定した運用を実現しています。既存AWXの運用については、以下のブログで紹介されているので、ぜひこちらもご参照ください。 techblog.zozo.com AWX Operatorとは? AWX Operator は、Kubernetes環境でAWXをデプロイ・管理するためのツールです。 github.com Kubernetesは、コンテナ化されたアプリケーションの管理を自動化するプラットフォームです。AWX Operatorを使うことで、Kubernetesクラスタ内にAWXを簡単にインストールし、設定やアップデートを効率的に行うことができます。また、デプロイ作業の自動化やサーバー設定の適用といったタスクを、Playbookで効率的に管理・実行が可能です。 Playbookとは? Playbookは、Ansibleが実行する一連のタスクを定義した YAML 形式のファイルです。例えば、以下のような操作を自動化できます。 サーバーの設定(例:WebサーバーやDBサーバーの構築) アプリケーションのデプロイ(例:アプリのインストールと設定) ネットワーク機器の設定(例:ルータやスイッチの構成変更) 以下は、Webサーバーを構築するPlaybookの例です。 --- - name : Install and configure Apache hosts : web_servers tasks : - name : Install Apache yum : name : httpd state : present - name : Copy configuration template : src : /path/to/httpd.conf.j2 dest : /etc/httpd/conf/httpd.conf - name : Restart Apache service : name : httpd state : restarted Playbookは「何を実行するか」を細かく順序立てて記載でき、リリースフローの中核を担います。 ジョブテンプレートとは? ジョブテンプレートは、AWXでPlaybookを実行するための「設定パッケージ」のようなものです。具体的には以下の要素を含みます。 Playbook :実行するタスクの定義。 Inventory :タスクを適用するサーバーやホストの一覧。 Extra Vars :実行時に渡すオプションのパラメータ(例:環境やリリース条件)。 Execution Node :Playbookを実行する対象ノード。 ジョブテンプレートを使うことで、「どのサーバーに、どのPlaybookを、どのパラメータで実行するか」を簡単に指定できます。そのため、リリース作業の自動化が容易になります。 AWX Operatorの選定理由 本プロジェクトで AWX Operator を選定した主な理由は以下の通りです。 既存のAWX運用ノウハウを活用 既にオンプレミスでAWXを利用しており、運用に関する知識や技術をそのまま活用できるため、スムーズに移行が可能であること。 Ansible Playbookの再利用が容易 従来のPlaybookをほぼそのままKubernetes環境に適用できるため、導入コストを低減し、既存の運用プロセスを簡単に移行できる。 Kubernetes環境でのデプロイ・管理が容易 弊社のインフラでは多くのリソースがコード化されており、EKSクラスタを使用しているため、AWX Operatorの導入により、インフラとアプリケーションの管理を一元化できるメリットがあった。 これらの理由から、AWX Operatorを選定し、GitHub Actionsと統合することで効率的なリリースフローを実現しています。 AWX Operatorの導入と構成 本プロジェクトでは、AWX OperatorをAmazon EKS上で運用しつつ、ジョブ実行ノード(以降Execution Nodeと称します)をオンプレミス環境に配置しています。この構成を採用した主な理由は、本プロジェクトのリリース対象がオンプレミス環境のサーバであるケースが大半を占めているためです。 オンプレミス環境に配置しない場合の選択肢 ジョブ実行ノードをオンプレミス環境に配置しない場合、AWX Operatorとジョブ実行ノードの両方をEKS上で運用できます。ただし、この場合、リリース対象がオンプレミス環境であることを考慮すると、以下のデメリットが懸念されます。 ネットワーク遅延 クラウド上のジョブ実行ノードがオンプレミスリソースにアクセスする際、クラウドとオンプレミス間で頻繁に通信が発生します。この通信に伴い、ネットワーク遅延がリリース全体の速度低下につながる可能性があります。 データ転送量の増大とコスト ジョブの実行に必要なデータ(例:ファイル、設定情報)がクラウドとオンプレミス間で往復するため、ネットワーク帯域を圧迫するリスクがあります。また、クラウドプロバイダーの通信料金(特にアウトバウンド通信料金)も増加し、運用コスト増につながる可能性があります。 今回の構成のメリット オンプレミス環境のリソースに対するリリースが大半を占めるため、ジョブ実行ノードをオンプレミス環境に配置する構成を採用することで、以下のメリットが得られます。 ネットワーク遅延の最小化 ジョブがオンプレミス環境内で直接実行されるため、ジョブの実行に必要な主要な通信が内部ネットワークで行われます。これにより、クラウドとオンプレミス間の往復通信が最小限となり、リリース速度が向上します。 データ転送量の削減とコスト効率の向上 ジョブの処理やデータのやり取りが主にオンプレミス環境内で完結するため、クラウドとの間で発生するデータ転送量が削減されます。これにより、ネットワーク帯域の負担を軽減すると同時に、クラウドプロバイダーの通信料金が抑えられるため、コスト効率が向上します。 以上のように、オンプレミスリソースに対する高速かつ効率的なリリースが可能になるため、本構成を採用しました。 AWX Operatorの導入方法については、 インストールガイド を参照してください。ここでは、 Execution Node をAWXに登録する手順をご紹介します。 事前準備 Execution Node では Ansible が必要です。Ansible公式のインストールガイドに従い、インストールします。 docs.ansible.com 構築手順 以下に、Execution Nodeの構成とAWX Operatorの設定手順を説明します。 1. AWX Operatorのインスタンスグループ作成とインスタンス追加 Webコンソールの「インスタンスグループ」メニューから、「インスタンスグループの追加」をクリックし、必要な項目を入力します。 名前 :インスタンスグループの名前を設定します(例:execution-group)。 ポリシーインスタンスの最小値 :インスタンスグループ内で最低限必要なインスタンス数を設定します。 ポリシーインスタンスの割合 :必要なインスタンス数を割合で指定できます。 Max concurrent jobs :同時に実行可能なジョブ数の上限を設定します。 Max forks :ジョブごとに使用できる最大フォーク数を設定します。 Webコンソールの「インスタンス」メニューから「追加」ボタンをクリックし、インスタンスを追加します。以下のポイントに従って設定します。 ホスト名 :Execution Nodeの完全修飾ドメイン名(FQDN)またはIPアドレスを指定します。 リスナーポート : 27199 を設定します。これは Receptor が使用するポートです。 インスタンスタイプ : Execution を選択します。 Peers from control nodes :制御ノードから自動的にピアリングする場合はこのオプションを有効にします。 2. インスタンスの関連付け 作成したインスタンスグループを選択した状態で、インスタンスタブより、追加したインスタンスを関連付けます。 3. Execution Nodeのバンドルインストール Execution Nodeでは、AWX Operatorがジョブを分散実行する基盤となる Receptorサービス を利用しています。Receptorは、AWXのコントローラーとExecution Node間の通信を担当するメッセージングシステムであり、ジョブの実行を効率的に分散・管理する重要な役割を果たします。以下に、Receptorを含むExecution Nodeのインストール手順を説明します。 バンドルのダウンロード :AWXコンソールの「インスタンス」詳細ページで、バンドルのダウンロードボタンをクリックし、必要なファイルを取得します。このバンドルにはTLS証明書、Receptor設定ファイルが含まれます。 インストール :ダウンロードしたファイルをExecution Nodeへ転送し、以下のコマンドで展開してインストールを実行します。 tar -xzf [ バンドルファイル ] ansible-galaxy collection install -r requirements.yml Tips :トラブルが発生した場合、Ansibleのフォーラムに解決策が掲載されている場合があります。 forum.ansible.com 以上の手順で、AWX OperatorにExecution Nodeが登録され、AWX上から Execution Node 経由でジョブを実行できるようになります。これにより、オンプレミス環境とAWS EKSを連携させたリリース自動化の基盤が構築され、システム全体の操作効率が向上します。 Receptorネットワーク構成について Receptorは、Execution NodeとAWXのコントローラー間で通信するための分散ネットワークを構成します。通常、以下の設定内容は前述のバンドルインストールで自動的に生成されるため、手動で編集する必要はありませんが、参考までに設定例を紹介します。 # Receptorノードの基本設定。ノードIDとピアとして許可するノードIDを指定します。 - node : id : <node-id> # このノードのID(任意の一意な名前) allowedpeers : <peer-node-ids> # 接続を許可するピアノードIDのリスト # ログレベルの設定。デフォルトはinfo、デバッグ用途にはdebugに設定可能。 - log-level : info # ジョブの実行設定。ansible-runnerを使用してジョブを実行します。 - work-command : worktype : ansible-runner # 実行するワークの種類 command : ansible-runner # 実行コマンド params : worker # ワーカーとして機能する設定 # サーバー用のTLS設定。接続に使用する証明書とキーのパスを指定します。 - tls-server : name : default filename : /etc/receptor/tls/tls.crt # TLS証明書ファイル key : /etc/receptor/tls/tls.key # TLSキー # クライアント用のTLS設定。接続先の証明書を検証するためのCA証明書を指定します。 - tls-client : name : receptor-client rootcas : /etc/receptor/tls/ca.crt # ルートCA証明書 # UDP接続の設定。ピアノードとの通信に使用するホストとポートを指定します。 - connection : type : udp peer : <peer-ip>:27199 # ピアのIPアドレスとポート # TCP接続の設定。UDPと同様にピアノードとの通信に使用します。 - connection : type : tcp peer : <peer-ip>:27199 # ピアのIPアドレスとポート 接続状況の確認 設定後、以下のコマンドで他ノードとの接続状態を確認できます。 sudo receptorctl status Receptorを用いることで、ジョブ実行の分散性や拡張性が向上し、大規模環境でも安定したリリースを実現します。また、ピアノード間の通信設定を最適化することで、効率的なネットワーク構築が可能です。 デバッグとトラブルシューティングのTips ログレベルの調整 :デフォルトの log-level: info では一般的なログが表示されますが、トラブルシューティングが必要な場合は debug に変更すると詳細なログを取得できます。 ノード再起動時の接続確認 :ノードの再起動後、 sudo receptorctl ping <peer-node-id> を使って個別に接続が正常かテストできます。 証明書の確認 :Receptorで使用するTLS証明書に問題がある場合は、 openssl コマンドを使って証明書の有効期限や接続テストを行うと便利です。 openssl s_client -connect < peer-ip > :27199 ジョブテンプレートのコード管理 AWXのCLI(Command Line Interface)を利用したジョブテンプレートとインベントリのコード管理を実施しており、GitリポジトリとAWX間での同期を保つようにしています。 docs.ansible.com 主要なコマンド ジョブテンプレートの作成 : awx job import コマンドで新規テンプレートを自動反映。 変更のエクスポート : awx export コマンドで変更を検出し、Gitと同期。 使用例:ジョブテンプレートの同期プロセス(GitHub Actionsを使用) 以下のようなGitHub Actions Workflowを設定し、AWXのジョブテンプレート変更をGitリポジトリに反映させるプルリクエストを自動で作成しています。 name : Sync AWX Job Templates on : schedule : - cron : '0 0 * * *' # 毎日0時に実行 workflow_dispatch : # 手動での実行トリガー jobs : sync-job-templates : runs-on : ubuntu-latest steps : - name : Checkout repository uses : actions/checkout@v4 # リポジトリのチェックアウト。最新のテンプレートと比較するために必要です。 - name : Install dependencies run : | sudo apt-get update sudo apt-get install -y jq python3-pip pip3 install awxkit # 必要な依存ツールとパッケージのインストールを行います。 - name : Export job templates run : | mkdir -p job_templates templates=$(awx job_templates list -f json | jq -r '.results[] | {id: .id, name: .name}' ) for template in $(echo "$templates" | jq -r '.name' ); do echo "Exporting template: $template" template_id=$(echo "$templates" | jq -r --arg name "$template" 'select(.name == $name) | .id' ) ./scripts/awx/export_job_template.sh "$template_id" done echo "Templates exported" # AWXから各ジョブテンプレートをエクスポートし、リポジトリ内のjob_templatesフォルダに保存します。 - name : Create Pull Request uses : peter-evans/create-pull-request@v6 with : commit-message : "Update AWX Job Templates" branch : "update-job-templates" title : "Update AWX Job Templates" body : "This PR updates the AWX job templates and ensures they are in sync with the latest operational settings." labels : "automated pr" base : "main" delete-branch : true # エクスポートしたテンプレートをもとにGitHubで自動プルリクエストを作成します。 リポジトリのチェックアウト 最初にリポジトリをクローンし、現在のジョブテンプレートと最新テンプレートとの差分を取得できるようにします。 依存関係のインストール awxkit などの依存ツールをインストールし、AWXからのデータ取得が可能な状態にします。 テンプレートのエクスポート AWXからジョブテンプレートをエクスポートし、各テンプレートのIDと名前を取得してローカルフォルダに保存します。 プルリクエストの作成 エクスポートしたテンプレートを基に、リポジトリに自動的にプルリクエストを作成し、更新をチームでレビューできるようにします。 このようにして、テンプレートの更新はプルリクエストを通じてレビューされ、AWXの設定内容がGitリポジトリに同期されるため、一貫性と追跡性を保証しながら変更内容を管理できます。 AWX APIを利用したGitHub Actionsとの連携 GitHub ActionsとAWX Operatorの連携は、AWXのREST APIを通じて行います。以下の図は、GitHub Actionsでのスクリプト実行からAWX APIを通じたジョブ実行、そしてリリースプロセス全体の流れを示しています。 この図をもとに、GitHub Actionsからリリース作業を開始し、ジョブを実行・監視するまでの一連の流れを解説します。 APIリクエストの基本構造 まず、APIリクエストを送信するためのcurl構造を作成します。以下の関数で、POSTやGETリクエストをAPI送信できるようにしています。 #!/usr/bin/env bash function call_awx_api() { token = " $1 " base_url = " $2 " method = " $3 " path = " $4 " if [ " ${method} " == " POST " ]; then req = " $5 " response = $( curl -s -X POST \ -H " Authorization: Bearer ${token} " \ -H " Content-Type: application/json " \ -d " ${req} " \ " ${base_url} /api/v2/ ${path} " ) echo " ${response} " return fi response = $( curl -s -X " ${method} " \ -H " Authorization: Bearer ${token} " \ -H " Content-Type: application/json " \ " ${base_url} /api/v2/ ${path} " ) echo " ${response} " return } この関数で利用している各パラメータの役割について、以下に簡単に説明します。 token :APIリクエストで認証するためのトークンです。本記事で紹介するプロジェクトでは、このリリース用に作成したAWXユーザーのトークンを使用しています。トークンの作成方法は、対象のAWXユーザーでログイン後、 ユーザー > AWXユーザー > トークンタブ にて作成可能です。トークンベース認証の詳細については以下のドキュメントをご参照ください。 docs.ansible.com このトークンはGitHubのSecretsに格納しており、GitHub Actionsから参照しています。 base_url :AWX OperatorのServiceで指定しているドメイン名です。APIエンドポイントのベースURLとして使用され、AWXサーバーのアドレスを指定します。 method :APIリクエストのHTTPメソッドを指定します。通常はGETまたはPOSTを指定し、POSTの場合は追加のデータ( req )が送信されます。 path :api/v2以降のパスを指定します。たとえば、job_templates/ /launchでジョブテンプレートの起動や、jobs/<job_id>でジョブのステータスを取得できます。APIの詳細についてはAWXのドキュメントを参照してください。 ansible.readthedocs.io ジョブの実行リクエスト 次に、GitHub ActionsからAWXのジョブを起動するためのリクエストを構成します。以下のように、リクエストボディで extra_vars を利用して、ジョブ実行に必要な変数(例:commit hash、GitHubトークン、リリースの順序)を渡します。これにより、後続のスクリプトでこれらの変数を利用できます。また、 limit パラメータでリリース対象のサーバーを指定して、ジョブが実行される範囲を制限しています。 # GitHub ActionsのワークフローからAWX OperatorのAPIを利用してジョブ実行するスクリプト例 requestbody = $( cat << EOS { "extra_vars": { "commit_hash": " ${ASP_COMMIT_HASH} ", "github_token": " ${ASP_GITHUB_TOKEN} " }, "limit": " ${limitation} ", "inventory": " ${inventory} ", "job_type": "run" } EOS ) response = $( call_awx_api " ${API_TOKEN} " " ${AWX_BASE_URL} " " POST " " job_templates/<id>/launch/ " " ${requestbody} " ) job_id = $( echo " ${response} " | jq -r ' .job ' ) echo " Job ID: ${job_id} " ここで行っている処理は以下の通りです。 extra_vars :後続のスクリプトで使用する変数(例:commit hash、GitHubトークンなど)を渡しています。 limit :指定されたサーバー範囲内でジョブを実行し、リリース対象のサーバーを制限します。 inventory :AWX上で管理されている特定のインベントリを指定します。 job_type :実行ジョブの種類を指定します。ここでは通常の実行を示すrunを設定しています。 ジョブの監視 ジョブの実行が開始されると、ジョブIDを取得し、そのステータスを定期的に監視します。以下のループで jq を使ってジョブのステータスを取得し、進行状況をチェックしています。ジョブが pending や running 以外の状態(成功や失敗など)になるまで監視し、完了したタイミングでループを終了します。 while true; do response = $( call_awx_api " ${API_TOKEN} " " ${AWX_BASE_URL} " " GET " " jobs/ ${job_id} / " ) job_status = $( echo " ${response} " | jq -r ' .status ' ) echo " Job Status: ${job_status} " if [ " ${job_status} " != " pending " ] && [ " ${job_status} " != " running " ]; then break fi sleep 5 done 上記の構成で、ジョブが完了するまでの間、定期的にジョブの進行状況を確認できるようにしています。ジョブの進行状況の監視が完了すれば、一連のリリース作業は終了となります。 この仕組みにより、GitHub Actionsを起点としたリリースフローが安定して実現し、手動作業による負担やリスクを大幅に軽減できました。 まとめ 後編では、GitHub Actionsと連携してリリース作業を具体的に遂行するためのツールであるAWX Operatorについて、その導入背景や構成、実装の詳細に焦点を当てて解説しました。本記事で取り上げた主要なポイントは以下の通りです。 主要なポイント AWX Operatorの導入と活用 Kubernetes環境での簡易なAWXデプロイを実現し、クラウドとオンプレミスを統合した分散リリース環境を構築。 Receptorを利用して、安定したジョブ分散と効率的な通信を可能に。 ジョブテンプレートの構成管理 Playbook、Inventory、Extra Varsを活用し、柔軟かつ一貫したリリースフローを管理。 GitHub Actionsとの連携によるリリースフローの自動化 REST APIを利用し、ジョブの実行・進行状況の監視・エラー検知を自動化。 手作業を排除し、迅速で信頼性の高いリリースを実現。 これにより、リリース作業の自動化による運用効率の向上が期待できます。リリース自動化の導入はまだ始まったばかりですが、すでに以下のような成果が期待されています。 期待される成果 リリースの安定化 自動化されたプロセスにより、手動エラーは排除され、リリースの一貫性の向上が期待されます。 エラーの削減 段階的リリースごとにエラーをリアルタイムで監視し、迅速に対処することで、エラーによる影響を最小限に抑えることが可能です。 リリース速度の向上 リリース作業の自動化により、全体の工数削減と迅速なリリースが見込まれます。 さいごに 本記事では、前編と後編の2部構成で、GitHub ActionsとAWX Operatorを活用したリリース自動化の取り組みをご紹介しました。従来の手動リリースから脱却し、効率化と安定性を実現することで、工数削減やリリースリスクの低減を目指しています。 リリース自動化は継続的な改善が必要な分野ですが、本取り組みを通じて、さらなる最適化や監視機能の強化を進める予定です。この記事が、皆さんのシステム運用やリリース自動化の参考になれば幸いです。 私たちZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。興味をお持ちの方は、ぜひ以下のリンクからご応募ください。 hrmos.co
アバター
はじめに こんにちは、EC基盤開発本部SRE部カート決済SREブロックの金田・小松です。普段はSREとしてZOZOTOWNのカート決済機能のリプレイスや運用を担当し、AWSやAkamaiの管理者としても活動しています。 本記事では、前編と後編に分けて、Classic ASPの手動リリースをGitHub ActionsとAWX Operatorを活用して自動化したプロジェクトについてご紹介します。手動で行っていたリリース手順を自動化することで、効率化と安定性をどのように実現したか、そのアプローチをお伝えします。 前編では、Classic ASPの手動リリース作業が抱える課題を解決するためにGitHub Actionsを活用したリリースプロセス自動化の概要について解説します。 後編では、GitHub Actionsと連携してリリース作業を具体的に遂行するためのツールであるAWX Operatorについて、導入の背景や構成、実装の詳細に焦点を当ててご紹介します。 目次 はじめに 目次 リプレイス前の環境とリプレイスプロジェクトについて 手動リリースの課題 Git管理とリリース手順の不一致 リリース作業の信頼性と一貫性の不足 リリース自動化へのアプローチ アーキテクチャ概要 GitHub Actionsの役割 AWX Operatorの役割 アーキテクチャ全体図 リリース方式: 25%ずつのカナリアリリース GitHub Actions 対象サーバー群の管理方法 ReleaseReview リリース時のサービス監視 ワークフローの分割 排他制御 lock処理の実装方式 lock処理の高速化 各サーバー上での処理について GitHub Apps経由のトークン取得 インストールアクセストークン ユーザアクセストークン デプロイ処理 トークンを使ったremote url設定 マージコミットにswitchしての差分取得とコミットされた状態に応じた処理 さいごに リプレイス前の環境とリプレイスプロジェクトについて ZOZOTOWNは、2004年12月にサービスを開始して以降、基本的なアーキテクチャは変えずにオンプレミス環境で動くモノリシックなシステムとして運用されてきました。リプレイス前のアプリケーションは、Classic ASPで構築されオンプレミス環境のWindowsサーバー上で動作しています。 しかし、サービスの拡大に伴い、モノリシックなシステムの運用や開発に課題が生じるようになりました。そこで2017年から、レガシー化したシステムの課題を解決するためにZOZOTOWNのマイクロサービス化をするためのリプレイスプロジェクトを進めています。リプレイス後のマイクロサービスでは、IaCやCI/CDの導入により、開発効率や運用効率の向上を図っています。 詳しくは以下のテックブログをご覧ください。 techblog.zozo.com 手動リリースの課題 ZOZOTOWNのリリース作業は、リプレイスされマイクロサービス化したシステムを除き、以下の手順を手作業で実施していました。 リリース対象ファイルの準備 バックアップの取得 リリース手順の準備 ファイルをどの順番でリリースするかの検討 WebDAVを利用したファイルのアップロードによるリリース この手作業のプロセスにより、リリースごとに多くの工数がかかり、作業負担が増大する課題を抱えていました。 また、以下のような問題も発生していました。 Git管理とリリース手順の不一致 リリース後に対象ファイルをmasterブランチにマージする運用のため、同時期に複数案件のリリースをする際、共通ファイルのコンフリクトが発生する場合もありました。この場合、リリース担当者が最新ファイルを手元でマージし直す必要があり、リリース担当者間の調整やコミュニケーションが求められ、工数増加やコミュニケーションミスが障害リスクを生む要因となっていました。 リリース作業の信頼性と一貫性の不足 手作業によるリリースは操作ミスが発生しやすく、リリースミスや障害リスクの温床となり、リリース担当者の精神的な負担を増大させていました。 これらの課題を解消するためには、リリースプロセスの自動化が不可欠です。自動化により作業負担が軽減され、ミスのリスクが抑えられることで、リリース作業の信頼性が向上し、開発および運用チームの負担も軽減されます。 さらに、Git管理状態とサーバーにリリースされている状態を常に一致させるため、GitOpsの概念を取り入れたリリース自動化を進めました。これにより、リリースの一貫性が担保され、ミスの削減とプロセスの効率化が期待できます。 リリース自動化へのアプローチ アーキテクチャ概要 これらの課題を解消するため、リリースプロセスの自動化にGitHub ActionsとAWX Operatorを採用しました。本節では、それぞれのツールが果たす役割について説明します。 GitHub Actionsの役割 GitHub Actionsは、リリースプロセス全体を制御する役割を担っています。主に以下の3つのタスクを実行します。 変更検知 GitHubリポジトリにコード変更(マージ)があると、リリースプロセスをトリガーして開始します。 ジョブ制御 リリースの進行状況に応じて、AWX Operatorへジョブ実行リクエストを送信し、次のステップを制御します。 結果収集と通知 AWX Operatorから返却された実行結果を確認し、Slackを通じてリリース進行状況を通知します。 GitHub Actionsは、リポジトリとリリース管理のハブとして機能し、全体のプロセスを統括します。 AWX Operatorの役割 AWX Operatorは、GitHub ActionsとAPIを介して連携し、リリース作業の具体的な実行を担当します。以下が主な役割です。 サーバー操作 対象サーバーに対して、必要なファイル配置やIISリサイクル、ロードバランサーを操作します。 リクエスト処理 GitHub Actionsから送信されたリリース条件やジョブパラメータをもとに、安全かつ正確に作業を実施します。 実行結果の返却 ジョブの実行結果や状況をGitHub Actionsに返却し、次のステップに移行するための判断材料を提供します。 アーキテクチャ全体図 以下の図は、GitHub ActionsとAWX Operatorを連携させたリリースフローを示しています。この仕組みにより、リポジトリの変更を起点としてリリース作業を自動化し、一貫性と効率性を実現しています。 GitHub Actionsがリポジトリの変更を検知してトリガーを発火。 リポジトリの差分情報やリリース条件をもとに、AWX Operatorへジョブ実行リクエストを送信。 AWX Operatorがサーバーグループごとに作業を実施。 実行結果をGitHub Actionsに返却し、Slackを通じて通知。 リリース方式: 25%ずつのカナリアリリース リリースを自動化するにあたり、すべてのWebサーバーを4つのグループに分割し、25%ずつロードバランサーから切り離した状態でファイルを更新する方法を採用しました。リリースが完了したサーバーは順次ロードバランサーに復帰させることで、段階的にサービスに影響を与えないリリースを実現しています。 この方式により、従来のリリースで必要だったファイルのリリース順を考慮する手間が不要になり、安全かつ効率的なリリースが可能です。また、25%ずつのカナリアリリースをすることで、万が一問題が発生した場合の影響範囲を限定でき、リスクを最小限に抑えられます。 具体的には、サーバーを4つのグループに分け、各グループを順番にリリース対象とした上で、以下の手順を実施します。 STEP1:リリース対象の1グループのサーバーをロードバランサーから切り離し、外部トラフィックの影響を遮断します。 STEP2:切り離したサーバーでリリース作業を実施し、IISリサイクルを行います。 STEP3:作業が完了したサーバーをロードバランサーに再接続し、トラフィックを復旧させます。 この手順を各グループで順番に繰り返します。こうすることで、リリース中のリスクとダウンタイムを最小限に抑えつつ、全体の安定性を維持できます。以降で、リリース自動化の具体的な仕組みについて、GitHub ActionsやAWX Operatorの役割を中心に詳しく説明していきます。 GitHub Actions 本記事で紹介するリリースの自動化では、masterブランチへのマージをトリガーにGitHub Actionsのワークフローを起動して本番のサーバーにファイルをリリースしています。 また25%ずつのカナリアリリースを実現するため、GitHub Actionsでは様々な工夫をしています。 対象サーバー群の管理方法 リリース対象となるサーバー群はyamlファイルで管理しています。 下記はサーバー群の管理ファイルのフォーマットです。 <env> : - type : サーバーグループのタイプ servergroups : - <server role> : lb_info : - ロードバランサーを操作するための情報 . . . limitations : - 後述するAWXに渡すhost情報 項目 説明 env 環境を指定します type サーバーグループのタイプを指定します。 ZOZOTOWNではonpremissのサーバーに加えてEC2のサーバーを増設することがあり、そのための設定です。 servergroups サーバーグループの設定をリストとして保持します。 servergroupsリストの1要素ごとに直列でReleaseReview → LB Down → デプロイ → LB Up 操作を行います server role サーバーの役割を任意の文字列で指定します。 同じservergroup内のserver role分、並列でデプロイ処理が実行されます lb_info ロードバランサー情報の設定を含むリスト limitations 対象サーバーの制限条件を指定します。 AWX用のサーバーパラメータをリスト形式で指定 上記のyamlファイルではリリース対象のサーバー情報の他にロードバランサーを操作するための情報も記載しています。このyamlファイルをworkflowで参照し、servergroups毎にデプロイ処理を行っています。 ReleaseReview 25%ずつのカナリアリリースを実施するにあたり、1つのグループのリリース完了後、即座に次のグループのリリースを進めてしまうとエラー発生の有無を確認する間がありません。各リリースの合間に確認時間を設けないと問題が発生した際にリリースを中断するのも難しくなってしまいます。 そこで本記事の自動リリースではグループごとのリリース処理を実行する前に、 GitHub Environments を利用した承認stepを取り入れています。 GitHub EnvironmentsはGitHub ActionsのJobに設定でき、 Protection Rule を設定できます。設定できるProtection Ruleにはいくつか種類がありますが、Required reviewersを設定すると必須レビュアーからの承認があるまでジョブの実行を止めることができます。 Required reviewersの承認はリリースの担当者が承認できるように設定しています。リリースの担当者は1グループのリリースが完了したら、リリースしたグループにエラーがないことを確認後、次のグループのリリース承認を行います。 ReleaseReviewについてはマイクロサービスのCI/CDを紹介したブログ記事でも詳しく説明していますので、ぜひご参照ください。 techblog.zozo.com リリース時のサービス監視 カナリアリリースによるリリースでは、リリース起因でのエラーが発生していないかを確認することが重要です。本記事での自動リリースではサーバー群を4つのグループに分割して順番にリリースしています。そこでグループ別にエラー発生の有無、エラー内容の確認、CPU負荷の確認ができるダッシュボードを用意して、リリースで問題が発生してないかの確認ができるようにしています。 リリースプロセスをよりよく把握するため、Splunkのダッシュボードを導入しています。リリース担当者は、ダッシュボードを使って、リリースされる各サーバーグループの状況をリアルタイムでチェックできます。段階的リリース(N%リリース)の際には、特にエラーやCPU使用率などのシステム負荷を詳細に監視できるようにしています。 さらに、500エラーが発生した場合、ダッシュボードから直接Gitの該当コード行へリンクできる機能を備えています。これにより、エラーの原因を素早く特定し、迅速な対応を可能にしています。 このダッシュボードはIaC化されており、SplunkのIaC化については、以前のブログ記事で詳しく紹介していますので、そちらもぜひご参照ください。 techblog.zozo.com ワークフローの分割 GitHub Actionsでは、柔軟なループ構文(例えば、forループ)のネイティブサポートがありません。特に複数回実行の必要なジョブやステップがある場合、同一ワークフロー内でループを実行できないことが大きな制約となります。本記事の自動化においても、対象サーバー群を設定ファイルで管理できるようにしたものの、GitHub Actionsでは繰り返し処理を使えないため工夫が必要でした。 この制約を解決する方法として、ワークフローの分割とBashスクリプトの組み合わせで柔軟な繰り返し処理をGitHub Actionsでも実現できるようにしました。 この手法では、まず親となるワークフローからBashスクリプトでforループを構築し、 gh workflow run コマンドで分割した子ワークフローを繰り返し実行します。子ワークフローとして実行したいワークフローは、 gh workflow run で実行できるように workflow_dispatch をトリガーとして設定しておきます。 gh workflow run < 実行したいワークフローファイルのpath > -f hoge =fuga --repo < 対象リポジトリ > --ref github.base_ref さらに、実行したワークフローのRunIDを追跡するため、 gh run list コマンドで取得したURLから実行したワークフローのRunIDを抽出します。 gh run list --workflow =< ワークフローファイル名 > --limit 1 --json url --repo < 対象リポジトリ名 > | jq -r ' .[0].url ' 取得したRunIDを使用し、 gh run watch コマンドで指定したワークフローの終了を待つことも可能です。例えば、以下のようにして指定のRunIDの実行が終了するまで待機し、その結果を取得できます。 gh run watch < runid > -i 30 --exit-status --repo < 対象リポジトリ名 > これらのコマンドをforループで繰り返し実行することで、柔軟な繰り返し処理を実装しています。 for i in < 繰り返す回数 > ; do gh workflow run < 実行したいワークフローファイルのpath > -f hoge =fuga --repo < 対象リポジトリ > --ref github.base_ref # ワークフローが実行中になるのを待つ sleep 10 # RunIDの取得 runurl = $( gh run list --workflow =< ワークフローファイル名 > --limit 1 --json url --repo < 対象リポジトリ名 > | jq -r ' .[0].url ' ) runId = $( basename " ${runUrl} " ) # gh run watchで終了待機と結果取得を行う gh run watch " ${runid} " -i 30 --exit-status --repo < 対象リポジトリ名 > done 排他制御 本記事の自動リリースでは、ワークフローが並列で実行されないように独自の排他制御を実装しています。排他制御が必要な理由としては以下が挙げられます。 複数のグループに分割してグループ毎にロードバランサーから下げてファイルを更新する方式のため、並列実行してしまうと複数のグループを本番から下げた状況になり、本番のサーバー数不足が想定される GitHub Actionsには concurrency というワークフローを直列で実行させるための設定がありますが、以下の理由で本記事の自動リリースでは採用できないため独自の排他制御を実装しています。 concurrencyは、 最大 1 つの実行ジョブと 1 つの保留中のジョブが存在できる とドキュメントに明記されており、3つ以上実行されると待機中のワークフローが古いものから順にキャンセルされる。 本記事の自動リリースでは、Mergeコミットの差分のみを展開する方式(※ 詳細は後述)としているため、ワークフローの実行がキャンセルされると展開されない差分ができてしまう。 lock処理の実装方式 GitHub Actionsで排他制御を実現するには、どのようにlockオブジェクトを実装するかが重要です。通常のシステムのようにデータベースやファイルシステムを利用できないため、独自の手法が必要です。 本記事で紹介する排他制御では、特定名称のGitブランチをlockオブジェクトとして扱い、そのブランチがGitHub上のワークフロー導入リポジトリに存在するかどうかで排他状態を判定しています。 具体的には、lockを取得したいワークフローがGitHubへブランチのPushを試み、Pushが成功すればlock獲得、失敗すれば他のワークフローがlockを保持していると判断します。これは、GitHub上でのブランチPushがアトミック操作であることを利用した方法です。 また、lockの解放は、lockブランチの削除によって行います。このシンプルな方式により、複雑な管理リソースを必要とせず、GitHub Actions内で効率的かつ確実な排他制御を実現できます。 lock処理の高速化 自動リリースを導入するリポジトリでは、長い履歴と多数の管理ファイルが存在するため、lock管理用のブランチ作成やPushに時間を要することが予想されます。そこで、効率的なリリースフローを実現するため、lock用ブランチはリポジトリの既存ブランチから派生させず、新たに空のリポジトリを git init でワークフロー上に作成する方法を採用しました。この空リポジトリ上で、lock状態を管理するためのファイルのみをcommitし、lock用ブランチとしてPushすることで処理の高速化を図っています。この手法により、リポジトリ全体をcloneする手間を省き、lock状態管理に必要な最小限のリソースのみを効率的に操作できるため、時間短縮とパフォーマンスの向上を同時に実現できます。 下記はワークフローで実行している git init コマンドを使って作成した空リポジトリから作成したブランチを導入リポジトリにpushするためのコマンド例です。 git init git remote add origin " https://x-access-token: ${ { github.token } }@github.com/<Repository名>.git " git config --global user.email " github-actions[bot] " git config --global user.name " github-actions[bot]@users.noreply.github.com " echo < lock情報 > > lock.txt git add . git commit -m " add lock files " git push origin HEAD: < lockブランチ名 > 各サーバー上での処理について 本記事で紹介する自動リリースの仕組みでは、GitHub Actionsから後述するAWX OperatorのAPIを介して各サーバー上でPowerShellスクリプトを実行し、デプロイ処理を行います。 サーバーからGitHubにアクセスする認証手段としては、一般的に デプロイキー や マシンユーザ が利用されます。しかしこれらの認証方法はサーバーごとに個別のSSHキーを生成・設定する必要があるため、複数のサーバーでGitHubへのアクセスが必要な場合には設定の負担が増大し、採用が現実的ではありません。 そこで本記事の自動リリースでは、GitHub Appsで一時的なアクセストークンを生成し、各サーバーに配布する方式を採用しました。 GitHub Apps経由のトークン取得 GitHub Appsについての詳細は以下のドキュメントをご参照ください。 docs.github.com GitHub Appsで取得できるトークンには以下の2種類があります。 インストールアクセストークン ユーザアクセストークン インストールアクセストークン GitHub AppsがリポジトリやOrgnizationのリソースへアクセスするために発行するトークンです。Botとして個別のリポジトリや組織へアクセスし、リポジトリ単位の自動化タスクを行う場合に使用します。アクセス権限はGitHub Appsの設定画面で細かく設定できます。トークンの有効期限もデフォルトでは1時間になっており、一時的な利用に適しています。 ユーザアクセストークン OAuthトークンの一種で、認証されたユーザの権限とリポジトリへのアクセス範囲を持ちます。ユーザーが持つ全ての権限に基づいた広範囲なアクセスが許可されるため、アプリがユーザーとしての行動をシミュレートするような用途に適しています。有効期限はデフォルトでは8時間で更新トークンを利用してトークンの再生成が可能です。 本記事の自動リリースでは、各サーバーからGitHubにアクセスするためのトークンが必要です。そのため、一時的な利用に適したインストールアクセストークンを発行して使用しています。このトークンは、デプロイ処理を実行するたびに新規発行するよう設定しており、セキュリティリスクを低減しつつ、最新のアクセス情報で安全にリリース操作を行うことが可能です。 GitHub Actionsでのインストールアクセストークン発行は、公式のトークン発行Actionである actions/create-github-app-token を使用しています。 このアクションの入力値として必要なApp IDとPrivateKeyは作成したGitHub Appsの設定画面で確認・作成できます。 上記の値は、それぞれリポジトリのSecretsに登録してGitHub Actionsから参照しています。 actions/create-github-app-token で取得したインストールアクセストークンは、AWXのAPIを介して各サーバーのPowerShellにパラメータとして渡すようにしています。 デプロイ処理 PowerShellスクリプトでは以下の処理を実行しています。 トークンを使ったremote url設定 マージコミットにswitchしての差分取得とコミットされた状態に応じた処理 トークンを使ったremote url設定 トークンを使用してGitコマンドからGitHubにアクセスするためにはremote urlの設定が必要です。下記が設定例になります。 git remote set-url origin https://x-access-token: < トークン > @github.com/ < owner > / < リポジトリ名 > .git この設定をすることで、origin指定のGitコマンドがトークンを使用したアクセスになります。 マージコミットにswitchしての差分取得とコミットされた状態に応じた処理 Gitリポジトリで最新のファイルを取得してリリースするのに一番シンプルな方法はディレクトリごと上書きする方式です。しかし自動リリースを導入するリポジトリはファイル数が大量にあり、毎リリースで全ファイルを上書きするのはパフォーマンス上の懸念がありました。またサーバー上のディレクトリ構成とリポジトリ上のディレクトリ構成が一致していないという課題もありました。 そこでマージされたpull requestの差分のみを取得・リリースする方式としました。 具体的には、GitHub ActionsからマージコミットのHashをパラメータとしてPowerShellスクリプトに渡します。PowerShellスクリプトは受け取ったHashにswitchして、 git log コマンドでマージコミットの親となるHashを取得します。マージコミットの親となるHashはPullRequestがマージされたbaseブランチとfeatureブランチ、それぞれのHashになります。 マージコミットの親となるHashを取得するコマンド例です。 # マージコミットにswitch git switch -f --detach < マージコミットのHash > git log -n 1 --pretty = format:%P --pretty=format:%P で親となるHashだけの出力になります。 詳細は公式ドキュメントをご参照ください。 git-scm.com 下記はマージコミットの git log コマンドの出力例です。 --pretty=format:%P オプションを付けて実行することで Merge: e1108e69e07 83daabd093a の e1108e69e07 83daabd093a のみ出力されます。 $ git log --merges -n 1 commit ab4474df796f1dd4c8c188f3d88e3366de79be12 ( HEAD - > master, origin/master, origin/HEAD ) Merge: e1108e69e07 83daabd093a Author: kane8n < takumi.kaneda@zozo.com > Date: Fri Nov 8 19:01:02 2024 + 0900 Merge pull request #317 from st-tech/fe-deploy-test Fe deploy test $ $ git log --merges -n 1 --pretty = format:%P e1108e69e07fa210b4a5584d0a724b9f7ebf160a 83daabd093a2bf7826a458bb9d356f861478fd0b $ 上記出力のスペースを ... に置換し、 git diff コマンドの引数とすることでマージコミットの差分が取得できます。 git diff コマンドにはファイルがどのような操作でコミットされたのか(追加されたのか・削除されたのかなど)の情報も取得するため、 --name-status オプションも付けて実行しています。 $ git diff --name-status e1108e69e07fa210b4a5584d0a724b9f7ebf160a...83daabd093a2bf7826a458bb9d356f861478fd0b A pc/wwwroot/FeDeployTest/brand/default.html D pc/wwwroot/FeDeployTest/brand/include/default_2021.html D pc/wwwroot/FeDeployTest/brand/include/default_2022.html A pc/wwwroot/FeDeployTest/css/common.css A pc/wwwroot/FeDeployTest/css/common.v2.css A pc/wwwroot/FeDeployTest/css/common.v3.css A pc/wwwroot/FeDeployTest/css/interview.css A pc/wwwroot/FeDeployTest/css/ mv .css A pc/wwwroot/FeDeployTest/css/ mv .v2.css A pc/wwwroot/FeDeployTest/default.html $ 上記出力をPowerShellスクリプトでパースして、追加(A)や修正(M)であればコピー、削除(D)ならサーバー上のファイルも削除というように処理しています。 さいごに 前編では、Classic ASPの手動リリース作業が抱える課題を解決するためにGitHub Actionsを活用したリリースプロセス自動化の概要について解説しました。後編では、リリース自動化の中核となるAWX Operatorについて解説します。こちらもぜひご覧ください。 https://techblog.zozo.com/entry/asp-auto-deploy-implementation-second-part techblog.zozo.com ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 hrmos.co
アバター
はじめに こんにちは、MA部MA基盤ブロックでマーケティングオートメーションのシステムを開発している長澤です。この記事ではBigQueryとDatadogを活用した監視を導入した話を紹介します。 はじめに、日々のマーケティングオートメーション(以下、MA)の開発・運用における課題としてシステム信頼性の向上がありました。ZOZOTOWNは年間の購入者数が1,100万人を超えており、MAによりユーザーの皆さまに向けて多様かつ大規模なキャンペーン配信を展開しています。そのため、MAのシステム信頼性の担保が重要課題でした。 その一環としてこれまでに導入されてきた監視が多数ありました。MAの監視を大別すると、一般的に知られるシステム監視とMA特有の監視があります。以下がその一部です。 一般的なシステム監視 OSのCPUやメモリの使用量 HTTPレスポンスのステータスコード SLA 死活監視 MA特有の監視 配信件数や配信失敗率(配信システム・配信チャネル・キャンペーン・集計時間などによる組み合わせ多数) 重複配信などの異常検知 配信からのECサイト・アプリへの流入数 この記事で取り上げるのは後者のMA特有の監視になります。既存のMA特有の監視はいくつかの課題を抱えていました。例えば監視対象のシステムが複数あり、システムによって監視方法が異なり統一されていなかったり、監視ツール自体に課題があったりしました。 これらの課題を解決するため、今回は新たにBigQueryとDatadogを活用した監視の仕組みを構築しました。元々BigQueryは監視のメトリクスのデータソースとして、Datadogは監視ツールとして部署内で活用していたのですが、このふたつを組み合わせた監視の仕組みはまだ存在していませんでした。既存の複数の監視方法の中で、それぞれが別々に活用されている状況でした。この記事では、BigQueryとDatadogを連携するメトリクス送信用アプリケーションによる監視の仕組みをご紹介します。 目次 はじめに 目次 既存の課題 時折通知に失敗する メトリクスを蓄積できない 監視設定のコード管理ができない 監視ツールの選定 監視のアーキテクチャと実装 取り組みの結果 今後の課題 まとめ 既存の課題 既存のMA特有の監視の課題としては以下のようなものがありました。 配信システムによって監視方法が異なる 各監視ツールに課題がある 時折通知に失敗する メトリクスを蓄積できない 監視設定のコード管理ができない 実際に使用していた(試験運用含む)監視ツールは、Redashアラート・Lookerアラート・Digdagで、いずれも本来は監視が主目的のツールではありませんでした。RedashとLookerはBIツールに付随するアラート機能として利用していました。Digdagは定期実行が可能なワークフローエンジンのため、定期的にBigQueryの集計結果が閾値を超えていたら通知するようにして監視代わりにしていました。 時折通知に失敗する 一番の課題として、時折監視の通知に失敗することがありました。RedashはAWS環境でセルフホスティングしていましたが、しばしばワーカーのCeleryのエラーが発生していました。LookerアラートはSaaSでしたが、試験運用中にしばしば通知が失敗してしまい、採用を見送った経緯がありました。 メトリクスを蓄積できない これは元々が監視ツールではないこともあり、いずれのツールでもできていませんでした。蓄積のためには別途自前の仕組みが必要でした。 監視設定のコード管理ができない RedashとLookerではアラート設定をコードで実装できないため、コード管理ができませんでした。Redashアラートは管理画面のフォーム以外の管理方法がありません。LookerはAPIでアラートの設定が可能ですが、別途それ用の実装が必要でした。 監視ツールの選定 こうした背景を踏まえて改めて監視ツールを選定し、Datadogを採用しました。採用理由としては、やはり監視ツールであるがゆえに監視のニーズを満たして既存の課題の解決に最適であったためです。加えて、すでに社内や自分の部署内において多数の運用実績があったのも大きな理由です。 Datadogは過去の運用で既知の通り、高いシステム信頼性で安定稼働していました。メトリクスの蓄積は元々機能として備えており、ダッシュボードで過去の推移を簡単に確認できます。また、監視設定はTerraformで記述可能なためコード管理ができます。 監視のアーキテクチャと実装 本題の監視の仕組みを説明します。 冒頭でも触れた通り、BigQueryのデータを集計し、その集計結果をメトリクスとしてDatadogに送信する監視用アプリケーションを開発しました。言語はGoを採用し、Cloud Runジョブで実行します。Cloud Schedulerでメトリクス送信の実行間隔を設定し、HTTPリクエストをCloud Runジョブに送信します。Datadogの監視はTerraformでコード管理しています。 アプリケーションの設計の要点として、監視対象のシステムや項目が増えてもアプリケーションの修正は不要にしました。新たに監視項目を追加する場合は以下の3つを追加または修正し、GitHub Actionsでそれぞれをデプロイする仕組みになっています。 Cloud Schedulerを管理するYAMLファイル BigQueryのSQLテンプレート Terraformの「datadog_monitor」リソースの定義 Cloud Schedulerを管理するYAMLファイルは以下のようなスキーマで記述します。GitHub ActionsでこのYAMLをパースし、Cloud Schedulerジョブを作成・更新します。 - name : zozo-notification-delivery-error-rate-job description : 配信基盤の配信失敗率 pause : false schedule : "*/10 * * * *" template_path : zozo_notification_delivery/error_rate 例えばこの設定は、配信の失敗率を集計するSQLテンプレートを10分おきに定期実行するジョブを作成します。アプリケーションのCloud Runジョブはひとつのみ作成しますが、監視項目ごとに実行するSQLテンプレートは異なります。そのため、Cloud Schedulerのデプロイ時にHTTPリクエストのボディでファイルパスを渡すようにしています。 Cloud Runジョブの実行時に任意の引数を渡したい場合は、以下のように「containerOverrides」で指定するため、この値にファイルパスを渡しています。また、それ以外に任意の引数を渡すことも可能な実装にしています。 gcloud scheduler jobs deploy http ${JOB_NAME} \ --description " ${DESCRIPTION} " \ --location asia-northeast1 \ --schedule " $( echo " ${SCHEDULE} " | sed -e ' s/\"//g ' ) " \ --time-zone " Asia/Tokyo " \ --uri " https://asia-northeast1-run.googleapis.com/apis/run.googleapis.com/v1/namespaces/ ${PROJECT_ID} /jobs/ ${CLOUD_RUN_JOB} :run " \ --http-method POST \ --message-body " { \" overrides \" :{ \" containerOverrides \" :[{ \" args \" :[' ${MESSAGE_BODY} ']}]}} " \ --max-retry-attempts 3 \ --oauth-service-account-email ${SERVICE_ACCOUNT} \ --project ${PROJECT_ID} アプリケーションの実装においては、SQLの結果をDatadogメトリクスAPIのインタフェースに合わせています。 docs.datadoghq.com 今回は時系列データをポストできる「Submit metrics」エンドポイントを利用しています。このエンドポイントでは、主にメトリクスの「名前」「タイムスタンプ」「値」「タグ」を送信します。 type MetricSeries struct { Metric string `json:"metric"` Points [][] float64 `json:"points"` Type string `json:"type"` Tags [] string `json:"tags"` } func sendToDatadog(metricName string , value float64 , tags [] string ) error { url := fmt.Sprintf( "https://api.datadoghq.com/api/v1/series?api_key=%s" , datadogAPIKey) metric := DatadogMetric{ Series: []MetricSeries{ { Metric: metricName, Points: [][] float64 { { float64 (time.Now().Unix()), value}, }, Type: "gauge" , Tags: tags, }, }, } data, err := json.Marshal(metric) if err != nil { return err } resp, err := http.Post(url, "application/json" , bytes.NewBuffer(data) // 省略 } これに合わせて、BigQueryのSQLテンプレートは「メトリクス名」と「集計した値」を出力するようにしています。例えば先ほどの配信失敗率のSQLテンプレートであれば、このような結果を返します。 [{ " metric_name ": " system1_push ", " value ": 0.0 } , { " metric_name ": " system1_mail ", " value ": 0.01 } , { " metric_name ": " system1_line ", " value ": 0.02 }] この結果に加えて、SQLテンプレート名やその他の管理用の情報をタグとしてメトリクスに追加し、Datadogに送信します。このようにしてSQLの詳細に関わらず、集計結果をメトリクスとして送信できるようにしています。 Terraformでは先ほどのメトリクス名と同じ数の監視を定義します。 resource "datadog_monitor" "zozo_notification_delivery_error_rate" { for_each = toset ( [ "system1_push" , "system1_mail" , "system1_line" , ] ) name = "【Notification Delivery】Error rate of $ { each.key } over the threshold" type = "metric alert" tags = [ "env:$ { local.env } " , "app:zozo_notification_delivery" , "template:error_rate" ] message = <<-EOT $ { local.notification_slack_channel } Error rate of $ { each.key } ({{value}}%) over the threshold {{threshold}}. template: https://github.com/st-tech/ma-datadog-metrics-sender/blob/main/templates/zozo_notification_delivery/error_rate.tmpl {{ #is_alert}} {{#is_exact_match "env" "prd"}} @pagerduty-zozo-ma-alert {{/is_exact_match}} <!subteam^XXXXXXXX|ma-alert-team> {{/is_alert}} {{ #is_no_data}} {{#is_exact_match "env" "prd"}} @pagerduty-zozo-ma-alert {{/is_exact_match}} <!subteam^XXXXXXXX|ma-alert-team> {{/is_no_data}} {{ #is_no_data_recovery}} {{#is_exact_match "env" "prd"}} @pagerduty-zozo-ma-alert {{/is_exact_match}} {{/is_no_data_recovery}} EOT validate = true query = "max(last_5m):bigquery.query.result.zozo_notification_delivery.error_rate.$ { each.key } {env:$ { local.env } } > XXX" notify_no_data = true no_data_timeframe = 25 ## minutes timeout_h = 0 require_full_window = false monitor_thresholds { warning = XXX critical = XXX } } 通知先はSlackとPagerDutyです。監視の閾値を超えた場合だけでなく、一定時間メトリクスの送信がなかった場合も「no_data」の設定によって通知します。「no_data」の設定によって、メトリクス送信アプリケーションに問題があった場合に、それ自体の監視がなくともメトリクス送信の異常を検知できます。 取り組みの結果 現在はこの監視の仕組みを構築し、監視の一部の移行が完了しています。Datadogの安定性とno_dataの設定により、監視の仕組みは安定して運用できています。時折メトリクス送信に失敗しますが、それも検知した上で対応できています。メトリクスの蓄積も既知の通りでしたが、過去の推移を簡単に確認するのに役立っています。監視設定はGitHubで管理しているため、PRレビューをしたり、過去の変更履歴を管理したりできています。Digdagワークフローでは監視項目ごとにアプリケーションコードを実装していましたが、移行後はSQLテンプレートで集計処理が完結するため、コードの実装を減らすことができました。その甲斐もあって移行もコストを抑えてスムーズに行えました。 将来的には複数のツールに点在している監視をこの仕組みに統一することで、拡張性がありつつ運用負荷が低い監視を実現します。 今後の課題 今後の課題としては、残りの監視の移行や不足している監視の追加があります。また、今回の課題とは別問題ですが移行後の監視の見直しを予定しています。見直しの例としては、監視の閾値が低く通知の頻度が多すぎるケースや、監視の集計軸の粒度が大きいために細分化が必要なケースなどです。 まとめ BigQueryとDatadogを活用したMAの監視について紹介しました。メトリクス送信用のアプリケーションを開発することで、BigQueryをデータソースとしてメトリクスを集計し、Datadogで監視できるようにしました。設計の工夫により、この監視の仕組み自体の運用コストを抑えられるようにしました。監視の一事例として参考になれば幸いです。 MA部ではMAの開発やSREを共に推進してくれる方を募集中です。ご興味のある方は下記リンクの採用募集からぜひご応募ください。 corp.zozo.com
アバター