有名テック企業の技術ブログを、ひとつのフィードで。
フィード
35件
はじめに 2025年新卒のブランドソリューション開発本部ZOZOMO部OMOブロックの東谷です! 私は筋トレが趣味なのですが、増量期(筋肉をつけるために体重を増やす時期)が終わろうとしています。早く痩せなきゃと思いつつ、つい揚げ物や甘いものを食べ、現実から逃げている今日この頃です。 早いもので入社からもう1年が経ちました。この1年を振り返って一番強く感じているのは、スクラムは「アジャイル開発の手法」であると同時に、新卒にとっての最高の学習環境だったということです。 配属直後の自分は、リファインメントの議論についていけず、実装中もどこから手をつけてよいか分からない状態でした。それでも1年後、チームの中でスクラムを一通り回せるようになりました。この変化は、研修や独学だけではなく、スクラムの各イベントそのものに大きく支えられました。 スクラムがよく語られるのは「ビジネス価値を最大化する仕組み」としての側面です。しかし新卒視点から見直してみると、これらはすべて先輩たちの思考プロセスを短時間で観察し、その場で質問できる場でもありました。書籍やドキュメントでは身につきにくい「思考の型」、つまり先輩がどんな問いを立て、何をよりどころに判断しているかという基準があります。これを学べる環境として、スクラムは新卒にとって非常に整った構造を持っていると感じています。 この記事では、「思考の型」を自分が新卒1年目に具体的にどう身につけたかを振り返ってみます。題材として取り上げるのは、プロダクトバックログリファインメント(以下、リファインメント)、スプリントプランニング(以下、プランニング)、モブプログラミングの3つです。前者2つはスクラムイベントで、モブプログラミングはチームで採用している開発プラクティスです。新卒エンジニアには「スクラムは新卒の最高の学習環境になりうる」という視点を、新卒受け入れを担う開発組織には育成設計のヒントを提供できればと思っています。 目次 はじめに 目次 配属直後の自分とチームの環境 スクラム開発で身についた3つの学び 1. リファインメント:機能を「課題解決の手段」として見る視点が身についた 先輩が立てる「問い」が、自分の視野を順に広げていった 議論を経て、機能の質と学びが見えた 2. プランニング:Acceptance Criteriaで「実装の自由度」を制御することを学んだ Acceptance Criteriaで「実装の自由度」を制御する 見積もり精度はAC定義の解像度の問題に集約される 3. モブプログラミング:「事実ベースで実装する」という姿勢が身についた 先輩は事実から方針を決めていた 「事実から始める」という型を学んだ AI時代に、思考の型はどう活きるか おわりに 配属直後の自分とチームの環境 配属前の自分にとって「開発」とは、仕様が決まったタスクを実装することとほぼ同じ意味でした。 学生時代に長期インターンとして参加していたのは、ITベンチャー企業のBtoBプロダクト開発チームでした。そのチームのバックログには仕様まで書かれたタスクが並んでおり、エンジニアはその中から自分でタスクを取って作業する開発サイクルでした。タスクはリーダーが起票していき、起票時点で何を作るかは決まっています。自分の仕事は、それをコードに落とす精度と速度を上げることだと思ってました。 そんな自分が配属先の今のチームに入ったとき、まず驚いたのは開発サイクルの「広さ」でした。 リファインメントでは、プロダクトバックログアイテム(PBI)の目的や受け入れ条件を整理し、チームで認識を揃えながら実装可能な粒度まで要件を具体化します。プランニングでは、スプリントゴールを踏まえてチーム全員で作業内容を確認し、相対見積もりをしながらスプリント内で達成する内容を決定します。開発中はモブプログラミングを通じてリアルタイムに知識共有と意思決定を行います。スプリントレビューでは完成したインクリメントをステークホルダーとともに確認し、次の方向性を議論します。 この1週間のスプリントを、新卒の自分も初日からチームの一員として回すことになります。それまでの開発経験と決定的に違ったのは、バックログにタスクが並ぶ前の段階から、自分も議論に参加するという点でした。 もう1つの驚きは、議論についていくために要求される知識の量です。リファインメントやプランニングでは、複数の前提知識を踏まえた議論が当たり前のように展開されます。例えば、自分の担当しているサービスが他のサービスとどう連携しているか、CQRSで構築されたデータの流れはどうか、認証基盤との関係はどうなっているのかなどです。配属直後の自分は、議論の中で出てくる用語の半分も追えていない状態でした。 「自分が貢献できるだろうか」。最初の数週間、率直に言ってそう感じていたのを覚えています。 ところが、振り返ってみるとこの環境が、自分にとってこれ以上ない学習機会になっていました。スクラムの各イベントが、まさに知識、経験が足りていない新卒にとって最適な学習装置になっていたからです。 スクラム開発で身についた3つの学び ここからは、新卒1年目で身についた3つの学びを、具体的なエピソードとともに紹介します。 1. リファインメント:機能を「課題解決の手段」として見る視点が身についた 私が所属しているチームでは、ZOZOTOWN上でブランド様の店舗在庫を確認し、商品の取り置きができるサービス「ZOZOMO店舗在庫取り置き」を開発しています。複数のマイクロサービスが連携し、CQRSを採用しているため、イベントを通じたデータの流れを理解することが開発の前提になるプロダクトです。 配属されてしばらく経った頃、ブランド様の店舗情報をシステム上で更新できる機能を実装しました。それまでは開発チームが手動で対応しており、完了までに3営業日ほどかかっていました。ブランド様を待たせることになり、運用者の認知負荷や作業時間も大きいという課題がありました。 議論に参加する前、私の頭にあったイメージは簡単でした。「入力フォームを作って、POSTで更新するAPIを叩けば完了」。配属されたばかりの私は、機能を「実装するもの」として捉え、実装イメージが浮かんだ時点で完成像が見えたつもりになっていました。 ところが、実際のリファインメントの議論はまったく別の地点から始まりました。 先輩が立てる「問い」が、自分の視野を順に広げていった 最初に出てきたのは、システム構造に対する問いでした。認証基盤との関係、マイクロサービスごとの責務、そしてデータがどのサービス間をどのように流れるのか、といった内容です。私が「POSTを1つ作ればいい」と思っていた機能は、実際には複数のサービスにまたがってイベントを発行し、各サービスがそれぞれの責務でデータを更新していくものでした。CQRSやイベント駆動の設計は独学で吸収するには時間のかかる領域ですが、議論の場で出てくる用語や設計判断についてその場で質問できることで、認知負荷の高い情報を一気に吸収できました。 次に、システム設計が具体化してくると、想定していなかった論点が次々と出てきます。「店舗情報が更新されたことをどう確認するのか」「確認するためにはどのデータが必要か」。考えるべきことが膨らんでいくなかで、先輩から自然に出てきたのが「この機能はそもそも何を解決するものだったっけ?」という問いでした。 そこから議論は、機能を実装する話からユースケースを実演してみる話に切り替わります。実際の運用者の動きを想像し、ときには運用者に直接ヒアリングしながら、ユーザー体験ベースで仕様が具体化されていきました。 例えば、「店舗情報として更新できるデータは何があるのか」を全部洗い出すところから始まりました。さらに「運用者が更新ボタンを押す前に、入力ミスがないと安心できる状態は何か」「更新した後、本当に意図通りに反映されたかをどう確認するか」など、ユーザー体験の細部にまで問いが続いていきます。これらに応える形で、機能の中身が具体化されていきました。 さらに出てきたのが、「この機能を実装した後の恒久的な運用フローはどうなるか?」という問いです。「今回のケース以外にも対応できる汎用性って必要だっけ?」「逆に今回のユースケースに限定すれば不要となる実装ってないっけ?」のように汎用化を考える問いと、削ぎ落とすための問いが、同じ場で同時に飛び交っていたことが印象的でした。 象徴的だったのが、ブランド情報の鮮度をめぐる議論です。ZOZOMOのシステム構成上、店舗が所属するブランド様の情報が変わったとき、システム側は正常に更新されますが、その変更がリアルタイムで運用ツールに表示されない仕組みでした。そのため運用上、「正確なデータを変更しているかどうか確認したい」と運用者から開発側へ問い合わせを受けるケースの発生も予測されました。今回の店舗情報の更新機能を考えるうえで、この運用上の課題へ手を入れる余地があると判断し、ブランド情報を最新状態として取得し直す機能を同じ画面の中で組み込むことになりました。この機能を実装した結果、関連する問い合わせは発生していません。目の前の機能要求だけでなく、運用される将来の状態まで含めて考えることで、機能の質が変わっていく瞬間でした。 議論を経て、機能の質と学びが見えた リファインメントの議論を経て、最終的な機能は、私が当初抱いていたイメージとは大きく違うものになりました。最終的にできたのは、店舗情報の更新作業だけでなく、その前後で必要になる作業まで1画面で完結できる機能でした。 1画面に統合したのは、更新前の確認(店舗IDから店舗名・ブランド名を表示)、更新後の確認(変更後データの表示)、そしてブランド情報の鮮度を保つための再取得機能の3つです。これにより、運用者は別ページに遷移したり、別件で開発側に問い合わせをしたりする必要がなくなりました。効率よく作業できる機能に仕上がっています。 この一件で体感したのは、機能の「核となる実装」は全体のごく一部にすぎないということでした。POSTのAPIとUIという核は確かにイメージできていましたが、それが実際のユーザーへ届きビジネス価値へとつながるまで、想像をはるかに超える議論と設計判断が積み重なっていました。 リファインメントに新卒のうちから参加できたことで、私は機能を「実装するもの」ではなく「課題を解決するもの」として見る視点を、議論の中で自然に身につけることができたと感じています。これは、先輩から受け取った最初の思考の型のひとつでした。 2. プランニング:Acceptance Criteriaで「実装の自由度」を制御することを学んだ リファインメントで十分実装が可能だと判断されると、次はプランニングでスプリントの計画を立て、タスクの洗い出しやタスクの規模を見積もります。配属直後の私にとって、この時間はリファインメント以上に難しいものでした。 規模の見積もりには、チームごとに蓄積されるベロシティ(過去のスプリントでどれくらいの規模を消化できたかという感覚値)があります。「このくらいの規模の変更ならだいたい何ポイント」という相場が、過去の実装経験から自然と形成されていきます。配属されたばかりの自分にはそれがなく、対象機能の核となる変更部分の理解もまだ浅い状態で、規模を出すのは正直難しい作業でした。 では、先輩はどう見積もっているのかなと気になりました。観察して印象的だったのは、実装を頭の中で先取りして見積もるやり方でした。ZOZOMOではDDDやCQRSを採用しているため、これはコードベースを頭の中で走らせる形になります。例えば「Query側のStore集約のUsecaseでデータを整形して、Infra層に店舗集約をUpsertするクエリを書いて、UnitTestとE2Eを書いて…」といったイメージです。見積もりは「数字の当てっこ」ではなく、この工程をどれだけ正確に頭の中で走らせられるかなのだと理解しました。そして、その想像力を支えているのは、過去の実装を積み重ねてきた経験にほかなりませんでした。 Acceptance Criteriaで「実装の自由度」を制御する 想像力と経験がある程度身についた状態で、より規模を正確に見積もり、要件を満たすために考えるべき重要な指標があることに気づいたのは、1年経ってからでした。それがAcceptance Criteria(受け入れ基準、以下AC)の解像度です。 ACとは、その機能が「完成した」と判断するための条件を具体的に言語化したものです。重要なのは、ACの粒度が実装の自由度をコントロールするということでした。 象徴的だったのが、ブランド様と店舗の紐付けを除外する設定を確認する機能の実装です。これはリファインメントの結果、ブランド様ごとに検索ができ、絞り込んだ結果をExcelとして出力する機能も追加するスコープに広がりました。Excelはメールで該当ブランド様に送り、店舗とブランド様の紐付きが正しいかを確認してもらうためです。 このときのACの一例は、「検索後、Excel出力ボタンを実行したタイミングでポップアップが表示され、絞り込みした店舗の属するブランド一覧がポップアップに表示されること」と定義しました。 このACは、自由度の制限の仕方が絶妙でした。検索のクエリや検索ロジック、Excel生成の方法そのものには制限がかかっていません。より良い実装方法があれば実装時に改善できる余地が残されています。一方で、出力時にポップアップで対象データを一覧表示するという最終的な体験の部分は明確に固定されています。これは複数ブランドを指定して検索できる仕様上、運用者が「どのブランド様のExcelを送ろうとしているんだっけ?」を実行直前に視覚的に確認できる状態を保つことで、ヒューマンエラーを抑えるためです。 ACが「実装の細部までガチガチに固定する文書」になっていると、開発者は工夫の余地を失います。逆にACが曖昧すぎると、実装中に判断を迫られる回数が増え、規模が大きくブレます。ACは、考えてよい部分と考えなくてよい部分を明確に切り分け、実装の自由度を意図的にコントロールするための装置なのだと、この実装を通じて理解しました。 見積もり精度はAC定義の解像度の問題に集約される ACの粒度をこの形でコントロールできていたから、実装中に時間をかけて考える部分と、考えずに型通り進めてよい部分の切り分けが明確でした。結果として、見積もった規模と要件の両方を、ある程度の確度で同時に守れる構造になります。 規模そのものを正確に当てに行くのではなく、ACの粒度をコントロールして実装中の判断回数を制御します。プランニングを1年続けた末に言語化できたのは、「見積もり精度の問題は、その手前のAC定義の抽象度に集約される」という知見でした。 数字を出すこと自体に意識を向けていた1年前の自分から、今は「ACで実装の自由度を制御することで、規模と要件の両立を狙う」ことに意識を向けるようになりました。プランニングの場の捉え方がこのように変わったことが、この1年で起きた最も大きな変化の1つです。ACの粒度を意図的に調整するという考え方も、私のなかに定着した思考の型のひとつです。 3. モブプログラミング:「事実ベースで実装する」という姿勢が身についた リファインメントとプランニングを経て、いよいよ実装フェーズです。私のチームでは実装の多くをモブプログラミングで進めます。複数人が同じ画面を見ながら、ナビゲーター役とドライバー役を交代しつつコードを書いていく形式です。 モブプロから学んだことは数多くありますが、今の自分に最も大きく影響しているのは「事実ベースで実装する」という姿勢です。 先輩は事実から方針を決めていた リファインメントやプランニングで要件をどれだけ詰めても、実装段階で「考慮しきれていなかったこと」は必ず出てきます。とくにマイクロサービス間でデータを連携している箇所では、外部要因も絡んで挙動が読みにくくなります。 ZOZOMOでも、他のマイクロサービスとイベントを通じたデータ連携をしています。しかし、さまざまな要因によりイベントの連携順序が入れ替わり、本来連携されるべきデータを正しく届けられないケースもありました。この問題に対処する仕組みを実装した際、モブプロで先輩のアプローチを間近で見たのが印象的でした。 先輩はまず、入れ替わりが起きたデータを全件出してくるところから始めました。一部ではなく全体を並べて共通項を探すと、どの経路の、どのタイミングで入れ替わりが起きているのかという事実が浮かび上がってきます。 そこから先輩が見出したのは、ZOZOMO側の開発基盤にデータが連携される箇所で、入れ替わりそのものを直すという根本的な解決策でした。全件のデータという事実から出発したからこそ見つけられた解決策です。 「事実から始める」という型を学んだ このやり方の何がすごいかというと、実装の前に「何を解決すべきなのか」が事実として明らかになっていることです。事実から始めれば「この具体的なケースを直すには何が必要か」という明確な目的が手元にあります。結果として、不要な処理が混ざらず、シンプルでビジネス価値の高い実装にたどり着きやすくなります。 このとき自分が何より学ん
はじめに こんにちは、SRE部 検索基盤SREブロックの富田です。2026年5月4日〜5日の2日間、New Yorkで開催された「AI Agent Conference 2026」に参加しました。 本記事では、現地の様子と印象に残ったセッションをご紹介します。 目次 はじめに 目次 AI Agent Conferenceとは 特徴 現地の様子 セッションレポート (1) Architecting for the Agentic Customer: Systems Design for Non-Human Actors (2) What Agents Want: Beyond One-Size-Fits-All Retrieval Systems (3) Measuring & Evaluating Agentic AI (4) Workflow Democratization & Operating in an Accelerated Development Environment おわりに AI Agent Conferenceとは AI Agent Conferenceは、エンタープライズ領域における自律型AI(Autonomous AI/Agentic AI)をテーマとする国際カンファレンスです。研究系のAIカンファレンスとは異なり、AIエージェントのエンタープライズ実装に焦点を当て、技術・運用・戦略にわたる深い知見が交わされます。 特徴 本カンファレンスの特徴は、「1つのカンファレンス、3つのAgenticテーマ」というコンセプトでセッションが構成されている点です。2026年は以下の3テーマで議論が展開されました。 Agentic Enterprises:AIエージェントによるビジネスオペレーションの変革 Agentic Engineering:AIエージェントシステムを支えるインフラと設計 Agentic Industries:金融・医療・法務・ロジスティクスなど業界別のユースケース 現地の様子 本カンファレンスは、New Yorkにあるヒルトン・ミッドタウンホテルで開催されました。来場者数は3,000人を超える規模でした。 Agentic Engineeringトラックは来場者数が多く、入場制限のかかるセッションも複数あるほどの人気でした。AIエージェントへの注目度が、想像していた以上に高かったというのが率直な印象です。 会場の様子 会場には70を超える企業ブースが並び、来場者がその場でプロダクトを体験できるブースも多く、終日盛況でした。 ブースのマップ 体験型のブース セッションレポート 検索機能を担当するSREとして、特に印象に残ったセッションを4つ紹介します。 (1) Architecting for the Agentic Customer: Systems Design for Non-Human Actors 本セッションでは、AIエージェントが消費者に代わって買い物をする「Agentic Commerce」に向け、EC事業者やプラットフォームが取るべきアーキテクチャ設計について紹介されました。 GoogleのHeiko Hotz氏によるセッション AIエージェントによる自律的な購買行動を支えるプロトコルは、ここ1年で急速に整備されています。代表例として、A2A(Agent-to-Agent Protocol)、AP2(Agent Payments Protocol)が挙げられます。さらに2026年1月にGoogleがUCP(Universal Commerce Protocol)を発表しました。 発表後の数週間でAmazon・Meta・Microsoft・Shopify・SalesforceがUCPに参画しました。業界全体で、エージェント間通信の標準化が一気に進みつつある状況です。 こうして「外側」の仕組みが急速に整う一方で、LLMベースのエージェント本体には本質的な弱点が残っています。ハルシネーション、予算を超えた購入、悪意のあるサイトに操作される脆弱性です。研究では、エージェントが偽の認証情報や虚偽の主張に騙されるケースが確認されています。セッションでは、これを「人間と同じ感覚で野に放つのは危険」と表現されていました。 セッションで提示されたのは、サンドイッチアーキテクチャと呼ばれる設計パターンです。LLMの強みと、入力が同じなら必ず同じ結果を返す決定論的レイヤー(ルールベースの処理層)を組み合わせ、エージェントの暴走を構造的に抑え込みます。 サンドイッチアーキテクチャの説明スライド 第1層(LLM):自然言語の意図を構造化属性に変換する。「スコットランドのハイランドをハイキングするための防水ジャケットがほしい」という曖昧な要求を、防水等級・サイズ・想定気温などの具体的な属性に落とし込む 第2層(決定論的レイヤー):UCPなどのプロトコルに準拠したAPI経由でEC事業者のデータを取得し、属性で確定的にフィルタする。ここでLLMは介入させず、ルールベースで候補を絞り込む 第3層(LLM):最終決定する。必要なら第1〜2層に戻ってループする このアーキテクチャでは、決定論的なフィルタリングが「LLMが暴走しても結果が破綻しない安全弁」として機能します。 EC事業者側の責務として、AEO (Agentic Engine Optimization) という概念も紹介されました*1。エージェントは画像や装飾ではなく、構造化された属性データしか見ません。商品ページがいくら美しくても、属性データに「急速充電に対応」という項目が抜けていれば、エージェントはその商品を選びません。実験では、ある属性が欠落しているだけで、エージェントは25ドル以上の価格差を付けないと購入対象に含めないという結果も示されました。 最後に、取引の全過程を後から追跡できる記録(監査証跡)の設計が、エージェント時代の信頼性の土台になる、という話で締められました。 AI時代の安全性は、LLMを賢くするだけでなく、その周りに置く決定論的レイヤーをどう機能させるかも重要だと感じました。また、エージェント駆動の購買が広がれば、商品検索の相手は「人間のユーザ」と「AIエージェント」両方になります。商品データの属性を漏れなく構造化し、AEOの観点で「エージェントから評価される」ことも、ECプラットフォームの検索機能の要件として加わってくると思いました。 (2) What Agents Want: Beyond One-Size-Fits-All Retrieval Systems 本セッションでは、エージェントの記憶や作業メモリの役割を果たすコンテキストレイヤーに求められる要件と、それを支えるベクトルデータベース(LanceDB)の設計思想について紹介されました。 LanceDB CEOのChang She氏によるセッション これまでのRAG (Retrieval-Augmented Generation)は、ユーザのクエリ1つに対して数件のテキストを返すだけで成立する世界でした。しかし、エージェントが本番環境で動き出すと、扱うデータとクエリパターンは一変します。 エージェントは計画を立て、並列で検索を投げます。各種ツールを実行して得られた中間結果を随時メモリに記録し、バラバラの情報を要約・集約しつつ、もし行き詰まったら一歩手前のステップに逆戻りして別のルートから調べ直す、といった自律的な試行錯誤を行います。 その結果、コンテキストレイヤーが管理すべきデータはテキストだけでなく、PDF・スクリーンショット・テーブル・動画フレーム・オーディオクリップ・イベントログ・JSONログまで膨らみます。こうしたデータの広がりに合わせて、クエリの種類も多様化します。意味的検索だけでなく、キーワード検索や、特定の顧客IDかつ過去7日間といった構造化フィルタへの対応も必要です。さらにデータの来歴(どの埋め込みモデルを使ったか、いつ書き込まれたか)も管理対象になります。 これらを別々のシステム(ベクトルDB・データレイク・OLAP・メタデータストア)に分散させると問題が生じます。エージェント自身が「どのツールを使うべきか」「返ってきた結果をどう統合するか」の判断にトークンと推論能力を浪費してしまいます。 LanceDBはこの課題に対し、以下のアプローチを取っています。 コンテキスト・来歴・特徴量・埋め込みベクトルの単一テーブル管理:エージェントはSQL、セマンティック検索、フルテキスト検索を同じテーブルに対して投げられる。ツール選択や結果統合の手間が消える データタイプ別ストレージ戦略の抽象化:インラインカラム、ページ単位管理、外部URL参照といったサイズに応じた格納戦略をエージェントから隠蔽する エージェント時代のスケール対応:100億行規模・10K QPSの書き込みは、従来のベクトルDBには想定外の負荷。p99レイテンシを悪化させず安定動作するよう、インデックス・シャーディング・量子化を再設計 バージョニングと再現性:エージェントが探索した分岐の特定時点に戻れる、デバッグ時のリプレイができる仕組み ベンチマーク結果では、エージェントフレームワーク標準のメモリ機能(インメモリの単純な検索)の取得精度が約52%なのに対し、LanceDBに置き換えると約76%まで上がります。取得時間も80秒台から数秒に短縮されると報告されていました。精度を上げると遅くなる、ではなく、正しい構造を選ぶと精度と速度が同時に上がるという結果は、素直に面白いと思いました。 また、AIエージェントが普及していくとデータ量やトラフィック量が大きく変化するため、今後の検索に求められる要件はさらに拡張されていくと感じました。 (3) Measuring & Evaluating Agentic AI 本セッションは、本番環境で動くエージェントの評価とモニタリングをテーマにしたパネルディスカッションでした。 エージェントのトレースは長く、深く、複雑です。1つのインタラクションの中で多段のChain-of-Thought(段階的に推論を重ねる思考過程)が走り、複数のツール呼び出しが連鎖します。そのため、「何が起きたか」を後追いで評価するには専用の仕組みが必要になります。 加えて、LLM-as-a-judge(LLM自身を評価者として使う手法)は、シンプルなユースケースなら安価ですが、エージェントが複雑化すると評価コストが急騰します。判定にも高価なモデルを使うため、本番規模ですべての出力をjudgeで評価する運用はコスト面で困難です。 さらに、オフライン評価(事前に用意した正解集に対する評価)と本番モニタリング(実行時の観測)が分断されており、両者をどう接続するかが課題になります。組織によって重視する軸もコスト・速度・リスクと異なるため、画一的な指標も置きにくい状況です。 パネリストからは、以下のような実践的なアプローチが共有されました。 指標はユースケース起点で設計する:「営業支援エージェントなら何を測るか」をユースケース単位でアカウンタビリティ・フレームワークとして定義する 「Cost per Successful Outcome」を中心指標に:単なる精度やレイテンシではなく、成功した結果1件あたりのコストで全体を見る LLM-as-a-judgeのコスト最適化:評価ごとに高価なLLMを呼ぶとコストが膨れ上がるため、本番規模ではSLM(小規模言語モデル)などの軽量モデルに切り替え、品質を保ちながらスケールさせる オフライン評価と本番モニタリングの継続的フィードバックループ:本番の挙動をオフライン評価に取り込み、評価セットを継続的に更新する 既存の観測スタックに乗せる:OpenTelemetryなど既存の観測基盤にエージェントの計装を統合し、モデル選択(LLMとSLMの使い分け)まで1つのダッシュボードで管理する また、Microsoftのagent-governance-toolkitも紹介されていました。ポリシー強制・サンドボックス・OWASP Agentic Top 10対応など、エージェント運用全体を標準化しようとするツールキットです。 個人的に印象に残ったのは「Cost per Successful Outcome」という指標の話でした。精度やレイテンシを個別に追うのではなく、成功した結果1件あたりのコストで全体を見るという発想です。この観点で見ると、高価なLLMを使い続けるよりも、品質を保ちつつ徐々に安価なモデルに切り替えていくのが自然な方向だと感じました。実際、パネルディスカッション内でも同様のアプローチが議論されていました。 「エージェントが本番で失敗したとき、誰が責任を取るのか(エンジニア・プロダクトオーナー・エージェント自身)」という問いも面白かったです。「もはやみんなが互いの役割を担うようになっているので、全体を1つのシステムとして見るしかない」というのもAI時代の形だと思いました。 (4) Workflow Democratization & Operating in an Accelerated Development Environment 本セッションでは、NVIDIA Applied AI Labが10週間で25個のCLIツールを4人で構築した実例を交えて、AIエージェントのみで完結する開発パイプラインの設計思想が紹介されました。 NVIDIA Applied AI LabのJulie Yaunches氏によるセッション 冒頭で「AIが生成できるコード量と、コードレビュー・CI・自動テストといった既存の開発プロセスが吸収できる量の間に大きなギャップが生まれている」と問題提起がありました。このギャップを放置すれば、品質劣化・技術的負債の蓄積・アーキテクチャドリフトが避けられません。セッションでは、10月以降は一行も自分でコードを書いていない、それでも品質を担保する仕組みが必要だった、というエピソードも紹介されました。 NVIDIA Applied AI Labが採用しているのは、Research・Gates・Sweepsという3つの実践です。 Research:何かを作り始める前に、開発対象のコードベースを理解するた
はじめに こんにちは、MA部SREブロックの片桐です。MA部ではメルマガやLINE、アプリプッシュ通知を配信するためのマーケティングオートメーションシステムを開発・運用しています。 MA部ではDBとして主にCloud SQL for MySQLを利用しており、調査や不具合対応のために開発メンバーがDBにログインして各種SQLを実行する場面があります。 このとき、共用の特権DBユーザーとパスワード認証を利用していました。しかし、この方式ではパスワード管理が必要になるほか、DB上のログイン主体も個人に紐づけにくい状態でした。 これらの課題を解決するために、人間によるDBへのログイン方式を、共用の特権DBユーザーとパスワード認証から個人のGoogle Cloudアカウントを使ったIAM認証へ移行しました。 あわせて、IAM認証でログインする各ユーザーには通常時は参照権限のみを付与し、書込系権限が必要な場合だけGitHub Actionsの承認付きワークフローから一時付与する運用にしました。 本記事では、共用DBユーザーによる運用から個人のIAM認証を使った運用へ移行した背景と、MySQLロールの一時付与を実現するための構成例を紹介します。 目次 はじめに 目次 従来の運用の課題 強い権限が常時使える 個人単位で追跡できない 目指した状態 全体構成 IAM認証で個人ログインにする IAM認証の有効化 IAMデータベースユーザーの作成 Cloud SQLへのログイン権限の付与 MySQLロールでDB内権限を分ける 通常時と一時付与用のロール ロールの作成 参照用ロールの付与 GitHub Actionsを承認ゲートにして書込系ロールを一時付与する GitHub Environmentで申請者以外の承認を必須にする 権限操作用サービスアカウントの作成 ワークフロー設定例 一時付与した書込系ロールを剥奪する 手動でロールを剥奪する 定期実行でロールを剥奪する 運用上の注意点と今後の改善 一時付与したロールを使うときの注意 ワークフロー入力値の検証 SQL本文のレビューと監査 一時付与した権限の失効タイミングの厳密化 MySQLロールの粒度 おわりに 従来の運用の課題 従来の構成ではデータベース操作用の共用特権DBユーザーを作成し、パスワード認証でCloud SQL for MySQLへログインしていました。 構成としては次のとおりです。 ここで利用しているCloud SQL Studioは、Google Cloudコンソール上からCloud SQLへ接続してSQLを実行できるWebベースの画面です。 この運用では、複数人が同じDBユーザーを使ってログインします。そのため、主に次のような課題がありました。 強い権限が常時使える 日常運用におけるデータ調査であれば、多くの場合はSELECTを実行できれば十分です。 しかし、共用の特権DBユーザーを使うと、参照だけで済む作業時でも特権によりデータ変更やDDL操作まで実行できてしまいます。 強い権限を持った状態でSQLを実行すると、誤操作時に本来不要だったデータ更新やスキーマ変更まで起きてしまう可能性があります。そのため、通常時は参照のみを許可し、必要なときだけ書込系権限を一時的に付与する運用にしたいと考えました。 個人単位で追跡できない 共用ユーザーでログインするため、データベースから見ると誰が操作しても同じユーザーに見えます。 そのため、DB上のログイン主体を開発メンバー個人のGoogle Cloudアカウントと結びつけにくい状態でした。 人間のDBログインを個人のIAM認証に寄せることで、少なくともDBへのログイン主体は個人単位で扱えるようになります。 目指した状態 共用特権DBユーザーの課題を踏まえ、今回の移行では次の状態を目指しました。 人間によるDBへのログインを、共用ユーザーではなく個人のGoogle Cloudアカウントに紐づける 通常時は参照権限のみを付与する 書込系権限は常時付与せず、必要なときだけ一時的に付与する 書込系権限の付与申請を簡単に行えるようにする 書込系権限の付与には、申請者以外の承認を必須にする 付与した書込系権限は、作業後または定期実行で剥奪する 今回の構成では、Cloud SQLへのログインにはIAM認証を利用します。一方で、ログイン後にどのSQLを実行できるかはMySQL側の権限で制御します。 さらに、書込系権限は常時付与せず、必要なときだけ承認付きで一時付与して、作業後または定期実行で権限を剥奪します。 そのため、今回の構成ではログイン可否、DB内権限、権限の一時付与、付与後の剥奪を次のように分けて考えました。 項目 役割 利用する仕組み 認証 誰がCloud SQLへログインできるかを制御する Cloud SQL IAM認証 認可 ログイン後に何を実行できるかを制御する MySQLロール 一時付与 必要時だけ書込系権限を付与する GitHub Actionsの承認付きワークフロー 剥奪 一時付与した権限を戻す 手動または定期実行のREVOKEワークフロー この構成により、通常時は参照権限のみを使い、書込系権限が必要な場合だけ承認付きで一時的に付与する運用にしました。 全体構成 今回構築した仕組みは、通常時のログイン経路、必要時における書込系権限の一時付与フロー、一時付与した権限の剥奪フローに分かれます。 通常時は、開発メンバーがCloud SQL Studioから自分のGoogle CloudアカウントでCloud SQL for MySQLへログインします。 本記事ではCloud SQL Studioから接続する例で説明しますが、接続元はこれに限りません。IAM認証に対応した接続方式であれば、同じ考え方を適用できます。 Cloud SQLへのログイン可否はIAMで制御し、ログイン後に実行できるSQLはMySQLロールで制御します。通常時は、開発メンバーに参照用のMySQLロールのみを付与します。 IAM認証へ移行した後の通常時の構成は次のとおりです。 この状態では、開発メンバーはCloud SQL StudioからSELECTを実行できます。一方で、書込系権限は通常時には付与しません。 データ修正などで書込系権限が必要な場合は、GitHub Actionsの手動ワークフローを実行します。ワークフローはGitHub Environmentの承認待ちになり、申請者以外のメンバーが承認すると、対象ユーザーに書込系のMySQLロールを一時的に付与します。 書込系権限を一時付与する流れは次のとおりです。 一時付与した書込系ロールは、作業後の手動実行または定期実行で剥奪します。剥奪の流れは次のとおりです。 剥奪は権限を戻す操作であるため、今回の例では付与時のような承認ゲートは設けていません。手動実行または定期実行でGitHub ActionsからSQL実行基盤を起動し、MySQLロールのREVOKEを実行します。 以降のコード例では、次のプレースホルダーを使います。 プレースホルダー 意味 YOUR_PROJECT_ID Google CloudプロジェクトID YOUR_PROJECT_NUMBER Google Cloudプロジェクト番号 YOUR_MEMBER_NAME 開発メンバーのメールアドレスの@より前の部分 YOUR_MEMBER_DOMAIN 開発メンバーのメールアドレスのドメイン YOUR_SA_NAME 権限操作用サービスアカウント名 YOUR_DB_NAME 対象のデータベース名 YOUR_TABLE_NAME 対象のテーブル名 YOUR_COLUMN_NAME 対象のカラム名 YOUR_WIF_POOL Workload Identity Pool名 YOUR_WIF_PROVIDER Workload Identity Provider名 YOUR_GITHUB_ENVIRONMENT_NAME 承認ゲートとして利用するGitHubのEnvironment名 IAM認証で個人ログインにする まず、個人のGoogle CloudアカウントでCloud SQL for MySQLへログインできる状態を作ります。 Cloud SQL for MySQLでIAM認証を利用するため、主に次の項目を設定しました。 Cloud SQLインスタンスでIAM認証を有効化する 開発メンバーごとのIAMデータベースユーザーを作成する Cloud SQLへログインするためのIAMロールを付与する Cloud SQL Studioを利用するためのIAMロールを付与する これらの設定は、Google Cloudコンソール、gcloud CLI、Terraformなどで行えます。MA部ではインフラ設定をTerraformで管理しているため、以降ではTerraformでの設定例を示します。 IAM認証の有効化 Cloud SQL for MySQLでIAM認証を有効化するには、インスタンスのデータベースフラグcloudsql_iam_authenticationを有効にします。 resource "google_sql_database_instance" "main" { # name, database_version, region などは省略しています settings { database_flags { name = "cloudsql_iam_authentication" value = "on" } } } IAMデータベースユーザーの作成 次に、開発メンバーをIAMデータベースユーザーとして作成します。 人間のGoogle CloudアカウントをIAMデータベースユーザーとして作成する場合は、typeにCLOUD_IAM_USERを指定します。 re
はじめに こんにちは。基幹システム本部 基幹開発部 商品管理ブロックの田中秀明です。 Claude CodeやCodexの利用が広がるほど、各人の使い方、プロンプト、レビュー観点、AIへ任せる範囲がばらつき始めました。AIを高度に使いこなせる人は開発の進め方そのものを変えられる一方で、これから使い始める人にとっては「どの工程で、どこまでAIに任せればよいのか」が分かりにくい状態になっています。 ZOZOでは2025年7月に、1人あたり月額200ドルを基準として、Claude Codeをはじめとする開発AIエージェントを全エンジニアに導入することを発表しました。 corp.zozo.com 利用可能なツールはClaude Code、Codex、Devin、Cursorなど多岐にわたっており、Claude Codeは数百名規模で利用されています。選択肢が増えること自体は前進ですが、組織として見ると、使い方が個人に閉じるほど開発プロセスの再現性は個人差に依存しやすくなります。 そこで基幹システム本部では、Claude CodeとCodexを組み合わせ、設計から実装、レビュー、必要に応じた画面確認までを支援する仕組みを作りました。この仕組みを/dev-initと/dev-resumeという2つの標準コマンドに集約しています。本記事では、この2コマンドの設計思想を紹介します。あわせて、内部でのClaude CodeとCodexの連携方法や、標準化によって変えようとしていることも説明します。 本記事で扱う内容は次の3点です。 AIの本質から逆算した「AIに任せる工程」と「人間が判断する工程」の線引き /dev-initと/dev-resumeの2コマンドに集約した理由 Claude Code × Codexの批判的対話による設計・実装レビューの仕組み 目次 はじめに 目次 問題は「AIを使っていないこと」ではなく「使い方が個人に閉じていること」 AIに任せる工程を、AIの本質から決める なぜ2コマンドなのか なぜ自前の標準コマンドなのか なぜClaude Code × Codexでレビューするのか 全体アーキテクチャ /dev-init:AIが迷わない初期状態を作る /dev-resume:AIが文脈を失わずに作業を続ける Codexレビューの中身 Playwright MCPで画面確認まで接続する セットアップ 標準化によって何が変わるか 残課題と今後 まとめ 問題は「AIを使っていないこと」ではなく「使い方が個人に閉じていること」 開発AIツールの導入初期は、まず個人が試して便利な使い方を見つけていく段階があります。これは自然な流れであり、実際にAIを使いこなすメンバーは、調査、設計、実装、レビュー観点の整理など多くの工程で生産性を高めています。 一方で、個人の工夫が増えるほど、組織としては別の問題が見えてきました。 ある人はJiraの内容を貼り付けて設計書を生成させる。別の人は差分だけを見せてレビューさせる。さらに別の人は、仕様の曖昧さをAIに洗い出させる。どれも有効な使い方です。しかし、プロンプト、判断基準、AIへ渡す情報、レビュー時に見る観点が人によって異なると、設計書、進捗管理、検証観点がバラバラの形式で蓄積されていきます。 この状態では、AI活用が進んでいるように見えても、組織全体の最低水準は上がりにくくなります。先端ユーザーの成果は伸びますが、その知見が再利用可能な形で残らないためです。 ZOZOでは、AI活用の状態を個人と組織の両面から捉えるために、All ZOZO AI Readiness Score、通称AZARSという指標も定義しています。AZARSでは、個人がAIをどの程度業務に組み込めているかだけでなく、組織として「AIを前提とした業務プロセスを仕組みに落とし込めているか」も見ます。求められるのは、AI活用が得意な人の存在に加えて、誰でも同じ入口から一定水準のAI駆動開発を始められる状態です。 この標準化で避けたかったのは、AIに「任せすぎる」ことと「任せなさすぎる」ことです。 任せすぎると、AIの出力を検証しないまま設計や実装が進み、要件の読み違いや品質低下につながります。一方で任せなさすぎると、調査、設計書化、タスク分解、レビュー観点の抽出のような、AIで短縮できる工程を人が手作業で続けることになります。この2つの間に、組織として再現可能な線を引く必要がありました。 ここから先は、その線を「2コマンドの形」にどう落とし込んだのかを、設計判断の順に紹介します。 AIに任せる工程を、AIの本質から決める AI駆動開発を標準化するうえで、最初に決めるべきことは「何をAIに任せるか」です。 現在のモデル性能だけを基準にすると、判断はすぐに古くなります。モデルやツールは短い周期で変わるため、「今このモデルならできる」「今このツールでは難しい」という視点だけで線を引いても、標準プロセスとして長く使い続けるのは難しくなります。 そこで、AIの仕組みからAIの本質的性質を「入力に対して、学習データと文脈からもっとも確からしい出力を返す変換機」であると捉えました。AIの出力品質は、入力の具体性、正解の一意性、学習データ上のパターンの豊富さに強く依存します。逆に言えば、入力と出力の対応関係が明確で、似たパターンが学習データに多くあるタスクほど、AIは安定して高い品質を出しやすくなります。 この性質は、情報の変換方向で整理できます。 AIに任せやすい変換パターンと人間が判断する領域 具体から抽象への変換は、複数の事例から共通項やパターンを抽出する作業です。たとえば障害報告から課題パターンを見つける、コード差分からレビュー観点を抽出する、仕様書から不足や矛盾を検出する作業はここに含まれます。AIの仕組みが力を発揮しやすい領域です。 同一レベルの変換は、ある形式の情報を別の形式に整形する作業です。Jiraチケットから設計書を作る、設計書から実装タスクを作る、Git diffからレビューコメントを作る作業がこれにあたります。入力と出力の対応が比較的明確であれば、AIに寄せやすい領域です。 一方で、抽象から具体への変換は注意が必要です。要求を整理して企画を立てる、複数の実現案からどれを選ぶか決める、限られた期間で何を優先するか判断する、といった作業がこれにあたります。ここでは入力にない情報を補い、組織事情、事業インパクト、リスク、顧客価値を踏まえて決める必要があります。AIは選択肢の列挙や論点整理を支援できますが、最終判断を標準コマンドに閉じ込めるにはリスクが高い領域です。 この整理から、次の方針にしました。 同一レベル変換はAIに任せる。Jiraから設計書、設計書から進捗管理表、仕様から実装、差分からレビューを作る 具体から抽象はAIに任せる。矛盾検出、レビュー観点抽出、影響範囲の整理に使う 抽象から具体は人間が主導する。企画、要求整理、優先順位、最終責任は人間が持つ つまり今回の2コマンドは、事業や要求の最終判断をAIに置き換えるものではありません。事業側から提供された企画や仕様の判断は人間が行うことを前提に、確定した仕様を正確に設計・実装・検証へつなげる領域を標準化するものです。 なぜ2コマンドなのか 標準化するとき、最初に迷うのはコマンドの粒度です。 工程ごとに細かく分ければ、調査、設計、タスク分解、実装、レビュー、テストといった各機能の責務は明確になります。しかし、入口が増えるほど利用者は「今どのコマンドを使うべきか」を判断しなければなりません。組織の最低水準として全員に使ってもらうには、最初の一歩が重くなります。 逆に、完全に1コマンドにする案もあります。利用者の認知コストは最小になりますが、開発初回だけは問題があります。設計書や進捗管理表がまだ存在しないため、AIが現在位置を特定する材料を持てません。初回は、以後のAI作業が参照する状態そのものを作る必要があります。 このため、初回作成と再開の2つに分けました。 /dev-init:Jira情報から、設計書、詳細設計兼・進捗管理表、必要に応じてテストパターンを作る /dev-resume:進捗管理表を読み、Gitの状態も見ながら、現在位置を特定して作業を再開する 選択肢 内容 長所 短所 判断 工程別に細かく分ける 調査・設計・実装・レビューを別コマンドにする 機能ごとの責務が明確 入口が増え、初心者のファーストステップが重い 不採用 完全に1コマンド化する 初回も再開もすべて1コマンド 利用者の認知コストは最小 初回は設計書・進捗管理表がなく、AIが現在位置を特定しにくい 不採用 初回作成と再開に分ける 初回/dev-init、以後/dev-resume 初回だけ不足情報を補い、以後は状態から再開できる コマンドが2つになる 採用 ここで重要なのは、2コマンドが「工程ごとに分ける」設計思想ではないことです。むしろ入口はできるだけ少なくしたいと考えています。/dev-init が存在する理由は、Git管理できる設計書・進捗管理表がまだ出力されていない初回だけ、AIが現在位置を特定できないからです。 進捗管理表が一度生成された後は、/dev-resumeがそのファイルを読み、現在のタスク、未着手タスク、ブロッカー、関連ファイル、Git diffを照合して次のアクションを提示できます。技術的に必要な初回だけを/dev-initとして分離し、それ以降は/dev-resumeに寄せます。このバランスが、利用者の学習コストとAIの技術的制約の接点でした。 なぜ自前の標準コマンドなのか AI開発支援のツールやフレームワークは次々に登場しています。汎用的な開発オーケストレーションツールを使う選択肢もあります。 それでも、基幹システム本部ではZOZOの開発工程に合わせた標準コマンドを用意しました。理由は、汎用性と最適化がトレードオフだからです。 汎用ツールは幅広い用途に対応できます。一方で、ZOZOのJira、Confluence、既存コード調査、設計書の粒度、進捗管理、レビュー観点、フロントエンド確認までを、日々の開発導線に深く埋め込むには限界があります。 各チームが自由にワークフローを作る方法もあります。現場ごとの自由度は高くなりますが、属人化が進み、組織全体の最低水準は上がりにくくなります。 そこで、利用者に見えるUIは/dev-initと/dev-resumeの2つに固定し、内部のプロンプトやSkills、連携ツールを継続的に更新する形にしました。世の中のAIツールやモデルが変わっても、利用者は毎回新しい操作体系を覚える必要がありません。標準コマンドの中身を改善すれば、組織全体のAI駆動開発をまとめてアップデートできます。この標準コマンドは、先端的な使い方を止めるためのものではありません。できる人は標準以上の使い方ができます。一方で、標準コマンドがあることで、組織としての最低水準を引き上げられます。先端ユーザーの知見を標準コマンドへフィードバックし、中身を改善し続けることが重要です。 なぜClaude Code × Codexでレビューするのか /dev-initと/dev-resumeではClaude Codeでベースラインを作成した後、Codexでレビューする仕組みを採用しています。 Claude Codeだけでも、設計書生成、実装、レビューはできます。それで十分な場面もあります。 ただし、品質を重視する場面で単一モデルに依存しすぎると、リスクがあります。モデルには得意不得意があり、提供側の性能調整の影響も受けます。また、同じモデルにセルフレビューさせる方法は実装しやすいものの、観点の独立性は強くありません。 そこでClaude CodeとCodexを別の役割で組み合わせました。 方式 長所 短所 採用判断 Claude Codeのみ 構成が単純で実行コストが低い 単一モデルの見落としや性能調整の影響を受けやすい 通常モードとして残す Claude Code × Codex 異なるモデルの観点を突き合わせられる コストと時間が増える 品質重視モードとして採用 Claude Codeは、全体のオーケストレーション、設計書生成、採否判断、修正実行を担当します。Codexは、リポジトリ内のコードを独自に調査したうえで、設計や実装を批判的にレビューします。 狙いは、片方のAIが出した答えを別のAIが独立した観点で疑うことです。 AIによるレビューの批判的対話は、次の3ラウンドを最小単位にしています。 Codexがリポジトリを独自調査し、設計や実装を批判的にレビューする Claude CodeがCodexの指摘を精査し、採用、却下、保留を判断する CodexがClaude Codeの却下判断や残存リスクを再批判する Claude CodeとCodexによる批判的対話レビュー レビュー回数を固定しているわけではありません。難しい変更では追加のサイクルが必要になり、軽い変更では早く収束します。自動実行では各レビューポイントにつき最大3サイクルまで実行し、同一の指摘内容が2サイクル連続で解消しない場合は対話型に切り替える設計です。これにより、難易度に応じてレビューの深さを変えつつ、無限に回り続けることを避けています。 全体アーキテクチャ 全体の流れは、Jira起票から始まります。 開発者はまず/dev-initを実行し、Jiraチケットの内容を渡します。Claude Codeはチケットの背景、受入条件、関連情報を解析し、サブエージェントを使ってコードベースやConfluenceを並列調査します。その結果をもとに、Confluence設計書、ローカルMarkdownの詳細設計を兼ねた進捗管理表、必要に応じてテストパターンを生成します。 その後の開発では/dev-resumeを使います。進捗管理表を読み、現在の進捗、未着手タスク、進行中タスク、関連ファイル、git status、git diffをもとに再開ポイントを提示します。実装後はCodexでレビューし、フロントエンド関連の変更でPlaywright MCPが利用できる場合は画面確認まで接続します。 Jiraからdev-init、dev-resume、レビュー、画面確認までの全体フロー 役割分担は次のように整理できます。 Claude Code:オーケストレー
はじめに こんにちは、Developer Engagementブロックの@wirohaです。5月13日に「RubyKaigi 2026 アフターイベント〜初参加LT・スポンサー4社のパネル〜」を開催しました。 株式会社ZOZO、株式会社リブセンス、株式会社TOKIUM、株式会社マイベストの4社共催で、RubyKaigi 2026を振り返るアフターイベントです。初参加エンジニアによるLTと、公募によるLT、各企業によるブース運営に関するパネルディスカッション、そして懇親会を行いました。 当日の雰囲気を含めてレポートします! 登壇内容まとめ 発表タイトル 登壇者 ESP32 IoTを動かしながらメモリ使用量を観測してみた話 株式会社ZOZO もっちゃん Rubyはただの言語に非ず 株式会社リブセンス こりん Rubyの内側を意識し始めた日 株式会社マイベスト koki515 RubyKaigi Mapを作って出そうとした話 株式会社TOKIUM ikeda 公募LT - パネルディスカッション 各社スポンサー担当 ESP32 IoTを動かしながらメモリ使用量を観測してみた話 speakerdeck.com 株式会社ZOZOのもっちゃんからは、ESP32とPicoRubyを使ってIoTシステムを構築した話がありました。メモリ消費量の節約への努力が感じられました。 Rubyはただの言語に非ず speakerdeck.com 株式会社リブセンスのこりんさんは、Rubyはただの言語ではなく文化であるとお話していました。RubyKaigi初参加ながら、RubyKaraokeといった関連イベントにも積極的に参加していたことが印象的でした。 Rubyの内側を意識し始めた日 speakerdeck.com 株式会社マイベストのkoki515さんは、Rubyコミッターの話を聞くことで内部構造をもっと理解したいと思うようになったそうです。RubyKaigiの会場には本屋さんがありCRubyの本を購入して読み始めたとのことで、良い学びの流れができているなと感じました。 RubyKaigi Mapを作って出そうとした話 speakerdeck.com 株式会社TOKIUMのikedaさんは、RubyKaigiの開催地を地図上にマッピングした「RubyKaigi Map」について発表しました。地震により当日披露が叶わなかったシステムを見ることができました。 ここまで、25卒の4名の若手エンジニアによる発表を紹介しました。「発表に慣れていない、緊張する」と言っていた方々もいましたが、堂々と意欲あふれる発表をされていました。 Spinelに貢献した話 speakerdeck.com 公募によるLT枠では、note株式会社のsacckeyさんよりRubyのAOTコンパイラであるSpinelにコントリビュートしたという発表がされました。「Spinelでは失敗するがCRubyでは成功する5行のRubyコード」という指標がわかりやすく、挑戦してみたくなる内容でした。 飛び入りLT 公募枠が1枠余っていたため、マイベストのKoyaさんが飛び入りでLTをしてくださいました。「カンマは演算子ではない」をテーマに、Rubyの文法を深掘りした内容でした。急遽対応いただきありがとうございました! パネルディスカッション 4社のスポンサー担当者による、ブース運営についてのパネルディスカッションを行いました。どんなブースを出して(出す予定で)いたか、その決め方や苦労などをお聞きできました。 当日見られなかったコンテンツを知ることができたり、SNSで話題になっていた投稿の裏側を知ることができたりと、興味深い内容が盛りだくさんでした。 最後に 発表の終了後には懇親会も行い、活発に交流する様子が見られました。ローカルオーガナイザーの方も参加してくださっていたため、参加者・運営・スポンサー企業といったさまざまな立場の方とのつながりが生まれていたように感じました。ご参加くださったみなさま、ありがとうございました! 来年のRubyKaigi 2027は宮崎での開催です。ZOZOは宮崎にオフィスがあるため、何か企画ができないものかと話し合っています。また来年もたくさんのRubyistたちとお会いできることを楽しみにしています! corp.zozo.com
.images-row {width: 100% !important;} Developer Engagementブロックの@ikkouです。2026年5月22・23日の2日間にわたりベルサール羽田空港で「TSKaigi 2026」が開催されました。 ZOZOはGold Sponsorとして協賛し、スポンサーブースを出展しました。ZOZOがTSKaigiに協賛するのは今回が初めてです。 technote.zozo.com 本記事では、前半はZOZOのWebフロントエンドエンジニアが気になったセッションを紹介します。後半では、ZOZOのスポンサーブースの様子と各社のブースにおけるコーディネートを写真中心に報告します。 ZOZOのWebフロントエンドエンジニアが気になったセッション 開発体験を左右するライブラリの API 設計 ― GraphQL スキーマ構築ライブラリから考える 「関数型プログラミング」を分解する.ts 純粋性について 型でエフェクトを表す いつテストを書くか?―ソフトウェア開発における安心と不安について考える LLM時代のリファクタリング戦略:AIエージェントによる段階的・安全なTS移行方法 TypeScript の型で副作用の実行順序を制御する ZOZOのスポンサーブースの紹介 協賛企業ブースのコーディネートまとめ おわりに ZOZOのWebフロントエンドエンジニアが気になったセッション 開発体験を左右するライブラリの API 設計 ― GraphQL スキーマ構築ライブラリから考える ssssotaです。izumin5210さんの「開発体験を左右するライブラリの API 設計 ― GraphQL スキーマ構築ライブラリから考える」を紹介します。 speakerdeck.com このセッションでは、スキーマや型情報をいかにTypeScriptの実装に接続するかという観点で、既存ライブラリのアプローチやその長短を深ぼる内容でした。弊社ではOpenAPIを使っているケースが非常に多く、いかにOpenAPIスキーマを実装に接続するかは往々にして発生する問題の1つです。 セッションではGraphQLに焦点が当てられていましたが、スキーマから実装を生成するスキーマファースト、コードからスキーマを生成するコードファースト、コードファーストのうちDecoratorsを使うパターン、DSL的な独自のbuilderパターン、計3パターンについて評価していました。比較・評価軸として、1.スキーマと実装の分離、2.型整合性、3.DBモデルとの接続、の3軸を用いています。 スキーマと実装の分離については、スキーマファーストが優れているのは言うまでもありませんが分離する強いモチベーションがなければ優先度は低くなります。型整合性は採用するライブラリのtype ergonomicに依りますが、コードファーストなDSL builderパターンが強い傾向にあります。DBモデルとの接続においてはGraphQL特有と見ることができますが、コードファーストなDSL builderパターンで型整合問題と合わせて解決できることを示唆しています。 セッションの最後には、自作のライブラリでこのギャップを埋める取り組みとAIを用いた評価結果を紹介していました。気になる方はスライドも合わせて確認してみてはいかがでしょうか。 私自身、OpenAPIスキーマと実装の接続に関して関心があり、ライブラリ(openapi-ts-hono)を作った経験から非常に共感できるところがありました。もちろんGraphQLとはギャップがありますが、スキーマと実装の分離、型整合性などは感覚としてもっていながらも、改めて言語化されることで気付きのあるセッションでした。 「関数型プログラミング」を分解する.ts www_REM_zzzです。おーみーさんの『「関数型プログラミング」を分解する.ts』を紹介します。 tsk-2026-aumy.vercel.app 自分の話ですが、TypeScriptに入門する前はScalaを書いていた経験があります。当時はコップ本と呼ばれる本とHaskellの公式ドキュメントが日本語で関数型プログラミングに入門する入口でした。Object指向プログラミングとは全く別の世界からやってきたような考え方で、面白くもあり、苦労もした過去があります。 このセッションでは、そもそも関数型プログラミングとは何なのかの考え方に触れながら、TypeScriptで真の関数型はできないのかに触れられています。僕もTypeScriptで真の関数型が書けたらいいのにと思った一人です(OCaml書けよというのは一旦置いといて)。スライドの中で語られた関数型プログラミングは「いい感じのソフトウェアを作るため」というのは本質的だなと思いました。ついつい手段に引っ張られてしまうところがあるのですが、心に留めておきたいです。 純粋性について 特に純粋性についてのところはReactでも他のライブラリでも語られる部分であり、意味の純粋性の部分は悩ましいと感じたことがあるので共感しました。 // 「副作用を表す値」を返すだけ(純粋関数) function pureAlert(msg: string) { return ["alert", msg] as const; } // 副作用の実行は別の関数に委ねる function executeAction(action: readonly ["alert" | "confirm", string]) { switch (action[0]) { case "alert": alert(action[1]); break; case "confirm": confirm(action[1]); break; } } const actions = [pureAlert("hey"), pureAlert("bye")]; actions.forEach((a) => executeAction(a)); 引用:https://tsk-2026-aumy.vercel.app/29 このような「何をするかの宣言」と「実行」が分離されている書き方は普段からできるし、メンテナンスを考えると普段から実践していきたいと思いました。 returnは「この関数の呼び出し元(= 継続)に値を渡して戻る」という考え方はTSを書いていてなんとなく感じていたものがはっきりと言語化されてスッキリした気持ちになりました。 型でエフェクトを表す () => T // 特に何も起きない純粋な処理 () => Option<T> // 失敗しうる処理 () => Promise<T> // 非同期処理 これを徹底すると 関数の型を見るだけで「何が起きるか・何が起きないか」がわかる 純粋な部分と副作用のある部分が型レベルで分離される 「支払い処理を起こしうる部分」だけを特定して二重実行を防げる これはTypeScriptを堅牢に書くうえで実践したいと思います。ちょうど業務でも似たシチュエーションがあることを思い出して、まず「この関数は副作用を持つか?」を命名(execute, get, !記法)で示すのが現実的な入口かなと思いました。 いつテストを書くか?―ソフトウェア開発における安心と不安について考える ジン(@Jin_pro_01)です。自分の気になったセッションとして、lacolacoさんの「いつテストを書くか?―ソフトウェア開発における安心と不安について考える」を紹介します。 docs.google.com このセッションでは、テストをどのような時に書くべきなのかを「開発者の安心と不安」を起点に問い直したlacolacoさんの気づきの共有、問いの提示、視点の提案をするというセッションでした。 セッションの中ではソフトウェアの保守性の本質は「変更容易性」であり、それは予期的変更容易性(変更する前に感じる不安)と経験的変更容易性(変更をする中で実際に感じる手応え)の二層モデルとして見ることができるとしていました。その上でテストはその両方にフィードバックを返すセンサーであるとし、変更前に感じる不安があるならそれを取り除く安心のために書き、変更のしやすさを試したり構造に問題が見つかったりするなら設計を見直すために書くという体系的な整理がされており、とても興味深いセッションでした。 自分が従事しているZOZOTOWNでは、新規機能の実装や既存機能の改修と並行で、フロントエンドリプレイスも各チームで進行しています。ZOZOTOWNの発展を止めずに開発を進める体制である一方、考慮すべきことが多く、自分にとっては比較的「予期的変更容易性」が低い状態だと表現できることに気づきました。そして、まさにこの「予期的変更容易性」を高めるためのテストへの投資価値が高いと感じました。 さらにAIを使ってコーディングをしていく時代に入り、開発の生産量が増える一方で、自分が直接書いていないコードや構造との距離は広がっていきます。その距離は新たな不安、つまり予期的変更容易性の低下にもつながると感じています。だからこそ変更の前後で「振る舞いが変わっていないこと」を担保し、その不安を取り除くセンサーとしてのテストの価値は、AI時代にこそますます高まっていくのだと考えました。 <p
こんにちは、基幹システム本部リプレイス推進部のssssotaです。本記事では、TSKaigi 2026、ZOZOのスポンサーブースで実施したクイズを紹介・解説します。 はじめに TSKaigi 2026は、2026年5月に実施されたTypeScriptに関するカンファレンスです。ZOZOはゴールドスポンサーとして参加し、スポンサーブースでTypeScriptやJavaScriptに関するクイズを実施しました。TSKaigi 2026のレポートは以下の記事にまとめていますので、あわせてご覧ください。 techblog.zozo.com 来場者の皆さんに体験してもらったクイズアプリはGitHubリポジトリで公開しています。RippleというUIフレームワークを用いてAIと共に実装しました。興味のある方はぜひリポジトリもご覧ください。 github.com Rippleに興味のある方は、私がTSKaigi 2026で発表した登壇資料もあわせてご覧ください。 speakerdeck.com 目次 はじめに 目次 Day 1 TypeScript Q1. 次のコードはエラーになる? (tsconfig strict:true) Q2. X の型は? Q3. TypeScript 7 はなんの言語で開発されている? Q4. enum を TypeScript コンパイラに渡すとどのような JavaScript コードが出力される? Q5. erasableSyntaxOnly でエラーになるのは? JavaScript Q6. 次のコードの出力は? Q7. 次の式の結果は? ランタイム Q8. 次の JavaScript ファイルを実行するとエラーになるのは? Q9. 次の JavaScript ファイルを実行するとエラーになるのは? Q10. URL クラスはどの組織・仕様グループで標準化されている? Day 2 TypeScript Q1. X の型は? Q2. satisfies の正しい挙動は? Q3. TypeScript 7 (tsgo) の開発コードネームは? Q4. 返り値の型が void で推論されるのは? JavaScript Q5. 次のコードの結果は? Q6. 次のうち JavaScript (ECMAScript) の予約語は? Q7. 次の式の結果は? ランタイム Q8. 次の TypeScript ファイルを実行するとエラーになるのは? Q9. globalThis.navigator.share() メソッドが使えるのは? Q10. fetch API はどの組織・仕様グループで標準化されている? おわりに Day 1 TypeScript Q1. 次のコードはエラーになる? (tsconfig strict:true) const a = 1 + '1'; 実行時エラー コンパイルエラー ならない 答えと解説 正解: 3. ならない JavaScriptではnumberとstringの + 演算はstringへの暗黙変換で評価され、TypeScriptもこのケースは許容するためコンパイル/実行どちらもエラーになりません。禁止したい場合はESLint (typescript-eslint) の restrict-plus-operands やOxlintの typescript/restrict-plus-operands ルールを使用する必要があります。 Q2. X の型は? type X = unknown extends number ? true : false; true false boolean 答えと解説 正解: 2. false unknown は最上位型で number に代入可能ではないため、Conditional Typeは false 側に分岐します(参考:TypeScript Playground) Q3. TypeScript 7 はなんの言語で開発されている? TypeScript Rust Go 答えと解説 正解: 3. Go TypeScript 7 (tsgo) はネイティブ実装としてGoで書き直されています。 Q4. enum を TypeScript コンパイラに渡すとどのような JavaScript コードが出力される? enum Hoge { a, b } const a: Hoge = Hoge.a; IIFEでHogeオブジェクトを構築する形に展開される const enumと同等にインライン定数へ展開される 答えと解説 正解: 1. IIFE で Hoge オブジェクトを構築する形に展開される 通常のenumはランタイムオブジェクトとして残り、IIFEで双方向マップを構築する形に展開されます。const enumはインライン化されます(参考:TypeScript Playground) Q5. erasableSyntaxOnly でエラーになるのは? class Hoge { private a?: number // A private b() {} // B constructor(private c: number) {} // C } A B C 答えと解説 正解: 3. C parameter properties (constructor の private c) は、コード除去するだけでは等価にできずエラーになります。 JavaScript Q6. 次のコードの出力は? console.log(typeof null); "null" "undefined" "object" 答えと解説 正解: 3. "object" 歴史的経緯により typeof null は "object" を返します(参考:typeof - JavaScript | MDN) Q7. 次の式の結果は? <pre class="code lang-javascript" data
はじめに こんにちは。WEARバックエンド部SREブロックの春日です。普段はWEARというサービスのSREとして開発・運用に携わっています。 本記事では、WEARのハイブリッド検索のリリースに伴い刷新した検索インデクシングシステム(以下、インデクサー)について、OpenSearch Ingestionを採用しようとした際にハマったポイントや、ベクトル検索のためのインデクサーを設計する上で工夫した点を中心に紹介します。 目次 はじめに 目次 背景 既存のインデクサーと刷新の動機 ベクトルデータの保持方法の検討 インデクサーの構成方針 BigQuery → S3 のデータ連携 日次更新の設計 差分更新の設計 初期設計:OpenSearch Ingestion+Lambdaプロセッサでのベクトル化とインデクシング 1万件の差分更新で表面化した問題 再設計:ベクトル化Lambdaを前段に出す 最終設計:S3+SQS+Lambdaで非同期にベクトル化とインデクシング ベクトル化Lambdaでの工夫 Bedrockのリージョン分散 1ファイル単位の処理量を制御する 出力形式と後処理 OpenSearch投入Lambdaでの工夫 external versionで古いデータで新しいデータを上書きすることを防止 処理完了後のファイル削除 Lambdaエラー時のファイル退避 非同期処理の完了待機 既存データに対する初回ベクトル化 結果 まとめ 背景 WEARでは、検索基盤としてAmazon OpenSearch Service(以下、OpenSearch)を利用しています1。これまでフリーワード検索ではタグマッチングを主軸としていましたが、タグが付与されていない検索ワードに対する検索結果の質と量に課題がありました。 これを改善するため、ベクトル検索と全文検索を組み合わせたハイブリッド検索(WEARではあいまい検索と呼んでいるため、以下「あいまい検索」と表記)をリリースすることになりました2。 あいまい検索のためにベクトル検索を導入するには、検索対象の各documentに対して、タイトル・説明文・タグなどを連結したテキストをベクトル化したフィールドを持たせる必要があります。しかしながら、既存のインデクサーでこのフローを実現するのは難しく、インデクサー自体を刷新することになりました。本記事ではその刷新の過程と、設計時に行った工夫を紹介します。 既存のインデクサーと刷新の動機 WEARではOpenSearchへのインデクシングをEmbulkを用いて行っていました。 embulk-input-bigqueryとembulk-output-elasticsearchなどを組み合わせ、BigQueryからOpenSearchへデータを連携する構成です。EmbulkのジョブはDigdagのworkflowで管理し、Amazon EKS(以下、EKS)上のJobとして実行していました3。インデクサーには差分更新と日次更新の2種類があり、それぞれ次の役割を持っていました。 差分更新:10分間隔で実行。直近で新規投稿・更新documentをインデクシングし、削除された投稿をindexから削除 日次更新:1日1回、新しいindexを作成して全件をインデクシングし、Blue/Greenでエイリアスを切り替える形で全件更新する。統計データなどの日次で更新すべき値はこのタイミングで反映 しかし、ベクトル検索の導入を検討するにあたり、この構成にはいくつかの課題がありました。 WEARで一番大きいコーディネートのindexは大量のdocumentを持っており、これらを毎日ベクトル化するのはコストと処理時間の両面で非現実的 BigQueryからOpenSearchへの連携中にベクトル化の処理を挟むのが困難 本対応の検討時点でEmbulkはすでにメンテナンスがされていない状態であり、長期的な保守性に不安 これらを踏まえ、ベクトル検索対応に必要な機能と、長期的な保守性の両方を満たす構成へとインデクサーを刷新する方針を決めました。 ベクトルデータの保持方法の検討 最初に取り組んだのが、ベクトルデータをどこに、どのタイミングで持たせるかという検討です。 既存の日次更新では新しいindexを毎日作成して全件インデクシングしていましたが、大量のデータを毎日全件ベクトル化するのは非現実的なため、ベクトルデータを別ストレージに保存しておく案を検討しました。しかし、ベクトル取得時のパフォーマンスやコスト面で見合わないと判断し、最終的には日次での全件更新そのものを廃止する方針を取りました。 毎日indexを全件更新するメリットの1つとして、indexの不整合が発生した場合に、日次での全件更新によって整合性を保つことができるという点がありました。これは例として、差分更新の失敗時のリトライで、古いデータで新しいデータが上書きされてしまうといった状況が挙げられます。全件更新を廃止するにあたり、この点をどう担保するのかが課題でしたが、後述する方法でdocumentのバージョニングを行うことで、不整合が発生しないようにしました。 新しい設計では、ベクトル化は差分更新のみで行い、日次更新では同じindexに対して日次で更新すべき値のみを上書きするように責務を分けました。これにより、ベクトル化を投稿の追加・更新時のみに限定でき、ベクトル化コストと処理時間の問題を回避できるようになりました。 インデクサーの構成方針 インデクサー刷新にあたって、ベクトル化を含む新しい構成として複数の選択肢を検討しましたが、OpenSearch Ingestionを軸とする構成を採用しました。判断のポイントは以下の通りです。 自前で運用する外部ツールは最小限にしたい(Embulkのように追加でメンテナンスが必要なツールを増やしたくない) データ抽出のSQLはバックエンドエンジニア、インデクサーのインフラ構築・運用はSREという責務分離を維持し、両者を疎結合にしたい AWS公式のLambdaプロセッサでベクトル化するパターンを参考にすれば、ベクトル化部分をLambdaへ切り出して柔軟に構成できそう これらを総合的に考慮し、Amazon S3(以下、S3)を起点としたAWS Lambda(以下、Lambda)の構成を方針として進めることになりました。 BigQuery → S3 のデータ連携 WEARではMicrosoft SQL ServerからBigQueryへリアルタイム連携をしており、インデクサー側もBigQueryからデータを取得しています。前述の通り、インデクサーはS3を起点としてデータを処理する設計を取っているため、BigQueryから取得したデータをS3に連携する必要があります。BigQueryから直接S3へ出力する機能はないため、いったんCloud Storage(以下、GCS)へ出力してからS3へ転送する形を取りました。 GCSへの出力にはBigQueryのEXPORT DATA文を利用しています。差分更新・日次更新いずれもJSON Lines形式でGCSへ出力するように記述しており、以下に差分更新を例にしたものを記載します。 EXPORT DATA OPTIONS( uri='gs://GCS_BUCKET/coordinates/diff/raw-data/YYYY/MM/DD/HH/mm/data_*.jsonl', format='JSON', overwrite=true ) AS -- 対象データを取得するクエリ ... uriにワイルドカード(*)を含めることで、BigQueryが出力サイズに応じて自動的に複数ファイルへ分割します。出力フォーマットはJSONを指定するとJSON Lines形式になります。 GCSからS3への転送方法は、差分更新と日次更新で異なるツールを使い分けています。 差分更新:rclone 日次更新:AWS DataSync(以下、DataSync) DataSyncは大量データの高速転送に適していますが、タスクの起動・実行に約5分かかります。差分更新は10分間隔で実行する上、ベクトル化のような時間のかかる処理も挟まるため、起動に時間のかかるDataSyncは許容できませんでした。差分更新ではデータ量がそこまで多くないこともあり、rcloneを採用しています。 日次更新の設計 日次更新では、もともとEmbulkで実装されていた全件更新を廃止し、差分更新と同じindexに対して統計データなどの日次で更新すべき値のみを上書きする方式に変更しました。日次更新の構成は以下の通りです。 日次更新はOpenSearch Ingestionを採用しており、S3に格納された全件データに対してS3 scanでOpenSearchへbulkでupsertしています。OpenSearch Ingestionのパイプライン定義の例は以下のとおりです。 version: 2 coordinates-daily-indexer: source: s3: acknowledgments: true delete_s3_objects_on_read: true scan: buckets: - bucket: name: ${BUCKET_NAME} filter: include_prefix: ["coordinates/daily/raw-data/"] aws: region: ap-northeast-1 sts_role_arn: ${STS_ROLE_ARN} codec: ndjson: {} processor: - delete_entries: with_keys: <s
はじめに こんにちは。プラットフォームSREブロックの酒部・高塚・亀井です。私たちは2026年5月14日〜15日に名古屋で開催された「クラウドネイティブ会議」に参加してきました。本記事では印象に残ったセッションをご紹介します! はじめに クラウドネイティブ会議とは セッションレポート キーノート:老舗IoTクラウドサービス組織の変革 -クラウドネイティブをはじめよう- GameDay:チームで挑むリアルな障害対応 100マイクロサービスのTerraform/Kubernetes管理地獄から抜け出すためのAI活用術 巨大組織の認知負荷をどう下げるか?ソフトバンクが描くCNAP×Backstageによるクラウドネイティブの新時代 生成AI時代に信頼性をどう保ち続けるか - Policy as Codeの実践 おわりに クラウドネイティブ会議とは クラウドネイティブ会議は、CloudNative Days、Platform Engineering Kaigi、SRE Kaigiという3つの大規模テックイベントで合同開催されたカンファレンスです。現地参加とオンライン参加のハイブリッド形式で開催され、会場の名古屋・中日ホール&カンファレンスには約1,000人が集まりました。 当日の様子は公式のXのポストまとめで見ることができます。現地の雰囲気を感じることができるので、ぜひご覧ください。 posfie.com セッションレポート ここからは各メンバーからのセッション紹介をお届けします。 キーノート:老舗IoTクラウドサービス組織の変革 -クラウドネイティブをはじめよう- kaigi.cloudnativedays.jp 高塚です。1日目のキーノートでは、パナソニックさんの事例として、巨大なモノリスだった老舗IoTサービスをマイクロサービス化した熱い話を聞くことができました。 見切り発車でマイクロサービス化を始めたものの、最初は問題が山積みだったとのことです。 老舗IoTクラウドサービス組織の変革 -クラウドネイティブをはじめよう- アーカイブ動画 6:19 より引用 かなり昔からあるAWSアカウントでしか見られない「ap-northeast-1b」アベイラビリティゾーンがあった話では会場もざわざわしていました。 老舗IoTクラウドサービス組織の変革 -クラウドネイティブをはじめよう- アーカイブ動画 7:00 より引用 GitLabの導入は、社内から「Gitを使うなんて危険だ、けしからん」という声も上がるなど「正直ここが一番しんどかった部分」だったそうです。しかし、そういった意見を軽々しく否定せず、既存のシステムがお客様に価値を提供してきたことに敬意を持ちながらクラウドネイティブ化を進めたそうです。 老舗IoTクラウドサービス組織の変革 -クラウドネイティブをはじめよう- アーカイブ動画 8:22 より引用 その結果、無事に本番稼働させることができました。 老舗IoTクラウドサービス組織の変革 -クラウドネイティブをはじめよう- アーカイブ動画 15:26 より引用 後半ではIoT特有のE2Eトレーシングについて紹介がありました。 組み込みデバイスはCPU・メモリが限られており、スパン送信による性能劣化を避けるため、OpenTelemetry SDKは利用せずにトレーシングを自前実装したとのことです。 老舗IoTクラウドサービス組織の変革 -クラウドネイティブをはじめよう- アーカイブ動画 23:45 より引用 また、IoTデバイスはネットワークやOSなどの問題が障害の原因になりやすいため、eBPFでカーネルイベントもスパンにしたそうです。 老舗IoTクラウドサービス組織の変革 -クラウドネイティブをはじめよう- アーカイブ動画 24:45 より引用 約30分の発表はとても濃い内容で、大変勉強になりました。また私もZOZOTOWNのマイクロサービス化を長年担当しているため、発表で赤裸々に語られる苦労話には「わかりみが深い〜」と100回くらい頷いていました。 本記事では主に技術的な話をご紹介しましたが、組織の文化を醸成する話や「これからクラウドネイティブをはじめる方へのメッセージ」などとても学びになる内容が盛りだくさんですので、ぜひアーカイブ動画をご覧ください。 GameDay:チームで挑むリアルな障害対応 酒部です。GameDayはKubernetes環境上で発生するさまざまな障害シナリオに対して、チームで協力して原因を特定し、復旧させるという内容でした。参加者にはKubernetes上で稼働するアプリケーション環境が提供され、その環境にはあらかじめ障害が仕込まれており、クエスト形式で出題される問題を解きながら、システムを正常な状態に復旧させていくという形式でした。 形式は約2時間のチーム対抗戦で、Kubernetes初心者から経験者まで幅広い層が集まっていました。ルールはシンプルで、障害を復旧することでスコアが加算され、最終的にチーム単位で順位が決まる仕組みです。 困ったときに相談できるメンターがサポートしてくれる体制に加え、行き詰まっても段階的なヒントで前に進める仕組みも用意されており、勝負というよりもチームで協力して問題に取り組む過程が重視されているように感じました。 題材として用意されていたのは、OpenTelemetry DemoをベースとしたECサイトのマイクロサービス構成です。各チームにCode Server、Grafana、Jaeger、Argo CD、GitHubリポジトリが一式与えられました。基本フローは障害を発見 → GitHub上で修正 → commit & push → Argo CDが自動同期 → Verifierによって各問題の合否判定する流れです。 問題の内容や解説は下記の公式記事に解説があるため、そちらをご覧ください。 kaigi.cloudnativedays.jp 結果としては、最後の問題だけ時間内に解くことができませんでした。ただ、作問者が解かせるつもりで作っていないと言うほどの難問で、ほとんどのチームが解けていなかったので、終了後残り時間で会場でも簡単な解説をしていただきました。解説後、会場のあちこちから「そういうことだったのか」「やられた」といった声が漏れ、参加者全員が思わず唸ってしまうような巧妙な問題設計だったことが印象に残っています。 観察・操作系のチュートリアル問題も用意されていて、Kubernetesに触り始めたばかりの方でも問題なく楽しめる内容で、自信を持っておすすめできるプログラムでした。 100マイクロサービスのTerraform/Kubernetes管理地獄から抜け出すためのAI活用術 kaigi.cloudnativedays.jp speakerdeck.com 酒部です。このセッションでは膨大なTerraformやKubernetesマニフェストファイルを扱う構成において、日々の運用をAIエージェントでどう解決するかがテーマでした。 セッションは大きく4パートで構成されており、以下のトピックが扱われました。 Linear × Codexで全マイクロサービスのTerraform / K8s manifest更新を効率化 AIによるレビューで200 PR/dayの50%を自動化 問い合わせ・トラブルシューティングをAIに任せる試み Production Readiness Check(PRC)のEvidence確認もAIにやってもらう 特に、2と4は弊チームと似た取り組みも紹介されており大変興味深い内容でした。 まず、PRレビューへのAI活用について、Notionに整理されたガイドラインがあるのに、サービス・人数の増加で存在を知らない人が増え、結果としてガイドラインが守られないという課題がありました。 解決アプローチはガイドラインをAIエージェントが利用できる形でリポジトリに降ろすというものです。Notionは引き続きSoT(Source of Truth)として残しつつ、AIレビュー用のコンテキストはリポジトリに固定する設計としました。 紹介されていた実例では、Codex ReviewがIAM設定の重複を「社内ガイドライン違反」として指摘し、参照したガイドラインのファイルパスまで引用していました。一般論ではなく、社内ルールに照らした具体的な指摘ができている点が印象的でした。AIに指摘されない部分こそが暗黙知であるという気づきから、「AIが指摘してこない部分」を観察することで、ドキュメント化されていない暗黙知が浮かび上がってくる、というメタな視点が学びになりました。 本番リリース前のProduction Readiness Check(PRC)の自動化ですが、開発者はEvidenceを集め、SREはその妥当性をチェックするという、両者にとって骨の折れるプロセスがあるそうです。ここで、いきなりAIに丸投げするのではなく、AIに任せるべき項目を選定したり、Code化できる項目はCIで自動チェックしたりするなど工夫されていました。 また、AIに任せるためフォーマットを整理する過程で、以前はやや解釈が難しいPRC項目もあったが、自動化に向けて解釈がブレないようにしたという副次効果も語られていました。AI活用のためのドキュメント整備が、人間にとっても理解しやすいドキュメント整備につながるとい
はじめに こんにちは、ZOZOTOWN開発本部Webバックエンドブロックのひでです。普段はZOZOTOWNのバックエンド領域を担当しています。 Webバックエンドブロックでは2026年1月より、カスタマーサポートチーム(以下CSチーム)から技術調査として開発側にエスカレーションされる問い合わせの効率化に取り組んでいます。エスカレーション後の調査では、データ・ログ・過去の会話・コードベースなど複数のツールを横断して情報を集める必要があります。そのため、1件あたりの対応に多くの時間がかかるという課題がありました。本記事ではこの課題に対し、AIを活用して開発側での一次回答までを自動化し、調査のリードタイムを平均70%(※アンケートベース)削減できた取り組みをご紹介します。 (※本記事における「CS問い合わせ」は、CSチームで解決に至らず、技術調査のためにエンジニアへエスカレーションされたものを指します。CS側でクローズする問い合わせは本記事の対象外です) 目次 はじめに 目次 背景・課題 CS問い合わせ対応の背景 抱えていた課題 1. 確認すべき情報源が多い 2. 情報源ごとにツールが異なり、メンバー間の習熟度に差がある 3. リプレイス過渡期で全体像を把握しづらい 解決の取り組み システム全体像 マルチリポジトリ対応のWorkflow設定 調査スキルの設計 Step 1: 問い合わせ内容の取得 Step 2: Webバックエンド担当判定(webbe-judge サブスキル) Step 3: 影響画面・リポジトリの特定(scope-finder サブスキル) Step 4: Slack過去類似問い合わせの調査 Step 5: Splunkログ調査 Step 6: コードベース調査 Step 7: 統合レポートの作成とスキル改善メモ 効果 調査リードタイムの大幅短縮 サポート担当への負荷集中の解消 調査品質の均質化 見えてきた課題 蓄積知識ファイルの更新が手動依存 実DBデータを参照する調査ができない 一次回答の精度ばらつきとハルシネーション 得られた知見 LLMは「オペレーションエンジン」として使える MCPでナレッジを集約せずに横断利用できる Skillが「動くマニュアル」になる マルチリポジトリ横断でリプレイス過渡期でも機能する 今後の展望 蓄積知識ファイルへの反映フローの仕組み化 BigQuery MCPなどによる実DBデータへの調査範囲の拡張 ハルシネーションを抑える評価ループの整備 他チームへの展開と各チーム固有Skillの整備 まとめ 背景・課題 CS問い合わせ対応の背景 CS問い合わせは次のようなフローで対応しています。 ユーザーが問い合わせを送信 CSチームが受け付けて一次対応 CS側で解決できず技術調査が必要と判断されたもののみ、過去の類似問い合わせをもとに関連するシステム担当チームへエスカレーション Webバックエンド内のCS問い合わせ回答メンバーが調査・回答 本記事で取り上げる自動化のスコープは、ステップ4のWebバックエンド内での調査です。このステップ4の調査において、徐々に次のような課題が見えてきました。 抱えていた課題 1. 確認すべき情報源が多い 会員データや注文データといった業務データ、アプリケーションログ、過去の問い合わせ会話、関連コードなど、複数の情報源を横断して確認する必要があります。問い合わせの内容によって1件あたり1〜2時間、内容によっては2時間以上かかることも珍しくありませんでした。 2. 情報源ごとにツールが異なり、メンバー間の習熟度に差がある それぞれの情報源は別々のツールで扱う必要があり、ツールの使い方や検索の勘所にもメンバーごとに差が出ます。新しくジョインしたメンバーや初めて触れる領域を担当する場合は独力での調査が難しく、専任のサポート担当が伴走する体制を取っていました。結果としてサポート担当に負荷が集中しがちで、調査・回答までのリードタイムが長くなる要因にもなっていました。 3. リプレイス過渡期で全体像を把握しづらい Webバックエンドの調査範囲は、下図の紫色で示したとおりリプレイス前環境とリプレイス後環境の両方にまたがります。同じ事象でもまず新旧どちらの環境で発生しているのかを切り分け、そのうえで該当する側の構成・ログ・コードを掘り下げる、という二段構えの調査が必要でした。 さらに画面によっては、新旧環境の構成が混在しているケースもあり、片方だけを見ても原因を特定できないことがあります。この場合は新旧両方の構成・ログを並行して確認する必要があり、調査の複雑さがさらに増します。 加えてリプレイスは日々進行しており、リプレイス全体像の把握自体が難しく切り分けに余計なコストがかかっていました。 解決の取り組み システム全体像 これらの課題に対し、CS問い合わせの一次回答までをAIで自動化する仕組みを構築しました。SlackとGitHubを起点とした以下の構成です。 各コンポーネントの役割と採用理由は次のとおりです。 コンポーネント 役割 採用理由 Devin Slackに投稿されたCS問い合わせを拾い、内容をGitHub Issueとして起票する Slack上での会話体験が良く、CSチームの普段のやりとりをそのまま自動化の入り口にできる Claude Code Actions GitHub Issueをトリガに起動し、調査用Agent Skillsを実行する調査エンジン Agent Skills機能で複数ステップの調査ロジックを定式化できる(検討時点ではDevinにAgent Skills機能がなく、現在は追加されている) Splunk MCP / Slack MCP ログ・過去会話を横断的に検索するインタフェース 既存運用しているSaaSにそのままアクセスできる 役割としては、Slack ⇄ GitHubのインタフェース層をDevinに、調査ロジックの実行エンジンをClaude Code Actionsに分担しています。 マルチリポジトリ対応のWorkflow設定 Claudeが複数リポジトリを横断して調査できるよう、Workflow YAMLを工夫しています。関連リポジトリを順にactions/checkoutでチェックアウトしてからClaudeを起動する構成です。 # claude code actionsの設定のymlファイル jobs: inquiry-investigation: runs-on: ubuntu-latest steps: - name: Checkout skill repo uses: actions/checkout@v4 - name: Checkout オンプレミスのリポジトリ uses: actions/checkout@v4 with: repository: org/onpre-repo path: repos/onpre-repo - name: Checkout FEのリポジトリ uses: actions/checkout@v4 with: repository: org/fe-repo path: repos/fe-repo - name: Checkout Akamaiのリポジトリ uses: actions/checkout@v4 with: repository: org/akamai-repo path: repos/akamai-repo - name: Checkout BFFのリポジトリ uses: actions/checkout@v4 with: repository: org/bff-repo path: repos/bff-repo - name: Run Claude Code Action uses: anthropics/claude-code-action@v1 with: mcp-config: .mcp/config.json これによりClaudeが「複数リポジトリにまたがるコードベース」を1つの調査文脈として扱えるようになりました。リプレイス前のオンプレミスからAkamaiのルーティング設定・FE・BFFまでを一気通貫で参照できる構成です。 調査スキルの設計
ZOZO開発組織の2026年4月分の活動を振り返り、ZOZO TECH BLOGで公開した記事や登壇・掲載情報などをまとめたMonthly Tech Reportをお届けします。 ZOZO TECH BLOG 2026年4月は、前月のMonthly Tech Reportを含む計11本の記事を公開しました。中でもWEARバックエンドの無停止移行に関する記事は詳細に記されています。ぜひご覧ください。 techblog.zozo.com 登壇 JAWS-UG山梨 【第11回】勉強会 4月4日に開催された「JAWS-UG山梨 【第11回】勉強会」に、SRE部の姫野が登壇しました。 AWS Data & AI イノベーションフォーラム:顧客成功事例から学ぶデータ活用の最前線 DAY1 4月9日に開催された「AWS Data & AI イノベーションフォーラム:顧客成功事例から学ぶデータ活用の最前線 DAY1」に、SRE部の伊藤が登壇しました。 Claude Code Skills実践! - 業務を効率化する活用事例 4月9日に開催された「Claude Code Skills実践! - 業務を効率化する活用事例」に、ZOZOTOWN開発3部の平林(@bayacollector)が登壇しました。 try! Swift Tokyo 2026 4月12-13日に開催された「try! Swift Tokyo 2026」に、ZOZOTOWN開発2部の續橋(@tsuzuki817)が登壇しました。 【ZOZO x Mercari x LayerX】企業R&D勉強会 〜 研究と実用化のリアル〜 4月24日に開催された「【ZOZO x Mercari x LayerX】企業R&D勉強会 〜 研究と実用化のリアル〜」に、ZOZO研究所の清水が登壇しました。 協賛 RubyKaigi 2026 2026年4月22日から24日の3日間にわたり函館で開催された「RubyKaigi 2026」にプラチナスポンサーとして協賛しました。 technote.zozo.com techblog.zozo.com 掲載 ZOZO独自のAI活用指標「All ZOZO AI Readiness Score(AZARS)」 4月8日にZOZO独自のAI活用指標である「All ZOZO AI Readiness Score(AZARS)」の導入を発表しました。 AZARS(アザース)は「組織AI活用レベル」と「個人AI活用レベル」の2つで構成され、生成AIを含むAI活用において、業務上期待される能力と状態をそれぞれ4段階で定義した指標です。本指標の導入により、主観的になりがちなAI活用度を全社統一の基準で可視化・評価することが可能になります。またAZARSは、エンジニアなどの開発者と、事業・コーポレート部門といった非開発者の双方に共通する指標を定めている点に特徴があります。これにより、職種にかかわらず、同一の基準でAI活用を推進することが可能になります。 corp.zozo.com この「AZARS」導入に関連した記事が複数のメディアに掲載されました。 www.itmedia.co.jp www.nikkei.com AI活用はどう可視化して推進する? ZOZOが導入した全職種共通のAI活用指標「All ZOZO AI Readiness Score(AZARS)」とは | ネットショップ担当者フォーラム ZOZOの似合うコーデAI ラボくん 4月27日に対話で日常の服選びをサポートするLINE公式アカウント「ZOZOの似合うコーデAI ラボくん」の開設を発表しました。 「ZOZOの似合うコーデAI ラボくん」は、ユーザーの言語化しにくいファッションの好みやニーズを「ラボくん」との会話によって引き出し、要望を具体化することで、ユーザーの嗜好や利用シーンに応じたコーディネートを提案します。多くの方が利用するLINEという日常的な接点をチャネルとすることで、ファッションの情報探索や相談をより身近で手軽なものにし、日常の服選びをサポートします。 corp.zozo.com この「ZOZOの似合うコーデAI ラボくん」開設に関連した記事が複数のメディアに掲載されました。 netkeizai.com www.ryutsuu.biz ZOZOがLINEでコーデ相談AI ファッションECの「検索から対話」の試金石 - WWDJAPAN 日経BOOKプラス 日経BOOKプラスのゼロから創らない戦略に、ZOZOのビジネスモデルに関する記事が掲載されました。 bookplus.nikkei.com その他 香りの総合プラットフォーム「カラリア」を運営する株式会社High Linkを完全子会社化 4月30日に株式会社High Linkの全株式を取得し完全子会社化 したことを発表しました。 当社は、今後の戦略の一つとして「Near Fashion領域」での成長を掲げ、ファッションの周辺領域における事業推進と利益創出を目指しています。ファッションと親和性が高い香水を中心にした事業を手掛けるHigh LinkをZOZOグループに迎えることで、当社グループはフレグランス市場への領域拡大を図るとともに、サブスクリプションサービスをはじめとする販売手法を取り入れ、ファッション周辺領域における事業展開を加速します。今後は当社の顧客基盤を活用したHigh Linkの各サービスへの送客や、当社のEC運営ノウハウおよびデータを活用し、香水との新たな出会いを促すディスカバリー体験の提供などにも取り組む予定です。 corp.zozo.com 2026年3月期 通期決算発表 4月30日に2026年3月期 通期決算発表を開示しました。詳細は以下のリンクにある開示資料をご確認ください。 <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%
はじめに こんにちは、ブランドソリューション開発本部ZOZOMO部FBZブロックの池上 寛登です。2026年3月にZOZOへ入社し、Fulfillment by ZOZO(以下、FBZ)のバックエンド開発を担当しています。 FBZに参画してまず直面したのは、ドメイン知識の壁でした。中でも強く実感したのが、コードレビューの場面です。Pull Request(以下、PR)のレビューには、判断の根拠がドキュメントに載っていない「暗黙知の壁」がありました。既存メンバーの指摘は的確ですが、新規参画者の自分には同じ品質でレビューする難しさがありました。 この課題を解決するために、暗黙知を形式知としてガイドライン化し、Claude Code SkillsとAmazon Bedrockに組み込んだPR自動レビュー基盤を作成しました。本記事では、その仕組みと設計判断を紹介します。 目次 はじめに 目次 背景:FBZにおけるPRレビューの課題 アプローチの全体像 ガイドラインの設計 暗黙知の収集 レイヤー別ガイドラインの作成 変更パスに応じた参照ルールの選択 実行基盤の構成 実行環境 PRサイズに応じたモデル選択 2段レビューによる誤検知抑制 Confidence(確信度)閾値と Severity(重要度)ラベル ai-reviewedラベルによる再レビュー ガイドライン更新の自動提案 効果 ドメイン固有のアンチパターン検出 実装品質の向上 新規参画者にとってのドメインリファレンス まとめ 背景:FBZにおけるPRレビューの課題 FBZはZOZOTOWNの倉庫リソースを活用し、外部のブランドが運営する自社ECへ物流・決済・返金などの機能を提供するフルフィルメントサービスです。在庫同期・注文管理など、扱うドメインは多岐にわたります。 さらにFBZは複数のリポジトリで構成されており、リポジトリごとに採用言語やアーキテクチャが異なります。PRレビューで見るべき観点もリポジトリごとに大きく変わるため、レビュアーには横断的な知識が求められます。 実装やレビューの参考になるようなガイドラインは存在したものの、リポジトリごとに保存場所が異なり、内容の鮮度や粒度も統一されていませんでした。結果として、判断はレビュアー個人の経験知に大きく依存し、次の3つの課題が顕在化していました。 レビュアーごとに観点が異なり、レビュー品質にばらつきが出る 新規参画者がドメイン知識をキャッチアップするまでに時間を要する 同じアンチパターンの指摘が、異なるPRで繰り返し発生する アプローチの全体像 これらの課題に対し、ガイドラインを中心に据えたPR自動レビュー基盤を構築しました。 取り組みは大きく次の3層に分かれます。 ガイドラインの設計:暗黙知を形式知へ落とし込み、レイヤー別ファイルとNG/OKペアで定義する 実行基盤の構成:Claude Code Actionを実行し、関連するガイドラインを読み込んでPRをレビューする ガイドライン更新の自動提案:過去のレビューコメントからガイドラインの更新提案を自動生成し、ルールの陳腐化を防ぐ それぞれを順に説明します。 ガイドラインの設計 暗黙知の収集 ガイドラインに落とし込む暗黙知は、PRのレビューコメントから収集しました。当初はSlack(コミュニケーションツール)の議論やConfluence(社内Wiki、ADR)の設計メモからも抽出を試みました。しかしFBZの場合は設計判断や指摘の根拠の多くがPRのレビュー会話に蓄積されていたため、最終的にPRを主な情報源とすることにしました。 過去のPRレビューを横断的に分析し、次の観点に該当する指摘をルール化しました。 同種の指摘が繰り返し発生している 既存のドキュメントではカバーされていない チーム全体で共有すべき設計判断が含まれている レイヤー別ガイドラインの作成 収集した暗黙知をルール化するため、ドメインで頻出する論点を抽出し、アーキテクチャレイヤーごとのファイルへ分割しました。具体的には、レイヤー責務・データ整合性・エラー設計・テスト/コーディング規約・インフラ/セキュリティといった観点で章を分け、全章で前提となる共通ファイルを別途用意しています。 各ルールはNG/OKペアの形式で記述しています。形式を統一することで、LLMがルールの境界を判定しやすくなります。 # NG: 呼び出し元で税率や端数処理をハードコードしている total = round(price * 1.1) # OK: ドメイン共通の計算ロジックを通し、税率や端数ルールを一元化できている total = PriceCalculator.with_tax(price) 変更パスに応じた参照ルールの選択 レビュー時にガイドラインを全て読み込むと、コンテキストが肥大化して精度も落ちます。そこで、変更ファイルのパスから参照すべきガイドラインを対応付ける表を定義しました。 この対応表は SKILL.md に記述しており、LLMが変更ファイル一覧と対応表を照らし合わせて、該当する章だけを動的にロードすることを実現しています。 'app/service/**': layer-rules/layer-responsibility.md 'app/dataaccess/**': layer-rules/data-integrity.md 'tests/**': layer-rules/test-and-coding.md 実行基盤の構成 実行基盤の全体像は以下のとおりです。図中の各要素については、次節以降で順に解説していきます。 実行環境 基盤としてはGitHub Actions上で動作するClaude Code Actionを採用し、モデル呼び出し先にはAmazon Bedrockを選びました。 ガバナンス要件として、社外のサービスへソースコードを送信せず、社内AWSアカウントに閉じた状態でモデルを利用する必要があったためです。GitHub ActionsからはOIDCでAWSにAssumeRoleし、Bedrockの推論プロファイル経由でClaudeモデルを呼び出します。これによりIAM・ログ・モデル呼出のすべてを社内環境に閉じた構成で運用できます。 PRサイズに応じたモデル選択 PRのサイズに応じて、レビューに使うモデルを動的に切り替えています。PRサイズは、差分の行数と変更ファイル数の組み合わせで判定しています。Opusは検証段階で精度向上の幅がコストに見合わなかったため、採用していません。SonnetとHaikuの使い分けで、精度・コスト・レイテンシをバランス良く確保できました。 PRサイズ モデル 設計判断 小規模・単一ファイル Claude Haiku 軽量な判定で十分な品質を確保できるため、コストとレイテンシを優先する 中〜大規模 Claude Sonnet レビュー本処理の標準モデル。大規模PRでは影響範囲の調査で往復回数が増えるため、ターン上限(LLMがツール呼び出しを連続で行える回数の上限)を引き上げて対応する 2段レビューによる誤検知抑制 LLMによる自動レビューで課題となるのが、誤検知(False Positive)と見逃し(False Negative)です。誤検知が多ければBotの指摘は信頼されなくなり、見逃しが多ければ自動レビュー自体の意義が失われます。 この課題に対し、単一セッション内でペルソナを切り替えながら2段でレビューする仕組みを取り入れました。 Reviewer Passは「網羅的に拾うペルソナ」として候補を出し切り、Validator Passは「批判的に再検証するペルソナ」として根拠を再確認します。役割を意識的に分離することで、互いに独立した判断が成立します。検証段階で複数のレビュー方式を比較したところ、この方式が最も誤検知を抑えられました。 Confidence(確信度)閾値と Severity(重要度)ラベル Validator Passでは、各指摘に対してチェック項目を採点し、合計値をその指摘のConfidenceとして算出します。チェック項目は「根拠コードを再読み込みしたか」「PR説明文を踏まえているか」「影響範囲を確認したか」などです。 指摘にはImportant・Pre-existing・Nitの3種類のSeverityを付与し、それぞれに投稿するためのConfidence閾値を設けています。Importantは厳しめ、Nitは緩めの閾値です。閾値を満たさない候補は、たとえReviewer Passで挙がっていても投稿しません。 Severityはコメント先頭に[Important]のようなテキストラベルとして付与します。PR作成者はラベルを見て対応の優先度を判断できます。 ai-reviewedラベルによる再レビュー 不要な再実行を避けてコストを抑えるため、レビュー完了時にPRへai-reviewedラベルを付与しています。PR作成者は修正後にラベルを外すだけで、必要なときだけ再レビューを起動できます。 また再レビュー時はノイズ抑制のため、ブロッキング相当の指摘であるImportantとPre-existingのコメントのみを投稿するよう制御しています。 ガイドライン更新の自動提案 ガイドラインは、コードベースや設計判断の変化に追従できないため、時間の経過とともに陳腐化します。これを避けるため、直近のレビューコメントと既存ガイドラインを照らし合わせ、追加・修正・削除を含む更新提案を起票するワークフローを毎月実行するようにしました。 また、ガイドライン更新時の判断基準を統一するため、追記場所・文体・重複チェック手順をまとめたメタガイドラインを別ファイルで用意しています。これにより、ガイドラインの一貫性を保つことができます。 効果 運用開始からまだ日が浅いですが、現時点で見えている効果は次のとおりです。 ドメイン固有のアンチパターン検出 ガイドラインに沿ったレビューが行われるため、レイヤー責務・データ整合性・エラー設計といったドメイン色の強い論点も、自動レビューで捕捉できるようになりました。 実装品質の向上 今回の対応でプロジェクトにおける暗黙知を形式知化できました。例えばこのガイドラインを .claude/rules/ に配置することで、開発時のClaude Codeも同じルールを参照する効果を期待できます。 新規参画者にとってのドメインリファレンス 副次効果として、ガイドラインは新規参画者の学習リファレンスとしても機能しています。私自身もこのガイドラインで「FBZのService層はどう書くのが正解か」を確認しながら実装を進めてきました。 まとめ 本記事では、Claude Code SkillsとAmazon Bedrockを組み合わせてFBZドメインに特化したPR自動レビュー基盤を構築した取り組みを紹介しました。 ドメイン知識を抱えるチームでPRレビューの自動化を検討している方は、ぜひ参考にしてみてください。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com
はじめに こんにちは、WEAR開発部SREブロックの木内です。普段はWEARのSREとして開発、運用に携わっています。 WEARは2013年にサービスを開始し長年オンプレミスで運用されてきましたが、過去にクラウド(AWS)へのシステムリプレイスを実施しています。その際にWebアプリのCDNとしてFastlyを採用し、オンプレミスからクラウドへの段階的な移行を実現しました。 採用の決め手は主に以下の点です。 パスベースのルーティングが可能(パスごとにオンプレミスとクラウドのオリジンを切り替えられる) ネイキッドドメインへの対応 設定変更の即時反映による迅速なロールバック リプレイスの詳細については、以下の記事をご参照ください。 techblog.zozo.com Fastlyを用いた構成はサービスを止めずに安全なリプレイスを実現するうえで大きく貢献しました。一方で、リプレイスが完了しFastlyを導入した当初の目的を達成した後も残り続けたことで、運用負荷やコストといった課題が顕在化してきました。 本記事では、過渡期の構成を整理し、CDNをFastlyからAmazon CloudFront(以下、CloudFront)へ移行してAWSに統一した取り組みを紹介します。 目次 はじめに 目次 移行の背景・課題 運用負荷 コスト 移行後の構成 移行方法 1. LP・アセットの切り替え 2. 動的コンテンツ・認証処理の切り替え 3. VCLの処理をCloudFront Functionsへ移行 4. DNS切り替え WAFの移行タイミング 設計のポイント 移行期間中のリクエスト CloudFront Functionsを用いたリダイレクト・リライト カスタムエラーレスポンス 得られた成果 運用のシンプル化 インフラコストの削減 まとめ 移行の背景・課題 移行前の構成は以下の通りです。 Fastlyをフロントに置き、バックエンドにALBとS3が2つずつ、計4つのオリジンを持つ構成です。 ALBは動的コンテンツの配信や認証処理を担っており、S3はLPやアセットなどの静的コンテンツを配信しています。S3の前段にはそれぞれ個別のCloudFrontを使用しています。 また、FastlyではVCL1を用いてCDN以外の多くの処理も担っていました。 パスごとのオリジン振り分け 各種ブロック(IP / ASN / リファラ など) Basic認証 メンテナンスモード リダイレクト / リライト 前述の通り、リプレイス時にFastlyを採用したことで安全にリプレイスを進めることができましたが、リプレイス完了後に以下のような課題が残りました。 運用負荷 AWSとFastlyの二重管理が運用負荷に繋がっていました。 WEARの大部分はすでにCloudFrontを使用しており、2つのCDNそれぞれでキャッチアップが必要だった AWSとFastlyそれぞれでユーザーやリソースの管理も必要だった 設定変更をWebチームとSREチームで担っていたため、Fastly専用のインフラリポジトリを別途設けており、CIの整備やレビューフローの維持など、リポジトリが増えることに伴う管理コストが発生していた コスト FastlyをAWSの前段に置く構成であるため、大きく分けて2種類のコストが発生していました。 Fastlyがユーザーへ配信するコスト AWSがFastlyへ配信するコスト また、LPやアセットはすでにCloudFrontを使用していたため、FastlyとCloudFront両方のCDNコストが発生していた点も見過ごせません。 これらの課題を解消するために、CDNをCloudFrontへ移行し、配信基盤をAWSへ統一する方針を取りました。 移行後の構成 移行後の構成は以下の通りです。 移行後は、ALB・S3など全てのバックエンドの前段に1つのCloudFrontを配置しています。 また、FastlyのVCLで実施していた処理はそれぞれ対応するAWSサービスへ移行しました。 処理 Fastly AWS CDN・配信 Fastly CDN CloudFront パスごとのオリジン振り分け VCL CloudFront(Cache Behavior) 各種ブロック(IP / ASN / リファラ など) VCL AWS WAF Basic認証 VCL AWS WAF メンテナンスモード VCL AWS WAF リダイレクト / リライト VCL CloudFront Functions Basic認証・メンテナンスモードはAWS WAF(以下、WAF)のカスタムレスポンス機能を利用して実装しています。WAFのルールにマッチしたリクエストに対して、任意のHTTPステータスコード・レスポンスヘッダー・レスポンスボディを返せる機能です。 セキュリティ関連の処理をWAFに集約することで一元管理できることに加え、CloudFront Functionsのコードサイズや実行時間の制限2を避けられる点も採用の理由です。 リダイレクト・リライトの実装にあたり、Lambda@EdgeとCloudFront Functionsを比較検討しました。Lambda@Edgeは複雑な処理や長時間実行に向いている一方、今回のような軽量なリダイレクト・リライト処理にはCloudFront Functionsが適しています3。加えてスケール量・リクエスト料金の面でも有利なため採用しました。 さらに、リダイレクト・リライトはルール数が多いため、jsをビルドしminify化しながらCloudFront Functionsのコードサイズ制限を超過しないよう工夫しています。CloudFront KeyValueStoreを使用したサイズ圧縮も検討しましたが、フロントとインフラ間の依存関係の簡素化や認知負荷の低減を優先したかったからです。 これらの設計を経て、FastlyのVCLに分散していたエッジ処理をAWS WAFとCloudFront Functionsに集約し、CDNレイヤーの機能をすべてAWS上で完結させる構成になりました。 移行方法 今回のCDN切り替えはフェーズを分けて段階的に実施しました。このCDNはPC・SP全体のトラフィックを受けているため、問題が発生すれば多くのユーザーへ影響が出てしまいます。そのため、いかにユーザー影響を出さず安全に移行するかが本プロジェクトの鍵でした。 具体的なフェーズは以下の通りです。Fastlyの後段にCloudFrontを配置し、影響範囲が小さいものから順にCloudFront経由へ切り替えていきました。 1. LP・アセットの切り替え 最初はS3で配信しているLPとアセットの切り替えです。静的コンテンツは影響範囲が限定的で切り戻しもしやすいため、最初の移行対象としました。 切り替えにあたっては全パスを網羅するURLリストを用意してスクリプトで動作確認を行い、移行前後の挙動に差異がないことを確認しながら実施しました。 2. 動的コンテンツ・認証処理の切り替え 次に動的コンテンツの配信や認証処理を担うALBの切り替えです。動的コンテンツの配信や認証処理を担っているため、CDN切り替えによるヘッダーやキャッシュの挙動の変化が配信や認証に影響を与えるリスクがありました。 影響範囲を抑えるためにALBは1台ずつ切り替え、バックエンドチームにも協力してもらい機能リグレッションがないことを確認しながら進めました。 加えて、ダークカナリアリリース(ユーザーには見えない形で一部のトラフィックを新しい構成に流し、本番環境で検証する手法)でCloudFront経由に流し、環境差異による不具合がないかも検証しました。 3. VCLの処理をCloudFront Functionsへ移行 続いてVCLのリダイレクト・リライト処理を移行しました。VCLの処理の中にはリクエスト元の国別判定をした上でリダイレクトをする処理もあり、障害につながりやすい部分であったため、切り戻しやすさを考慮して2段階に分けて対応しました。 動作確認はFastlyで実施していたリダイレクト・リライト処理を網羅的に検証できるスクリプトをWebチームが作成してくれており、そちらを活用し移行前後の挙動に差異がないことを確認しながら進めました。 スクリプトはDenoのテストフレームワークで書かれており、各パスへのリクエストに対してステータスコード・リダイレクト先・レスポンスヘッダーを検証します。さらにHTMLレスポンス内のJS・CSSアセットも正常に取得できるかまで確認しており、移行後の挙動を一通りカバーしています。 また、VCLの移行はオリジンの切り替えと異なり、パスごとに挙動が細かく異なります。そのため追加のスクリプトも作成し、リダイレクト・リライト対象外のパスへの影響がないか、Serverヘッダーでオリジンが意図した経路を通っているかを、1パスずつ地道に確認していきました。 4. DNS切り替え 最後にDNSを切り替えてFastlyからCloudFrontへ完全移行しました。 WEARはDNSにAkamaiを使用しています。今回、AkamaiのChange Listを活用し、ダウンタイムなしで切り替えを実現しました。 CloudFrontのIPアドレスは固定されていないため、FastlyのIPを登録していたAレコードからCloudFrontのドメインを指定するCNAMEへの変更が必要でした。しかしAレコードとCNAMEは共存できないため、削除と追加を別々に適用すると瞬断のリスクがありました。Change ListはDNSレコードの変更をまとめてアトミックに適用できる機能で、これを活用することでダウンタイムなしでの切り替えを可能にしています。切り替えは有事の際に素早く切り戻せるよう、事前にTTLを60秒に下げた上で実施しました。 また、次のセクションで詳しく説明しますが、ブロックやBasic認証等のWAF切り替えもこのタイミングで同時に実施しています。 WAFの移行タイミング WAFはIP・国・ASNなどの判定を、送信元IPまたはX-Forwarded-For(XFF)ヘッダーのIPアドレスを基に行います。 DNS切り替え前はFastlyがリクエストを受け取るため、AWS WAFから見るとアクセス元のIPがFastlyのIPになります。この状態でWAFを先に切り替えると、ブロックルールがFastlyのIPに対して適用され、意図しないブロックや、本来ブロックすべきリクエストが通過してしまうリスクがありました。 XFFを参照することでクライアントIPに基づく判定も可能ですが、以下の理由から採用しませんでした。 Fastlyの判定ロジックが送信元IPを利用しており、仕様を変えたくなかった DNS切り替え前後でXFFの内容が変わるため、切り替えのタイミングで挙動が変わることを避けたかった そのため、DNS切り替えまでの間はFastly側のブロック設定を残し、WAFの切り替えはDNS切り替えと同時に行いました。 なお、事前の動作確認はCloudFrontのFQDNに直接curlでアクセスして行いました。DNS切り替え前はFastlyがエンドポイントのため、CloudFrontのFQDNに対してアクセスする必要があります。CloudFrontはHTTPS接続時にHostヘッダーの値でSSL証明書を選択するため、Hostヘッダーにドメインを指定することで正しく証明書検証が行えます。 CloudFront FunctionsについてもDNS切り替え前はリクエストがFastlyを経由するため、リクエスト元の国別判定で同様の問題がありました。 こちらはWebチームが解決策を検討・実装してくれました。DNS切り替え前の移行期間中に限り、Fastly側でCloudFrontの仕様に準ずるヘッダーを付与しました。CloudFront Functions側でもそのヘッダーを参照することで、正しく地域判定が行える構成にしています。 設計のポイント ここまで移行の手順を紹介しました。次に、移行にあたって設計上特に考慮が必要だったCloudFrontのビヘイビア設計について紹介します。 CloudFrontでは、リクエストのURIパターンに応じて処理方法を定義
はじめに こんにちは、ZOZOTOWN開発1部iOSブロックの荻野です(@juginon)。 みなさんに日々使っていただいているZOZOTOWN iOSアプリのホーム画面ですが、実は2024年秋から2026年の年初まで約1年半、水面下でリアーキテクチャを行っていました。 リアーキテクチャに着手する前の当時の私はアーキテクチャ設計への理解がまだ浅く、「実際に手を動かしながら身につけたい」という動機でこのリアーキテクチャを主導しました。自分にとってはチャレンジングな取り組みで、アーキテクチャ設計やテスト設計への理解が実践を通して大きく深まったプロジェクトになりました。 本記事では、そのリアーキテクチャのすべての軌跡と、そこで得た学びをお伝えします。 なお、本記事で紹介するホーム画面リファクタリングは、iOSチーム全体で取り組んでいるアーキテクチャ刷新の具体的な事例の1つでもあります。チームとしての取り組みや知識共有の仕組みについてはZOZOTOWNのiOSアーキテクチャとチーム進化の軌跡にもまとめています。本記事と合わせて読むと、個々の取り組みとチーム全体の文脈をより立体的に理解できます。 目次 はじめに 目次 ホーム画面について タブ モジュール ホーム画面が抱えていた課題 当初の設計と、その後の運用実態の乖離 継承構造が不要になった ログ管理の複雑化 MVCによるViewControllerへの責務集中 高い改修頻度 リファクタリングの設計方針 方針1: 影響範囲を最小化しながら段階的に進める 方針2: 段階的に責務を分離する Step1: Objective-Cレガシー型への依存を剥がす Step2: 最も独立性の高いAPIを対象に、ViewModel/UseCaseを部分導入する 小さく始めることの重要性 Step3: MallHomeViewController全体にViewModel/UseCaseを導入する 問題1. Swift Concurrencyへの移行 問題2. モジュール構築メソッドの整理 Step3完了後にログに関するバグが発覚 バグを引き起こした原因 Step3-ex: 命名整理とユニットテストの追加 命名の整理 ユニットテストの追加 不確かさに気づいた時点でテストを書く Step4: HomeViewControllerにViewModel/UseCaseを導入する TDDによる設計の共有 オンボーディング周りの状態管理 リファクタリング前の課題 ステートマシンによる再設計 長期リファクタリングを進める上でのポイント おわりに ホーム画面について ZOZOTOWN iOSアプリのホーム画面は以下のように、主にタブとモジュールによって構成されています。 タブ 画面上部に表示されている「すべて」「コスメ」部分を指します。タブは切り替えが可能で、すべてタブではアパレル・シューズ・コスメ等すべての商品が、コスメタブではコスメ商品特化の画面表示になります。 実装上は以下の2種類のViewControllerで構成されています。 HomeViewController: ホームタブのルート画面となる画面全体を管理するViewController ヘッダーや検索窓など、両方のタブで共通して表示する部分、ホーム画面全体の管理を担う MallHomeViewController: すべてタブ/コスメタブのコンテンツを管理するViewController それぞれのタブで表示が変わる部分の管理を担う モジュール 各タブのコンテンツは、複数の「モジュール」と呼ばれるブロックが縦に並んだ構成です。モジュールとは、性別選択・バナー・チェックしたアイテムといった、個々のコンテンツ単位のことです。 ユーザーがホーム画面をスクロールすると、これらのモジュールが順番に表示されます。 ホーム画面が抱えていた課題 当初の設計と、その後の運用実態の乖離 ホーム画面の複雑さを理解するには、2021年のフルリニューアル時の背景を知る必要があります。 2021年3月のZOZOTOWNフルリニューアルで初めてタブ構成が導入されました。当時は3つのタブがあり、MallHomeViewControllerを基底クラスとした3つのサブクラスによる継承構成を採用しました。各タブで固有の処理が発生することを見越した設計です。 当時の取り組みについてはZOZOTOWNアプリ Home画面のリニューアルにおけるアーキテクチャ再設計でも詳しく紹介されています。 しかし、フルリニューアルから3年以上が経過し、運用を重ねる中で当初の設計前提が変わっていきました。 継承構造が不要になった 従来ではMallHomeViewControllerを継承する各タブのクラスを作成していましたが、各タブで固有の処理は実際にはほとんど発生しませんでした。 タブの種類を保持するだけで十分な状態で、各タブで専用のクラスを作成する構造はかえって全体像の把握を難しくしていました。 ログ管理の複雑化 リニューアル当初はGA(Google Analytics)のみだったログ送信を専用のLoggerクラスが管理していました。しかしその後、社内分析用ログなど複数種別のログが追加されていく中で、Logger自身が複雑な状態管理を担うようになっていきました。 複数のフラグがLoggerの内部に積み重なり、MallHomeViewControllerが持つ状態と常に同期させる必要が生じました。また、ログに関する責務分離が適切に行われていない部分もあり、こういった構造がコードを読む際のコストを高める要因の1つになっていきました。 MVCによるViewControllerへの責務集中 2021年当時はMVCアーキテクチャを採用していたため、API呼び出し・UI状態管理・ビジネスロジックの調整が MallHomeViewControllerに集中していました。前述のLoggerクラスとの状態同期もVCが直接担っており、改修を加えるたびにVC・Logger双方への影響を考慮しなければなりませんでした。こうした積み重ねで行数は再び1000行を超えるまでに膨らんでしまっていました。 特に問題だったのは、UICollectionViewへのデータ構築と商品押下時のログデータ作成が混在する500行弱の巨大なメソッドです。どこを触れば何が変わるのか把握するだけで大きなコストがかかる状態でした。 高い改修頻度 ZOZOTOWNのホーム画面は平均月1ペースで改修案件が入り、多い時期には3案件が同時並行で走ることもあります。 このリアーキテクチャが開始してから現在まででも、ホーム画面のモジュールを無限スクロールできる機能や、モジュール内のアイテムで動画を表示する機能など、規模の大きな案件がリリースされています。 影響範囲の把握が困難なFat ViewControllerは、改修のたびにリスクを伴い、チームの開発速度を下げる原因になっていました。 リファクタリングの設計方針 課題は明確でしたが、1000行超のVCを一気に書き換えるのはリスクが高すぎます。そこで以下の方針を立てました。 なお、このリファクタリングは通常の機能開発と並行して進めており、稼働の約2割をこの取り組みに充てながら進めていました。1年半という期間はそのためです。 方針1: 影響範囲を最小化しながら段階的に進める 各ステップの影響範囲を小さく保つことで、問題発生時の修正コストを抑えられ、PRの変更量も少なくなりレビューの負担を減らせます。また各ステップを独立してリリース可能な単位とすることで、他案件の進行をブロックしません。 以上のメリットを意識しながら以下のステップで進める計画を立てました(当初は4ステップ、結果として5ステップになりました)。 ステップ 内容 Step1 Objective-Cレガシー型への依存を剥がす Step2 最も独立性の高いAPIを対象に、ViewModel/UseCaseを部分導入する Step3 MallHomeViewController全体にViewModel/UseCaseを導入する Step3-ex Step3完了後にバグが発覚し、命名整理とユニットテストを追加 Step4 HomeViewControllerにViewModel/UseCaseを導入する ステップを設計する上でのポイントを3点紹介します。 Step1を最初に行った理由 MallHomeViewControllerにはObjective-Cのレガシーな型への依存がありました。MVVM化を先に進めると、ViewModel/UseCaseはObjCの型を扱う設計になります。その後ObjC依存を除去すると、ViewModel/UseCaseの設計変更も必要になり手戻りが発生します。そのため、MVVM化の前段階として依存の除去を最初のステップとしました。 MallHomeViewControllerから先に着手した理由 タブの中身を管理している MallHomeViewController は、着手開始から間もなく後続案件の改修が入る予定でした。そのため、それより前にMVVM化を完遂させることを優先しました。 Step2とStep3を分けた理由 ホーム画面では複数のAPIを呼び出しており、最初から全APIを対象とするとMVVM化の影響範囲が大きくなりすぎます。まず独立性の高い一部のAPIに絞ってViewModel/UseCaseを導入することで、アーキテクチャの全体像を小さな変更で確認でき、問題が発生した際の修正コストも抑えられます。 方針2: 段階的に責務を分離する UseCase → ViewModel → ViewControllerの順で責務を分離していき、最終的に以下の構成を目指しました。当時のアーキテクチャガイドラインではUseCaseの採用が定められていました。またAPIリクエスト・ログ送信・ビジネスロジックが複合的に絡むホーム画面の規模感においても、ViewModelの肥大化を防ぐうえで適切な設計判断でした。 ここで紹介している大まかな全体方針は、以前チームメンバーのなんしーさんが行った商品詳細画面のリアーキテクチャにおける進め方を参考にしています。 Step1: Objective-Cレガシー型への依存を剥がす MallHomeViewControllerでは、商品情報を表示する部分がObjective-Cで書かれたレガシーな型に依存しており、APIレスポンスからレガシーな型へ変換する不要な依存がありました。そのため、最初のステップはMVVM化でなく不要な依存の除去から始めました。 以下の3段階で依存を剥がしました。 商品の情報表示において必要な情報を持つUIModelを作成 APIレスポンスをそのUIModelに変換するTranslatorを作成 Translatorは外部APIのレスポンス型をUIModelの型に変換する責務を持つ 外部APIの型定義が変更されてもViewModelやVCへ直接影響しない構造になる レガシーな型を使わない新しいセルを実装し、移行 最終的にMallHomeViewControllerからObjective-Cレガシー型への依存を完全に除去しました。 Step2: 最も独立性の高いAPIを対象に、ViewModel/UseCaseを部分導入する Step1でクリーンな基盤ができたため、いよいよMVVM化に着手します。設計計画で「最も独立性が高い」と判断した世代別ランキングモジュールから始めました。 世代別ランキングモジュールとは、ユーザーが世代(~10代、20代など)を選択すると、その世代の人気アイテムがランキング形式で表示されるモジュールです。 ヘッダーの世代選択ボタンをタップして切り替えると、対応するランキングが再取得・再表示されます。 以下の特徴があったため、ホーム画面のMVVM化における最初のステップとして工数がかからず、アーキテクチャの全体像を実装しながら理解できる最適な題材と判断し、着手しました。 世代別ランキング専用の独立したAPIを持つ ユーザーが世代を選択したときだけ更新される 他のモジュールの更新と独立して動作する 小さく始めることの重要性 Step2は全部で7つのPRを作成しました。UseCase作成→UIModel作成→ViewModel作成→ViewControllerからUseCase/ViewModelへ処理を移動する流れで修正を加えていきました。 巨大なViewControllerを一気に書き換えようとすると、変更が大きくなりすぎてレビューが困難になり、バグ混入リスクも高まります。Step2でOpenした7つのPRのほとんどが100行未満のコード追加に収まっており、レビューでの指摘もほとんどなくスムーズにマージできました。 <p
はじめに こんにちは、データ基盤ブロックの平本(@cisetn)です。 本記事では、ZOZOTOWNのリアルタイムデータ連携基盤の中核であるETL層を作り直した事例を紹介します。対象はオンプレミスのSQL ServerからBigQueryへリアルタイムにデータを連携する基盤です。そのETL層をGoで実装したプラグイン(実行基盤はFluent Bit)で再設計しました。 ZOZOのリアルタイム連携基盤は2020年に一度紹介記事を公開していますが、それ以降、段階的にアーキテクチャを見直してきました。本記事はその中でもETL層の再設計にフォーカスします。 想定読者は、リアルタイム連携基盤やストリーミング処理基盤の設計・運用に関わる方です。 本記事で扱うこと、扱わないことは次のとおりです。 扱う:ZOZOのリアルタイム連携の全体像、今回リプレイスした基盤の背景・設計・実装 扱わない:BigQuery側のテーブル設計、SQL Server側のChange Tracking設定、利用側(BI・分析クエリ等) 目次 はじめに 目次 ZOZOのリアルタイムデータ連携の全体像 これまでの変遷 リプレイスに至った背景 顕在化してきた課題 新基盤アーキテクチャ 設計の軸 技術選定:Fluent Bit + Goプラグイン 全体構成 大量のデータをリアルタイムで捌くために考えたこと 新基盤の構成 INPUT内部:取得とエンコードを分けた OUTPUT内部:送信とACK確認を分けた 結果 今後の展望:Change Data Captureへの移行 まとめ ZOZOのリアルタイムデータ連携の全体像 本題の前に、ZOZOにおけるリアルタイム連携の全体像を軽く俯瞰しておきます。本記事のテーマがあくまで「その中のひとつ」であることを共有するためです。 ZOZOではデータソースが多岐にわたります。オンプレミスのものもあれば、クラウド上のものもあり、MySQL、SQL Server、DynamoDBなどさまざまです。当然、差分を検知する手段もソースに応じて変わりますし、連携の実現方式も1つではありません。 マネージド / SaaSで済むケース:例えばMySQL → BigQueryであればDatastreamを利用する 専用のパイプラインを組む必要があるケース:例えばDynamoDB → BigQueryのように、対応するマネージドサービスがない場合は、別途データ連携のパイプラインを構築する必要がある 結果として、ZOZOのリアルタイム連携基盤は複数系統に分かれて共存しています。本記事で扱うのは、そのうちオンプレ SQL Server → BigQueryの系統です。本番環境(prd)で約400のテーブルを連携対象としており、新規の連携依頼も日々発生するため、データ基盤の運用において比重の大きな系統となっています。SQL ServerのChange Tracking機能で変更を検知し、プラグインで取得したレコードをPub/Sub経由でBigQueryに流しています。 これまでの変遷 実は、本記事で扱う系統は今回が初めてのリプレイスではありません。以下の変遷を経ています。 時期 アーキテクチャ 主目的 2020 Qlik Replicate→ fluentd + Dataflow→ BigQuery 安定性向上 + コスト削減 2024 fluentd + BigQuery Subscription(Dataflow を廃止) コスト削減 2025 プラグインによる ETL 層の再設計+ BigQuery Subscription 効率改善(メモリ・スループット・コスト) 2024年には、ストリーム処理層のDataflowを廃止し、Pub/SubのBigQuery Subscriptionに置き換えるリプレイスが行われました。このフェーズの主目的はコスト削減です。 そして今回、ETL層をプラグインで再設計したのが本記事のテーマです。詳細な背景と目標は次章で述べますが、結果として、コスト削減・メモリ効率の改善・スループット向上・運用課題の解消といった効果につながりました(数値は末尾)。 リプレイスに至った背景 誤解のないよう先に述べておくと、旧基盤の設計が「悪かった」わけではありません。2020年当時、ZOZOのデータ基盤はまさに拡大していくフェーズにあり、リアルタイム連携の需要も増え始めたばかりでした。そうした状況では、プラグインが豊富なfluentdとDataflowのように既存のツールを組み合わせて素早く構築できる構成は合理的な選択だったかと思います。実際、信頼性(データ欠損が起きないこと)はチェックポイント機構などによって担保できており、長く運用されてきました。チェックポイント機構は、処理済みのChange TrackingバージョンをBigQueryに保持する仕組みです。Pod再起動時はそこから再開できます。 顕在化してきた課題 一方で、運用を続け、データ量や利用要件が増えていく中で、効率の側面でいくつかの課題が徐々に顕在化してきました。 メモリ効率:結果セットを一括でメモリに載せる実装のため、メモリ使用量がデータ量に比例して増加する構造でした。大量更新時のOOMを避けるためには「ピーク時のデータ量」を見越した大きなメモリを常時確保しておく必要があり、データ量が増えるにつれてリソース見積もりの難しさが目立つようになってきました。 コスト:上記のメモリ確保がそのままコストに直結します。メモリがトランザクション単位のデータ量に比例する構造であるかぎり、「ピーク時のデータ量」の見積もりを下回るとOOM直行となります。そのため運用上の工夫(時間帯別のスケーリング等)では本質的な改善が難しく、リソースの常時確保によるコスト増を抱え続けるしかありませんでした。 性能:逐次処理ベースの実装のため、1トランザクションあたりの規模が大きいテーブルでは、リアルタイム性を保ちにくい場面もありました。 運用:依存していたコンテナイメージがEOLを迎えており、継続利用にリスクがありました。加えて、内部状態の可視性が低く、障害発生時の原因特定にも時間がかかる状況でした。 一言でまとめると、各所でガタが出始めており、信頼性を維持したまま効率(メモリ・スループット・コスト)の側面を改善するため、リプレイスを検討するタイミングに来ていた、ということです。 新基盤アーキテクチャ 設計の軸 新基盤の設計指針はシンプルで、キャパシティプランニングの軸を「ピーク時のデータ量」から「単位時間あたりの処理量」に変えることに尽きます。信頼性(データ欠損が起きないこと)は旧基盤からチェックポイント機構によって担保されており、新基盤でもそのまま引き継いでいます。そのため本記事のテーマは信頼性を維持したまま、効率(メモリ・スループット・コスト)をどう改善したかです。 技術選定:Fluent Bit + Goプラグイン 今回のリプレイスは、前フェーズ(2024年のDataflow撤廃 + BigQuery Subscriptionへの切り替え)の延長線上にあります。前フェーズでDataflow関連の費用がまるごと不要になり大きなコスト削減は既に達成済みで、下流(Pub/Sub HubとBigQuery Subscription)も整理されている状態でした。一方でETL層はfluentdベースのまま残っており、メモリ効率とスループットの面で課題が顕在化していたため、今回はその続きとしてETL 層の中身を作り直すことにしました。下流はそのまま踏襲し、ソース側(Change Tracking設定)にも手を加えません。 このスコープと、既存のPub/Sub Hub構成・BigQueryテーブル設計を維持する制約のもとで、マネージドCDCサービスやOSSのCDCミドルウェアの活用も検討しました。ただし我々のケースでは、既存テーブル設計とPub/Sub Hubへの直接出力をそのまま組み合わせ続けられる選択肢を見つけられず、プラグインとして実装する形に決めました。 採用したのはFluent Bit + Goプラグインです。決め手は次のとおりでした。 既存基盤がfluentdベースで運用されていたため、Fluent Bitへの移行が素直:プラグインモデル・設定構造・デプロイ手順といった運用ノウハウがそのまま活きる INPUT(Change Tracking取得)とOUTPUT(Pub/Sub送信)の挙動を自分たちで細かく調整できる。後述の非同期ACK並列確認のような最適化も、プラグインとして自前で書いているからこそ仕込める Fluent BitのBuffer・バックプレッシャー機構をそのまま活用できる Goプラグイン公式サポートにより、後述する並列処理をgoroutineとchannelで素直に書ける 全体構成 以下の図は主要コンポーネントのみを示した簡略図です。 ETL層(Fluent Bit + Goプラグイン)はGKE上で動作します。プラグインはデータ取得(INPUT)とPub/Subへの送信(OUTPUT)の2つで構成されており、それぞれの実装の詳細は次章で扱います。 大量のデータをリアルタイムで捌くために考えたこと 新基盤の設計で常に意識していたのは、「大量のデータをいかにリアルタイムで捌くか」という問いでした。データ量が増えてもパイプラインが詰まらず、メモリ消費がデータ量に比例しない構造をどう実装するかを検討しました。前章で述べた「単位時間あたりの処理量を軸にする」方針を、Fluent Bitのパイプライン上に乗せて具体化していった話を、本章で紹介します。 なお、Fluent Bitのパイプライン構造の全体像については、公式ドキュメントもあわせてご覧ください。 新基盤の構成 Fluent Bitのパイプライン構造はINPUT → Filter → Buffer → Router → OUTPUTという形です。新基盤ではこのうちINPUTとOUTPUTをGoプラグインで実装しました。チャンク単位の処理やバックプレッシャーといったBuffer周りの機構はFluent Bit Engineが標準で備えています。そのためプラグイン側はINPUTとOUTPUTの"箱の中"の設計に集中できました。 設計の出発点として、データ取得から送信までの各処理を「どこがボトルネックになるか」で整理し、並列化方針を決めました。 処理 特性 並列化方針 CT取得(クエリ → カーソル) I/O bound(DB側) 単一スレッド(DBがボトルネック) エンコード CPU bound Worker数で並列化 Pub/Sub Publish I/O bound(NW) 非同期APIで並列化 ACK確認 I/O bound(NW待ち) 別Workerプールで並列化 CPU boundとI/O boundを別レーンに分け、それぞれを独立した並列度で動かす設計です。以下、INPUT内部・OUTPUT内部の順で紹介します。 INPUT内部:取得とエンコードを分けた INPUT内部の設計では、メモリとCPUを独立した軸として扱えるようにしました。 メモリの設計:結果セット全体を展開せず、カーソルで小分けに読み進める方式を採用。1回のクエリで読むレコード数 RecordsPerChunk をプラグインの設定で指定でき、本番では10,000件/チャンク CPUの設計:取得処理とエンコード処理を別レーンに分け、エンコードは複数のWorkerで並列実行 取得とエンコードの間に中間キュー(jobs queue)を挟むことで、取得側はエンコードの完了を待たずに次のチャンクを先行投入できます。キュー容量がゼロだと直列に戻ってしまうため、本実装ではjobs queueの容量をWorker数の5倍に設定しています。 この構造のもとで、同時にレコード形式でメモリに乗るチャンク数はNumWorkers × 6個で頭打ちになります。内訳は「jobs queue上の最大NumWorkers × 5個 + 各Workerが処理中の1個」です。 同時メモリ上のレコード数 = RecordsPerChunk × (jobs queue + 処理中 Worker) = RecordsPerChunk × (NumWorkers × 5 + NumWorkers) = RecordsPerChunk × NumWorkers × 6 = 10,000 × NumWorkers × 6 例えばNumWorkers = 2なら、データ量に関わらず常に約12万レコード分のメモリしか確保しなくて済みます。100万件規模のトランザクションが流れてきても、結果セット全体を一括ロードしてしまう旧基盤と違ってOOMにはなりません。 なお、Fluent Bit上でカーソル方式を実装するときには工夫が必要でした。Fluent BitはINPUTに対して定期的に「データをちょうだい」と呼び出してくる構造になっており、素朴に書くと毎回新規にクエリを発行してしまいます。それでは結果セットが毎回頭から読み直されてしまうため、カーソル状態をプラグイン側に持ち越し、呼び出しごとに「続きから」読み進めるようにしました。 OUTPUT内部:送信とACK確認を分けた OUTPUT内部では、送信処理とACK確認処理を別レーンに分離しました。Pub/SubのPublishは同期的に書くと「送信 → ACK待ち → 次へ」と直列化してしまい、ACK待ちのネットワークI/Oが支配的になります。これだとスループットがACKレイテンシに律速されてしまうため、両者を分離して並列化する方針を取りました。 送信側:非同期APIを呼んで即座にFuture相当の結果を受け取り、次へ進む。送信そのものは止まらない 確認側:受け取ったFutureのACK確認専用のWorkerプールを設け、複数並列で確認する 各メッセージが独立したACKタイムアウトを持つようになり、1件の遅延が後続全
2026年4月22日〜24日に開催されたGoogle Cloud Next '26へ参加してきました。昨年に引き続きアメリカ・ラスベガスで開催され、弊社からはMA部の平井・林・木野、AI事業戦略部の川田・桜井の5名が参加しました。なお、昨年参加した様子は以下の記事で紹介しています。 techblog.zozo.com 今年はAIエージェントを『実戦』に投入し、いかに賢く、安全に使うのかに焦点を当てたセッションが多い印象でした。本記事では、現地での様子と特に興味深かったセッションをピックアップして紹介します。 また、Recapのオンラインイベント「Google Cloud Next 2026 Recap in ZOZO」を2026年5月18日に開催しました。このイベントでは、Google Cloud Next '26について、今回のテックブログで紹介できなかった内容など、より詳細に共有しております。 現地の様子 私たちは会期の前々日にラスベガスの空港に到着したのですが、空港内にはさっそくGoogle Cloud Nextの広告が流れており、イベントに向けた熱意が一気に高まりました。 Google Cloud Nextの広告を見かけたハリー・リード国際空港の様子 昨年に引き続き会場は、ラスベガスのマンダレイ・ベイホテル コンベンションセンター。非常に盛り上がっており、特に各セッションや展示ブースでのデモでは参加者から活発な質問が飛び交っていたのがとても印象的でした。 熱気に包まれる会場内の様子 以降では、現地に参加したメンバーが気になったセッションを紹介します。 セッション紹介 What's new in Cloud Run こんにちは、MA部配信基盤ブロックの木野です。私は通知系(LINE/Mail/アプリ)の開発をしています。 公開資料「What's new in Cloud Run」のP.1より引用 このセッションでは、Cloud Runが単なるWebアプリのデプロイ先ではなく、より幅広いワークロードを受ける汎用実行基盤へ広がっていることが紹介されていました。セッション全体のメッセージは、Cloud Runが「on-demand compute for everyone」であるという点に集約されており、Vibe Coded Apps、AI Agents、AI Models、Large Scale Appsという4つの観点から新機能が説明されていました。 冒頭では、AI Studioで生成したマルチプレイヤーゲームをそのままCloud Runに公開するデモが紹介されており、Cloud Runが「作ったものをすぐにクラウドへ出す」ための基盤として強く打ち出されていました。また、Cloud Run公式のFully managed MCP Serverも発表されており、人間が操作する実行基盤というだけでなく、AIエージェントから直接デプロイや管理の対象になる基盤へ寄ってきていることも印象的でした。 GA対応したCloud Run Worker Pools 私が特に興味を持ったのは、Cloud Run worker poolsのGAです。Worker poolsは、HTTPリクエストを受けることが本質ではない常駐workerやpull consumer、runnerのような処理に対して、Cloud Run上のより自然な置き場を与える機能だと感じました。 Cloud RunにはこれまでもServiceやJobがありましたが、Serviceはrequest-driven、Jobはrun-to-completionであり、そのどちらにもきれいに当てはまらない処理を表現しづらい場面がありました。セッションでも、Temporalのworkerのようなlong polling前提の処理がworker poolsに適している例が紹介されていました。 この点は、私たちの配信基盤にもそのままつながります。例えばPub/Subのpull consumerや、ループし続ける常駐worker、定期的に状態を見て後続処理を進めるfinalizerのような処理は、実態としてはHTTPエンドポイントを持つことが本質ではありません。それにもかかわらず、これまではCloud Run Serviceの形に寄せるためにヘルスチェックや待受用のコードを持たせていました。worker poolsが一般提供されたことで、こうした処理をより素直な形で実装でき、配信基盤の見通しや運用性を改善できる可能性があります。 Cloud Run Instancesとbuilt-in dev loop 公開資料「What's new in Cloud Run」のP.30より引用 もう1つ興味深かったのが、Cloud Run Instancesとbuilt-in dev loopの流れです。セッションでは「ローカルでクラウドをエミュレートしようと頑張るのではなく、Cloud Run上でそのまま開発する」というメッセージが明確に打ち出されていました。ローカルの変更をCloud Run instanceに同期し、そのままdev scriptをクラウド側で実行することで、pushしてデプロイを待つ前に即時検証できる世界観が示されていました。さらに、SSH supportも合わせて紹介されており、Cloud Runを本番の実行基盤として使うだけでなく、開発や調査の場としても扱う方向性が見えてきたと感じました。 これは、複数サービスをまたぐ検証が多い配信基盤の開発体験にとって特に大きい変化だと思います。現在でもローカルでの統合テストやcontainer_testのような仕組みは有効ですが、実サービス依存に近い確認をしたい場合は、どうしてもdev環境への反映待ちや、共有環境ゆえの状態差分が問題になります。もしbuilt-in dev loopが成熟すれば、各開発者が自分の変更をCloud Run側へすぐに反映し、実サービス依存に近い状態で軽く検証を回せるようになります。さらに、人間が行う確認フローとPR後のE2EやCIの構成も近づけられる可能性があり、複数サービスをまたぐ開発・検証体験を大きく変えるアップデートだと感じました。 加えて、このセッションはCloud Runの新機能を個別に列挙するだけでなく、「Cloud Runはどこまで守備範囲を広げようとしているのか」という観点で見ると、とても示唆が多い内容でした。これまではHTTPサービスをスケールさせるためのプロダクトという見方が中心だったと思いますが、今回の発表では、AIエージェントの実行基盤、長時間動くworkerの置き場、さらにはCloud Run上での開発ループまで含めて整理されていました。配信基盤のように非同期処理、複数サービス連携、運用時の可観測性が重要なシステムにとっては、単なる機能の追加以上に、Cloud Runをどう使うかの前提そのものが変わり始めていると感じています。 セッションを通しての感想 Cloud Runは長らく「HTTPサービスを手軽に動かす場所」という印象が強かったのですが、今回のセッションを通して、AIエージェント、常駐worker、開発ループまで含めたより広い実行基盤へ進化していることがよく分かりました。特に私たちのように、非同期処理や複数サービス連携を多く持つシステムにとっては、今後の設計や検証フローを見直すきっかけになるセッションでした。 What's new in AlloyDB: Scale PostgreSQL for agentic AI and hybrid clouds こんにちは、MA部MAシステム開発ブロックの平井です。私は自社マーケティングシステム「ZMP」の開発をしています。ZMPではユーザー毎に最適化された情報を配信するパーソナライズ配信機能があり、そのデータベースとしてGoogle CloudのAlloyDBを利用しています。そこで、私はAlloyDBに関するセッションを聴講しました。 「What's new in AlloyDB」セッション会場の様子 このセッションでは、AlloyDBのアップデートをエンタープライズ・分析機能の観点、AI関連機能の観点から説明していました。 エンタープライズ・分析機能に関するアップデート Hot Standby 公開資料「What’s new in AlloyDB: Scale PostgreSQL for agentic AI and hybrid clouds」のP.8より引用 Hot Standbyは、スタンバイ中のノードがWALを受け続けながらアクティブなインスタンスとして動く機能です。この機能によって、起動時間の短縮とプライマリー昇格の加速によるRTOの改善、メモリーキャッシュの暖気とフェイルオーバー後のパフォーマンス低下の抑制により一貫したパフォーマンスの維持が可能になります。 Read Pool Autoscaling 公開資料「What’s new in AlloyDB: Scale PostgreSQL for agentic AI and hybrid clouds」のP.9より引用 Read Pool Autoscalingは、読み取りインスタンスがワークロードに応じて自動でスケーリングする機能です。また、事前に決められたスケジュールでスケーリングすることも可能です。例えばサイバーマンデーやブラックフライデーなどあらかじめ高負荷が予想される場合にとても有効です。私たちのパーソナライズ配信システムでも読み取りインスタンスを利用していて、負荷がスパイクする傾向があるため、Read Pool Autoscalingが一般提供された際は、その効果を速やかに検証したいと考えています。 Transparent Query Forwarding 公開資料「What’s new in AlloyDB: Scale PostgreSQL for agentic AI and hybrid clouds」のP.11より引用 Transparent Query Forwardingは、プライマリーノードで受け付けた読み取りクエリを読み取りノードにフォワードする機能です。読み取りノードにクエリをフォワードすることでプライマリーノードの負荷を軽減し、クラスター全体のリソースを有効活用するために設計されました。アプリケーション側で必要だったライブラリを利用したプライマリノードと読み取りノードのコネクションの作成/クエリフォワード設定が不要になります。また、書き込みと読み込みの一貫性を担保しているため、アプリケーション側で古い情報を参照する心配がありません。 LakeHouse Federation for AlloyDB 公開資料「What’s new in AlloyDB: Scale PostgreSQL for agentic AI and hybrid clouds」のP.16より引用 Lakehouse Federation for AlloyDBは、AlloyDBからBigQueryやIcebergにあるデータを簡単にクエリできる機能です。AlloyDB上のトランザクションデータとBigQuery上の履歴データ、集
はじめに こんにちは、カート決済部カート決済サービスAブロックの道場です。ZOZOTOWN内のカート機能や決済機能の開発、保守運用を担当しています。 現在、ZOZOTOWNのカート決済画面はリプレイスが進行中です。既存システムとリプレイス後のシステムが並行して開発される中、既存システムへのさまざまな機能改修を、リプレイス側にも取り込む必要があります。その際、条件の組み合わせが膨大になるテストを手動で網羅的に実施することが現実的でなく、特に注文金額の計算結果の正確性を人間が1件ずつ確認するには大きなコストがかかっていました。 本記事では、Claude CodeとPlaywright CLIを組み合わせて、自然言語によるE2Eテストを自動化した仕組みをご紹介します。Confluence(Atlassian社が提供するナレッジ共有ツール)に自然言語でテスト手順を記述することでAIが自律的にブラウザを操作し、計算検証も含めてE2Eテストを完結させています。コードを書かずにテストを作成・実行できるため、テスト自動化の属人化解消にもつながりました。 目次 はじめに 目次 背景・課題 リプレイスに伴う二重開発とテストの課題 なぜ従来のE2E自動化では足りなかったのか AIエージェント駆動のE2Eテストシステム 全体アーキテクチャ Playwright CLIによるブラウザ操作 Agent Skillsによる操作手順の定義 テストケースの設計と期待値の保証 Confluenceベースのテストケース管理 計算が必要なテストの期待値保証 テスト実行の6つのStep テスト支援ツールの構築 atlassian-cli:Confluence操作のCLI zozo-sql-server-cli:SQL Serverクエリ実行CLI AIエージェントが必要なツールを自ら作る 従来のテスト自動化との比較 実践から得られた知見 テストケースの実績 実践を通じた気づき まとめ 背景・課題 リプレイスに伴う二重開発とテストの課題 冒頭の通り、ZOZOTOWNのカート決済画面ではリプレイスが進行中です。既存システムとリプレイス後のシステムが並行して動作する期間中、既存システムに対するさまざまな機能改修をリプレイス側へ取り込む必要があります。 これらの改修をすべて取り込み、条件の組み合わせが爆発的に増加するテストケースを検証する工数が大きな課題となりました。 たとえば、ある案件の機能を取り込む場合、以下のような因子が絡み合います。 ユーザーの属性(性別・年齢 等) 購入商品の種類・金額 割引・クーポンの有無 ポイント利用の有無 キャンペーン期間の内外 これらを組み合わせると、1つの案件だけで100件以上のテストケースが発生することもありました。さらに、各テストケースでは注文フローの複数画面(配送・支払い選択、注文の確認 等)で表示値の確認が必要です。そして、PC用の画面とスマートフォン(以下、SPと表記します)用の画面がそれぞれ存在するため、検証量は実質的にさらに倍になります。 カート決済画面では、注文金額の計算ロジックにさまざまな要素が関わっており、前述の通り案件ごとに条件の組み合わせが大きくなりがちでした。さらに、期待値は複雑な計算式で決まるため、人間が1件ずつ手計算したうえで画面の表示と照合するには多くの時間がかかっていました。 なぜ従来のE2E自動化では足りなかったのか ZOZOTOWNでは、手動テストに加えて品質管理部によるコードベースのE2E自動テストも活用しています。しかし、そのような従来のコード記述型の自動テストを使ったアプローチでは以下の課題がありました。 プログラミングスキルへの依存:CSSセレクタやロールを使った要素特定のコードを書く必要があるため、開発者でなければ作成・保守が難しい UI変更への追従コスト:UIの変更に応じて、要素特定の方法やテスト内容のメンテナンスが必要になる テストコードの属人化:記述・保守できる人が限られるため、特定の開発者への依存が生じる 実現したかったのは、テスト手順を自然言語で書くだけで、AIが要素を自動で見つけて操作し、計算検証まで完結する仕組みです。そのためのアプローチとして、Claude CodeのAgent SkillsとPlaywright CLIを組み合わせた自動化システムを構築しました。 AIエージェント駆動のE2Eテストシステム 全体アーキテクチャ 構築したシステムの全体像は以下の通りです。 各コンポーネントの役割は次の通りです。 コンポーネント 役割 Confluenceページ テストデータ・手順・期待値を自然言語で記載したテストケース管理の場 エージェント(zozotown-qa-tester) テストの実行フローを定義するClaude Codeエージェント Agent Skills ZOZOTOWNの操作手順やCLIの使い方をMarkdownで定義した再利用可能なリファレンス 計算サービス(TypeScript) 期待値を算出するための計算ロジック実装 Playwright CLI コマンドでブラウザを操作するCLIツール atlassian-cli Confluenceの読み取りと、エビデンスを含めた結果の記載を行う自作CLI zozo-sql-server-cli SQL Serverへのクエリ実行と結果の画像化を行う自作CLI Claude CodeのエージェントがConfluenceからテストケースを読み取ります。Agent Skillsを参照しながらPlaywright CLIでブラウザを操作し、結果をConfluenceに書き戻します。 Playwright CLIによるブラウザ操作 Playwright CLIは、ブラウザ操作をコマンドで実行できるCLIツールです。テストコードを書く代わりに、コマンド1つでブラウザを操作できます。Playwright MCPもありますが、CLIの方がトークン使用量を節約できるため選択しています。 特徴的なのはスナップショット機能です。ページを開くと、Playwright CLIはページの構造をYAML形式で取得します。このとき各要素にはref番号が付与されています。AIはこのスナップショットを読んで要素を特定し、ref番号を使って操作します。 # ref番号を使って要素をクリック playwright-cli click e42 --session=pc # テキストを入力 playwright-cli fill e15 "test@example.com" --session=pc # スクリーンショットを取得 playwright-cli screenshot --output screenshots/cart-top.png --session=pc CSSセレクタやロールを明示的に指定しなくても、AIがスナップショットを解釈して要素を特定できます。そのため、セレクタベースの実装に比べると、軽微なUI変更には追従しやすくなります。 PCとSPの切り替えは設定ファイルで行います。 // playwright-cli.json(PC用) { "browser": { "launchOptions": { "headless": false }, "isolated": false, "contextOptions": { "viewport": { "width": 1400, "height": 1080 } } } } // playwright-cli-sp.json(SP用) { "browser": { "launchOptions": { "headless": false }, "isolated": false, "contextOptions": { "viewport": { "width": 430, "height": 932 }, "userAgent": "Mozilla/5.0 (iPhone; ...) Safari/604.1", "isMobile": true, "hasTouch": true } } } PCテストとSPテストは別セッションで同時に実行できるため、テスト時間の短縮にも貢献します。 Agent Skillsによる操作手順の定義 Agent Skillsでは、Claude CodeのSkill機能を活用してZOZOTOWN固有の操作手順を定義しています。コードベースのPlaywrightにおけるPage Object Modelに相当する役割を、Markdownによる自然言語の手順書で担うイメージです。 操作手順は次のように自然言語で記述します。 # ログイン手順リファレンス ## 手順 1. 以下のページを開く - PC: `/_member/login.html` - SP: `/sp/_member/login.html` 2. `メールアドレス` 入力欄にメールアドレスを入力する。 3. <span class="synSpecia
はじめに こんにちは、ZOZOTOWN開発1部iOSブロックの@kitasukeです。 前回の記事「ZOZOTOWN iOS のアーキテクチャとチームの進化」では、MVCからMVVM、そしてMVVM + Repositoryへのアーキテクチャ進化を取り上げました。あわせて、レビュー文化をチームに根づかせてきた3年間も振り返っています。 ただ、アーキテクチャを文章で定義しても、書き手によって命名や責務分割はぶれが生じますし、AIに任せると過去の望ましくない実装パターンまで律儀に再現されます。ドキュメントによる「努力目標」では、アーキテクチャは守りきれません。 そこで発想を逆にしました。アーキテクチャを「守るべきルール」ではなく、構造化されたスキーマとして定義し、人間とAIの双方がそれに従うしかない形にします。Swiftの型システムがコンパイル時に不正を弾くのと同じ発想を、アーキテクチャのレイヤーにスキーマという形で持ち込みます。それが本記事で紹介する「スキーマでアーキテクチャを縛る」アプローチです。副産物として、設計からコードを自動生成するパイプラインも動いています。 目次 はじめに 目次 どんなスキーマを定義したのか architecture-guidelines.md — コンポーネントをスキーマで縛る architecture-templates.md — スキーマから Swift を導出するルール どうやって縛っているのか 画面ごとの設計を YAML で表現する /architectureと/codegen — 実際の運用 /architecture: 仕様書やデザインから YAML を起こす /codegen: YAMLからSwiftのコードを生成する 何が変わったのか AIの書くコードがレビューを通る水準になった レビューで「プロダクト品質」の話ができるようになった まとめ どんなスキーマを定義したのか 全体像はこうなっています。 仕様書 (Confluence) / デザイン (Figma) / 既存コード │ ▼ /architecture ┌─────────────────────┐ │ 設計 YAML │ ←── AI / Codegen 向け │ Human Doc (Markdown) │ ←── 人間向けレビュー資料 └─────────────────────┘ │ ▼(人間がレビュー・編集) │ ▼ /codegen Swift コード一式 ↑ 全工程でガイドラインとテンプレートが参照される 土台となっているのが、チームで整備した 2つのドキュメント です。 architecture-guidelines.md — 各コンポーネントのスキーマ(何が正しいか) architecture-templates.md — スキーマからSwiftを導出するテンプレート(どう書くか) architecture-guidelines.md — コンポーネントをスキーマで縛る 各コンポーネント(ViewModel、Repository、Translatorなど)を、型・依存・命名・必須ルール・禁止パターンなどのフィールドで厳密に定義しています。たとえばViewModelのスキーマは次のとおりです。 ### ViewModel - type: `@MainActor final class` - imports: [Foundation, Combine] - imports_forbidden: [APIModule] - depends_on: [RepositoryProtocol, UIModelTranslator, DataModel, UIModel] - nested_types: [ViewState, Router] - naming: {Feature}ViewModel - required: - ViewState enum で画面状態を管理(複数 Bool 禁止) - @Published private(set) で外部からの直接変更を防止 - 1 ユーザーアクション = 1 input メソッド(did{Verb}{Noun}) - forbidden: - キャッシュロジック(Repository の責務) - ログ送信の直接呼び出し(UseCase/別 Repository に分離) 自由に書ける余地を意図的に潰しているのがポイントです。ViewModelがAPIモジュールをimportした時点でアウトです。@Published を private(set) にしなかった場合もアウトです。自己流のMVVM解釈を許さない設計になっています。 architecture-templates.md — スキーマから Swift を導出するルール スキーマだけではSwiftコードの具体的な書き方までは決まりません。命名規則、ファイルの生成順序、各レイヤーのSwiftコードテンプレートなどを、もう一段別のドキュメントで固めています。ガイドラインがスキーマで、テンプレートが導出規則です。この2つが揃うことで、アーキテクチャのスキーマから具体的なSwiftコードが一意で決まる状態になりました。 どうやって縛っているのか 人間・AI・ツールの全員が、同じスキーマで動くようになっています。順に見ていきます。 画面ごとの設計を YAML で表現する コンポーネントのスキーマが決まっても、画面ごとの実装は別物です。そこで、画面ごとの設計を1枚のYAMLで記述します。 feature: ProductList domain: Product api: - id: fetchProducts method: GET path: /products response: items: [Product] actions: - trigger: didAppear api: fetchProducts - trigger: didTapRetry api: fetchProducts condition: "state == .error" models: data: - name: Product fields: id: String name: String brandName: String price: Int imageURL: URL ui: - name: ProductListUIModel fields: nameText: String brandText: String priceText: String このYAMLは、ガイドラインが定めたスキーマの「値」にあたります。画面のAPI、アクション、データモデルが構造化されて並んでいるだけで、曖昧さの入り込む余地はありません。 /architectureと/codegen — 実際の運用 この縛りを日々の開発で実行しているのが2つのスラッシュコマンドです。 /architecture: 仕様書やデザインから YAML を起こす 重要なのは、このYAMLを人間がゼロから書いているわけではないという点です。Confluenceの仕様書やFigmaのデザインを入力にすると、/architectureコマンドが設計YAMLと人間向けMarkdownの大部分を自動生成します。 人間の作業は「書く」ではなく「判断する」に寄っています。生成されたYAMLを読み、責務分割やエッジケースの扱いなど設計判断が必要な箇所だけに手を入れます。スキーマが縛ってくれているので、AIが起こしたYAMLも標準から外れた形にはなりません。 /codegen: YAMLからSwiftのコードを生成する レビューが終わったYAMLを /codegen に渡すと、Swiftコード一式が出力されます。具体的には、View / ViewModel / Repository / プロトコル / モック / ユニットテストの雛形 / 依存注入のコードです。 たとえば先ほどの ProductList.yaml のうち、以下の部分に注目します。 actions: - trigger: didAppear api: fetchProducts - trigger: didTapRetry api</
Developer Engagementブロックの@ikkouです。2026年4月22日から24日の3日間にわたり北海道は函館市の函館サーモン・まるなまアリーナで「RubyKaigi 2026」が開催されました。 日本Rubyの会「RubyKaigi 2026」特別ライトアップ 今回の函館開催にあわせ、通常の白色のみの五稜郭タワーのライトアップが、Rubyをイメージした特別色のレッドにライトアップされていました。 ZOZOは今年もプラチナスポンサーとして協賛し、スポンサーブースを出展しました。 technote.zozo.com 本記事では、前半はWEARのバックエンドエンジニアが気になったセッションを紹介します。後半では、ZOZOの協賛ブースの様子と各社のブースにおけるコーディネートを写真中心に報告します。 ZOZOとWEARとRubyKaigi WEARのバックエンドエンジニアが気になったセッション A Faster FFI そもそもFFIとは? 現状のFFIの課題 なぜFFIは遅いのか? 改善1 改善2 FFXの仕組み 最終的なパフォーマンス ruby.wasm can also enable JavaScript to call Ruby libraries. The Less-Told Story of Socket Timeouts なぜopen_timeoutが必要だったのか net/httpのtimeoutライブラリ依存問題 resolv_timeout + connect_timeoutでは代替できない open_timeoutのAPI仕様 試してみる Ruby 3.4: fast_fallback ON Ruby 3.4: fast_fallback OFF Ruby 4.0: open_timeout fast_fallback ON + open_timeout fast_fallback OFF + open_timeout 同時指定はエラー 全体の対比 おわりに Autoresearching Ruby Performance with LLMs Autoresearchとは なぜ「ループ」が重要なのか 4つのループパターン Ralphループ Autoresearchループ Factoryループ Sidekiqでの実証実験 実験の背景 "マージされないコードを生成することの意味" PRレビューとは何か? 4つのレッスン Lesson 1: 「自動調査」であって「自動変更」ではない Lesson 2: 自分がオーナーで Architect でないものに Autoresearch を適用しない Lesson 3: ループはビター・レッスンを実践する Lesson 4: 人間のゲートをソフトウェアゲートに変換する Software Factory とその課題 まとめ おわりに Exploring RuboCop with MCP The Journey of Box Building Ruby Boxとは何か Ruby Boxの仕組み Ruby Boxが生まれた歴史 おわりに ZOZOブースの紹介 協賛企業ブースのコーディネートまとめ RubyKaigi 2026 アフターイベント〜初参加LT・スポンサー4社のパネル〜を開催します おわりに ZOZOとWEARとRubyKaigi 私たちが運営するファッションコーディネートアプリ「WEAR by ZOZO」のバックエンドはRuby on Railsで開発されています。2013年にVBScriptで構築されたシステムでしたが、2020年頃からコードフリーズし、Rubyへのリプレイスを開始しました。現在もリプレイスを進めながら、新規の機能もRubyで開発しています。また、Matzさんを技術顧問としてお迎えし、毎月Matz MTGと称したオンラインミーティングを実施しています。 ZOZOとRubyKaigiの関係は、ZOZOの前身であるVASILY時代のRubyKaigi 2017に遡ります。コロナ禍を経て再開したRubyKaigi 2022からはWEARのバックエンド開発を担うチームが中心となって協賛とスポンサーブースの出展を続けています。 RubyKaigi2017参加レポート(全日分)とスライドまとめ RubyKaigi2018参加レポート RubyKaigi 2019参加レポート〜sonots登壇セッション & エンジニア8名による厳選セッション RubyKaigi 2022参加レポート 〜エンジニアによるセッション紹介〜 RubyKaigi 2023参加レポート 〜エンジニアによるセッション紹介〜 RubyKaigi 2024 参加レポート RubyKaigi 2025 協賛&参加レポート WEARのバックエンドエンジニアが気になったセッション 今年はWEARチームから6名のバックエンドエンジニアがRubyKaigiに参加しました。本パートでは各エンジニアが特に気になったセッションを個々の視点で紹介します。 A Faster FFI chikaです。私からはAaron Patterson(@tenderlove)氏の「A Faster FFI」を紹介します。 このセッションでは、「RubyはC言語より速くなるか?」という問いからスタートし、具体的にはRubyのFFIを高速化し、ネイティブのC言語(C拡張)よりもRubyを速く実行できるか? というのがメインの議題でした。 余談ですが、Aaron氏はRubyKaigi 2026の外国人登壇者の中でおそらく唯一日本語でスピーチする方です。流暢な日本語に加えて時折ジョークも交え、大変ユニークなセッションであり毎年楽しみに拝聴しています(最初の挨拶では「外国人みたいな名前ですが、外国人です」と日本語でジョークを言っていて面白かったです)。 そもそもFFIとは? FFIとはForeign Function Interfaceの略称で、一般的にはRubyのような高水準言語からC言語やRust, Zigなどで書かれた外部の関数を呼び出すための「概念」のことを指します(FFI自体は、あるプログラミング言語から他のプログラミング言語で定義された関数などを利用するための機構)。 Rubyでは主にlibffiというライブラリやfiddle gemを介して利用され、プラットフォームごとの呼び出し規則の違いを吸収してくれます。 例:FFIを使ったhello world #include <stdio.h> void hello(void) { printf("Hello, World from C!\n"); } require 'ffi' module Hello extend FFI::Library ffi_lib File.expand_path('libhello.dylib', __dir__) attach_function :hello
はじめに こんにちは、ZOZOTOWN開発本部でiOSエンジニアをしている續橋(@tsuzuki817)です。 2026年4月13日〜14日に開催されたtry! Swift Tokyo 2026にて、「GeoJSON×SwiftUI:地図を“美しく”描くための技術」というタイトルで20分のトークをしました。 speakerdeck.com www.youtube.com 本記事では、プロポーザルの準備から採択、トーク作成、社内での練習とフィードバック、そして登壇当日までの道のりをお伝えします。これからカンファレンスへの登壇を考えている方の参考になれば幸いです。 目次 はじめに 目次 try! Swift Tokyo とは ZOZOのiOSエンジニア全体でプロポーザルに挑む プロポーザルを考える会の開催 GPTを活用した「try! Swift プロポーザル コーチ」 採択 — LT枠から20分トークへ トーク作成 チーム内トーク練習とフィードバック スライドの改善 — 英語化とビジュアライズ ビジュアライズの強化 英語化 登壇当日 組織横断で登壇を支える意義 まとめ try! Swift Tokyo とは try! Swift Tokyoは、世界中からSwift開発者が集まる国際カンファレンスです。2026年は4月12日〜14日の3日間にわたって立川ステージガーデンで開催されました。4月12日がワークショップ、4月13日〜14日がカンファレンス本編という構成です。 トークの言語は自由で、AI翻訳による同時通訳が提供されています。多くのスピーカーが英語で発表する中、私はスライドを英語で作成し、発表自体はAI翻訳に任せて日本語で行いました。 ZOZOのiOSエンジニア全体でプロポーザルに挑む プロポーザルを考える会の開催 try! Swift Tokyo 2026のプロポーザル募集が告知されたタイミングで、ZOZOのiOSエンジニア全体に向けて「プロポーザルを考える会」を企画しました。 企画の背景には、いくつかの想いがありました。正直なところ、私自身もtry! Swiftへのプロポーザル提出は初挑戦で不安がありました。一人で世界的なカンファレンスに挑むのはハードルが高いと感じていました。だからこそ仲間を募り、みんなで一緒に挑戦したいと考えました。一人だと尻込みしてしまうことでも、みんなで「お祭り」のように取り組めば乗り越えられると思いました。そして、結果(採択)以上に、みんなで知見を出し合う「プロセス」そのものを大事にしたいと考えました。この活動を通じて、プロダクトの枠を超えた横のつながりを深めたいという想いもありました。 ZOZOTOWN、WEAR、FAANSなど複数のプロダクトチームから横断的に参加を募り、2026年1月7日に開催したところ、8名が参加しました。 会のアジェンダは以下のとおりです。 イントロ — 会の目的説明、try! Swift Tokyo 2026のスケジュール共有(締切1月末、採択2月中旬) 採択傾向の共有 — 過去セッションの分析 プロポーザル例の紹介 — 私のプロポーザル2案を紹介し、フィードバックを募集 ネタ出しブレスト — 各自のアイデアを出し合う まとめ & Next Action — 興味のあるネタの深掘り担当決め、次回開催について 「ネタがなくてもとりあえず参加して、みんなのネタを見てプロポーザルが閃くかもしれない」というスタンスで、飛び入り参加も歓迎としました。結果的に、この会をきっかけに、ZOZOTOWN・WEAR・FAANSの3プロダクト横断で5名がプロポーザルを提出できました。 GPTを活用した「try! Swift プロポーザル コーチ」 プロポーザルの質を高めるために、ChatGPTのカスタムGPT機能で「try! Swiftプロポーザルコーチ」を作成しました。 このコーチは、雑にネタを投げるとtry! Swiftに提出できる形へプロポーザルを整形してくれるものです。過去のtry! Swiftで採択されたプロポーザルの傾向や、効果的なプロポーザルの書き方の知見を組み込んでいます。 作成したコーチは、プロポーザルを考える会の場でも参加者に共有しました。自分のプロポーザルを磨くだけでなく、他のプロダクトチームのメンバーにも活用してもらうことで、全体のプロポーザルの質を底上げできました。ツールとして共有することで、個人の経験や知見に依存せず、誰でも一定水準のフィードバックを得られる仕組みになったのは大きな成果でした。 採択 — LT枠から20分トークへ プロポーザルは「GeoJSON×SwiftUI:地図を"美しく"描くためのサイズ設計と描画テク」というタイトルでLT枠として提出しました。 2月中旬、try! Swift運営チームから採択のメールが届きました。驚いたのは、LT枠として提出していたにもかかわらず、20分のトーク枠での登壇を打診されたことです。運営からは「可能であれば20分のトークでお願いしたい」とのことで、二つ返事で承諾しました。 ZOZOTOWN、WEAR、FAANSの各チームから合わせて5名がプロポーザルを提出しましたが、採択されたのは私のみでした。それでも、組織横断の取り組みから「次こそは」という機運が生まれたことは大きな収穫でした。 トーク作成 採択が決まってからは、20分のトーク内容を作り込んでいきました。 テーマは、個人開発アプリ「旅行思い出マップ」で直面した、GeoJSONを使った地図描画の技術的な挑戦です。市町村レベルの地形に沿って写真を切り抜き、地図上にパズルのように配置する機能を実装しました。本トークでは、図法(投影法)の違いによる地図の歪みの解決方法を解説する内容としました。 まず、Claude Codeを使ってプロポーザルと実際のプロジェクトのコードから発表の流れを組み立てました。しかし、最初に生成された構成は同じ内容の重複や、話の順番の繋がりの悪さが目立ちました。細かく指示を出して何度も修正を重ね、ようやく納得のいく台本に仕上がりました。 台本が完成した後は、Claude Codeでベースのスライドを生成しました。ただ、生成されたスライドは好みに合わなかったため、Keynoteで自分好みにスライドを分割したり、画像を差し込んだりして手作業で仕上げていきました。 トークの構成は以下のとおりです。 イントロ & アプリ紹介 MapKitの限界と画像ベースの苦労(Before) GeoJSONとの出会い(転換点) 描画パイプライン全体像 GeoJSONパース 座標変換とMercator投影 SwiftUIでの描画 地図を"美しく"する工夫 まとめ チーム内トーク練習とフィードバック スライドの初版が完成した段階で、3月27日にチーム内でトーク練習会を実施しました。実際に20分の通し発表をし、メンバーからリアルタイムでフィードバックをもらいました。 フィードバックは以下のようなものでした。 GeoJSONの説明タイミング —「GeoJSONに触れてから1分くらい経っていたので、もう少し早めに説明へ入ったほうがいい」 技術的な説明の速度 —「GeoDataProviderやMKGeoJSONDecoder、MKGeoJSONFeatureのくだりが速くてあまり入ってこない。全体的な流れのイメージがあるといい」 ビジュアルの追加 —「地図の説明はビジュアルで見たい」「もうちょっと実際の地図との関連性がわかるといい」 Before-Afterの対比 —「プログラムと図(Before-After)をセットで見られるともっと良さそう」「プログラムのページをわかりやすくするだけでもっと良くなりそう」 コードの細かな指摘 — インデントやスラッシュの数など、スライド上のコード品質についても指摘をもらいました チームメンバーの率直なフィードバックのおかげで、説明の流れや視覚的なわかりやすさについて多くの改善点を発見できました。一人では気づけない「聴衆視点」の指摘は非常に貴重でした。 スライドの改善 — 英語化とビジュアライズ 練習会でのフィードバックを受けて、以下の改善をしました。 ビジュアライズの強化 フィードバックで特に多かった「視覚的にわかりにくい」という指摘に対応するため、以下を追加・改善しました。 Before-Afterの図をコードとセットで表示 描画パイプラインの全体像を図解 離島を含む地図描画のビジュアル例を追加 実際の地図との対比がわかるスクリーンショットを追加 ビジュアライズにはClaude Codeを活用しました。実際のGeoJSONデータを渡してSwiftUIのコードを生成してもらい、そこから画像を出力する形で対応しました。実データを使って図を描画できるため、スライド上の説明と実際の動作が一致した正確なビジュアルを効率よく用意できました。 英語化 try! Swift Tokyoは国際カンファレンスのため、スライドは英語で作成しました。発表自体はAI翻訳による同時通訳を活用し、日本語で行う方針としたため、日本語で作成したスライドを英語に翻訳する作業が必要でした。 英語化にはClaude Codeを活用し、作業を効率的に進めました。ただし、AIによる翻訳だけでは伝えたい感情やニュアンスを正確に表現しきれない部分がありました。そこで、チーム内で英語に強いメンバーの、らぷ(@laprasdrum)と小松(@tosh_3)にレビューを依頼しました。2人には「ここはこういう意図で伝えたい」という部分の表現をかなり細かく指摘してもらい、機械的な翻訳では得られない自然で伝わる英語に仕上げられました。AIと人間のレビューを組み合わせることで、スライドの完成度を大きく引き上げられたと感じています。 登壇当日 4月11日のスライド提出締め切りを経て、4月12日のワークショップ、そして4月13日〜14日のカンファレンス本番を迎えました。 会場に着いて受付でスピーカーのプレートをもらいました! 会場は想像していたよりもかなり広く、とても迫力がありました。 歴代のtry! Swiftのポスターが飾られており、10周年のタイミングで参加できてとても光栄に思いました。 登壇終了後にステージから撮影した写真です。本番中は緊張してしまい、会場の方をほとんど見られていなかったのが反省点です。 登壇とAsk the Speakerを何とか終え、開放感からすぐにスポンサーブースを回りました。 可愛いお菓子もデプロイされていました! 組織横断で登壇を支える意義 今回の登壇を振り返ると、個人の努力だけでなくZOZOのiOSエンジニア全体の支えがあったからこそ実現できたと強く感じます。 プロポーザル段階: ZOZOTOWN・WEAR・FAANSの各チームから横断的にプロポーザルを考える会を開催し、互いのアイデアにフィードバックを送り合いました ツールの共有: GPTを活用したプロポーザルコーチを作り、全員が活用できるようにしました トーク練習: 社内でリハーサルを行い、聴衆視点からの具体的なフィードバックをもらえました 英語レビュー: 英語に強いメンバーがスライドの英語をレビューしてくれました カンファレンス登壇は個人の活動と思われがちですが、組織横断で取り組むことで、プロポーザルの質・トークの質ともに大きく向上します。また、たとえ今回採択されなかったメンバーも、この過程で得た経験は次のチャレンジに活きるはずです。 まとめ try! Swift Tokyo 2026での登壇は、プロポーザルの準備からトーク完成まで約3か月の道のりでした。この経験を通じて、組織横断で挑戦することの価値を改めて実感しました。 カンファレンスへの登壇を検討されている方は、ぜひチームを巻き込んでみてください。一人では出てこないアイデアやフィードバックが、必ず登壇の質を高めてくれるはずです。 ZOZOのiOSエンジニア一同、今後もカンファレンスへの登壇を組織横断で積極的に推進していきます。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com
ZOZO開発組織の2026年3月分の活動を振り返り、ZOZO TECH BLOGで公開した記事や登壇・掲載情報などをまとめたMonthly Tech Reportをお届けします。 ZOZO TECH BLOG 2026年3月は、前月のMonthly Tech Reportを含む計19本の記事を公開しました。特に次の3記事は反響も大きく、とても多くの方に読まれています。ぜひご一読ください。 techblog.zozo.com techblog.zozo.com techblog.zozo.com 登壇 【Flutter推し活】Flutter好きが集うLT会 Studyplus x Linc'well 3月13日に開催された「【Flutter推し活】Flutter好きが集うLT会 Studyplus x Linc'well」に、新規事業部の大野(@junjun_1345)が登壇しました。 ZOZO フロントエンドMeetup 3月18日にZOZOで主催した「ZOZO フロントエンドMeetup」に、ZOZOTOWN開発3部の揚原、WEAR開発部の岩崎、ZOZOTOWN開発1部の佐藤、そしてZOZOTOWN企画開発部の片岡が登壇しました。 掲載 Apps in ChatGPT対応 OpenAIの対話型AI「ChatGPT」の新機能「Apps in ChatGPT」にファッション領域でいち早く対応し、アプリ連携を開始しました。このことが複数のメディアで取り上げられました。まだ試したことがない方はぜひ一度お試しください。 corp.zozo.com www.fashionsnap.com netkeizai.com ZOZOEDUCATION つくっちゃお! ZOZO初となる、子どもが“つくって売る”に挑戦できる教育プロジェクト「ZOZOEDUCATION つくっちゃお!」を始動しました。本プロジェクトの一環として、Tシャツのデザインから販売までを、親子が自宅で気軽に体験できるTシャツづくりキットを3月25日より販売開始しています。このことが複数のメディアで取り上げられました。親子で体験できる楽しいキットですので、ぜひお試しください。 corp.zozo.com www.nikkei.com kosodate.mynavi.jp 以上、2026年3月のZOZOの活動報告でした! ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com
はじめに こんにちは、SREブロックの岩切です。普段はZOZOTOWN Yahoo!店の連携基盤のリプレイスを担当しています。 ZOZOTOWN Yahoo!店では、FTPによるデータ連携の遅延をSplunkアラートで検知し、PagerDutyにインシデントを作成して運用しています。しかし、遅延が解消してもインシデントは自動でResolveされず、手動で対応する必要がありました。 Splunk × PagerDutyの運用では、「アラートは自動だがResolveは手動」という課題に悩まされがちです。本記事では、追加のミドルウェアなしでインシデントを自動Resolveする実装パターンを紹介します。 目次 はじめに 目次 この記事で得られる知見 背景・課題 自動Resolveの要件整理 解決策の検討 アプローチ1:Splunk Add-onのparam.dedup_keyを使う アプローチ2:Events API v2統合を使う アプローチ3:Event Transformer統合 + Service Event Orchestration(採用) 全体のアーキテクチャ インシデント作成(遅延発生時) インシデント自動Resolve(遅延解消時) 設定の詳細 Splunk側の設定 トリガーアラート(既存設定の変更) 解消アラート(新規作成) PagerDuty側の設定 Event Transformer統合(新規作成) Service Event Orchestration 動作シナリオ 正常時(遅延なし) 遅延発生から解消までの流れ まとめ この記事で得られる知見 Splunk Add-on for PagerDutyの制約と、その回避方法 PagerDutyのdedup_keyを活用したインシデントのライフサイクル管理 「検索結果が0件のときだけアラートを発火する」SPLテクニック 背景・課題 ZOZOTOWN Yahoo!店では、商品情報などのデータをFTPで連携しています。FTPによるデータ反映が一定時間遅れると、Splunkのアラートが発火し、PagerDutyのインシデントが作成されます。 しかし、遅延が解消してもインシデントは自動Resolveされず、毎回オンコール担当者が手動でResolveしていました。この手動対応が繰り返され、運用上の負担になっていました。 手動Resolveの課題は以下のとおりです。 対応コスト:遅延解消を確認し、PagerDutyを開いてResolveする作業が都度発生する Resolve忘れのリスク:インシデントが残り続けると、新たなアラートとの区別がつきにくくなる オンコール負荷:深夜・休日に遅延が解消しても、Resolveのためだけに対応が必要になる場合がある 自動Resolveの要件整理 PagerDutyのインシデントを自動Resolveするには、以下の2つを満たす必要があります。 インシデント作成時と同じdedup_keyでresolveイベントを送ること イベントのevent_actionがresolveであること dedup_keyとは、PagerDutyがイベントをインシデントに紐づけるための一意キーです。同じdedup_keyを持つイベントは同一インシデントとして扱われ、重複排除やResolveの対象になります。 要件自体はシンプルに見えますが、Splunk Add-on for PagerDutyにはresolveイベントを直接送信する機能がありません。そのため、Splunk側とPagerDuty側の両方に工夫が必要でした。 解決策の検討 最終的な設計に至るまで、いくつかのアプローチを検討・検証しました。 各アプローチの説明へ入る前に、本記事で登場するPagerDutyの主要な概念を整理します。 Event Transformer統合:Splunkなどの外部ツールからイベントを受け取り、PagerDuty形式に変換する統合タイプ。incident_keyの設定により、受信したイベントのどのフィールドをdedup_keyとして使うかを決定する Service Event Orchestration:サービスに届いたイベントをルールベースで加工する機能。条件に応じてevent_actionの変更、優先度の設定などが可能 アプローチ1:Splunk Add-onのparam.dedup_keyを使う Splunk Add-onのparam.dedup_keyパラメータでdedup_keyを明示的に指定する方法です。しかし、Event Transformer統合はincident_key設定に基づいてdedup_keyを自動生成するため、Splunk側のparam.dedup_keyは無視されます。この方法では意図したdedup_keyを指定できず、採用を見送りました。 アプローチ2:Events API v2統合を使う Events API v2統合であれば、ペイロードのdedup_keyをそのまま使えます。しかし、Splunk Add-onはSplunk固有のペイロード形式で送信するため、Events API v2統合ではペイロードを解釈できず、インシデントが作成されませんでした。 アプローチ3:Event Transformer統合 + Service Event Orchestration(採用) 新しいEvent Transformer統合を作成し、incident_key=sourceに設定します。SPLにeval source="yshp-ftp-delay-warning"を追加することで、トリガーと解消で同じdedup_keyを生成します。そのうえで、Service Event Orchestrationでresolveに変換します。 検討した3つのアプローチの比較 全体のアーキテクチャ 自動Resolveの仕組みは、インシデント作成とインシデント自動Resolveの2つのフローで構成されます。設計のポイントは以下の2点です。 dedup_keyをSPL側で強制的に統一する:eval source="yshp-ftp-delay-warning"でトリガーと解消に同じ値を付与 resolveはPagerDuty側で変換する:Splunkからはtriggerとして送り、Orchestrationでresolveに変換 自動Resolveの全体アーキテクチャ インシデント作成(遅延発生時) Splunk:「Yahoo!FTPデータ反映遅延警告」アラートが遅延ファイルを検出して発火 SPL:末尾のeval source="yshp-ftp-delay-warning"により結果にsourceフィールドを付与 Event Transformer:incident_key=sourceの設定によりdedup_key="yshp-ftp-delay-warning"を生成 Service Event Orchestration:event.summaryに「解消」を含まないため、そのまま通過(trigger)。なお、Splunkのsearch_name(アラート名)はPagerDutyではevent.summaryとして受信される 結果:インシデント作成(同一dedup_keyなら重複排除) インシデント自動Resolve(遅延解消時) Splunk:「Yahoo!FTP遅延解消チェック」アラートが遅延ファイル0件を検出して発火 SPL:| stats count | where count = 0 | eval source="yshp-ftp-delay-warning"で遅延ファイルが0件のときだけ結果を返す Event Transformer:dedup_key="yshp-ftp-delay-warning"(トリガーと同一キー) Service Event Orchestration:event.summaryに「解消」を含むため、event_actionをresolveに変換 結果:同一dedup_keyのインシデントを自動Resolve 以上の仕組みで解決した技術的課題をまとめます。 課題 解決方法 Splunk Add-onはresolveを送れない Service Event Orchestrationでtrigger→resolveに変換 trigger/resolveのインシデント紐づけ SPLにeval source="yshp-ftp-delay-warning"を追加し、Event Transformerのincident_key=sourceで同一dedup_keyを生成 解消イベントの識別 event.summaryに「解消」を含めてOrchestrationルールで判別 既存統合のincident_key=search_nameではdedup_key不一致 FTP遅延専用のEvent Transformer統合を新規作成しincident_key=sourceに設定 設定の詳細 Splunk側の設定 トリガーアラート(既存設定の変更) 「Yahoo!FTPデータ反映遅延警告」アラートに以下の変更を加えました。 integration_key/urlを新しいEvent Transformer統合に変更 SPL末尾に| eval source="yshp-ftp-delay-warning"を追加 発火条件・スケジュールは変更なし 解消アラート(新規作成) 「Yahoo!FTP遅延解消チェック」アラートを新規作成しました。 解消アラートの設定画面 設定項目 値 備考 SPLクエリ トリガーと同一ベース + | stats count | where count = 0 | eval source="yshp-ftp-delay-warning" 遅延ファイル0件のときのみ結果を返す counttype number of events <td style="text-align:l
はじめに こんにちは、ZOZOTOWN企画開発部 企画フロントエンド2ブロックのパクサンイです。普段はZOZOTOWNにあるCMSベースのLPページのメンテナンスや機能追加、企画LPページ環境のメンテナンスを担当しています。 ZOZOTOWNの複数のWebアプリケーション間で、プロモーション用ランディングページコンポーネントを共有するために、LitベースのWeb Componentsを導入しました。本記事ではその事例を紹介します。 ZOZOTOWNでは多数のLPページが開設・更新されており、従来はiframeを使った埋め込み方式でUIを共有していました。しかし、この方式にはさまざまな課題が存在し、レガシー環境からNext.jsベースの新環境へのリプレイスを進める中で、フレームワークに依存しないUI共有アーキテクチャが必要となりました。 本記事では、iframeベースの共有方式が抱える具体的な課題と、LitベースのWeb Componentsを採用した理由と選定プロセスを解説します。さらに、フレームワーク非依存なコンポーネント共有基盤を設計・実装する中で得た経験を共有します。 対象読者 マルチWebアプリケーション環境でUI共有に課題を感じているフロントエンドエンジニア iframeを使ったUI共有方式の代替手段を探している方 Web Componentsの導入を検討している方 目次 はじめに 対象読者 目次 背景・課題 ZOZOTOWNフロントエンドのマルチWebアプリケーション構成 LPコンポーネントの共有仕様 従来のiframeベース共有方式とその課題 1. レイアウト制御の煩雑さ 2. UI制御の複雑化 3. SEOの制約 アプローチ:Web Componentsの導入 要件整理 技術選定:Lit基盤Web Components Litを選択した理由 npmパッケージ方式を除外した理由 設計・実装 全体アーキテクチャ 1. 利用側アプリケーションによるデータ取得・加工 2. Lit ContextによるProps Drilling防止 3. Scriptローディングによる独立したUI更新 4. Shadow DOMからLight DOMへの切り替え ビルド・配信 全体フロー LPコンポーネント開発側(コンテンツ共有専用リポジトリ) 利用側Webアプリケーション 効果 学んだこと 今後の課題 今後の展望 まとめ 最後に 参考資料 背景・課題 ZOZOTOWNフロントエンドのマルチWebアプリケーション構成 現在、ZOZOTOWNのフロントエンドは3つのマルチWebアプリケーションで運用されています。 リポジトリ 説明 主な役割 リポジトリA(レガシー環境) 統合リポジトリ 既存の全ページを管理 リポジトリB(リプレイス環境) コアメインページ ホーム、カート、検索結果、商品詳細ページなど リポジトリC(リプレイス環境) 企画ページ フルスクラッチLP、CMS活用LP レガシー環境では複数のサービスが単一リポジトリで管理されていたため、共通UI共有に関する課題はありませんでした。しかし、リプレイス後にマルチWebアプリケーションが増えたことで、従来の方式ではUIを再利用できなくなりました。 LPコンポーネントの共有仕様 ZOZOTOWNでは特定のLPコンポーネントを複数のページで表示しています。一部のページでは以下の2つの形態で表示されます。 単独ランディングページ — header/footerを含むフルページ モーダル表示 — 特定ページのバナークリック時に、header/footerなしでコンテンツセクションのみをモーダルで表示 つまり、ほぼ同一のUIでありながら、header/footerの有無、SEOメタタグ、計測用トラッキングスクリプトの有無などで差異がある仕様でした。 従来のiframeベース共有方式とその課題 リプレイス後は以下の方式でUIを共有していました。 環境 運用方式 リポジトリA(レガシー) LPページ配信 + iframe用LPページ(header/footerなし)配信 リポジトリB・C(リプレイス) 特定ページにバナー表示 → クリック時にモーダル内でiframeとしてリポジトリAのLPを埋め込み このiframe方式には以下の課題が存在していました。 1. レイアウト制御の煩雑さ iframeは独立したドキュメントを読み込むため、フレームサイズの調整や使用箇所ごとの非表示領域の処理は対応していたものの、煩雑な部分がありました。 2. UI制御の複雑化 各バリエーションに応じて非表示にすべき子コンポーネントもあり、クエリパラメータやpostMessageで解決できるものの、ケースが増えるほど複雑化しました。 3. SEOの制約 検索エンジンはiframe内のコンテンツをsrc側の所有として認識するため、SEO上の制約がありました。 アプローチ:Web Componentsの導入 要件整理 上記の課題を解決するために、以下の4つの要件を整理しました。 要件 説明 各アプリのデプロイなしにUI更新 iframe方式の利点であった各マルチWebアプリケーションのデプロイなしにUI変更が反映されることを維持 iframe脱却 各アプリケーションでネイティブにUIをレンダリング フレームワーク非依存 React、Vueなど、どのフレームワークでも使用可能であること 軽量バンドルサイズ 利用側に負担のない最小限のサイズを維持 技術選定:Lit基盤Web Components Web Componentsはブラウザのネイティブコンポーネントモデルであり、特定のフレームワーク(React、Vueなど)に依存せず、ブラウザが直接理解する標準技術です。主に以下の3つの中核技術で構成されています。 Custom Elements:開発者が独自のHTMLタグを定義できる。タグ名にはハイフン(-)を含む規約がある。 Shadow DOM:コンポーネントのスタイルとマークアップを外部ページから隔離(Encapsulation)する。 HTML Templates:<template>と<slot>要素により、再利用可能なマークアップ構造を定義する。 このWeb Componentsをより効率的に開発するため、Litライブラリを採用しました。 Litを選択した理由 選定基準 Litの特徴 バンドルサイズ 約5KB(minified + compressed)で非常に軽量 リアクティブプロパティ Reactive Propertiesにより状態変更時に自動再レンダリング テンプレート Tagged Template Literalsベースで別途コンパイル不要 パフォーマンス Virtual DOM diffingなしに動的部分のみを直接更新 相互運用性 すべてのLitコンポーネントはネイティブWeb Componentであり、HTMLを使うあらゆる場所で動作 npmパッケージ方式を除外した理由 LPページはテキスト更新の頻度が高く、UIも不定期に変更されます。npmパッケージで運用すると、変更のたびに各環境でパッケージ更新+デプロイが必要となり、運用負荷が大きいため除外しました。 設計・実装 全体アーキテクチャ コンテンツ共有専用リポジトリを新たに構築し、以下の設計原則を適用しました。 1. 利用側アプリケーションによるデータ取得・加工 ZOZOTOWNにはページアクセス時に初期設定すべき値やAPIフェッチのためのロジックが各アプリケーションに存在します。これらのロジックをコンテンツ共有専用リポジトリにも含めると管理が二重になりメンテナンス負荷も大きくなるため、このリポジトリではUIレンダリングのみを責任範囲としました。 利用側の親アプリケーションでデータを取得・加工してpropsで渡す形式を採用しています。 2. Lit ContextによるProps Drilling防止 UI内部で必須的に共有すべき情報(デバイス種別、性別など)は、Lit Contextを活用したカスタム要素を設けて処理しました。 Lit ContextはReactのContext APIと同様の概念で、Props Drillingなしに上位から下位コンポーネントへデータを渡すことができます。 3. Scriptローディングによる独立したUI更新 各Webアプリケーションで別途デプロイなしにUI変更が可能なよう、Scriptローディングを採用しました。各アプリケーションでは<script>タグで必要なコンポーネントのJSファイルを読み込み、クライアントでWeb Componentがレンダリングされます。 4. Shadow DOMからLight DOMへの切り替え Web Componentsの代表的な特徴であるShadow DOMは、スタイルを完全に隔離し、コンポーネント内部のCSSが外部に影響せず、外部CSSも内部に影響しません。 しかし、今回のケースでは、Shadow DOMで隔離して管理するUIではなく、利用側から自由にスタイルだけでなく要素にもアクセスできることが重要でした。そのため、Shadow DOMの代わりにLight DOMを採用しました。 ビルド・配信 Viteを使用してLit基盤Web Componentをビルドし、S3にデプロイしてCDN経由で配信します。 全体フロー LPコンポーネント開発側(コンテンツ共有専用リポジトリ) Lit + Vite dev serverでローカル開発 各テスト環境にてHTML + JSで動作確認 問題なければ各環境(S3)にデプロイして確認 利用側Webアプリケーション SSR時にCMS APIでデータ取得(スケジュールに応じて変更されるテキストなどはCMSで管理) クライアントで<script>タグによるJSファイルローディング、Web Componentのレンダリング カスタムタグへCMS API仕様に合わせたデータをpropsで渡す 効果 この仕組みの導入により、以下の効果が得られました。 マルチWebアプリケーション間でiframeを使わずにUIコンポーネントを共有できるようになった 各アプリケーション側のリリース(デプロイ)なしでコンテンツ更新が可能になった 利用側からスタイルだけでなく要素へのアクセスも自由に可能になった(Light DOM採用) CMS連携により、エンジニア以外でも直接スケジュールベースのデータ管理が可能に 学んだこと Litを通じて開発する中で、Web Componentsのベースとなるウェブ標準技術をより深く理解し、関心を持つようになりました。また、CSS変数などを活用してJavaScriptなしにCSSだけでスタイルを制御する方法も知ることができました。 今後の課題 Web Components公式のSSR対応はまだ限定的ですが、Lit SSRなど複数の解決策がライブラリやコミュニティで共有されています。現在、このプロジェクトで管理しているLPページの仕様ではWeb ComponentのSSRは不要ですが、将来に備えた準備は必要だと考えています。 また、現在の運用方式では、Scriptローディング+CMSデータ連携という構造上、テストが非常に重要であり補強が必要です。<
はじめに こんにちは、FAANS部フロントエンドブロックの中島です。普段はFAANSのiOSアプリ開発を担当しています。FAANS iOSチームではSwift 6移行の取り組みをしています。以前、Strict Concurrency CheckingをTargetedに変更した過程で得た知見を紹介しました。今回TargetedからCompleteに変更するとXcodeで約1400個の新たな警告が出ました。機械的に対応できる警告もありますが、曖昧な知識だと修正が難しいケースもありました。本記事では、Swift 6移行時の警告やエラー解決を通じて得た知見を共有します。実際に遭遇した警告への対処法など、移行作業を始める前に押さえておきたかったポイントを中心に解説します。 移行当初はXcode 16.4だったので、最新のXcodeでは警告がエラーとなる可能性もありますが、本記事では警告で統一します。また、Swift 6でビルドをするとそれまで警告だったものがエラーになりビルド自体が通らなくなるため、まずはSwift 5の段階ですべての警告を解消しました。その後、Swift 6へ切り替えてビルドが通ることを確認し、新たに不具合が発生していないかを検証しました。 目次 はじめに 目次 Completeに変更後、新しく発生した警告の分類 Sendable非準拠の警告原因 解決方法 コンテキストの不一致について 1. nonisolatedのコンテキストでMainActorを利用するケース 解決方法 2. クロージャ内がnonisolatedになるケース Sendableクロージャにおけるケースの実例 解決方法1. Task { @MainActor in } の利用 解決方法2. async関数の利用 3. nonisolatedなプロトコルをMainActorで実装するケース 自作プロトコルのケース ライブラリのプロトコルのケース 実行時にクラッシュするケースの紹介 クラッシュの原因 Combineでコンテキスト不一致によるクラッシュのケース sendingキーワードについて Region Based Isolationについて Task-isolatedな値について Combineのsinkする値について その他の警告 1. passing closure as a 'sending' parameter risks causing data races 2. static property '...' is not concurrency-safe because it is nonisolated global shared mutable state まとめ さいごに Completeに変更後、新しく発生した警告の分類 Strict Concurrency CheckingをTargetedからCompleteへ変更後、新たに発生した警告を分類して集計しました。Xcode 16.4でMinimum Deployment TargetをiOS 16、Swift Language VersionをSwift 5に設定してビルドした際に発生した警告です。 警告概要 警告例 分類 割合 Sendable非準拠 type '...' does not conform tothe 'Sendable' protocol Sendable 約46% nonisolatedでMainActorを利用 call to main actor-isolated initializer '...' in a synchronous nonisolated context コンテキスト不一致 約44% SendableクロージャでMainActorを利用 main actor-isolated property '...' can notbe referenced from a Sendable closure コンテキスト不一致 約2% SendableクロージャでNonSendableを利用 capture of '...' with non-sendabletype '...' in a @Sendable closure Sendable /コンテキスト不一致 約2% nonisolatedプロトコル要件の不一致 main actor-isolated instance method'...' cannot be used to satisfynonisolated protocol requirement コンテキスト不一致 約2% sending引数のデータ競合 sending '...' risks causing data races Sendable /コンテキスト不一致 約2% static/class変数のデータ競合 static property '...' is notconcurrency-safe because it isnonisolated global shared mutable state Sendable /コンテキスト不一致 約2% 上の表を見てわかるように次の2つに関連する警告がほとんどでした。 Sendableに非準拠 コンテキストの不一致 Sendableに準拠できていないことによる警告は、Non-SendableのクラスなどをActor境界を超えて利用したり、Sendable指定の引数に渡したりしたときに発生します。コンテキストの不一致は、あるActorから別のActorのプロパティやメソッドを利用していると出る警告です。すべての警告を本記事で解説するのは難しいですが、警告内容が異なっても同じような直し方や考え方が非常に多いです。 Sendable非準拠の警告原因 FAANSでSendable非準拠の警告が最も発生したのはAPIレスポンスモデルでした。API通信でOpenAPI(Swagger)を用いており、自動生成されたコードを利用しています。レスポンスの型はpublicなstructやenumでしたが、publicな型は暗黙のSendable準拠が行われないため、明示的にSendableを付与する必要があります。そのため、次のコードのようにUICollectionViewで指定するアイテムの型がSendableに準拠する必要があるので、警告が出ていました。 解決方法 移行当初、Swift 6用の自動生成テンプレートがBeta版だったため利用を見送りました。テンプレートのコードにSendableが付与されていることを確認し、暫定対応としてimport文に@preconcurrencyを付与して警告を抑制しました。現在はすでにStable版がリリースされているため、今後対応する場合はSwift 6用のテンプレートを利用することで解決可能です。FAANSアプリにおいても近いうちに対応を予定しています。 コンテキストの不一致について 他の警告も簡単に解決できるとよいのですが、これから登場するコンテキストの不一致にはさまざまなパターンがあり、一筋縄ではいかないケースもありました。しかし、このコンテキストの不一致を理解すれば、Swift 6対応において発生する警告のほとんどを解決できます。次の3つについて実例を交えて説明します。 nonisolatedのコンテキストでMainActorを利用するケース クロージャ内がnonisolatedになるケース nonisolatedなプロトコルをMainActorで実装するケース 1. nonisolatedのコンテキストでMainActorを利用するケース nonisolatedのコンテキストでMainActorのメソッドやプロパティを利用したことが原因で発生する警告を解説します。FAANSでは一覧表示のためにUICollectionViewを多くの画面で利用しており、セクションごとにレイアウトを切り替える実装をしています。セクション名をenumで管理してその中でレイアウトを生成するメソッドを定義しています。しかしenum自体はnonisolatedなコンテキストであるため、MainActorのUICollectionViewLayoutなどを扱うと警告が発生しました。 また、UINavigationControllerを設定するためのヘルパー関数があります。UINavigationControllerはMainActorに隔離されているため、そのヘルパー関数をnonisolatedなstructに定義すると警告が出ました。 解決方法 enumやstructに@MainActorを付与してMainActorと同じコンテキストにそろえました。このように単にMainActorにできていなかったというケースは非常に多く、機械的に修正可能です。 2. クロージャ内がnonisolatedになるケース 次に、クロージャ内がnonisolatedになるケースを紹介します。クロージャ内のコンテキストは利用側と同じ場合もあれば、異なる場合もあります。コード例を用いて説明します。 通常のクロージャは利用側のクラスのコンテキストを引き継ぎます。上記のコード例はMainActorのクラスなのでクロージャ内もMainActorです。一方、Sendableを付与したクロージャはnonisolatedと判断されます。nonisolatedのコンテキストでMainActorのプロパティを変更しているので警告が出ます。この警告を解決するにあたって、実務で遭遇したケースを紹介します。 Sendableクロージャにおけるケースの実例 FAANSアプリではKingfisherという画像ダウンロードライブラリを利用しています。downloadImageメソッドで画像をダウンロードし、後続処理をクロージャ内で実装しています。発生した警告についてコード例とともに説明します。 クロージャ内でCapture of 'self' with non-Sendable type 'Downloader?' in a '@Sendable' closureの警告が出ました。はじめに示した例ではMainActorのクラスでSendableクロージャを利用しましたが、このケースではクラスにMainActorが付与されていません。Sendableクロージャで扱う値はSendableである必要があるので、Non-Sendableのselfをキャプチャしたことで警告が出ました。 ViewModelクラスはダウンロードした値を格納し、状態を持つのでSendableにするのは難しい状況です。実際のコードではUIViewControllerがこのクラスを保持しています。UIViewControllerはMainActorなのでViewModelクラスもMainActorにしました。 しかし、Sendableのクロージャはnonisolatedと判断されるため、nonisolatedのコンテキストでMainActorのプロパティを変更できないという警告が新たに出ました。 解決方法1. Task { @MainActor in } の利用 nonisolatedのクロージャ内でTask { @MainActor in }を利用してMainActorのコンテキストで値をセットするように変更しました。 @MainActor class ViewModel { var coverImage: UIImage? func setImage(url: String) { guar