TECH PLAY

株式会社ZOZO

株式会社ZOZO の技術ブログ

987

はじめに こんにちは。Developer Engagementブロックの @wiroha です。12月17日に「 AWS re:Invent 2024 Recap in ZOZO 」を開催しました。12/2〜6日の5日間に渡ってラスベガスで開催されたAWS re:Invent 2024を振り返るRecapイベントです。 登壇内容まとめ はじめに、アマゾンウェブサービスジャパン合同会社でソリューションアーキテクトを務める3名より、Ad & Marketing、Serverless、そしてEKS Auto Modeについて発表していただきました。その後、re:Invent 2024に現地参加したZOZOのエンジニア4名が、それぞれの視点で発表しました。 発表タイトル 登壇者 Ad & Marketing 関連 re:cap –頭出し- アマゾンウェブサービスジャパン合同会社 関藤 様 AWS re:cap Serverless アマゾンウェブサービスジャパン合同会社 朴 様 EKS Auto Mode アマゾンウェブサービスジャパン合同会社 堀内 様 EKSとAmazon Qのアップデート ZOZO 纐纈 Amazon Novaのすゝめ基盤モデルの性能比較を添えて ZOZO 土田 ( @andex_tokyo ) 英語が苦手でも学びが得られるWorkshopについて ZOZO 佐藤 ( @taquaki_satwo ) ガバナンスを支える新サービス ZOZO 江島 ( @sejima1105 ) Ad & Marketing 関連 re:cap –頭出し- アマゾンウェブサービスジャパン合同会社 関藤 様による発表 speakerdeck.com AWS re:cap Serverless アマゾンウェブサービスジャパン合同会社 朴 様による発表 speakerdeck.com EKS Auto Mode アマゾンウェブサービスジャパン合同会社 堀内 様による発表 speakerdeck.com EKSとAmazon Qのアップデート ZOZO 纐纈による発表 speakerdeck.com Amazon Novaのすゝめ基盤モデルの性能比較を添えて ZOZO 土田による発表 speakerdeck.com 英語が苦手でも学びが得られるWorkshopについて ZOZO 佐藤による発表 speakerdeck.com ガバナンスを支える新サービス ZOZO 江島による発表 speakerdeck.com 最後に 発表後にはAWSに関するクイズ大会を実施し、参加者同士で盛り上がりました。みなさまの正解率が高く、流石詳しい方が多いなと感じました。また、参加者にTシャツやポーチ、エコバッグなどのノベルティをプレゼントしました。 終了後にも登壇者やre:Invent現地参加者への質問などそれぞれ情報交換が行われ、有意義な時間を過ごすことができました。 時間の都合上、今回のRecapイベントでのZOZOからの登壇者は4名でしたが、AWS re:Invent 2024には総勢13名が現地参加していました。現地の様子やセッションの詳細に触れている参加レポート記事もあわせてご覧ください。 techblog.zozo.com ZOZOではAWSを活用しながら働くエンジニアを募集中です。ご興味のある方は以下のリンクからぜひご応募ください。 hrmos.co corp.zozo.com
はじめに こんにちは。Developer Engagementブロックの @wiroha です。12月16日に「 GitHub Universe 2024 Recap in ZOZO 」を開催しました。10/29-30日の2日間に渡ってサンフランシスコで開催されたGitHub Universe 2024を振り返るRecapイベントです。 登壇内容まとめ 各社から次の4名が登壇しました。 発表タイトル 登壇者 GitHub Universe 2024 Recap GitHub 服部 佑樹 様 iOS開発におけるCopilot For XcodeとCode Completion 株式会社ZOZO 山田 楓也 GitHub Copilot のテクニック集 株式会社ZOZO 佐藤 優羽 GitHubで育つインナーソース文化 : ニフティでの挑戦事例 ニフティ株式会社 芦川 亮 様 GitHub Universe 2024 Recap GitHub 服部 佑樹さまによる発表 服部さまからは、GitHub Universe 2024で発表された最新情報についてお話しいただきました。GitHub Copilotで使用できるAIモデルの選択肢が増える機能は、特に注目を集めているそうです。 iOS開発におけるCopilot For XcodeとCode Completion 株式会社ZOZO 山田 楓也による発表 speakerdeck.com 山田からはCopilot For XcodeとCode Completionの補完力の比較について発表しました。シチュエーションやバージョンによって異なるという前提がありつつ、今回の3番勝負ではGitHub Copilotが優位という結果になったそうです。 GitHub Copilot のテクニック集 株式会社ZOZO 佐藤 優羽による発表 speakerdeck.com 佐藤からはGitHub Copilotを効果的に活用するためのテクニック集を紹介しました。参加したみなさまの知らないテクニックも多く、役に立っていたようです。 GitHubで育つインナーソース文化 : ニフティでの挑戦事例 ニフティ株式会社 芦川 亮さまによる発表 speakerdeck.com ニフティ株式会社の芦川さまからは、インナーソースの挑戦事例をお話しいただきました。内製の便利ツールに対して、エンジニアがOSSのようにコントリビュートするパターンが成功例として印象的でした。どのように導入し、その結果がどうなっているのか、実践的なお話を聞くことができました。 プレゼント企画 プレゼント企画 すべての発表が終わった後、GitHubに関するクイズに答えてノベルティをプレゼントする企画を実施しました。GitHubを日常的に使用していればわかるような問題からファン向けのコアな問題まで、幅広い出題内容で盛り上がりました。 最後に 話題になったロゴも含むGitHubステッカー 本イベントを通じてGitHubの最新情報や、実際の活用事例を知ることができました。みなさまありがとうございました。今後もさまざまなイベントを企画していく予定ですので、ぜひご期待ください。 ZOZOでは一緒に働く仲間を募集中です。ご興味のある方は以下のリンクからぜひご応募ください。 corp.zozo.com
Developer Engagementブロックの @ikkou です。2024年もいよいよ終わりに近づいてきました。この季節の風物詩、「アドベントカレンダー」には皆さんも参加されましたか? ZOZOは例年アドベントカレンダーに参加し、2020年以降、記事数を100本、125本、175本、225本と増加。そして今年は過去最高の計275本の記事を公開しました! 本記事ではその概要をお伝えします。 ZOZO Advent Calendar 2024 今年は合計11個のカレンダーを完走し、12月1日から25日の間に275本の記事を公開しました! ZOZO ADVENT CALENDAR 2024、無事完走しました 🎉 今回はなんとシリーズ11まで、合計275件の記事を公開しました! 読んでいただいた皆さん、ありがとうございました! https://t.co/bwNIOJf5Ip #zozo_engineer #Qiitaアドカレ #Qiita pic.twitter.com/IWyhUwsolM — ZOZO Developers (@zozotech) 2024年12月26日 qiita.com 実施概要 ZOZOのアドベントカレンダーは以下の形式で運用しています。 形式 : 任意参加 運用方法 : Slackチャンネルで実施と参加を呼びかけ、各自が空いている日に登録 公開先 : ZOZO TECH BLOG、Qiita、Zenn、note、個人ブログなど 参加人数 : 140名(昨年より17名増) 公開記事数 : 合計275本 もっとも多くの記事を書いたのは昨年に続き@shiozakiさんで、今年は計29本の記事を公開しています。特に「シリーズ 3」は「JSON以外の◯SON」シリーズとして、ひとりで25記事を完走しています。 また、今年はチーム単位でひとつのカレンダーを自主的に担当する動きも見られました。 シリーズ 2: 推薦基盤 シリーズ 4: データSRE シリーズ 5: カート決済SRE 特定の技術領域に興味がある方は、ぜひ各シリーズの記事をチェックしてみてください。 アドベントカレンダーは、アウトプットの練習や執筆スキルを高める絶好の機会です。ZOZOではテックブログをアウトプットの主軸に置いていますが、「まだテックブログを書く自信が無い」「テックブログに書くにはネタが小粒」のような場合に、アドベントカレンダーは良い機会です。 運営目線での取り組みについてはDay 1の記事として公開しています。この記事中でも触れていますが、アドベントカレンダーを「お祭り感覚」で楽しむ文化が、ZOZOの開発組織には根付いています。これはZOZOの開発組織の特徴のひとつと言えるでしょう。 zenn.dev 2024年の振り返り ZOZOのアドベントカレンダーでは例年その年を振り返る記事を公開しています。 開発組織の振り返り ZOZOの開発組織については、昨年同様に執行役員 兼 CTOの @sonots が「振り返りと現状」を記事にまとめています。 qiita.com 推薦基盤チームの取り組み 推薦基盤チームに特化した「振り返りと現状」も記事にまとまっています。 qiita.com 生成AI活用事例の特集 2021年から2023年まではコーポレート広報チームによる「ファッションテックハイライト」を公開していましたが、今年は切り口を変えてZOZOの生成AI活用事例を紹介する特集記事を公開しています。こちらもあわせてご覧ください。 technote.zozo.com 過去のアドベントカレンダー ZOZOでは2018年から毎年アドベントカレンダーに参加しています。過去の取り組みは以下をご覧ください。 年 カレンダー 2023年 ZOZO Advent Calendar 2023 2022年 ZOZO Advent Calendar 2022 2021年 ZOZO Advent Calendar 2021 2020年 ZOZOテクノロジーズ Advent Calendar 2020 シリーズ 1 ZOZOテクノロジーズ Advent Calendar 2020 シリーズ 2 ZOZOテクノロジーズ Advent Calendar 2020 シリーズ 3 ZOZOテクノロジーズ Advent Calendar 2020 シリーズ 4 2019年 ZOZOテクノロジーズ Advent Calendar 2019 シリーズ 1 ZOZOテクノロジーズ Advent Calendar 2019 シリーズ 2 ZOZOテクノロジーズ Advent Calendar 2019 シリーズ 3 ZOZOテクノロジーズ Advent Calendar 2019 シリーズ 4 ZOZOテクノロジーズ Advent Calendar 2019 シリーズ 5 2018年 ZOZOテクノロジーズ Advent Calendar 2018 シリーズ 1 ZOZOテクノロジーズ Advent Calendar 2018 シリーズ 2 ZOZOテクノロジーズ Advent Calendar 2018 シリーズ 3 最後に ZOZOでは、プロダクト開発以外にも、アドベントカレンダーのような外部への発信も積極的に取り組んでいます。 一緒にサービスを作り上げる仲間をはじめ、エンジニアとしての技術力向上や外部発信に意欲的な方を積極的に募集しています。ご興味のある方は、以下のリンクからぜひご応募ください! corp.zozo.com
はじめに こんにちは、ブランドソリューション開発本部FAANS部の加藤です。私の開発しているショップスタッフの販売サポートツールFAANSでは、この度、コーディネート動画の投稿機能が実装されました。動画の投稿機能は、動画のトリミングや音声の編集ができ、投稿された動画はアプリ上で閲覧できます。 この記事では、動画の投稿機能を開発する上で直面した問題と、その解決方法をお伝えします。 目次 はじめに 目次 動画の投稿機能の流れ トリミング画面を1から作成する サムネイル画像の作成方法 トリミングコントローラからトリミング時刻の算出 動画のループ再生方法 エンコードされた動画が再生できるまでにラグが発生する問題への対処法 まとめ さいごに 動画の投稿機能の流れ まず、動画の投稿機能の流れを以下の図で説明します。ユーザーは最初に投稿したい動画を選択して、選択した動画に対してトリミング範囲、動画に付与する音楽、音楽の再生範囲を決定します。その後、動画に関する情報(動画内のモデルが使用しているアイテムの情報・動画の説明など)を動画情報の入力画面で入力して、投稿ボタンを押すことで動画のエンコードとアップロードが行われます。動画の投稿後は、一覧画面から投稿された動画とその詳細情報を閲覧できます。 今回、これらの動画投稿の機能を実装する上で、以下の3つの問題に直面しました。 トリミング画面を1から作成する トリミング区間でループ再生する エンコードされた動画が再生できるまでにラグが発生する トリミング画面を1から作成する iOSでは動画のトリミング機能を実装する方法の1つとして、UIKitのUIImagePickerControllerを用いた実装方法が挙げられます。UIImagePickerControllerを用いると下記、画像左側のように画面上部のトリミングコントローラーでトリミング範囲を指定できます。一方で、FAANSのトリミング機能は、トリミング後の動画長を1分以下に収める制限があります。そのため、トリミング後の動画長が1分を超える場合には、即時、画面上にアラートを出すことが求められるのですが、UIImagePickerControllerでは実現できません。そこで、複数のViewを組み合わせて、画像右側のようなオリジナルのトリミング画面を作成しました。 FAANSのトリミングコントローラーは、以下の画像のような5種類のViewで作成されています。View1は動画の各時刻におけるサムネイル画像が並べられたViewです。また、View2はView1に重ねられており、トリミング範囲外のView1に影をつけるためのViewです。ユーザーはView1を見ながら、どの部分をトリミング時刻にするかを伸縮可能なView3の両端をドラッグすることで指定します。具体的には、View3の両端に重ねられている透明なView5に触れており、ドラッグで動くView5の位置から動画のトリミング時刻を算出します。View2、3の内側は、View4を用いてくり抜かれており、下側のView1が見える状態になっています。次節では、トリミングコントローラのView1に配置するサムネイル画像を作成する方法と、View5の位置からトリミング後の動画の再生時刻を算出する方法を紹介します。 サムネイル画像の作成方法 本節では、トリミングコントローラー(View1)内に表示する動画の各時刻のサムネイル画像の作成方法について述べます。以下のようなプログラムでサムネイル画像を生成しました。 // トリミングビューに表示するサムネイルの数を計算する関数 private func thumbnailCount () -> Int { let thumbnailWidth : CGFloat = 30 // 各サムネイルの幅を固定値で設定 return Int(trimmingViewWidth / thumbnailWidth) // トリミングコントローラーの幅に基づいてサムネイル数を計算 } // 動画のサムネイルを生成し、トリミングコントローラーに追加する関数 private func setupThumbnails () { guard let videoURL = videoState?.path else { return } let asset = AVAsset(url : videoURL ) let imageGenerator = AVAssetImageGenerator(asset : asset ) // 動画の画像を生成するジェネレーターを設定 imageGenerator.appliesPreferredTrackTransform = true let duration = CMTimeGetSeconds(asset.duration) // 動画の全体時間を取得 let interval = duration / Double(thumbnailCount()) // サムネイル間の時間間隔を計算 // サムネイルの数だけループ for i in 0 ..< thumbnailCount() { // 各サムネイルの生成時間を設定 let cmTime = CMTime(seconds : interval * Double(i), preferredTimescale : 600 ) let timeValue = NSValue(time : cmTime ) // timeValueを参照して、サムネイル画像を生成 imageGenerator.generateCGImagesAsynchronously(forTimes : [ timeValue ] ) { [ weak self ] _, cgImage, _, _, _ in guard let self = self else { return } if let cgImage = cgImage { DispatchQueue.main.async { let imageView = UIImageView(image : UIImage (cgImage : cgImage )) // i番目のサムネイル画像の表示位置を設定 imageView.frame = CGRect( x : CGFloat (i) * ( self .trimmingViewWidth / CGFloat( self .thumbnailCount())), y : 0 , width : self.trimmingViewWidth / CGFloat( self .thumbnailCount()), height : self.trimmingViewHeight ) self .trimmingView.addSubview(imageView) self .trimmingView.sendSubviewToBack(imageView) } } } } } 上記のプログラムでは、thumbnailCountでサムネイル画像の幅を定義して、何枚のサムネイル画像をトリミングコントローラー内に配置できるかを算出します。算出された値で動画内の時刻を等間隔で指定して、指定した時刻のサムネイル画像をAVAssetImageGeneratorのgenerateCGImagesAsynchronouslyで生成します。あとは、生成された画像をtrimmingView(土台となるView)上に配置して完成です。 トリミングコントローラからトリミング時刻の算出 本節では、トリミングコントローラーからトリミング時刻を算出する方法について述べます。画像のように、黄色のView(以下、trimmingRangeView)とサムネイル画像が設置されたView(以下、trimmingView)の境界を基準として、トリミング時刻を算出します。 トリミング時刻算出のプログラムは以下の通りです。 private let handleWidth : CGFloat = 17 // trimmingViewの両端の幅 // トリミング範囲の開始時刻と終了時刻を計算する関数 private func calculateTrimmedTimeRange () -> ClosedRange < Double > ? { guard let videoURL = videoState?.path else { return nil } let asset = AVAsset(url : videoURL ) let videoDuration = CMTimeGetSeconds(asset.duration) // trimmingViewの長さに対する時刻の算出基準位置の割合を算出 let leftHandlePosition = (trimmingRangeView.frame.minX + handleWidth - trimmingView.frame.minX) / trimmingViewWidth let rightHandlePosition = (trimmingRangeView.frame.maxX - handleWidth - trimmingView.frame.minX) / trimmingViewWidth // 算出された割合×動画長でトリミング範囲後の時刻を算出する let trimmedStartTime = max( 0.0 , leftHandlePosition * videoDuration) let trimmedEndTime = min(rightHandlePosition * videoDuration, videoDuration) return trimmedStartTime ... trimmedEndTime } トリミング時刻を算出するために、AVAssetを用いて動画長( )を取得します。つぎに、trimmingRangeViewの時刻算出の基準点とtrimmingViewの長さの割合 を算出します。そして、 を計算することでトリミング時刻を算出できます。これを左右の基準点で行い、トリミングの開始時刻と終了時刻を算出できます。以下にtrimmingRangeViewの位置に応じて、トリミング時刻を更新しているgifを示します。gifのように算出されたトリミング時刻が1分を超える場合には、アラートを出すようにすることで、トリミング後の動画長を制限するFAANSオリジナルのトリミング画面を作成できました。 以上がトリミング画面を1から実装する方法の紹介です。 動画のループ再生方法 FAANSには2種類の動画再生が存在します。指定したトリミング区間に基づいてループ再生する場合(以下、エンコード前)と、動画の初めから終わりまでをループ再生する場合(以下、エンコード後)です。まず、比較的シンプルなエンコード後の動画の再生方法について述べます。プログラムは以下の通りです。 // 動画ファイルのURLから、AVPlayerを使用してプレイヤーを初期化 let player = AVPlayer(playerItem : . init (url : videoURL )) player.play() // 動画の再生 // 以下は表示のロジック let playerLayer = AVPlayerLayer() playerLayer.player = player playerLayer.videoGravity = .resizeAspect playerLayer.frame = bounds layer.addSublayer(playerLayer) // 動画の再生終了を監視するオブザーバーを追加 NotificationCenter. default .addObserver( self , selector : #selector(playerDidFinishPlaying(_ : )), name : .AVPlayerItemDidPlayToEndTime, object : player.currentItem ) // 動画再生終了時に呼び出されるメソッド @objc private func playerDidFinishPlaying (_ notification : Notification ) { guard let playerItem = notification.object as? AVPlayerItem , playerItem == playerLayer.player?.currentItem else { return } // 再生位置を最初に戻す playerLayer.player?.seek(to : .zero) { [ weak self ] _ in self ?.playerLayer.player?.play() } } 上記のプログラムでは、オブザーバー(.AVPlayerItemDidPlayToEnd)で動画の終了を監視しており、動画の終了時にplayerDidFinishPlayingが呼び出されます。playerDidFinishPlayingが呼び出されたとき、動画が初期の状態、すなわち0:00に戻されます。このプログラムでエンコード後の動画であれば、ループ再生できます。 一方でエンコード前の動画は、指定されたトリミング区間に基づいて、映像の途中でループする必要があるため、AVPlayerItemDidPlayToEndは利用できません。そこで、エンコード前の動画のループ再生を以下のように実装しました。 // 動画ファイルのURLから、AVPlayerを使用してプレイヤーを初期化 let player = AVPlayer(playerItem : . init (url : videoURL )) player.play() // 動画の再生 // AVPlayerの動画を表示するためのAVPlayerLayerを設定 let playerLayer = AVPlayerLayer() playerLayer.player = player playerLayer.videoGravity = .resizeAspect playerLayer.frame = bounds layer.addSublayer(playerLayer) // 再生中の時刻を定期的に監視するためのオブザーバーを設定 var timeObserverToken : Any? let timeInterval = CMTime(seconds : 0.01 , preferredTimescale : CMTimeScale (NSEC_PER_SEC)) // 0.01秒間隔で監視 timeObserverToken = player.addPeriodicTimeObserver(forInterval : timeInterval , queue : .main) { [ weak self ] _ in guard let self = self , let currentItem = playerLayer.player?.currentItem else { return } let currentItemTimeSeconds = CMTimeGetSeconds(currentItem.currentTime()) // 再生時間の取得 // プレイヤーが再生可能な状態(readyToPlay)である場合に処理を続行 if currentItem.status == .readyToPlay { playbackTimeDidChange(currentTime : currentItemTimeSeconds ) } } // 再生時刻が変更された時に呼び出される関数 func playbackTimeDidChange (currentTime : Double ) { guard let range = videoState.value?.video.timeRange else { return } // トリミング区間が代入されている変数 let startTime = range.lowerBound // トリミング区間の開始時刻 let endTime = range.upperBound // トリミング区間の終了時刻 if currentTime >= endTime { // トリミング区間の終了時刻を再生時刻が超えたときにトリミング区間の開始時刻に戻す player.seek(to : startTime , toleranceBefore : .zero, toleranceAfter : .zero) { [ weak player, weak self ] completion in guard completion else { return } player?.play() // シーク後に再生を再開 } } } 上記のプログラムでは、AVPlayerの再生時刻をオブザーバー(addPeriodicTimeObserver)を用いて0.01秒間隔で監視しています。AVPlayerの再生時刻がトリミング区間の終了時刻を超えた場合、AVPlayerの再生時刻をトリミング区間の開始時刻にシークすることで、トリミング区間におけるループ再生を実現しています。 エンコードされた動画が再生できるまでにラグが発生する問題への対処法 FAANSでは、エンコードした動画をアップロードした際に、エンコードされた動画のURLが発行されます。発行されたURLを参照して動画を再生しますが、S3に動画がコピーされるまでの間、動画を再生できません。そのため、動画が再生できるようになるまでの間はインジケータを表示して、再生可能になった時点でインジケータを非表示にして動画を表示する必要があります。下記の図は動画が再生できないケースの模式図です。本節では、動画が再生可能かどうかの監視方法について述べます。プログラムは以下の通りです。 private func configureVideoPlayer () { guard let videoURL = self .videoURL else { return } self .videoPlayer = AVPlayer(playerItem : . init (url : videoURL )) self .cancellable?.cancel() // 前回の監視がある場合はキャンセル(メモリリークを防止) // AVPlayerItemのステータスを監視 self .cancellable = videoPlayer?.currentItem?.publisher( for : \.status) .sink { [ weak self ] status in self ?.handleStatus(status : status ) // ステータスに応じて処理を実行 } } private func handleStatus (status : AVPlayerItem.Status ) { switch status { case .readyToPlay : // 動画が再生可能な状態になった場合 self .isLoadingVideo.value = false // インジケータを非表示に設定 case .failed : // 再生に失敗した場合 // エラーがファイルの未存在である場合にリトライ処理を実行 guard let error = videoPlayer?.currentItem?.error as NSError? , error.code == NSURLErrorFileDoesNotExist else { self .isLoadingVideo.value = false return } // 2秒後に再試行を行う(非同期で再生設定を再実行) DispatchQueue.main.asyncAfter(deadline : .now() + 2 ) { [ weak self ] in guard let self = self else { return } self .configureVideoPlayer() } default : break } } このプログラムでは、AVPlayerItemのステータスをCombineのpublisherを用いて監視し、ステータスが変化した際に対応します。ステータスがreadyToPlayの場合には再生可能であるため、インジケータを非表示にします。また、ステータスがfailedでエラーコードがNSURLErrorFileDoesNotExistの場合は、動画をS3にコピーしている最中と判断し、インジケータを表示したまま2秒後に再試行します。それ以外のエラーの場合には、S3のコピー以外のエラーとして処理を中断します。この実装で、S3への動画コピーが完了して再生可能になるまでインジケータを表示できます。 まとめ 今回は、FAANSにおける動画投稿に関する機能の実装方法について紹介しました。トリミング機能の実装方法の紹介では、トリミング画面を1から作る方法を解説し、サムネイル画像を作成する方法やトリミングコントローラーからトリミング時刻を算出する方法について述べました。また、動画をループ再生する方法と、エンコードされた動画が再生できるまでにラグが発生する問題の対処法についても紹介しました。この記事が同じような問題に遭遇した方や、これから動画に関する機能を開発しようとしている方の参考になれば幸いです。 さいごに ZOZOでは、一緒にサービスを作り上げてくれる仲間を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com
はじめまして! 25卒内定者のだーはまです。11月26日から27日にかけて湯河原で開催された内定者向け開発合宿に参加しました。この合宿では、みんなでハッカソンに取り組むだけでなく、温泉や足湯、美味しい食事を堪能しながら内定者同士の親睦を深めました。 合宿の概要 この合宿には参加を希望する25卒の内定者が集まり、2日間でハッカソンを行います。さらに先輩のエンジニアや役員がサポート役で参加します。 ハッカソンのルールは以下の通りです。 形式:個人開発 テーマ:日常で感じている課題を解決するアイデアを考えてみよう 集合 1日目は朝10時に湯河原駅へ集合するところから始まります。遅刻しないか心配でしたが、心配性な性格が功を奏し30分前に到着しました。ちなみに、僕のほかに心配性な同期4名ほどが30分前に集合していました。早く到着したメンバーで湯河原駅近くの喫茶店へ行きました。開発や大学などの話で盛り上がり、楽しい時間を過ごせました。 宿について 合宿場所は湯河原駅からバスで10分ほどの距離にある旅館「おんやど惠」です。 おんやど惠は、開発合宿に最適な施設と設備が揃った場所でした。露天風呂付きの温泉や足湯は心身ともにリラックスできます。ネットワーク環境も非常に安定しており、30人程度で同じWi-Fiを使用しても快適に開発を進められました。さらに、徒歩30秒のコンビニで気軽に買い出しができ、作業に集中できます。 自己紹介タイム 今回の開発合宿に参加した内定者とサポートしてくれる社員の自己紹介がありました。みんなそれぞれ特徴的な自己紹介をしてくれました。 昼食 自己紹介が終わると、昼食の時間です。3種類の美味しいお弁当が用意されていました。お弁当を選ぶ順番はくじ引きで決まります。僕は9番目というなんとも言えない順番だったのですが、一番人気のカルビ弁当を選べました。開発合宿が最高の形でスタートしました。 開発 こんにちは! 25卒内定者のてぃーてぃーです。さて、昼食後すぐに開発が始まりました。開発で行き詰まったところがあれば先輩社員の方たちにアドバイスをもらい、時には他の内定者の様子が気になりどんなものを作っているのかチラチラ見ながら、翌日の成果発表会に向けて開発を進めていきました! 今回の開発合宿で、みんなの開発に対する真剣な眼差しやエンジニアとしての姿を初めて見ることができました。 夕食 1日目の開発時間が終了し、夕食の時間がやってきました。食事はコース形式で、お刺身など様々な和食料理が出てきました。とても美味しかったです! 内定者や社員にかかわらず自由に着席し、食事とお酒を味わいました。入社歴や職種に関わらず、社員同士の距離が近いということがZOZOの魅力だと改めて感じました! 自由時間 夕食後は就寝まで自由時間です。立派な温泉に入ったり、再度集まって開発を続けたりしました。みんな寝る時間ギリギリまで開発や発表準備を続けており、とても熱意を感じました! 部屋は4人部屋でしたので、男性陣は職種ごと(モバイル、バックエンド、SRE、機械学習など)に分かれて寝泊まりしました。 成果発表会 こんにちは。ここからは25卒内定者のじゅんじゅんがレポートします! 2日目になり、いよいよ合宿の集大成を発表する時間になりました。発表順はくじ引きで決めます。 発表は8分と質疑応答の形式で行われました。質疑応答では、技術的な深掘りからフリートークまで、和気藹々とした発表会になりました! 表彰式 休憩後、表彰式が行われました。CPO賞1名と、先輩スタッフ賞2名、内定者賞1名の発表が行われました。それぞれ豪華な景品があったので、ドキドキしながら発表を聞きました。 懇親会 最後は旅館から近くのお店に移動し、開発合宿を振り返りながら先輩との仲を深めるために懇親会を行いました。この2日間ですっかり打ち解けていたので、年齢や職種などは関係なく、みんなでとても盛り上がりました! 僕はZOZOTOWN CPOの橋本さんと同じテーブルで、ZOZOの話からプライベートの話まで色々と聞くことができ、人生の勉強になりました。 受賞作品の紹介 ここからは、表彰された4つの作品を紹介します。 CPO賞「MOMENT CAPSULE」 プロダクト紹介 このアプリは、アナログの魅力を残しつつ、デジタルでつながる新しい形の「写真タイムカプセル」を実現するアプリです。指定した開封日時になるまで中身を閲覧できません。 開発経緯 このアプリは、タイムカプセルの魅力を手軽に楽しめる形で提供したいという思いから生まれました。きっかけは、妹が10年後の自分に向けた手紙を受け取ったエピソードで、「タイムカプセルってエモいな」と感じたことです。しかし、従来のタイムカプセルには以下のような課題があり、実現のハードルが高いものでした。 埋める場所の確保が必要 掘り返すためにみんなで調整しないといけない 何を入れたらいいか迷ってしまう 時間が経つと忘れてしまう可能性がある そこで、「もっと手軽で簡単なタイムカプセルを作れないか」と思い開発しました。 先輩スタッフ賞「まだ飲む?」 プロダクト紹介 飲酒者に数学の問題を解かせ、正答率に応じて泥酔度を算出し、泥酔度に応じてまだ飲んでも大丈夫かどうかをLINEグループにpush通知するアプリです。 開発経緯 年末になると飲み会が増え、それに伴いお酒による失敗も増加します。その原因を分析したところ、泥酔しているかどうかが自分でわからなくなり、まだ飲めると思い続けることで飲みすぎてしまい、結果として論理的思考力が低下し失敗につながるという流れが明らかになりました。そこで、論理的思考力をテストして酔っ払っているかどうかを周囲に認知させることで、この失敗を回避できるのではないかと思いました。 先輩スタッフ賞「メイクレシピ保存アプリ」 プロダクト紹介 メイクレシピの画像を読み込んで文字列化し、画像とともに保存できるアプリです。将来的には、保存したデータをもとに共通のコスメなどをレコメンドする機能を実装したいと考えています。 開発経緯 SNSで流行したメイクレシピの有益な情報が、画像のままではデータとして活用できず、埋もれてしまっている現状に課題を感じました。また、多くの人が他者と共有するために作成したメイクレシピがカメラロールの中で眠ったままになってしまう状況をもったいないと感じたことが、開発のきっかけです。 内定者賞「Decision Coin」 プロダクト紹介 Decision Coinはグループ内において、コインをアプリ内通貨として取引をしながら、ゲーム感覚で意思決定を行うアプリです。 開発経緯 日常生活における意思決定の難しさに着目して開発しました。家族、恋人、友達、研究室、サークルなど、ほぼ全てのコミュニティで意思決定が必要となりますが、価値観のすり合わせは言葉だけでは難しいことがあります。そのため、価値観の定量化を行う術がないか模索していました。また、最近自分が仮想通貨にハマっていることから、この意思決定を取引としてゲーム感覚で行うことで、より円滑なコミュニケーションと合意形成を実現できるだろうと考えました。 おわりに 2日間の開発合宿では、設備が整った旅館で温泉に入り放題という最高の環境の中、体を癒やしながら開発に集中できました。また、同期や先輩と開発や技術、ZOZOについて語り合う中で、社内の雰囲気の良さや高い技術力を実感しました。そして、これまで泊まりがけで過ごす機会のなかった、これから一緒に働く同期の人となりについても知ることができました。この合宿で得た知識と経験を活かし、さらに成長していきたいと思います。ありがとうございました!
はじめに こんにちは、WEAR Webフロントエンドチームでテックリードをしている冨川( @ssssota )です。業務でUniversal Linksのテストを効率化するために、独自のパッケージを開発し、 GitHub および npm で公開しました。本記事ではそのモチベーションと利用方法などを紹介します。 目次 はじめに 目次 背景・課題 apple-app-site-association ファイル 挙動確認の課題 解決の取り組み パッケージの機能 root権限とmacOS依存 デモ おわりに 背景・課題 まず、Universal Linksについて紹介します。Universal Linksは、ブラウザなどからiOSアプリを開くためのディープリンク技術の一種です。 apple-app-site-association というファイルをWebサーバに配置することで、WebページとiOSアプリの関連付けを行います。 apple-app-site-association ファイル apple-app-site-association ファイルは、Webサーバに配置するJSONファイルです。このファイルには、Universal Linksの挙動を制御するための情報を記述します。以下に例を示します。 { " applinks ": { " details ": [ { " appID ": " HOGE1.com.example.app ", " components ": [ { " # ": " nondeeplinking ", " exclude ": true } , { " / ": " /search/ ", " ? ": { " q ": " * " } } ] } , { " appIDs ": [ " HOGE2.com.example.app ", " HOGE2.com.example.app " ] , " components ": [{ " / ": " /*/posts/* " }] } ] } } このように、 apple-app-site-association ファイルには、アプリIDと対象URLのマッピングが記述されています。このファイルをWebサーバに配置することで、対象URLに対してiOSアプリが開かれるか否かを制御できます。一見するとglobのような記述と、パス、クエリ、フラグメントを設定でき柔軟に見えます。しかし、挙動が予想しにくい面もあります。 例えば、 * はワイルドカードで / という文字にもマッチします。上記の例では /*/posts/* というパス設定を記述していますが、 /foo/posts/ や /foo/bar/posts/baz/qux なども該当します。これはglobに慣れているエンジニアにとっては違和感を感じるかもしれません。 挙動確認の課題 Webサーバにファイルを配置する関係上、アプリエンジニアではなくWebエンジニアがファイルを管理することがあります。これはWebアプリのパスを管理しているエンジニアが管理するという意味では非常に合理的ではあるものの、挙動確認はやや困難です。設定のミスによって、意図せずアプリが起動するようになるなどのリスクもあります。 apple-app-site-association ファイルの挙動確認は、 swcutil というmacOSにプリインストールされたコマンドを用いることで行うことができます。しかし、このコマンドはドキュメントが少ない他、Universal Linksの挙動確認においては使い勝手が悪いです。以下にいくつかの使用例を示します。 $ swcutil verify -j ./apple-app-site-association -u '/' swcutil must be run as root $ sudo swcutil verify -j ./apple-app-site-association -u '/' { s = applinks, a = HOGE.com.example.app, d = www.example.com }: Pattern "/" matched. $ sudo swcutil verify -j '{"applinks":{"details":[{"appIDs":["HOGE1.com.example.app","HOGE2.com.example.app"],"components":[{"#":"nondeeplinking","exclude":true},{"/":"/search/"}]}]}}' -u '/search/' { s = applinks, a = HOGE1.com.example.app, d = www.example.com }: Pattern "/search/" matched. { s = applinks, a = HOGE2.com.example.app, d = www.example.com }: Pattern "/search/" matched. まず、 sudo コマンドでroot権限を使用していることに気づきます。非root権限で実行すると swcutil must be run as root というメッセージが標準エラー出力に出力されます。また、規模の大きなWebアプリケーションでは多数のURLパスのテストが必要となるケースも考えられます。このような場合シェルスクリプトなどを用い反復することになります。しかしながら出力はプログラムから扱いやすい形式とはいえません。 解決の取り組み 先のような課題を解決するため、 universal-links-test というNPMパッケージを開発しました。このパッケージは、 swcutil コマンドをラップした関数を提供し、 apple-app-site-association ファイルの挙動確認をサポートします。 NPMパッケージとして提供することで、Web開発者が容易に導入し、テストと共に apple-app-site-association ファイルを管理できるようになります。 パッケージの機能 現在、 universal-links-test は主に verify 関数を提供します 1 。この関数は apple-app-site-association ファイルのファイルパス(もしくはJSON)とURLを受けとり、指定されたURLによってどのアプリが開かれるかをMapで返します。以下に例を示します。 import { verify, type AppleAppSiteAssociation } from "universal-links-test"; const aasa: AppleAppSiteAssociation = { applinks : { details : [{ appIDs : [ "HOGE.com.example.app" ] , components : [ { "#" : "nondeeplinking" , exclude : true } , { "/" : "/search/" } ] , }] , } , } ; const result: Map < string , "match" | "block" > = await verify(aasa, "/search/" ); console .log(result. get ( "HOGE.com.example.app" )); // => "match" root権限とmacOS依存 verify 関数は swcutil コマンドのラッパーであるため、macOSかつroot権限が必要です。 universal-links-test はこの課題に対し完全な解決策は提供できないものの、緩和策を提供します。 それが universal-links-test/sim というモジュールです。これは universal-links-test 同様、 verify 関数を提供しています。 swcutil の挙動をシミュレートしているため、macOSやroot権限でなくとも利用できます。開発時には universal-links-test/sim を、CI環境では universal-links-test を利用することで、Universal Linksの挙動確認を確実に行えます。 GitHub Actionsでの利用例を以下に示します。 universal-links-test はmacOSかつroot権限を必要とするため、CIはmacOSランナーを利用する他、 sudo コマンドを用いてroot権限を取得する必要があります。すべてのテストをroot権限で実行するのはセキュリティ上望ましくないため、VitestやJestなどのテストランナーを使っている場合でも、対象ファイルのみ sudo で実行するなど配慮が必要です。以下の例では node --test コマンドを使うことで隔離しています。 // universal-links.test.js import { verify } from "universal-links-test" ; import { test } from "node:test" ; import * as assert from "node:assert" ; const appleAppSiteAssociationPath = "path/to/apple-app-site-association" ; test ( "Universal Links test" , async () => { const cases = [ [ "HOGE.com.example.app" , "/search/" , "match" ] , [ "HOGE.com.example.app" , "/search/#nondeeplinking" , "block" ] , [ "HOGE.com.example.app" , "/" , undefined ] , ] ; for ( const [ appID , path , expected ] of cases ) { const result = await verify ( appleAppSiteAssociationPath , path ) ; assert . strictEqual ( result . get ( appID ) , expected ) ; } }) ; # .github/workflows/test.yml # ... jobs : test : runs-on : macos-latest # macOSランナーを利用 steps : - uses : actions/checkout@v4 - uses : actions/setup-node@v4 with : node-version : 22 - run : npm ci - run : sudo node --test universal-links.test.js # root権限が必要 デモ デモページ をGitHub Pagesで公開しています。このページは universal-links-test/sim を利用しているため、ブラウザから環境問わずUniversal Linksの挙動確認できます。 おわりに universal-links-test は、Universal Linksの挙動確認をサポートするためのNPMパッケージです。Web開発者が容易に導入し、 apple-app-site-association ファイルの管理と挙動確認ができます。また、 universal-links-test/sim を利用することでmacOSやroot権限がなくともUniversal Linksの挙動確認ができます。 universal-links-test では、今後も機能追加やバグ修正していく予定です。ぜひご利用いただき、フィードバックをお寄せいただければ幸いです。 私たちZOZOでは一緒にサービスを作り上げてくれる方を募集しています。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com v0.1.0リリース時点での機能です。今後のバージョンで機能変更される可能性があります。 ↩
はじめに こんにちは。Developer Engagementブロックの @wiroha です。12月10日に「 株式会社ユーザベース×株式会社ZOZO×株式会社PR TIMES 3社合同フロントエンド勉強会 」を開催しました。3社のエンジニアが集まり、フロントエンドに関する取り組みを発信するイベントです。 登壇内容まとめ 各社から次の7名が登壇しました。 発表タイトル 登壇者 PR TIMESにおけるNext.jsとキャッシュの付き合い方 株式会社PR TIMES 柳 龍哉 Next.jsのアップデートに伴い、mswを2系に上げた話 株式会社ZOZO 田中勇太 こつこつ育てるSLO 株式会社ユーザベース ニッシー☆ 三年間の関わりから見る PR TIMES エンジニアリングの変化 Shogo SENSUI (@1000ch) useSyncExternalStoreを使いまくる 株式会社ZOZO 冨川宗太郎 あの日見たUIの名前を俺たちはまだ知らない 〜サイドピーク、はじめました〜 株式会社ユーザベース 田端 樹人 Recoilを剥がしている話 株式会社PR TIMES 桐澤 康平 PR TIMESにおけるNext.jsとキャッシュの付き合い方 株式会社PR TIMES 柳 龍哉さまによる発表 speakerdeck.com Next.jsの運用構成、キャッシュ戦略、Next.jsとの付き合い方についてお話ししました。キャッシュ戦略はページの特性によって異なる工夫をしていました。 Next.jsのアップデートに伴い、mswを2系に上げた話 株式会社ZOZO 田中勇太による発表 speakerdeck.com MSW 1.xから2.xへ上げた際に苦労したことや、2.xのメリットついて共有しました。メリットはスライド内でコードを比較して示しているため、そちらをご覧ください。 こつこつ育てるSLO 株式会社ユーザベース ニッシー☆さまによる発表 speakerdeck.com SLOの設定とその運用について、具体的な事例を交えて説明しました。「SLOの名前を具体的にする」「CUJベースでもっと具体的な命名にする」「指標をアップデートする」といったノウハウが共有されました。 三年間の関わりから見る PR TIMES エンジニアリングの変化 Shogo SENSUI (@1000ch)さまによる発表 speakerdeck.com PR TIMESにアドバイザーとして入り、積み重ねてきた改善についてお話ししました。課題解決の思考フレームワークを導入することで議論の質が向上したそうです。Backendと同居していたFrontend実装の別リポジトリ化は大きな成果だと感じました。 useSyncExternalStoreを使いまくる 株式会社ZOZO 冨川宗太郎による発表 speakerdeck.com useSyncExternalStoreの活用方法とその効果について説明しました。ブラウザ側でしか使えない関数やリソースを必要なときだけ利用できるようになり、Hydration Errorの回避などのメリットが得られるそうです。 あの日見たUIの名前を俺たちはまだ知らない 〜サイドピーク、はじめました〜 株式会社ユーザベース 田端 樹人さまによる発表 speakerdeck.com 画面右側からページがせり出してくる表示方法「サイドピーク」の実装方法を発表しました。現在はNotionで使用されていますが、まだ珍しいUIです。多くの段階を経て実現できており、同じUIを実装したい方にとって参考になる内容でした。 Recoilを剥がしている話 株式会社PR TIMES 桐澤 康平による発表 speakerdeck.com RecoilがReact 19で動かず対応される見込みがないことから、脱却するために取り組んでいることを共有しました。複数のパターンがあり一律に置き換えられるものではなく、影響範囲が広い場合もあり、戦略立てて進める必要がありそうでした。 最後に 本イベントは3社からエンジニアが集まることで、自社内だけでは得られない情報や知見を共有できる機会となりました。今後もこういったイベントを開催していきますので、ぜひご参加ください! ZOZOでは一緒に働く仲間を募集中です。ご興味のある方は以下のリンクからぜひご応募ください。 corp.zozo.com
はじめに こんにちは。SRE部プラットフォームSREブロックの松石です。 12月2日〜12月6日にラスベガスで開催された AWS re:Invent 2024 に、弊社から13名のエンジニアが現地参加しました。この記事では熱気あふれる会場の様子と現地参加したメンバーのそれぞれが印象に残ったセッションについてご紹介します。 目次 AWS re:Inventとは 現地の様子 セッションレポート おわりに AWS re:Inventとは re:InventはAmazon Web Services(AWS)が主催するAWS最大のカンファレンスです。このイベントでは、AWSの様々なサービスのアップデートや新サービスが発表されます。今年は世界中から約60,000人、日本から約1,700人の参加者がラスベガスに集まりました。今年のre:Inventでは、昨年から盛り上がりを見せる生成AIブームの影響が新サービスの発表や各セッション、EXPOでの企業ブースに多く見られました。 現地の様子 私は本イベントへの参加が今回初めてだったのですが、会場の熱気や日本では味わえない国際的な雰囲気とスケールの大きさに終始圧倒されました! 会場の入口 会場の入口にはAWS re:Invent 2024と書かれたWelcomeボードがありました。 EXPO こちらは会場内のEXPOエリアです。今年は国内外の総勢数百社の企業がブースを出しており、各社のプロダクトのデモを直接見たり触れたりできます。また、EXPOでは各プロダクトに精通するエンジニアやその他のスタッフに気軽に質問や相談したり、議論を交わしたりもできます。 さらに、EXPOでは恒例のSWAG集めも活発に行われており、現地参加したメンバーがたくさんのSWAGを集めていました! 基調講演 基調講演では生成AI関連やクラウド運用の効率化などの新サービスや新機能が発表されました。 以下の画像は新しいNewSQLデータベースであるAmazon Aurora DSQLが発表された瞬間です。 朝食や昼食 イベント開催期間中は、朝食や昼食が無料で提供されます。さらには、会場内の各所でドリンクや軽食が提供され、Workshopに持ち込むこともできました。 AWS認定者ラウンジ 会場内には、AWSの認定資格の保有者のみが使える休憩スペースがありました。ここでも参加者同士の交流や認定資格の保有者限定のSWAGなどが配布されていました。 その他 イベントの初日には、 APJ on Tour ’24 というAWS主催のパーティが開催されました。このイベントは、アジア太平洋地域のイベント参加者が交流できるコミュニティイベントです。 3日目には参加したメンバーのうち3名が毎年恒例の5Kマラソンに参加しました。定員は先着2,000名とのことでした。朝6時とかなり早かったですが、昇ってくる朝日を横目に走り、ゴール後には参加者に限定SWAGと記念メダルが渡されました。このイベントでは、ラスベガスの街中を走るというとても貴重な体験ができました! re:Invent 4日目には「re:Play」というパーティが開催され、ライブステージや巨大滑り台など音楽やアクティビティを楽しむことができました! セッションレポート ここからは現地参加メンバーが気になったセッションを紹介します。 Breakout Session Multi-Region strong consistency with Amazon DynamoDB global tables (DAT425-NEW) Understanding security & privacy on Amazon Bedrock, featuring Remitly (AIM360) Achieving scale with Amazon Aurora PostgreSQL Limitless Database (DAT420) The Future of Kubernetes on AWS (KUB201) Deep dive into Amazon Aurora DSQL and its architecture (DAT427) Chalk Talk IT transformation for nonprofits: Maximize impact with modernization (IMP205) Next-generation SaaS tenant isolation strategies (SAS312) Build resilient, high-performance applications with Amazon Aurora DSQL (DAT334) Code Talk Streamline Amazon EKS operations with generative AI (KUB321) Innovation Talk Security insights and innovation from AWS (SEC203-INT) Workshop Increase your database agility with Amazon FSX (STG404-R) Gen AI incident detection & response systems with Aurora & Amazon RDS (DAT307) Multi-Region strong consistency with Amazon DynamoDB global tables (DAT425-NEW) SRE部カート決済SREブロックの飯島です。 KeynoteではAmazon DynamoDB global tablesがマルチリージョンで強い整合性をサポートすることが発表されました。現在はプレビューであり、利用可能なリージョンはus-east-1(バージニア北部)とus-east-2(オハイオ)、us-west-2(オレゴン)の3つです。 この発表後に新たに追加されたBreakoutセッションに参加してきました。このようにre:InventではKeynoteで発表された新機能に関するセッションが追加されます。注目度が高いものだとすぐに予約が埋まってしまうため要チェックです。 Amazon DynamoDB global tablesとは選択したマルチリージョン間でDynamoDBテーブルを自動的にレプリケーションしてくれるフルマネージドな機能です。この機能を利用することで以下のメリットが得られます。 低遅延:ユーザは最も近いリージョンでデータにアクセスできる。 高可用性・対障害性:単一リージョン障害が発生しても他のリージョンからデータにアクセスし続けられる。 今回globale tablesのオプションで結果整合性(最終的な一貫性)と強い整合性(強固な一貫性)が選択できるようになりました。 これまでデフォルトの設定だった結果整合性では、タイミングによっては古いデータへアクセスすることもあります。例えば以下キャプチャのus-east-1のテーブルで更新があった場合、もし更新がレプリケーションされる前にus-west-2へアクセスすると古いデータが返ってきます。 画像引用元: https://youtu.be/R-nTs8ZD8mA?feature=shared&t=285 一方で、強い整合性ではいつどのリージョンのテーブルにアクセスしても最新のデータが返ってきます。 画像引用元: https://youtu.be/R-nTs8ZD8mA?feature=shared&t=368 以下の表はマルチリージョンにおける結果整合性と強い整合性の比較です。 マルチリージョンの結果整合性 マルチリージョンの強い整合性 強い整合性のある読み取り (ConsistentRead: true) は古いデータを返すことがある 強い整合性のある読み取り (ConsistentRead: true) は古いデータを返さない 書き込みと強い整合性のある読み取り (ConsistentRead: true) のレイテンシが低い 書き込みと強い整合性のある読み取り (ConsistentRead: true) のレイテンシが高い 競合は最後の書き込みを優先して解決される 競合が発生すると ReplicatedWriteConflictException が返される RPO (Recovery Point Objective) は 1 桁秒 RPO (Recovery Point Objective) はゼロ 先述した通りマルチリージョンの強い整合性は古いデータを返しませんが、結果整合性よりも「書き込みと強い整合性のある読み取り」のレイテンシが高く、整合性とレイテンシの間にはトレードオフがあります。異なるリージョンにおいて同じデータへの書き込みにより競合が発生した際の挙動や、RPO(Recovery Point Objective)にも違いがあります。 以下は強い整合性を使用する際の注意点です。 プレビュー機能のため本番環境での使用には適さない 強い整合性はすべてのレプリカテーブルに適用される global table作成後は整合性の切り替えができない 利用可能な3つのリージョンに展開が必要 例:us-east-1にDynamoDB tableを作成した場合、us-east-2とus-west-2にレプリカテーブルが作られる 強い整合性でglobal tableを作成すると結果整合性に変更はできません。逆も然りです。一度すべてのレプリカテーブルを削除してからglobal tableを再度作成する必要があります。 本セッションではデモや強い整合性をどのように実現しているか詳細な説明も行われました。興味のある方はリンクからぜひご覧ください。 AWS re:Invent 2024 - Multi-Region strong consistency with Amazon DynamoDB global tables (DAT425-NEW) 画像引用元: https://youtu.be/R-nTs8ZD8mA?feature=shared&t=1123 Increase your database agility with Amazon FSX (STG404-R) SRE部基幹プラットフォームSREブロックの斉藤です。普段はZOZOの倉庫システムやブランド様向けの管理ページなどのサービスのオンプレミスとクラウドの構築・運用に携わっています。またDBREとしてZOZOTOWNのデータベース全般の運用・保守も兼務しています。 ZOZO Advent Calendar 2024にて、技術セッション以外の場面でのre:Inventの魅力について紹介していますので併せてご覧ください。 qiita.com 目的と概要 Amazon FSx for NetApp ONTAPを活用し、データベースのクローンを作成する手法を学ぶための2時間のWorkshopに参加しました。事前準備されたEC2インスタンスやAmazon FSx環境を使用するため、ラップトップを持参し、会場内のWi-Fiを利用して操作します。 主な内容 Amazon FSxを用いたAmazon EC2上でのセルフマネージドデータベース「SQL Server、PostgreSQL、MySQL」の中から選択して操作する(SQL Serverを選択)。 高度な機能「スナップショット、クローン作成、バックアップ、レプリケーション」の中でもクローン作成に焦点を当て、データベースの迅速なコピーについて学習する。 画像引用元: Workshopの公開資料 Amazon FSx for NetApp ONTAPとは NetAppのデータ管理ソフトウェアであるONTAPをクラウド上で利用できるようにした、AWSのマネージドサービスです。エンタープライズ向けのデータ管理やストレージニーズに対応しており、オンプレミス環境やハイブリッドクラウド環境で広く使われているNetApp ONTAPの機能をそのまま活用できるというものです。 画像引用元: Workshopの公開資料 SnapCenterのインストールとFSx for ONTAP ファイルシステムの確認 用意されたEC2インスタンスにリモートデスクトップで接続できる状態にしてWindows Serverにログインします。 PowerShellを起動してFSx for ONTAPファイルシステムにSSHで接続し、ボリュームの状態を確認しておきます。さらに、snapcenter.exeを起動して、NetApp SnapCenterをインストールします。 NetApp SnapCenterとは データ保護・管理ソリューションで、データベース、仮想マシン、アプリケーション、ファイルシステムのバックアップ、リストア、クローン作成を一元管理できるツールです。 SnapCenterへストレージシステムの追加 SnapCenterにクローン元とクローン先のストレージシステムを追加します。 SnapCenterのナビゲーションペインから「Storage Systems」を選択します。 「New」をクリックします。 ストレージシステムの情報を入力していきます。 Storage System:データベースの管理IPアドレスを入力します。 Username:ストレージにアクセスするためのユーザーで、操作可能な権限が割り当てられます。 Password:ユーザーのパスワードを入力します。 SnapCenterへホストの追加 SnapCenterにクローン元とクローン先のホストを追加します。 SnapCenterのナビゲーションペインから「Hosts」を選択します。 「Managed Hosts」タブから「Add」をクリックします。 表示されたポップアップにデータベースのホスト情報を入力します。 Host Name:データベースのホスト名を入力します。 Credentials:実行に使用する認証情報を選択します。 Select Plug-ins to Install:「Microsoft Windows」と「Microsoft SQL Server」を選択します。SQL Serverホストを追加すると、必要なプラグインをデータベースホストに適用し、自動検出ができるようになります。 「Submit」をクリックして完了です。 クローンの作成 クローンを作成して別のインスタンスにデータベースのコピーを作成します。 SnapCenterのナビゲーションペインから「Resources」を選択し、さらにユーザーデータベースを選択します。 クローンを作成する対象となるバックアップを選択し、「Clone」を選択します。 画面が切り替わったら、「Clone settings」配下の「Clone Options」を入力します。 Clone server:クローン先のデータベースホスト名を入力します。 Clone instance:クローン先のデータベースインスタンス名を入力します。 Clone name:インスタンス内にクローンされるデータベース名を入力します(一意である必要があります)。 Choose mount option:「Auto assign volume mount point under path」を選択します。さらに、FSx for ONTAPのディスク上のディレクトリを指定します。このディレクトリは、クローン作成時に新しいボリュームとしてマウントされるルートロケーションに使用されます。 「Next」をクリックして完了です。 ※「Logs」から「Summary」は、今回特に設定せずにデフォルト値で進行していました。 クローンの確認 SQL Server Management Studioから確認するとクローンしたデータベースがDEVSQLインスタンス上に作成されました。 PowerShellを起動してFSx for ONTAPファイルシステムに再度SSHで接続し、ボリュームの状態を確認してみるとクローンしたボリュームが追加されていることが確認できました。 まとめ re:Inventで初めて海外のWorkshopに参加しました。不安や緊張を感じていましたが、スタッフが親切でサポートも手厚く、ブラウザベースで進行する形式だったため、手順を翻訳して学習を進められました。 今回は、Amazon FSx for NetApp ONTAPの一部機能について学びましたが、他にも多くの高度な機能が提供されています。これを機にさらなる理解を深め、弊社のEC2インスタンス上で稼働するSQL Serverの運用改善やコスト効率化に活用できる可能性を検討したいと思います。 Understanding security & privacy on Amazon Bedrock, featuring Remitly (AIM360) ブランドソリューション開発本部FAANS部バックエンドブロックの田島です。今年のAWS re:Inventでは、生成AIに関するセッションが昨年以上に強い存在感を放っている印象でした。その中でも、個人的に印象に残った「生成AIアプリケーションにおけるセキュリティ」に関するセッションについて紹介します。生成AIアプリケーションの開発においても、セキュリティやプライバシーの配慮は欠かせません。それに加え、「責任あるAI(Responsible AI)」というキーワードに示されるように、生成AI特有の考慮事項に対応する仕組みが求められます。 このセッションでは、まず生成AIアプリケーションのセキュリティについて、包括的な考え方が紹介されました。具体的には、セキュリティを3つのステージに分けて整理し、それぞれの要件が説明されました。 生成AIモデルの安全性の確保 安全な訓練データの使用や、事後学習フェーズでモデルの振る舞いを調整するアラインメントなど、生成AIモデル自体に関わるセキュリティ要件です。 モデルのアクセス制御と管理 モデルへの安全なアクセス制御や、運用中の監視・管理に関する要件です。 アプリケーション全体のセキュリティ モデルにアクセスする前のデータフロー管理や、ハルシネーションの防止、出力がポリシーに準拠しているかの確認などが含まれます。 さらに、多層的なセキュリティアプローチとして以下の決定論的制御と確率論的制御を組み合わせる重要性が強調されていました。 決定論的制御(Deterministic Controls) 再現性や説明可能性が求められる制御方法です。例えば、IAMを用いたアクセス制御やKMSによる暗号化などが該当します。 確率論的制御(Probabilistic Controls) 入力が意図したユースケースに沿っているかの確認や、出力結果の有害性をチェックするなど、生成AIモデル特有のアプローチです。例えば、機械学習モデルを用いて出力内容を評価する方法が該当します。この制御は柔軟性が高い反面、完全な再現性は保証できないため、一定のリスクを伴います。 続いて、これらの概念がAmazon BedrockというAWSの生成AIサービスと関連付けて解説されました。Bedrockはさまざまな生成AIモデルが利用でき、また、それを使ったアプリケーションの構築を容易に実現できるフルマネージドサービスです。責任あるAIを実現する仕組みとして「Guardrails for Amazon Bedrock」という機能が昨年発表されました。今回個人的に特に気になったのはGuardrailsの新機能である「Automated Reasoning checks」です。 Automated Reasoning checksは、前述の3つステージのうち3つ目の「アプリケーション全体のセキュリティ」に該当します。そして、この機能は決定論的制御と確率論的制御を組み合わせたアプローチを採用し、具体的には以下のような仕組みで動作します。 画像引用元: https://youtu.be/3Sxw6IIYhdE?feature=shared&t=2495 まず、ソースドキュメントとその内容をもとに回答を生成する目的をAutomated Reasoningサービスに送信し、自動推論ポリシーが生成されます。このポリシーは「以上」「以下」といった論理式を含む決定論的なルールとして構築されます。続いて、ユーザーからの質問やそれに対する回答が、このポリシーに準拠しているかどうかが判断されます。この検証部分は決定論的であるため、結果に再現性があります。一方で、ポリシー自体を生成するプロセスは確率論的です。ただし、この生成されたポリシーは人の手で修正可能なため、柔軟性と信頼性の両立を図りやすい点が特徴的です。 本セッションを通じて生成AIアプリケーション構築時のセキュリティに関する包括的な考え方を頭の中で整理できたのがまず大きな学びでした。また、Automated Reasoning checksは、論理式を使った決定論的なアプローチで生成AIアプリケーションの信頼性と説明可能性を高めるための画期的で興味深い仕組みだと感じました。実用的な生成AIアプリケーションの構築において、信頼性や説明可能性が障壁になりやすいと考えています。今後もその課題のための仕組みが強化されていくことで、生成AIアプリケーションの実用性がさらに広がることを期待しています! Streamline Amazon EKS operations with generative AI (KUB321) 計測システム部SREの土田です。 ZOZOMAT や ZOZOMETRY 等の計測技術のSRE業務を担当しております。計測システム部ではAIを活用した業務効率化に力を入れており、re:inventではAIを用いた様々なソリューションを紹介するセッションがあり、それらに参加しました。 本パートではEKSの障害時のオペレーションをAI活用して迅速に行えるようにするCodeTalkのセッション内容を紹介します。 目的と概要 CodeTalkとは、ライブコーディングやコードサンプルを使用して、AWSのエンジニアが実装したソリューションを詳細に解説するセッションです。他のセッションと異なり、POCレベルで作ってみたようなものも紹介されていて、新しいことがたくさん学べたセッションタイプでした。 このセッションでは、EKSの障害発生時にアラートとともに改善策を提案するSlack通知およびWebコンソールの実装を行なっていました。 実装されたもの Slack通知ではCloudWatchアラームの内容に加えて、具体的に問題がありそうなリソースについても記載を加えています。 エラーログや対処をまとめたRunbookを元に推奨されるActionが提案されるWebコンソール 構成 CodeTalkで解説されたソリューションの構成は以下の通りです。 画像引用元: セッションの公開資料 P.6 このセッションではデータのembeddingおよびsemantic searchはfaissを使用して行っていましたが、フルマネージドの Bedrock Knowledge Base でも実現可能と思っています。 github.com 処理の流れは以下の通りです。 1. KubernetesのログやRunbookのベクトル化 KubernetesのログやRunbookをチャンクに分割し、埋め込みモデル(Titan Text Embeddings V2)でembeddingを行い、ベクトルストアに保存します。 ユーザーの質問処理 ユーザーが「なぜ私のアプリの応答時間が長いのか?」といった質問を入力します。この質問も埋め込みモデルによってembeddingを行い、ベクトル化されます。 セマンティック検索 ユーザーの質問のベクトルデータをもとに、ベクトルストアでベクトルの近傍検索が行われ、質問に関連するKubernetesのログやRunbookが取得されます。 コンテキストの構築 セマンティック検索によって得られた関連情報がユーザーの質問と組み合わされ、コンテキストが構築されます。 プロンプト拡張 構築されたコンテキストと元のユーザーの入力が結合され、プロンプトが拡張されます。 大規模言語モデルでの応答生成 拡張されたプロンプトがAmazon Bedrockの基盤モデル(Amazon NovaやClaude3.5等)に送られ、対応策を含む応答が生成されます。 画像引用元: セッションの公開資料 P.8 このセッションで作られたソースコードおよび資料は以下のリポジトリに格納される予定です。 github.com まとめ 実際に実装されたコードを見ながら、ソリューションの解説を受けると、具体的な手順が分かりやすく、すぐに手を動かしたくなりました。 EKSの運用はハイコンテキストで、CloudWatchやDatadogと睨めっこすることが多いですが、生成AIを活用してこれらの作業を少しでも効率化できないか模索していきたいと思います! Gen AI incident detection & response systems with Aurora & Amazon RDS (DAT307) EC基盤開発本部SRE部商品基盤SREブロックの佐藤です。私は生成AIを活用したインシデント検知および対応(IDR)を行うシステムのワークショップに参加しました。 ワークショップの概要 AWSには以前より、 AWS Incident Detection and Response というエンタープライズサポート向けの有人インシデント対応サービスが提供されています。今回のワークショップは人間ではなく生成AIを活用し、インシデントの検知および対応を自動化するシステムをハンズオン形式で構築しました。 Amazon Bedrockについて Amazon Bedrockがどのようなサービスか整理します。 Amazon Bedrock :主要なAI企業やAWSが提供する生成AIの基盤モデル(Foundationモデル・FM)を簡単に統合・利用できるサービス。インフラ管理不要で機械学習に関する専門知識がなくても利用可能。 Amazon Bedrock Agents :生成AIエージェントを簡単に作成できるBedrockの機能。ユーザーのリクエストに対して基盤モデルを拡張して追加情報を収集し、複数のステップに分解したアクションを実行する。 Amazon Bedrock Knowledge Bases :基盤モデルの検索拡張生成(RAG)を行うために、様々なデータソースと接続させるサービス。 関連資料 aws-samples/aurora-postgresql-pgvector: Leverage pgvector and Amazon Aurora PostgreSQL for Natural Language Processing, Chatbots and Sentiment Analysis 用語 ワークショップの冒頭では前提となる用語の説明がありました。 基盤モデル(FM):事前トレーニングされたディープラーニングモデル。知識が不十分な場合、FMはハルシネーションと呼ばれる誤った出力を生成する傾向がある。 ベクトルの埋め込み:データを数値表現に変換しベクトルとしてマッピングしたもの。データ間の固有の特性と関係性を捉える数学的表現。 検索拡張生成(RAG):ハルシネーションの対策として新しいデータソースから情報を取得するための情報検索コンポーネントを導入すること。 エージェントワークフロー:データを収集し、そのデータを使用して自己決定したタスクを実行し、定められた目標を達成できるプログラム。目標は人間が設定するが、AIエージェントはその目標を達成するために必要なアクションを独自に選択する。 システム概要 使用するサービス Amazon CloudWatch Amazon DynamoDB Amazon Bedrock Amazon Aurora PostgreSQL Amazon API Gateway AWS Lambda 処理の流れ AuroraとRDSをCloudWatchが監視し、インシデントが発生するとその情報をDynamoDBに書き込む。 事前にインシデントに対するRunbooksをS3にアップロードしBedrock Knowledge Basesと同期し基盤モデルの拡張(RAG)を行う。 ユーザーはWebアプリケーションを操作してインシデントとRunbookの確認、AuroraとRDSの復旧をする。 ユーザーからの指示によって、Bedrock AgentsがBedrock Knowledge BasesからRunbookの取得とAuroraとRDSの設定変更を行う。 ワークショップの内容 ワークショップは以下の順番で進みました。 セットアップ 基盤モデルの拡張 エージェントの設定 インシデントのシミュレート Webアプリケーションをデプロイし復旧する 1. セットアップ このワークショップでは基盤モデルとしてAnthropicのClaude3とAmazon Titanを利用しました。 なお、Bedrock Knowledge Bases、Bedrock Agentsは事前に用意されたものを使用しました。 2. 基盤モデルの拡張 Runbookと呼ばれるインシデントの対応手順が書かれたMarkdownファイルをBedrock Knowledge BasesにアタッチしたS3へアップロードしました。以下は、CPUインシデントに対するRunbookの例です( 引用元 )。 # Title Runbook to remediate RDS CPU Utilization alert ## Issue PostgreSQL database instance is running out of high CPU utilization. ## Description This run book provides the step by step instructions to address the high CPU Utilization in the RDS instance. Follow the instructions in this run book to remediate the high CPU utilization incident. ## Steps 1. Check if the RDS instance is in available state. If the status is available, continue otherwise abort the process. 2. Get the current CPU utilization metrics for the last 1 hour for the RDS instance. . 3. Check if the maximum CPU utilization from the CPU metrics is above 80% , then scale up the RDS instance to the next availabe instance type. アップロードしたRunbookは、Bedrock Knowledge Basesのコンソール上で「同期」を実行することで、埋め込まれた基盤モデルによって解析されます。 解析されたデータは、小さなブロック(チャンク)に分割され、PostgreSQLの拡張機能である pgvector を利用してベクトルデータとして保存されます。これだけでRAGによる基盤モデルの拡張とベクトル埋め込みが簡単に実現できました。 3. エージェントの設定 Bedrock Knowledge BasesをBedrock Agentsにアタッチし、アクショングループと呼ばれるAIエージェントが実行するLambda関数を確認しました。下記はアクショングループに定義されたDBインスタンスの状態を確認する関数です( 引用元 )。 def check_rds_state (db_instance_identifier): """ Function to check the current state of the RDS cluster """ try : response = get_instance_details_helper(db_instance_identifier) status = response[ 'DBInstanceStatus' ] message = f "The RDS status is {status}." if status != 'available' : message += " It is unavailable state, please try again later." lambda_logger.info(message) return message except Exception as e: lambda_logger.error(f "Error checking status: {str(e)}" ) lambda_logger.error(traceback.format_exc()) return f "Error: {str(e)}" 4. インシデントのシミュレート pgbench を使用してデータベースのCPUまたはI/Oに高負荷のワークロードを発生させ、CloudWatchアラームをトリガーします。そのインシデント情報がDynamoDBに正しく保存されることを確認しました。 5. Webアプリケーションをデプロイし復旧する Streamlit で構築したWebアプリケーションをデプロイし、以下の動作を確認しました。 DynamoDBから取得したインシデント情報を画面に表示する。 UIを操作してBedrock Knowledge Basesにインシデントを照会し、関連するRunbookを取得する。 UIを操作してBedrock Agentsを呼び出す。Bedrock AgentsはBedrock Knowledge BasesからRunbookを取得し、ステップに基づいて、対応するアクショングループの関数を呼び出して実行する。 さらに、時間が足りず完成には至りませんでしたが、DBのインフラストラクチャに関する質問に答えるエージェントを作成する項目もありました。これはBedrock AgentsからDynamoDBやAurora、RDSなどのデータベースにリアルタイムでクエリを送信し、情報を取得することで実現する内容でした。 感想 初めて生成AIを活用したシステムを作成しましたが、作業自体は非常に簡単で、Amazon Bedrockの使いやすさに感心しました。生成AIの複雑さをうまく隠して、多くの人に普及させようとするAWSの意図が伝わってきた気がします。今までは、業務にどのように利用すべきか具体的なアイデアが浮かんでいませんでしたが、このワークショップを通じて、普段の運用にも取り入れられる可能性があると感じました。今後はこの経験をもとに、業務で生成AIを活用する方法を検討していきたいと思います。 Achieving scale with Amazon Aurora PostgreSQL Limitless Database (DAT420) ZOZOMO部SREブロックの蔭山です。普段は Fulfillment by ZOZO や ZOZOMO のSREを担当しています。 今回は興味を持っていたサービスの1つであるAmazon Aurora PostgreSQL Limitless Databaseがどのように実現されているかが紹介されたセッションに参加しました。その内容について簡単ではありますがご紹介します。 内容 今回のセッションはBreakoutセッション形式で実施され、以下の内容が話されました。 画像引用元: セッションの公開資料 P.3 まずデータベースでのスケーリングの問題が取り上げられました。データベースでスケーラビリティを確保するにシャーディングが必要となる、シャーディングを実現しようにも以下の4つの大きな問題があると述べられました。 Querying(クエリの実行) Consistency(一貫性の維持) Re-sharding(再シャーディング) Database capacity management(キャパシティ管理) 画像引用元: セッションの公開資料 P.7 それらの問題を解決するためにAmazon Aurora PostgreSQL Limitless Databaseが誕生したとのことです。ちなみにAuroraが誕生してちょうど10周年とのことでした。 画像引用元: セッションの公開資料 P.12 Aurora PostgreSQL Limitless Databaseの特徴について、サンプルとしてCustomer(会員)、Order(注文)、Tax Rate(税率)を使って説明されました。 画像引用元: セッションの公開資料 P.15 画像引用元: セッションの公開資料 P.16 このあとは実際のAurora PostgreSQL Limitless Databaseのアーキテクチャについて詳しく説明がありました。 シャードグループは通常のAuroraクラスタと同じ操作が可能となっており、それぞれ以下の責務を担っているとのことでした。 Distributed transaction routers(Routers): スキーマ構成やシャーディングされたデータの位置情報を保持 Data access shards(Shards): シャーディングされた各データを保持 画像引用元: セッションの公開資料 P.23 PostgreSQLと同じトランザクションレベルを獲得するため、各ノードで正確に時間を同期できるクロックサーバーを実現し、時間をベースにトランザクションを実現しているとのことでした。 画像引用元: セッションの公開資料 P.41 クエリ実行に関してはPostgreSQL互換を保ちつつ、独自にカスタマイズされたDBエンジンが実行計画を作ってクエリの実行を担っているとのことでした。 画像引用元: セッションの公開資料 P.47 実際に測定されたパフォーマンスについても語られており、複数のアプリケーションから1秒あたり250万回以上の更新処理を行ってもレイテンシは数ミリ秒程度とのことでした。 画像引用元: セッションの公開資料 P.53 まとめ 今回深くまで説明いただいたことでAurora PostgreSQL Limitless Databaseがどれほど工夫されて実現されているかを身近に感じることができました。 将来使いたいと思っていたデータベースサービスの1つだったので、ユースケースを想定しやすくなったのも良かったなと感じました。 IT transformation for nonprofits: Maximize impact with modernization (IMP205) YSHP部YSHPブロックの川俣です。YSHP部では主にZOZOとYahoo!ショッピング間のデータ連携を担当しております。今後システムリプレイスを控えており、そのタイミングでモダン化も予定しております。今回はモダン化の流れや手法を学習するChalk Talkに参加いたしましたので紹介させていただきます。 まず今回のChalk Talkの大きな要素としては下記5つで構成されていました。 画像引用元: セッションの公開資料 P.3 まずは「Why modernize?(なぜモダン化するのか?)」という目的とメリットの確認です。 「機敏性、スピード、革新性」「パフォーマンスと復元力」「効率性とコストの最適化」 これらは「モダン化」自体が目的とならないように事あるごとに立ち返りたい観点です。YSHP部としては効率性を重視して運用改善を図りたいところです。 画像引用元: セッションの公開資料 P.6 「Strategy(戦略)」の中ではマネージドサービスを利用することで「機敏性」を確保しつつトータルの運用コストを下げるという大まかな戦略について話がありました。その中で述べられていた一文を紹介させてください。 Migration and modernization is a journey 私が参加したその他のセッションでも共通してモダン化を「旅」と表現していました。「決断疲れ」や「スキルギャップ」などシステム面以外の課題も解決していき目的に辿り着く様はまさに「旅」と言っても過言ではないと感じます。 画像引用元: セッションの公開資料 P.8 次に「Modernization pathways(モダン化の道筋)」です。段階的に何から始めれば良いのか悩みがちな部分なので体系的なアプローチを示して貰えるのは非常に助かります。この部分はモダン化を推進していく際のステップになるため重要なポイントです。 画像引用元: セッションの公開資料 P.10 「Whiteboarding」のセクションでは講師の方がその場で参加者の方のシステム構成をヒアリングし、ホワイトボードにモダン化したパターンを示していきます。左側がオンプレミス環境の構成図ですが、YSHP部の構成と近しいものがあり、既存構成は世界共通なんだなと非常に親近感を覚えました。 右側のモダン化後の構成図は複数AZ、なおかつAmazon ECSを想定した構成です。AWS A2C(App2Container)を利用しながらアプリケーション部分をコンテナ化していき運用していくケースです。A2Cを利用してコンテナをECRに登録し、コントロールプレーンはECS、データプレーンはFargateという構成です。Fargateを利用する事で運用管理のコストを少なくしていく狙いがあります。 既存アプリケーションをいきなりAWS Lambda等の複数サービスに分解せず、まずはコンテナ化していく手法は先述の「Modernization pathways(モダン化の道筋)」に沿った形です。 こちらは別のモダン化パターンです。オンプレのDBをVIEWを介し、AWS Schema Conversion ToolやAWS Database Migration Serviceを利用してDynamoDBに移行していくケースです。当該Chalk Talkでは触れられませんでしたがAWS DMSはスキーマの変換に生成AIを用いる機能が追加されました。この機能の詳細はAWS Database Blogの記事「 New – Accelerate database modernization with generative AI using AWS Database Migration Service Schema Conversion 」をご覧ください。 「Wrap-up」でまとめられた要点はこちらです。 モダン化は価値実現のために複数の経路を採用しながら進める漸進的なプロセスです。 「どこから始めるべきか、何を使うべきか」という疑問を解決するために、「モダン化の道筋」を活用します。 「モダン化の道筋」に沿ったAWSサービスとパートナーツールをモダナイゼーションプラクティスに組み込みます。 画像引用元: セッションの公開資料 P.11 AWSには、それぞれの状況や段階に応じて最適なサービスがあると改めて実感しました。今回具体的な手法やサービスを知れたことで今後のモダン化に対する熱量がぐっと上がりました! 「Modernization pathways(モダン化の道筋)」に沿って段階的なおかつ確実にモダン化を推進していこうと思います。 Security insights and innovation from AWS (SEC203-INT) フロントSREブロックの江島です。ZOZOTOWNのエンドユーザーに近い部分(フロントエンド、BFF等)を担当領域としています。また、全社のAWS管理者としての役割も担っています。私からは「Security, compliance & identity」の分野におけるInnovation talkの内容を紹介します。 Innovation talkとは、AWSのシニアリーダーが登壇するトーク型セッションです。各分野の重要課題に関してAWSにおける最新の取り組みが紹介されます。BreakoutセッションやWorkshop等よりもやや抽象度が高めな内容かなと思います。セッションはAWSのCISOであるChris Betz氏を中心に進められました。内容としては、大きく3つのトークテーマに沿った話がありました。 1つ目のトークテーマは「セキュリティに関するオーナーシップの重要性」についてです。このトークテーマでは、どんなに優れたツールがあってもオーナーシップ無しではセキュリティは成立しないということが述べられ、それを達成するための手段として「ガーディアンプログラム」が取り上げられました。これは、各開発チームの一部メンバーをセキュリティ有識者に育て上げ、開発チームの内側からセキュリティ推進を図っていくというものです。 2つ目のトークテーマは「最新サービス紹介」でした。Vice President & Distinguished EngineerのBecky Weiss氏より、考え方の根底にあるメッセージと共に最新のセキュリティサービスの紹介がありました。 特に非常に興味深かったのがDeclarative Policies(宣言型ポリシー)です。 これは以下のような特徴を持ちます。 AWSサービスの設定状態を定義するポリシー機能 宣言されたポリシーに反する設定は拒否される(強制される) メンバーアカウントから設定変更を行おうとした場合にはカスタムエラーメッセージを返せる 現時点ではEC2の一部ユースケースのみが対象(サポート範囲は今後拡大される予定) 個人的にはAWS Security Hubのコントロールに対応するポリシーが増えてくればコンプライアンス遵守を効率よく進められそうな気がしており、今後のサポート範囲拡大に期待しています。 3つ目のトークテーマは「量子コンピューティングや生成AIといった最新技術に対するセキュリティについて」でした。量子コンピュータによる攻撃にも耐えることができる暗号アルゴリズムの開発、生成AIの時代においてモデルに対するセキュリティを確保するための仕組みが紹介されました。 最後は挑戦的なメッセージで締めくくられ、非常に刺激を受けました。 全体感を掴むためにも自身の興味がある分野についてはInnovation talkを聴講して頂くことをお勧めします。 Next-generation SaaS tenant isolation strategies (SAS312) こんにちは。計測プラットフォーム開発本部バックエンドブロックの髙橋です。普段はZOZOMAT、ZOZOGLASS、ZOZOMETRYなどの計測プロダクトのバックエンドの開発・運用をしています。 直近リリースしたZOZOMETRYはSaaS型のサービスです。そのため、よりSaaSプロダクトの設計について知見を深めたく、「Next-generation SaaS tenant isolation strategies」というセッションに参加しました。本セッションではまず、「SaaSでのテナント分離とは?」という基本的な部分から始まりました。 その後、次の内容を講師が説明しました。 テナント分離をどのようにして行うか? サイロモデル ブリッジモデル プールモデル なぜやるか? プライバシー、セキュリティのため コンプライアンス強化 スケーラビリティ、コストコントロールを含めたプロダクトのアジリティを上げるため 一般的にテナントを分離する際はJWTの中にテナント情報を入れ込み、その情報からどのテナントのリソースにアクセスするかを判別するような設計が多いかと思います。 ZOZOMETRYでは、JWT内のCognitoのアカウントIDを使ってテナントの照合をしていました。 本セッションでは、STSとJWTを用いた次世代のテナント制御について説明がありました。具体的には、JWTをSTSのAssumeRoleWithWebIdentityと組み合わせて使用し、一時的な認証情報を取得する方法が紹介されました。これにより、JWT検証の責務をSTSに移譲することが可能になり、アプリケーション側でのJWT検証の実装負荷やバグによるセキュリティ上の脆弱性のリスクを軽減できるというメリットがあります。ご興味のある方はセッション中に紹介された以下のGitHubリポジトリに関連した要素を持ったコンテンツがありますので、触れてみることをおすすめします。 https://github.com/aws-samples/data-for-saas-patterns/tree/main/samples/tenant-isolation-patterns https://github.com/aws-samples/aws-saas-tenant-usage-and-cost-attribution https://github.com/aws-samples/aws-saas-operations-workshop/tree/v2 https://github.com/aws-samples/aws-saas-cell-based-architecture 大きめのセッションだとどうしても登壇者→聴衆の一方向になりがちですが、Chalk Talkでは講師が参加者に話を振ったり、参加者側がセッション中に質問出来たりしたことはとても新鮮でした。 Build resilient, high-performance applications with Amazon Aurora DSQL (DAT334) ブランドソリューション開発本部WEARバックエンド部SREブロックの小林です。ここからは新規発表されたDSQLに関連するセッションを2本ご紹介します。まずは私からDSQL概要や従来のAuroraデータベースとの違いに関するセッションを、続いてDSQLアーキテクチャにDeep diveしたセッションとWorkshopについて遠藤より紹介いたします。 このセッションはChalk Talk形式でAmazon Auroraの歴史やAurora DSQLの特徴について解説されました。スライドタイトルとセッション名が不一致ですが、セッション内容に違いはありませんのでご安心ください。 まずはAuroraの歴史について言及されました。Amazon Auroraは2014年にMySQL互換としてリリースされ、2017年にPostgreSQL互換にも対応しました。その後もAurora Serverless、Limitless Databaseなど様々なサービスを発表しています。 続いてAurora DSQLの紹介です。DSQLはPostgreSQL互換の分散データベースです。2024/12/4セッション当日の時点ではPostgreSQL 16互換としてプレビューされています。クエリの実行結果や実行計画にほとんど違いはないとされ、JOINやAGGREGATE、DML、DDLもサポートされています。また、Ruby on Railsをはじめとしたほとんどのドライバも標準ドキュメントの通りに動くと説明されていました。 しかし、一部未対応の機能も存在します。外部キー制約やPL/pgSQL、地理情報で使用するPostGISなどはサポートしていないようです。特に外部キー制約は分散システム上でオーバーヘッドになりうるため、初期リリースでは対応が見送られるとのことでした。ロードマップ自体には含まれているそうなので、今後のリリースに期待です。 この中でAuroraとDSQLの違いについても取り上げられました。Aurora自体はPostgreSQLやMySQLのオープンソースバージョンを基盤にして作成されています。しかし、DSQLはPostgreSQLのコードを「約半分」利用しつつ、独自に構築した箇所が存在するという説明がありました。 具体的にはFrontend、Query executionはPostgreSQL由来ですが、Transaction control、User storageはDSQL独自のものです。User storageには「ジャーナル」という機能があり、これはDSQLの根底にある技術だと説明されていました。ジャーナルはデータの永続化を確保するための中心要素であり、ストレージへの書き込みとは異なり、書き込みが確認された時点で「永続化」とみなされるとのことでした。この仕組みにより、ストレージ層へのアクセスが大幅に最適化され、システム全体のスループットが向上します。 続く大きなトピックはトランザクション管理の同時実行制御に関する説明です。DSQLでは同時実行制御に楽観的同時実行制御(OCC:Optimistic Concurrency Control)を採用しています。ロックを使用しないOCCを採用することで、クライアントの遅延や長時間実行されるクエリによる他トランザクションへの影響を最小限に抑えるとのことです。 個人的にはこの同時実行制御部分に注意が必要だと感じました。たしかにOCCではPCC(悲観的同時実行制御:Pessimistic Concurrency Control)のように行やテーブルに対してロックを取得しません。しかし、コミット段階には競合のチェックはもちろん行われるため、競合が発生した場合は何らかの対策が必要です。スピーカーもこの点には触れており、アプリケーション側でリトライやアボートのロジックを組み込む必要があります。 その他マルチリージョンにおけるActive/Active構成の仕組みや、分散データベースのコア部分である時刻同期についても解説されました。特に自動復旧メカニズムへの信頼性が強調され、他リージョンとのデータ整合性を確実に維持する仕組みが解説されました。 まとめ DSQLはAuroraと名前を冠しているものの、ある意味Auroraとは別のデータベースとして位置づけられるべきものであると感じました。同時実行制御や各種機能のサポート状況など、まだまだ検証が必要な箇所は多くありますが、今後のリリースに期待が持てる内容でした。どこかで実際の検証を通したアウトプットができればと思います。 Deep dive into Amazon Aurora DSQL and its architecture (DAT427) はじめに EC基盤開発本部SRE部カート決済SREの遠藤です。普段はカート・注文関連のマイクロサービス構築と、DBREとしてデータベースの保守・改善に携わっています。 今回のre:Inventの新発表の1つであるAurora DSQLについて、Deep diveセッションとWorkshopに参加しました。 Aurora DSQLのアーキテクチャ 前述の通りTime Sync Serviceによって正確な時刻同期が可能になりました。これにより分散DBの課題であるノード間のレイテンシの問題がある程度解消されています。その上で非常に興味深い技術選定とアーキテクチャ選定によって、強い整合性と短いレイテンシを実現しています。 Deep diveセッションでは、Aurora DSQLのアーキテクチャについて解説されました。特徴はレイヤー間の独立性です。各レイヤーは特定のワークロードの要求に基づいて、水平方向に独立してスケール可能です。 読み取りが多いワークロードではStorageレプリカが増加し、書き込みが多い場合はJournalというコンポーネントが分割され水平にスケールします。 以下が各コンポーネントの概要です。 1.Router Transaction and Session Routerと紹介されていました。PostgreSQLプロトコルを受け取りPG Bouncerのように接続プーリングを提供します。トランザクションの開始時に各接続を適切な場所にルーティングします。 2.Query Processor リクエストはRouterを通してQuery Processorへ流れます。Query ProcessorはPostgreSQLのデータベースエンジンにあたり、AWS Lambdaの高速なスケーラビリティを可能にしているFirecracker上で稼働しています。 書き込みはQuery Processor内に留まり直接Storageへはアクセスしません。Commit処理は後続のAdjudicatorが担い、Query Processor間の通信も不要なためスケーラビリティが確保されています。 3.Adjudicator Adjudicatorは、書き込み時に複数のトランザクション間の競合を検証し処理の分離性を確保します。Query Processorは複数のAdjudicatorに対して「このトランザクションをコミットしても良いか?」と問い合わせます。問題なければAdjudicatorがJournalへログを書き込みトランザクションを終了します。 分散DBのため、複数のAdjudicatorが協調して動作し、分散合意アルゴリズムを使用してデータの一貫性を保ちます。従来、NewSQLと呼ばれる他の分散DBではPaxos、Raftといったアルゴリズムが主流ですが、DSQLはよりシンプルでパフォーマンスの優れた異なるアルゴリズムを採用しているようです。 ここについてはさらなる情報公開を期待したいです。 4.Journal Journalはデータの永続性と原子性を保証します。ログをStorageへ書き込みます。このJournalは、S3、DynamoDB、Kinesis、Lambdaなど多くのAWSサービスを支えるインフラの一部として10年以上かけて構築されたもののようです。必ず1つのJournalがStorageへ書き込むことで原子性が保たれています。 5.Storage 効率的なデータ検索に特化したストレージエンジンです。役割が分離されているため、永続性や同時実行制御の責任は負いません。 パフォーマンス最適化策の1つとして、本来Query Processor上で行われる一部の処理をStorage側にプッシュダウンできます。WHERE句のフィルター操作などをストレージノードで行うことにより、通信にかかるラウンドトリップとデータ量の削減に繋がりレイテンシが向上します。 Workshop 最終日にはDSQLワークショップが開催されたので参加しました。大変人気で予約できなかったのですが、1時間並んで当日参加できました。 実際にAurora DSQLを構築して以下のようなことを確認しました。 マルチリージョンでの書き込みとOCCによる楽観ロックの挙動 データモデリングのベストプラクティス アプリケーション側のリトライを考慮した実装 DSQLはPostgreSQL互換ですが、FK制約が作成できなかったり、ビューや一時テーブルが使えないなど、制約も大きいです。今後のアップデートに期待したいです。 まとめ Deep diveセッションとWorkshopに参加して、分散DBそのものにとても興味が湧きました。正確な時刻同期など技術の発展に伴い少しずつCAP定理の壁を突破していると実感しました。新しい概念や用語も多く理解するまで苦労しましたが、腰を据えて分散DBのアーキテクチャを学ぶ良い機会になりました。 The Future of Kubernetes on AWS (KUB201) 計測システム部SREの纐纈です。今回2回目のre:Invent参加となりました。私からはAmazon EKS周りのアップデートや今後の開発方針についてのセッションについてまとめたいと思います。 こちらのセッションでは、AWSのKubernetesプロダクト部門統括であるNathan Taber氏が、Kubernetesの最新動向とAmazon EKSの進化について解説しました。 Kubernetesは多くの企業で本番環境に導入されている一方で、その運用は特に大規模環境や分散環境で複雑化しています。AWSはこれに対応するため、Amazon EKSの機能強化を継続的に行っています。 今回のセッションでは以下の新機能や改善点などが紹介されました。 Extended Version Support: Kubernetesバージョンのサポート期間を延長。 Upgrade Insights: アップグレード時に互換性や影響を事前に把握できるツール。 Enhanced Control Plane Observability: コントロールプレーンの状態を可視化し、トラブルシューティングを容易化。 サービス開始以後7年が経過しようとしていますが、より使いやすいプラットフォームになるよう様々な改善が施されていることも示されていました。 画像引用元: セッションの公開資料 P.12 さらに、EKS Auto ModeやEKS Hybrid Nodesといった新機能により、Kubernetes運用の自動化やハイブリッド環境の対応が一層強化されました。 特にEKS Auto Modeに関しては、クラスタのノード管理が不要になり、適切なリソースをAWS側で選択してくれるのでコスト最適化にも繋がります。 画像引用元: セッションの公開資料 P.50 最後に、AWSの今後の方針としては、マルチクラスター管理や開発者体験の向上に注力し、Kubernetesの運用をよりシンプルで効率的にする方針を示しています。Kubernetesの専門知識を持ったチームでなくともEKSを運用できるようにすることで、より多くのユーザーに使ってもらえるプラットフォームにすることを目指しているようです。 画像引用元: セッションの公開資料 P.86 おわりに AWS re:Invent 2024に参加し、Keynoteやセッション、EXPOで多くのことを学べました。また、国内外のエンジニアの方々と交流し、日本では味わえないことを体験できるのが現地参加の醍醐味です! 今回得た知見を社内外に共有しながら、これからもAWSを活用してプロダクトとビジネスの成長に貢献していきます。 ZOZOではAWSが大好きなエンジニアを募集しています。奮ってご応募ください! corp.zozo.com corp.zozo.com
はじめに こんにちは、ZOZOTOWN企画開発部・企画フロントエンド1ブロックの ゾイ です。 ZOZOTOWNトップページでは、セール訴求や新作アイテム訴求、未出店ブランドの期間限定ポップアップ、著名人コラボなどの企画イベントが毎日何かしら打ち出されています。私はそのプラットフォームとなる企画LPをメインに実装するチームに在籍しています。 本記事では、Figmaのコメントに関する課題を解決するために開発したZOZO専用Figmaウィジェットの実装方法と、それによる業務効率化の成果をご紹介します! 目次 はじめに 目次 背景・課題 事前準備 1. Figmaコンポーネントを作る 2. Figmaのフックを利用する 3. メニューをカスタマイズする 活用ケース 1. 修正依頼 2. アニメーションの指示 3. 仕様確認 ユーザーの反応 終わりに 背景・課題 ZOZOTOWNではFigmaを利用して開発を行なっています。特に企画LPではアニメーションの指示など、デザインデータだけでは伝えられない情報を共有する必要があります。しかし、Slackだと後でスレッドを探すことに時間がかかってしまう、Figmaのコメント機能だとホバーしないと内容を読めないといった問題があったため、確認に時間がかかってしまう課題がありました。そのため、直近では Comment Note というFigmaのウィジェットを用いて共有することが増えましたが、以下の課題が残っていました。 返信するたびにカードを樹形図のように増やす必要があった為、確認に時間がかかる 対応有無が分かりづらい FigmaのMarketplaceでZOZOのデザイナーが必要とする機能を全て持っていて、上記の問題も解決するウィジェットを探したのですが、なかなかありませんでした。調べてみたらFigmaのウィジェットはReactで開発できるらしく、簡単に開発できそうだったので自分で開発することにしました。 今回の記事ではFigmaのウィジェットの開発方法と、デザイナーとエンジニア間の業務効率化のため取り込んだことについて話したいと思います。 ※ 今回の記事で紹介しているFigmaのウィジェットは組織限定で公開しています。 事前準備 基本的にはFigma公式のドキュメントを参考にしながら開発を進めました! www.figma.com FigmaウィジェットはReactを元に作られているため、Reactに慣れているならほぼ学習コストなしで実装できると思います。少し違和感がある点としては、HTML要素ではなくFigmaの要素を作るという概念でしょうか。 UIは「 Comment Note 」というウィジェットを参考にし、ZOZOのデザイナーが求めている機能を追加する形で開発を進めました。 1. Figmaコンポーネントを作る 添付の「修正」ラベルを例として、基本的な要素の作り方から説明したいと思います。 上記をFigma上で作るためのコードは以下のとおりです。 AutoLayout というFigmaのコンポーネント を利用したらFigmaのフレームを作ることができ、各Propsでスタイルを指定できます。例えるとしたらFigmaではコンポーネントがHTMLになり、propsがCSSになる感じでしょうか。 cornerRadius のような例外もありますが、概ねCSSプロパティーの名前が似てるのでなんとなく推測して開発できました。 export const Label = ({ color , text } : Props ) => { return ( < AutoLayout direction = "horizontal" horizontalAlignItems = "center" verticalAlignItems = "center" padding = {{ horizontal : 8 }} height = { 20 } fill = { color } cornerRadius = { 2 } > < Text fill = "#fff" fontSize = { 11 } fontWeight = "bold" > { text } </ Text > </ AutoLayout > ) } FigmaのGUIでも指定している内容が反映されていることが確認できます。実はFigmaにはあまり詳しくなかったので、今回の開発を通して逆に色々学ぶことができてよかったと思います。 2. Figmaのフックを利用する 1でUIを作ることができたので、次は機能の開発に進みたいと思います。今回はウィジェット内のチェックボックスを例として説明したいと思います。こちらのチェックボックスは依頼を受けている人が依頼者に対応有無を伝えるために利用することが多いです。 ReactのuseStateがFigmaでは useSyncedState になるなど、若干命名には違いがありますが、基本的にはReactと同じ概念になります。クリックイベントを通してテキストなどを変えたい場合は、以下のように実装できます。 const { useSyncedState } = widget const [ completed , setCompleted ] = useSyncedState ( 'completed' , false ) export const Checkbox = () => { return ( < AutoLayout // 省略 onClick = {() => setIsCompleted ( ! isCompleted )} > { isCompleted && ( < SVG width = { 15 } height = { 15 } src = { < FilledCheckboxIcon /> } /> )} </ AutoLayout > ) } 3. メニューをカスタマイズする Figmaウィジェットの上に表示されるメニュー(添付)は usePropertyMenu のフックを使って開発できます。1で紹介したラベルのテキストを、「修正」以外にも変更できるようにしたいので、メニューで変えられるようにしたいと思います。 下記のコードはドロップダウンメニューを追加する場合の例です。itemTypeには dropdown 以外にも、 action などにも変更できます。usePropertyMenuの1つ目の引数にはメニューに入れたい配列を、2つ目の引数には各メニューと連携する機能を渡すことができます。 const { usePropertyMenu } = widget usePropertyMenu ( [ { itemType : 'dropdown' , propertyName : 'labelSelector' , tooltip : 'Label selector' , selectedOption : label , options : LABEL_LIST , } , // 省略 ] , ({ propertyName , propertyValue }) => { case 'labelSelector' : setLabel ( propertyValue as string ) break // 省略 default : break } ) 活用ケース 他にも機能はありますが、上記の3つを組み合わせると大体の機能を開発できると思います。開発したウィジェットは実際以下のように活用しています! 1. 修正依頼 デザイナー:修正が発生したらFigmaの該当部分にウィジェットを追加してエンジニアに依頼する エンジニア:対応が終わったらチェックマークをクリックしてデザイナーさんに対応完了のことをお知らせする 2. アニメーションの指示 デザイナー:アニメーションの仕様や参考資料をウィジェットに貼る エンジニア:上記を参考にアニメーションを実装する デザイナー:アニメーションのスピードを調整したい場合、ウィジェットに貼る エンジニア:上記を参考にアニメーションを調整する 3. 仕様確認 デザイナー:デザインデータを作成する エンジニア:気になる部分があったらウィジェットに貼る デザイナー:ウィジェットにて返信する ユーザーの反応 ウィジェットを利用しているデザイナーの方々から以下のようなご意見をいただきました! コメントの返信をカードで一括確認できるようになったので、一目で見てやりとりが分かりやすくなった。 ラベルがZOZOのロゴの色になっていて、こだわりを感じた。 チェックを入れたらコメントノートがフェイドアウトされ、対応有無がわかりやすくなった。 終わりに 本記事ではFigmaウィジェットを紹介しました。ZOZO専用のFigmaウィジェットの導入によって開発フローやコミュニケーションコストを改善できて良かったと思います! 株式会社ZOZOでは、アイデア次第でこんなふうに自由度の高い開発を経験できる環境が整っています! ご興味のある方はぜひ、ご応募お待ちしております! corp.zozo.com
はじめに こんにちは。Developer Engagementブロックの @wiroha です。12月15日(日)に、ZOZOにて中高生女子を対象とした体験イベント「 Girls Meet STEM〜ITのお仕事を体験しよう〜 」を開催しました。これは 公益財団法人山田進太郎D&I財団 が実施する「Girls Meet STEM」プログラムの一環です。中高生女子にIT業界の仕事を体験できる実践的な機会を提供し、将来の進路やキャリア選択の幅を広げるための後押しを行うことを目的としています。 当日は約20名の参加者が集まり、プログラミング体験、女性エンジニアとの交流、オフィスツアーを通じて、ITの仕事の魅力を体感しました。本記事では、当日の様子をご紹介します。 イベント概要 日時:2024年12月15日(日)13:00~16:00 会場:ZOZO西千葉本社 対象:中学1年生~高校3年生までの戸籍上または性自認が女性の方 定員:20名 www.shinfdn.org オープニング 今回、ZOZOやZOZOTOWNに興味を持って参加してくださった方が多く、イベントのオープニングでZOZOの事業や働く環境について紹介しました。自分のスマートフォンにZOZOTOWNアプリを入れているという参加者もいて嬉しく思います。 プログラミング体験会 プログラミング体験会では、プログラミングとは何かといった簡単な講義の後、PCを使って実際にプログラミングに取り組みました。パズルゲーム感覚でプログラミングを学べるiPad・Mac用アプリ「 Swift Playgrounds 」を使って、目標までのクリアを目指して挑戦しました。 順番にステージをクリアしながら、条件分岐や繰り返しといったプログラミングの基礎を学びました。サポートするのは4名の女性社員・内定者アルバイトです。密にコミュニケーションを取りながら、真剣にプログラミングに取り組む参加者の姿が印象的でした。 今日学んだ条件分岐や繰り返しがZOZOTOWNアプリ内のどの部分で使われているのかも解説しました。実際の仕事やプログラミングの活用方法についても理解が深まっていればと思います。 トークセッション 次に行われたのは、ZOZOで働く女性エンジニアによるトークセッションです。学生時代の経験やエンジニアになろうと思ったきっかけ、仕事の面白いところや進路選択などについて語りました。 モチベーションは周りの人に喜んでもらえることであったり、自分自身の楽しさであったり、それぞれの意見が出ました。進路選択についても途中で変えた方や文系からエンジニアに進んだ方など、実際の例を聞くことで自分の進路について考えるきっかけになったのではないでしょうか。 質問会 その後はグループに分かれてZOZOスタッフに質問する時間を設けました。参加者からは「エンジニアになるためにできていた方がいい教科は何ですか?」「文系だからIT分野に行けるか不安です」「ZOZOを選んだ理由は何ですか?」といった質問・相談が寄せられました。参加者からは「いろんな選択肢を知ることができて、不安が晴れました」といった声がありました。 オフィスツアー イベントの最後には、西千葉本社のオフィスツアーを実施しました。メッセージが込められたアートや遊び心のある会議室、絨毯の模様や色使いの工夫など、ZOZOらしいデザインが施されたオフィス内を案内しました。クイズなども挟みながら楽しく見学しました。普段はなかなか入ることのできないIT企業の職場環境を直接見ることで、働くイメージを膨らませる機会になったのではないでしょうか。 最後に 参加したみなさんに感想を伺うと、「ゲームのキャラクターが可愛くてまた遊びたい」「エンジニアの仕事はかっこいいなと思った」「周りが女の人ばかりで安心できて良かった」といった声がありました。今回初の試みではありましたが、イベントの目的を達成できていたようで何よりです。 また、サポートした社員たちからは「昔の自分と同じようなことで悩んでいる学生にアドバイスできて良かった」「イベントを通して何か踏み出すきっかけになれてたら嬉しい」といった感想が寄せられました。 ZOZOはこれまでもさまざまな女性活躍推進のための活動に取り組んできており、今後もこうした機会を提供していきたいと考えています。イベントを通じて、少しでも多くの女性がIT業界に興味を持ち、挑戦するきっかけになれば幸いです。
はじめに こんにちは、EC基盤開発本部SRE部カート決済SREブロックの金田・小松です。普段はSREとしてZOZOTOWNのカート決済機能のリプレイスや運用を担当し、AWSやAkamaiの管理者としても活動しています。 前編では、手動リリース作業が抱える課題を解決するために、GitHub Actionsを活用したリリースプロセス自動化の概要について解説しました。GitHub Actionsによる変更検知、ジョブ制御、結果通知の仕組みを構築することで、手作業の工数削減と安定性向上を実現する一連のフローをご紹介しました。 techblog.zozo.com 後編では、GitHub Actionsと連携してリリース作業を具体的に遂行するためのツールであるAWX Operatorについて、導入の背景や構成、実装の詳細に焦点を当ててご紹介します。AWX Operatorを活用することで、いかに効率的なリリース自動化を実現したのかをお伝えします(図における赤枠部分を中心に説明します)。 目次 はじめに 目次 リリース自動化の中核となるAWX Operator AWXとは? AWX Operatorとは? Playbookとは? ジョブテンプレートとは? AWX Operatorの選定理由 AWX Operatorの導入と構成 オンプレミス環境に配置しない場合の選択肢 今回の構成のメリット 事前準備 構築手順 1. AWX Operatorのインスタンスグループ作成とインスタンス追加 2. インスタンスの関連付け 3. Execution Nodeのバンドルインストール Receptorネットワーク構成について 接続状況の確認 デバッグとトラブルシューティングのTips ジョブテンプレートのコード管理 使用例:ジョブテンプレートの同期プロセス(GitHub Actionsを使用) AWX APIを利用したGitHub Actionsとの連携 APIリクエストの基本構造 ジョブの実行リクエスト ジョブの監視 まとめ 主要なポイント 期待される成果 さいごに リリース自動化の中核となるAWX Operator リリース自動化の話に入る前に、まずAWX Operatorに関連する基本用語について整理します。 AWXとは? AWXは、AnsibleのWebベースのUIやAPIを提供するオープンソースプロジェクトで、Ansibleの自動化タスクの管理、スケジュール、実行が可能です。 github.com 既存のZOZOTOWNオンプレミス環境でもAWXサーバが存在しており、オンプレミス環境における運用作業を自動化しています。例えば、以下のような様々な運用タスクの自動化に活用されています。 数百台のクライアントやサーバーへのエージェント一括インストール 定期バッチ処理の自動停止・再開 Windows Updateの自動適用 ネットワーク疎通確認 IISアプリケーションプールのリサイクルによるWebサービスの可用性維持など これらの作業を自動化することで、人的エラーを最小限に抑えながら、安定した運用を実現しています。既存AWXの運用については、以下のブログで紹介されているので、ぜひこちらもご参照ください。 techblog.zozo.com AWX Operatorとは? AWX Operator は、Kubernetes環境でAWXをデプロイ・管理するためのツールです。 github.com Kubernetesは、コンテナ化されたアプリケーションの管理を自動化するプラットフォームです。AWX Operatorを使うことで、Kubernetesクラスタ内にAWXを簡単にインストールし、設定やアップデートを効率的に行うことができます。また、デプロイ作業の自動化やサーバー設定の適用といったタスクを、Playbookで効率的に管理・実行が可能です。 Playbookとは? Playbookは、Ansibleが実行する一連のタスクを定義した YAML 形式のファイルです。例えば、以下のような操作を自動化できます。 サーバーの設定(例:WebサーバーやDBサーバーの構築) アプリケーションのデプロイ(例:アプリのインストールと設定) ネットワーク機器の設定(例:ルータやスイッチの構成変更) 以下は、Webサーバーを構築するPlaybookの例です。 --- - name : Install and configure Apache hosts : web_servers tasks : - name : Install Apache yum : name : httpd state : present - name : Copy configuration template : src : /path/to/httpd.conf.j2 dest : /etc/httpd/conf/httpd.conf - name : Restart Apache service : name : httpd state : restarted Playbookは「何を実行するか」を細かく順序立てて記載でき、リリースフローの中核を担います。 ジョブテンプレートとは? ジョブテンプレートは、AWXでPlaybookを実行するための「設定パッケージ」のようなものです。具体的には以下の要素を含みます。 Playbook :実行するタスクの定義。 Inventory :タスクを適用するサーバーやホストの一覧。 Extra Vars :実行時に渡すオプションのパラメータ(例:環境やリリース条件)。 Execution Node :Playbookを実行する対象ノード。 ジョブテンプレートを使うことで、「どのサーバーに、どのPlaybookを、どのパラメータで実行するか」を簡単に指定できます。そのため、リリース作業の自動化が容易になります。 AWX Operatorの選定理由 本プロジェクトで AWX Operator を選定した主な理由は以下の通りです。 既存のAWX運用ノウハウを活用 既にオンプレミスでAWXを利用しており、運用に関する知識や技術をそのまま活用できるため、スムーズに移行が可能であること。 Ansible Playbookの再利用が容易 従来のPlaybookをほぼそのままKubernetes環境に適用できるため、導入コストを低減し、既存の運用プロセスを簡単に移行できる。 Kubernetes環境でのデプロイ・管理が容易 弊社のインフラでは多くのリソースがコード化されており、EKSクラスタを使用しているため、AWX Operatorの導入により、インフラとアプリケーションの管理を一元化できるメリットがあった。 これらの理由から、AWX Operatorを選定し、GitHub Actionsと統合することで効率的なリリースフローを実現しています。 AWX Operatorの導入と構成 本プロジェクトでは、AWX OperatorをAmazon EKS上で運用しつつ、ジョブ実行ノード(以降Execution Nodeと称します)をオンプレミス環境に配置しています。この構成を採用した主な理由は、本プロジェクトのリリース対象がオンプレミス環境のサーバであるケースが大半を占めているためです。 オンプレミス環境に配置しない場合の選択肢 ジョブ実行ノードをオンプレミス環境に配置しない場合、AWX Operatorとジョブ実行ノードの両方をEKS上で運用できます。ただし、この場合、リリース対象がオンプレミス環境であることを考慮すると、以下のデメリットが懸念されます。 ネットワーク遅延 クラウド上のジョブ実行ノードがオンプレミスリソースにアクセスする際、クラウドとオンプレミス間で頻繁に通信が発生します。この通信に伴い、ネットワーク遅延がリリース全体の速度低下につながる可能性があります。 データ転送量の増大とコスト ジョブの実行に必要なデータ(例:ファイル、設定情報)がクラウドとオンプレミス間で往復するため、ネットワーク帯域を圧迫するリスクがあります。また、クラウドプロバイダーの通信料金(特にアウトバウンド通信料金)も増加し、運用コスト増につながる可能性があります。 今回の構成のメリット オンプレミス環境のリソースに対するリリースが大半を占めるため、ジョブ実行ノードをオンプレミス環境に配置する構成を採用することで、以下のメリットが得られます。 ネットワーク遅延の最小化 ジョブがオンプレミス環境内で直接実行されるため、ジョブの実行に必要な主要な通信が内部ネットワークで行われます。これにより、クラウドとオンプレミス間の往復通信が最小限となり、リリース速度が向上します。 データ転送量の削減とコスト効率の向上 ジョブの処理やデータのやり取りが主にオンプレミス環境内で完結するため、クラウドとの間で発生するデータ転送量が削減されます。これにより、ネットワーク帯域の負担を軽減すると同時に、クラウドプロバイダーの通信料金が抑えられるため、コスト効率が向上します。 以上のように、オンプレミスリソースに対する高速かつ効率的なリリースが可能になるため、本構成を採用しました。 AWX Operatorの導入方法については、 インストールガイド を参照してください。ここでは、 Execution Node をAWXに登録する手順をご紹介します。 事前準備 Execution Node では Ansible が必要です。Ansible公式のインストールガイドに従い、インストールします。 docs.ansible.com 構築手順 以下に、Execution Nodeの構成とAWX Operatorの設定手順を説明します。 1. AWX Operatorのインスタンスグループ作成とインスタンス追加 Webコンソールの「インスタンスグループ」メニューから、「インスタンスグループの追加」をクリックし、必要な項目を入力します。 名前 :インスタンスグループの名前を設定します(例:execution-group)。 ポリシーインスタンスの最小値 :インスタンスグループ内で最低限必要なインスタンス数を設定します。 ポリシーインスタンスの割合 :必要なインスタンス数を割合で指定できます。 Max concurrent jobs :同時に実行可能なジョブ数の上限を設定します。 Max forks :ジョブごとに使用できる最大フォーク数を設定します。 Webコンソールの「インスタンス」メニューから「追加」ボタンをクリックし、インスタンスを追加します。以下のポイントに従って設定します。 ホスト名 :Execution Nodeの完全修飾ドメイン名(FQDN)またはIPアドレスを指定します。 リスナーポート : 27199 を設定します。これは Receptor が使用するポートです。 インスタンスタイプ : Execution を選択します。 Peers from control nodes :制御ノードから自動的にピアリングする場合はこのオプションを有効にします。 2. インスタンスの関連付け 作成したインスタンスグループを選択した状態で、インスタンスタブより、追加したインスタンスを関連付けます。 3. Execution Nodeのバンドルインストール Execution Nodeでは、AWX Operatorがジョブを分散実行する基盤となる Receptorサービス を利用しています。Receptorは、AWXのコントローラーとExecution Node間の通信を担当するメッセージングシステムであり、ジョブの実行を効率的に分散・管理する重要な役割を果たします。以下に、Receptorを含むExecution Nodeのインストール手順を説明します。 バンドルのダウンロード :AWXコンソールの「インスタンス」詳細ページで、バンドルのダウンロードボタンをクリックし、必要なファイルを取得します。このバンドルにはTLS証明書、Receptor設定ファイルが含まれます。 インストール :ダウンロードしたファイルをExecution Nodeへ転送し、以下のコマンドで展開してインストールを実行します。 tar -xzf [ バンドルファイル ] ansible-galaxy collection install -r requirements.yml Tips :トラブルが発生した場合、Ansibleのフォーラムに解決策が掲載されている場合があります。 forum.ansible.com 以上の手順で、AWX OperatorにExecution Nodeが登録され、AWX上から Execution Node 経由でジョブを実行できるようになります。これにより、オンプレミス環境とAWS EKSを連携させたリリース自動化の基盤が構築され、システム全体の操作効率が向上します。 Receptorネットワーク構成について Receptorは、Execution NodeとAWXのコントローラー間で通信するための分散ネットワークを構成します。通常、以下の設定内容は前述のバンドルインストールで自動的に生成されるため、手動で編集する必要はありませんが、参考までに設定例を紹介します。 # Receptorノードの基本設定。ノードIDとピアとして許可するノードIDを指定します。 - node : id : <node-id> # このノードのID(任意の一意な名前) allowedpeers : <peer-node-ids> # 接続を許可するピアノードIDのリスト # ログレベルの設定。デフォルトはinfo、デバッグ用途にはdebugに設定可能。 - log-level : info # ジョブの実行設定。ansible-runnerを使用してジョブを実行します。 - work-command : worktype : ansible-runner # 実行するワークの種類 command : ansible-runner # 実行コマンド params : worker # ワーカーとして機能する設定 # サーバー用のTLS設定。接続に使用する証明書とキーのパスを指定します。 - tls-server : name : default filename : /etc/receptor/tls/tls.crt # TLS証明書ファイル key : /etc/receptor/tls/tls.key # TLSキー # クライアント用のTLS設定。接続先の証明書を検証するためのCA証明書を指定します。 - tls-client : name : receptor-client rootcas : /etc/receptor/tls/ca.crt # ルートCA証明書 # UDP接続の設定。ピアノードとの通信に使用するホストとポートを指定します。 - connection : type : udp peer : <peer-ip>:27199 # ピアのIPアドレスとポート # TCP接続の設定。UDPと同様にピアノードとの通信に使用します。 - connection : type : tcp peer : <peer-ip>:27199 # ピアのIPアドレスとポート 接続状況の確認 設定後、以下のコマンドで他ノードとの接続状態を確認できます。 sudo receptorctl status Receptorを用いることで、ジョブ実行の分散性や拡張性が向上し、大規模環境でも安定したリリースを実現します。また、ピアノード間の通信設定を最適化することで、効率的なネットワーク構築が可能です。 デバッグとトラブルシューティングのTips ログレベルの調整 :デフォルトの log-level: info では一般的なログが表示されますが、トラブルシューティングが必要な場合は debug に変更すると詳細なログを取得できます。 ノード再起動時の接続確認 :ノードの再起動後、 sudo receptorctl ping <peer-node-id> を使って個別に接続が正常かテストできます。 証明書の確認 :Receptorで使用するTLS証明書に問題がある場合は、 openssl コマンドを使って証明書の有効期限や接続テストを行うと便利です。 openssl s_client -connect < peer-ip > :27199 ジョブテンプレートのコード管理 AWXのCLI(Command Line Interface)を利用したジョブテンプレートとインベントリのコード管理を実施しており、GitリポジトリとAWX間での同期を保つようにしています。 docs.ansible.com 主要なコマンド ジョブテンプレートの作成 : awx job import コマンドで新規テンプレートを自動反映。 変更のエクスポート : awx export コマンドで変更を検出し、Gitと同期。 使用例:ジョブテンプレートの同期プロセス(GitHub Actionsを使用) 以下のようなGitHub Actions Workflowを設定し、AWXのジョブテンプレート変更をGitリポジトリに反映させるプルリクエストを自動で作成しています。 name : Sync AWX Job Templates on : schedule : - cron : '0 0 * * *' # 毎日0時に実行 workflow_dispatch : # 手動での実行トリガー jobs : sync-job-templates : runs-on : ubuntu-latest steps : - name : Checkout repository uses : actions/checkout@v4 # リポジトリのチェックアウト。最新のテンプレートと比較するために必要です。 - name : Install dependencies run : | sudo apt-get update sudo apt-get install -y jq python3-pip pip3 install awxkit # 必要な依存ツールとパッケージのインストールを行います。 - name : Export job templates run : | mkdir -p job_templates templates=$(awx job_templates list -f json | jq -r '.results[] | {id: .id, name: .name}' ) for template in $(echo "$templates" | jq -r '.name' ); do echo "Exporting template: $template" template_id=$(echo "$templates" | jq -r --arg name "$template" 'select(.name == $name) | .id' ) ./scripts/awx/export_job_template.sh "$template_id" done echo "Templates exported" # AWXから各ジョブテンプレートをエクスポートし、リポジトリ内のjob_templatesフォルダに保存します。 - name : Create Pull Request uses : peter-evans/create-pull-request@v6 with : commit-message : "Update AWX Job Templates" branch : "update-job-templates" title : "Update AWX Job Templates" body : "This PR updates the AWX job templates and ensures they are in sync with the latest operational settings." labels : "automated pr" base : "main" delete-branch : true # エクスポートしたテンプレートをもとにGitHubで自動プルリクエストを作成します。 リポジトリのチェックアウト 最初にリポジトリをクローンし、現在のジョブテンプレートと最新テンプレートとの差分を取得できるようにします。 依存関係のインストール awxkit などの依存ツールをインストールし、AWXからのデータ取得が可能な状態にします。 テンプレートのエクスポート AWXからジョブテンプレートをエクスポートし、各テンプレートのIDと名前を取得してローカルフォルダに保存します。 プルリクエストの作成 エクスポートしたテンプレートを基に、リポジトリに自動的にプルリクエストを作成し、更新をチームでレビューできるようにします。 このようにして、テンプレートの更新はプルリクエストを通じてレビューされ、AWXの設定内容がGitリポジトリに同期されるため、一貫性と追跡性を保証しながら変更内容を管理できます。 AWX APIを利用したGitHub Actionsとの連携 GitHub ActionsとAWX Operatorの連携は、AWXのREST APIを通じて行います。以下の図は、GitHub Actionsでのスクリプト実行からAWX APIを通じたジョブ実行、そしてリリースプロセス全体の流れを示しています。 この図をもとに、GitHub Actionsからリリース作業を開始し、ジョブを実行・監視するまでの一連の流れを解説します。 APIリクエストの基本構造 まず、APIリクエストを送信するためのcurl構造を作成します。以下の関数で、POSTやGETリクエストをAPI送信できるようにしています。 #!/usr/bin/env bash function call_awx_api() { token = " $1 " base_url = " $2 " method = " $3 " path = " $4 " if [ " ${method} " == " POST " ]; then req = " $5 " response = $( curl -s -X POST \ -H " Authorization: Bearer ${token} " \ -H " Content-Type: application/json " \ -d " ${req} " \ " ${base_url} /api/v2/ ${path} " ) echo " ${response} " return fi response = $( curl -s -X " ${method} " \ -H " Authorization: Bearer ${token} " \ -H " Content-Type: application/json " \ " ${base_url} /api/v2/ ${path} " ) echo " ${response} " return } この関数で利用している各パラメータの役割について、以下に簡単に説明します。 token :APIリクエストで認証するためのトークンです。本記事で紹介するプロジェクトでは、このリリース用に作成したAWXユーザーのトークンを使用しています。トークンの作成方法は、対象のAWXユーザーでログイン後、 ユーザー > AWXユーザー > トークンタブ にて作成可能です。トークンベース認証の詳細については以下のドキュメントをご参照ください。 docs.ansible.com このトークンはGitHubのSecretsに格納しており、GitHub Actionsから参照しています。 base_url :AWX OperatorのServiceで指定しているドメイン名です。APIエンドポイントのベースURLとして使用され、AWXサーバーのアドレスを指定します。 method :APIリクエストのHTTPメソッドを指定します。通常はGETまたはPOSTを指定し、POSTの場合は追加のデータ( req )が送信されます。 path :api/v2以降のパスを指定します。たとえば、job_templates/ /launchでジョブテンプレートの起動や、jobs/<job_id>でジョブのステータスを取得できます。APIの詳細についてはAWXのドキュメントを参照してください。 ansible.readthedocs.io ジョブの実行リクエスト 次に、GitHub ActionsからAWXのジョブを起動するためのリクエストを構成します。以下のように、リクエストボディで extra_vars を利用して、ジョブ実行に必要な変数(例:commit hash、GitHubトークン、リリースの順序)を渡します。これにより、後続のスクリプトでこれらの変数を利用できます。また、 limit パラメータでリリース対象のサーバーを指定して、ジョブが実行される範囲を制限しています。 # GitHub ActionsのワークフローからAWX OperatorのAPIを利用してジョブ実行するスクリプト例 requestbody = $( cat << EOS { "extra_vars": { "commit_hash": " ${ASP_COMMIT_HASH} ", "github_token": " ${ASP_GITHUB_TOKEN} " }, "limit": " ${limitation} ", "inventory": " ${inventory} ", "job_type": "run" } EOS ) response = $( call_awx_api " ${API_TOKEN} " " ${AWX_BASE_URL} " " POST " " job_templates/<id>/launch/ " " ${requestbody} " ) job_id = $( echo " ${response} " | jq -r ' .job ' ) echo " Job ID: ${job_id} " ここで行っている処理は以下の通りです。 extra_vars :後続のスクリプトで使用する変数(例:commit hash、GitHubトークンなど)を渡しています。 limit :指定されたサーバー範囲内でジョブを実行し、リリース対象のサーバーを制限します。 inventory :AWX上で管理されている特定のインベントリを指定します。 job_type :実行ジョブの種類を指定します。ここでは通常の実行を示すrunを設定しています。 ジョブの監視 ジョブの実行が開始されると、ジョブIDを取得し、そのステータスを定期的に監視します。以下のループで jq を使ってジョブのステータスを取得し、進行状況をチェックしています。ジョブが pending や running 以外の状態(成功や失敗など)になるまで監視し、完了したタイミングでループを終了します。 while true; do response = $( call_awx_api " ${API_TOKEN} " " ${AWX_BASE_URL} " " GET " " jobs/ ${job_id} / " ) job_status = $( echo " ${response} " | jq -r ' .status ' ) echo " Job Status: ${job_status} " if [ " ${job_status} " != " pending " ] && [ " ${job_status} " != " running " ]; then break fi sleep 5 done 上記の構成で、ジョブが完了するまでの間、定期的にジョブの進行状況を確認できるようにしています。ジョブの進行状況の監視が完了すれば、一連のリリース作業は終了となります。 この仕組みにより、GitHub Actionsを起点としたリリースフローが安定して実現し、手動作業による負担やリスクを大幅に軽減できました。 まとめ 後編では、GitHub Actionsと連携してリリース作業を具体的に遂行するためのツールであるAWX Operatorについて、その導入背景や構成、実装の詳細に焦点を当てて解説しました。本記事で取り上げた主要なポイントは以下の通りです。 主要なポイント AWX Operatorの導入と活用 Kubernetes環境での簡易なAWXデプロイを実現し、クラウドとオンプレミスを統合した分散リリース環境を構築。 Receptorを利用して、安定したジョブ分散と効率的な通信を可能に。 ジョブテンプレートの構成管理 Playbook、Inventory、Extra Varsを活用し、柔軟かつ一貫したリリースフローを管理。 GitHub Actionsとの連携によるリリースフローの自動化 REST APIを利用し、ジョブの実行・進行状況の監視・エラー検知を自動化。 手作業を排除し、迅速で信頼性の高いリリースを実現。 これにより、リリース作業の自動化による運用効率の向上が期待できます。リリース自動化の導入はまだ始まったばかりですが、すでに以下のような成果が期待されています。 期待される成果 リリースの安定化 自動化されたプロセスにより、手動エラーは排除され、リリースの一貫性の向上が期待されます。 エラーの削減 段階的リリースごとにエラーをリアルタイムで監視し、迅速に対処することで、エラーによる影響を最小限に抑えることが可能です。 リリース速度の向上 リリース作業の自動化により、全体の工数削減と迅速なリリースが見込まれます。 さいごに 本記事では、前編と後編の2部構成で、GitHub ActionsとAWX Operatorを活用したリリース自動化の取り組みをご紹介しました。従来の手動リリースから脱却し、効率化と安定性を実現することで、工数削減やリリースリスクの低減を目指しています。 リリース自動化は継続的な改善が必要な分野ですが、本取り組みを通じて、さらなる最適化や監視機能の強化を進める予定です。この記事が、皆さんのシステム運用やリリース自動化の参考になれば幸いです。 私たちZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。興味をお持ちの方は、ぜひ以下のリンクからご応募ください。 hrmos.co
はじめに こんにちは、EC基盤開発本部SRE部カート決済SREブロックの金田・小松です。普段はSREとしてZOZOTOWNのカート決済機能のリプレイスや運用を担当し、AWSやAkamaiの管理者としても活動しています。 本記事では、前編と後編に分けて、Classic ASPの手動リリースをGitHub ActionsとAWX Operatorを活用して自動化したプロジェクトについてご紹介します。手動で行っていたリリース手順を自動化することで、効率化と安定性をどのように実現したか、そのアプローチをお伝えします。 前編では、Classic ASPの手動リリース作業が抱える課題を解決するためにGitHub Actionsを活用したリリースプロセス自動化の概要について解説します。 後編では、GitHub Actionsと連携してリリース作業を具体的に遂行するためのツールであるAWX Operatorについて、導入の背景や構成、実装の詳細に焦点を当ててご紹介します。 目次 はじめに 目次 リプレイス前の環境とリプレイスプロジェクトについて 手動リリースの課題 Git管理とリリース手順の不一致 リリース作業の信頼性と一貫性の不足 リリース自動化へのアプローチ アーキテクチャ概要 GitHub Actionsの役割 AWX Operatorの役割 アーキテクチャ全体図 リリース方式: 25%ずつのカナリアリリース GitHub Actions 対象サーバー群の管理方法 ReleaseReview リリース時のサービス監視 ワークフローの分割 排他制御 lock処理の実装方式 lock処理の高速化 各サーバー上での処理について GitHub Apps経由のトークン取得 インストールアクセストークン ユーザアクセストークン デプロイ処理 トークンを使ったremote url設定 マージコミットにswitchしての差分取得とコミットされた状態に応じた処理 さいごに リプレイス前の環境とリプレイスプロジェクトについて ZOZOTOWNは、2004年12月にサービスを開始して以降、基本的なアーキテクチャは変えずにオンプレミス環境で動くモノリシックなシステムとして運用されてきました。リプレイス前のアプリケーションは、Classic ASPで構築されオンプレミス環境のWindowsサーバー上で動作しています。 しかし、サービスの拡大に伴い、モノリシックなシステムの運用や開発に課題が生じるようになりました。そこで2017年から、レガシー化したシステムの課題を解決するためにZOZOTOWNのマイクロサービス化をするためのリプレイスプロジェクトを進めています。リプレイス後のマイクロサービスでは、IaCやCI/CDの導入により、開発効率や運用効率の向上を図っています。 詳しくは以下のテックブログをご覧ください。 techblog.zozo.com 手動リリースの課題 ZOZOTOWNのリリース作業は、リプレイスされマイクロサービス化したシステムを除き、以下の手順を手作業で実施していました。 リリース対象ファイルの準備 バックアップの取得 リリース手順の準備 ファイルをどの順番でリリースするかの検討 WebDAVを利用したファイルのアップロードによるリリース この手作業のプロセスにより、リリースごとに多くの工数がかかり、作業負担が増大する課題を抱えていました。 また、以下のような問題も発生していました。 Git管理とリリース手順の不一致 リリース後に対象ファイルをmasterブランチにマージする運用のため、同時期に複数案件のリリースをする際、共通ファイルのコンフリクトが発生する場合もありました。この場合、リリース担当者が最新ファイルを手元でマージし直す必要があり、リリース担当者間の調整やコミュニケーションが求められ、工数増加やコミュニケーションミスが障害リスクを生む要因となっていました。 リリース作業の信頼性と一貫性の不足 手作業によるリリースは操作ミスが発生しやすく、リリースミスや障害リスクの温床となり、リリース担当者の精神的な負担を増大させていました。 これらの課題を解消するためには、リリースプロセスの自動化が不可欠です。自動化により作業負担が軽減され、ミスのリスクが抑えられることで、リリース作業の信頼性が向上し、開発および運用チームの負担も軽減されます。 さらに、Git管理状態とサーバーにリリースされている状態を常に一致させるため、GitOpsの概念を取り入れたリリース自動化を進めました。これにより、リリースの一貫性が担保され、ミスの削減とプロセスの効率化が期待できます。 リリース自動化へのアプローチ アーキテクチャ概要 これらの課題を解消するため、リリースプロセスの自動化にGitHub ActionsとAWX Operatorを採用しました。本節では、それぞれのツールが果たす役割について説明します。 GitHub Actionsの役割 GitHub Actionsは、リリースプロセス全体を制御する役割を担っています。主に以下の3つのタスクを実行します。 変更検知 GitHubリポジトリにコード変更(マージ)があると、リリースプロセスをトリガーして開始します。 ジョブ制御 リリースの進行状況に応じて、AWX Operatorへジョブ実行リクエストを送信し、次のステップを制御します。 結果収集と通知 AWX Operatorから返却された実行結果を確認し、Slackを通じてリリース進行状況を通知します。 GitHub Actionsは、リポジトリとリリース管理のハブとして機能し、全体のプロセスを統括します。 AWX Operatorの役割 AWX Operatorは、GitHub ActionsとAPIを介して連携し、リリース作業の具体的な実行を担当します。以下が主な役割です。 サーバー操作 対象サーバーに対して、必要なファイル配置やIISリサイクル、ロードバランサーを操作します。 リクエスト処理 GitHub Actionsから送信されたリリース条件やジョブパラメータをもとに、安全かつ正確に作業を実施します。 実行結果の返却 ジョブの実行結果や状況をGitHub Actionsに返却し、次のステップに移行するための判断材料を提供します。 アーキテクチャ全体図 以下の図は、GitHub ActionsとAWX Operatorを連携させたリリースフローを示しています。この仕組みにより、リポジトリの変更を起点としてリリース作業を自動化し、一貫性と効率性を実現しています。 GitHub Actionsがリポジトリの変更を検知してトリガーを発火。 リポジトリの差分情報やリリース条件をもとに、AWX Operatorへジョブ実行リクエストを送信。 AWX Operatorがサーバーグループごとに作業を実施。 実行結果をGitHub Actionsに返却し、Slackを通じて通知。 リリース方式: 25%ずつのカナリアリリース リリースを自動化するにあたり、すべてのWebサーバーを4つのグループに分割し、25%ずつロードバランサーから切り離した状態でファイルを更新する方法を採用しました。リリースが完了したサーバーは順次ロードバランサーに復帰させることで、段階的にサービスに影響を与えないリリースを実現しています。 この方式により、従来のリリースで必要だったファイルのリリース順を考慮する手間が不要になり、安全かつ効率的なリリースが可能です。また、25%ずつのカナリアリリースをすることで、万が一問題が発生した場合の影響範囲を限定でき、リスクを最小限に抑えられます。 具体的には、サーバーを4つのグループに分け、各グループを順番にリリース対象とした上で、以下の手順を実施します。 STEP1:リリース対象の1グループのサーバーをロードバランサーから切り離し、外部トラフィックの影響を遮断します。 STEP2:切り離したサーバーでリリース作業を実施し、IISリサイクルを行います。 STEP3:作業が完了したサーバーをロードバランサーに再接続し、トラフィックを復旧させます。 この手順を各グループで順番に繰り返します。こうすることで、リリース中のリスクとダウンタイムを最小限に抑えつつ、全体の安定性を維持できます。以降で、リリース自動化の具体的な仕組みについて、GitHub ActionsやAWX Operatorの役割を中心に詳しく説明していきます。 GitHub Actions 本記事で紹介するリリースの自動化では、masterブランチへのマージをトリガーにGitHub Actionsのワークフローを起動して本番のサーバーにファイルをリリースしています。 また25%ずつのカナリアリリースを実現するため、GitHub Actionsでは様々な工夫をしています。 対象サーバー群の管理方法 リリース対象となるサーバー群はyamlファイルで管理しています。 下記はサーバー群の管理ファイルのフォーマットです。 <env> : - type : サーバーグループのタイプ servergroups : - <server role> : lb_info : - ロードバランサーを操作するための情報 . . . limitations : - 後述するAWXに渡すhost情報 項目 説明 env 環境を指定します type サーバーグループのタイプを指定します。 ZOZOTOWNではonpremissのサーバーに加えてEC2のサーバーを増設することがあり、そのための設定です。 servergroups サーバーグループの設定をリストとして保持します。 servergroupsリストの1要素ごとに直列でReleaseReview → LB Down → デプロイ → LB Up 操作を行います server role サーバーの役割を任意の文字列で指定します。 同じservergroup内のserver role分、並列でデプロイ処理が実行されます lb_info ロードバランサー情報の設定を含むリスト limitations 対象サーバーの制限条件を指定します。 AWX用のサーバーパラメータをリスト形式で指定 上記のyamlファイルではリリース対象のサーバー情報の他にロードバランサーを操作するための情報も記載しています。このyamlファイルをworkflowで参照し、servergroups毎にデプロイ処理を行っています。 ReleaseReview 25%ずつのカナリアリリースを実施するにあたり、1つのグループのリリース完了後、即座に次のグループのリリースを進めてしまうとエラー発生の有無を確認する間がありません。各リリースの合間に確認時間を設けないと問題が発生した際にリリースを中断するのも難しくなってしまいます。 そこで本記事の自動リリースではグループごとのリリース処理を実行する前に、 GitHub Environments を利用した承認stepを取り入れています。 GitHub EnvironmentsはGitHub ActionsのJobに設定でき、 Protection Rule を設定できます。設定できるProtection Ruleにはいくつか種類がありますが、Required reviewersを設定すると必須レビュアーからの承認があるまでジョブの実行を止めることができます。 Required reviewersの承認はリリースの担当者が承認できるように設定しています。リリースの担当者は1グループのリリースが完了したら、リリースしたグループにエラーがないことを確認後、次のグループのリリース承認を行います。 ReleaseReviewについてはマイクロサービスのCI/CDを紹介したブログ記事でも詳しく説明していますので、ぜひご参照ください。 techblog.zozo.com リリース時のサービス監視 カナリアリリースによるリリースでは、リリース起因でのエラーが発生していないかを確認することが重要です。本記事での自動リリースではサーバー群を4つのグループに分割して順番にリリースしています。そこでグループ別にエラー発生の有無、エラー内容の確認、CPU負荷の確認ができるダッシュボードを用意して、リリースで問題が発生してないかの確認ができるようにしています。 リリースプロセスをよりよく把握するため、Splunkのダッシュボードを導入しています。リリース担当者は、ダッシュボードを使って、リリースされる各サーバーグループの状況をリアルタイムでチェックできます。段階的リリース(N%リリース)の際には、特にエラーやCPU使用率などのシステム負荷を詳細に監視できるようにしています。 さらに、500エラーが発生した場合、ダッシュボードから直接Gitの該当コード行へリンクできる機能を備えています。これにより、エラーの原因を素早く特定し、迅速な対応を可能にしています。 このダッシュボードはIaC化されており、SplunkのIaC化については、以前のブログ記事で詳しく紹介していますので、そちらもぜひご参照ください。 techblog.zozo.com ワークフローの分割 GitHub Actionsでは、柔軟なループ構文(例えば、forループ)のネイティブサポートがありません。特に複数回実行の必要なジョブやステップがある場合、同一ワークフロー内でループを実行できないことが大きな制約となります。本記事の自動化においても、対象サーバー群を設定ファイルで管理できるようにしたものの、GitHub Actionsでは繰り返し処理を使えないため工夫が必要でした。 この制約を解決する方法として、ワークフローの分割とBashスクリプトの組み合わせで柔軟な繰り返し処理をGitHub Actionsでも実現できるようにしました。 この手法では、まず親となるワークフローからBashスクリプトでforループを構築し、 gh workflow run コマンドで分割した子ワークフローを繰り返し実行します。子ワークフローとして実行したいワークフローは、 gh workflow run で実行できるように workflow_dispatch をトリガーとして設定しておきます。 gh workflow run < 実行したいワークフローファイルのpath > -f hoge =fuga --repo < 対象リポジトリ > --ref github.base_ref さらに、実行したワークフローのRunIDを追跡するため、 gh run list コマンドで取得したURLから実行したワークフローのRunIDを抽出します。 gh run list --workflow =< ワークフローファイル名 > --limit 1 --json url --repo < 対象リポジトリ名 > | jq -r ' .[0].url ' 取得したRunIDを使用し、 gh run watch コマンドで指定したワークフローの終了を待つことも可能です。例えば、以下のようにして指定のRunIDの実行が終了するまで待機し、その結果を取得できます。 gh run watch < runid > -i 30 --exit-status --repo < 対象リポジトリ名 > これらのコマンドをforループで繰り返し実行することで、柔軟な繰り返し処理を実装しています。 for i in < 繰り返す回数 > ; do gh workflow run < 実行したいワークフローファイルのpath > -f hoge =fuga --repo < 対象リポジトリ > --ref github.base_ref # ワークフローが実行中になるのを待つ sleep 10 # RunIDの取得 runurl = $( gh run list --workflow =< ワークフローファイル名 > --limit 1 --json url --repo < 対象リポジトリ名 > | jq -r ' .[0].url ' ) runId = $( basename " ${runUrl} " ) # gh run watchで終了待機と結果取得を行う gh run watch " ${runid} " -i 30 --exit-status --repo < 対象リポジトリ名 > done 排他制御 本記事の自動リリースでは、ワークフローが並列で実行されないように独自の排他制御を実装しています。排他制御が必要な理由としては以下が挙げられます。 複数のグループに分割してグループ毎にロードバランサーから下げてファイルを更新する方式のため、並列実行してしまうと複数のグループを本番から下げた状況になり、本番のサーバー数不足が想定される GitHub Actionsには concurrency というワークフローを直列で実行させるための設定がありますが、以下の理由で本記事の自動リリースでは採用できないため独自の排他制御を実装しています。 concurrencyは、 最大 1 つの実行ジョブと 1 つの保留中のジョブが存在できる とドキュメントに明記されており、3つ以上実行されると待機中のワークフローが古いものから順にキャンセルされる。 本記事の自動リリースでは、Mergeコミットの差分のみを展開する方式(※ 詳細は後述)としているため、ワークフローの実行がキャンセルされると展開されない差分ができてしまう。 lock処理の実装方式 GitHub Actionsで排他制御を実現するには、どのようにlockオブジェクトを実装するかが重要です。通常のシステムのようにデータベースやファイルシステムを利用できないため、独自の手法が必要です。 本記事で紹介する排他制御では、特定名称のGitブランチをlockオブジェクトとして扱い、そのブランチがGitHub上のワークフロー導入リポジトリに存在するかどうかで排他状態を判定しています。 具体的には、lockを取得したいワークフローがGitHubへブランチのPushを試み、Pushが成功すればlock獲得、失敗すれば他のワークフローがlockを保持していると判断します。これは、GitHub上でのブランチPushがアトミック操作であることを利用した方法です。 また、lockの解放は、lockブランチの削除によって行います。このシンプルな方式により、複雑な管理リソースを必要とせず、GitHub Actions内で効率的かつ確実な排他制御を実現できます。 lock処理の高速化 自動リリースを導入するリポジトリでは、長い履歴と多数の管理ファイルが存在するため、lock管理用のブランチ作成やPushに時間を要することが予想されます。そこで、効率的なリリースフローを実現するため、lock用ブランチはリポジトリの既存ブランチから派生させず、新たに空のリポジトリを git init でワークフロー上に作成する方法を採用しました。この空リポジトリ上で、lock状態を管理するためのファイルのみをcommitし、lock用ブランチとしてPushすることで処理の高速化を図っています。この手法により、リポジトリ全体をcloneする手間を省き、lock状態管理に必要な最小限のリソースのみを効率的に操作できるため、時間短縮とパフォーマンスの向上を同時に実現できます。 下記はワークフローで実行している git init コマンドを使って作成した空リポジトリから作成したブランチを導入リポジトリにpushするためのコマンド例です。 git init git remote add origin " https://x-access-token: ${ { github.token } }@github.com/<Repository名>.git " git config --global user.email " github-actions[bot] " git config --global user.name " github-actions[bot]@users.noreply.github.com " echo < lock情報 > > lock.txt git add . git commit -m " add lock files " git push origin HEAD: < lockブランチ名 > 各サーバー上での処理について 本記事で紹介する自動リリースの仕組みでは、GitHub Actionsから後述するAWX OperatorのAPIを介して各サーバー上でPowerShellスクリプトを実行し、デプロイ処理を行います。 サーバーからGitHubにアクセスする認証手段としては、一般的に デプロイキー や マシンユーザ が利用されます。しかしこれらの認証方法はサーバーごとに個別のSSHキーを生成・設定する必要があるため、複数のサーバーでGitHubへのアクセスが必要な場合には設定の負担が増大し、採用が現実的ではありません。 そこで本記事の自動リリースでは、GitHub Appsで一時的なアクセストークンを生成し、各サーバーに配布する方式を採用しました。 GitHub Apps経由のトークン取得 GitHub Appsについての詳細は以下のドキュメントをご参照ください。 docs.github.com GitHub Appsで取得できるトークンには以下の2種類があります。 インストールアクセストークン ユーザアクセストークン インストールアクセストークン GitHub AppsがリポジトリやOrgnizationのリソースへアクセスするために発行するトークンです。Botとして個別のリポジトリや組織へアクセスし、リポジトリ単位の自動化タスクを行う場合に使用します。アクセス権限はGitHub Appsの設定画面で細かく設定できます。トークンの有効期限もデフォルトでは1時間になっており、一時的な利用に適しています。 ユーザアクセストークン OAuthトークンの一種で、認証されたユーザの権限とリポジトリへのアクセス範囲を持ちます。ユーザーが持つ全ての権限に基づいた広範囲なアクセスが許可されるため、アプリがユーザーとしての行動をシミュレートするような用途に適しています。有効期限はデフォルトでは8時間で更新トークンを利用してトークンの再生成が可能です。 本記事の自動リリースでは、各サーバーからGitHubにアクセスするためのトークンが必要です。そのため、一時的な利用に適したインストールアクセストークンを発行して使用しています。このトークンは、デプロイ処理を実行するたびに新規発行するよう設定しており、セキュリティリスクを低減しつつ、最新のアクセス情報で安全にリリース操作を行うことが可能です。 GitHub Actionsでのインストールアクセストークン発行は、公式のトークン発行Actionである actions/create-github-app-token を使用しています。 このアクションの入力値として必要なApp IDとPrivateKeyは作成したGitHub Appsの設定画面で確認・作成できます。 上記の値は、それぞれリポジトリのSecretsに登録してGitHub Actionsから参照しています。 actions/create-github-app-token で取得したインストールアクセストークンは、AWXのAPIを介して各サーバーのPowerShellにパラメータとして渡すようにしています。 デプロイ処理 PowerShellスクリプトでは以下の処理を実行しています。 トークンを使ったremote url設定 マージコミットにswitchしての差分取得とコミットされた状態に応じた処理 トークンを使ったremote url設定 トークンを使用してGitコマンドからGitHubにアクセスするためにはremote urlの設定が必要です。下記が設定例になります。 git remote set-url origin https://x-access-token: < トークン > @github.com/ < owner > / < リポジトリ名 > .git この設定をすることで、origin指定のGitコマンドがトークンを使用したアクセスになります。 マージコミットにswitchしての差分取得とコミットされた状態に応じた処理 Gitリポジトリで最新のファイルを取得してリリースするのに一番シンプルな方法はディレクトリごと上書きする方式です。しかし自動リリースを導入するリポジトリはファイル数が大量にあり、毎リリースで全ファイルを上書きするのはパフォーマンス上の懸念がありました。またサーバー上のディレクトリ構成とリポジトリ上のディレクトリ構成が一致していないという課題もありました。 そこでマージされたpull requestの差分のみを取得・リリースする方式としました。 具体的には、GitHub ActionsからマージコミットのHashをパラメータとしてPowerShellスクリプトに渡します。PowerShellスクリプトは受け取ったHashにswitchして、 git log コマンドでマージコミットの親となるHashを取得します。マージコミットの親となるHashはPullRequestがマージされたbaseブランチとfeatureブランチ、それぞれのHashになります。 マージコミットの親となるHashを取得するコマンド例です。 # マージコミットにswitch git switch -f --detach < マージコミットのHash > git log -n 1 --pretty = format:%P --pretty=format:%P で親となるHashだけの出力になります。 詳細は公式ドキュメントをご参照ください。 git-scm.com 下記はマージコミットの git log コマンドの出力例です。 --pretty=format:%P オプションを付けて実行することで Merge: e1108e69e07 83daabd093a の e1108e69e07 83daabd093a のみ出力されます。 $ git log --merges -n 1 commit ab4474df796f1dd4c8c188f3d88e3366de79be12 ( HEAD - > master, origin/master, origin/HEAD ) Merge: e1108e69e07 83daabd093a Author: kane8n < takumi.kaneda@zozo.com > Date: Fri Nov 8 19:01:02 2024 + 0900 Merge pull request #317 from st-tech/fe-deploy-test Fe deploy test $ $ git log --merges -n 1 --pretty = format:%P e1108e69e07fa210b4a5584d0a724b9f7ebf160a 83daabd093a2bf7826a458bb9d356f861478fd0b $ 上記出力のスペースを ... に置換し、 git diff コマンドの引数とすることでマージコミットの差分が取得できます。 git diff コマンドにはファイルがどのような操作でコミットされたのか(追加されたのか・削除されたのかなど)の情報も取得するため、 --name-status オプションも付けて実行しています。 $ git diff --name-status e1108e69e07fa210b4a5584d0a724b9f7ebf160a...83daabd093a2bf7826a458bb9d356f861478fd0b A pc/wwwroot/FeDeployTest/brand/default.html D pc/wwwroot/FeDeployTest/brand/include/default_2021.html D pc/wwwroot/FeDeployTest/brand/include/default_2022.html A pc/wwwroot/FeDeployTest/css/common.css A pc/wwwroot/FeDeployTest/css/common.v2.css A pc/wwwroot/FeDeployTest/css/common.v3.css A pc/wwwroot/FeDeployTest/css/interview.css A pc/wwwroot/FeDeployTest/css/ mv .css A pc/wwwroot/FeDeployTest/css/ mv .v2.css A pc/wwwroot/FeDeployTest/default.html $ 上記出力をPowerShellスクリプトでパースして、追加(A)や修正(M)であればコピー、削除(D)ならサーバー上のファイルも削除というように処理しています。 さいごに 前編では、Classic ASPの手動リリース作業が抱える課題を解決するためにGitHub Actionsを活用したリリースプロセス自動化の概要について解説しました。後編では、リリース自動化の中核となるAWX Operatorについて解説します。こちらもぜひご覧ください。 https://techblog.zozo.com/entry/asp-auto-deploy-implementation-second-part techblog.zozo.com ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 hrmos.co
はじめに こんにちは、MA部MA基盤ブロックでマーケティングオートメーションのシステムを開発している長澤です。この記事ではBigQueryとDatadogを活用した監視を導入した話を紹介します。 はじめに、日々のマーケティングオートメーション(以下、MA)の開発・運用における課題としてシステム信頼性の向上がありました。ZOZOTOWNは年間の購入者数が1,100万人を超えており、MAによりユーザーの皆さまに向けて多様かつ大規模なキャンペーン配信を展開しています。そのため、MAのシステム信頼性の担保が重要課題でした。 その一環としてこれまでに導入されてきた監視が多数ありました。MAの監視を大別すると、一般的に知られるシステム監視とMA特有の監視があります。以下がその一部です。 一般的なシステム監視 OSのCPUやメモリの使用量 HTTPレスポンスのステータスコード SLA 死活監視 MA特有の監視 配信件数や配信失敗率(配信システム・配信チャネル・キャンペーン・集計時間などによる組み合わせ多数) 重複配信などの異常検知 配信からのECサイト・アプリへの流入数 この記事で取り上げるのは後者のMA特有の監視になります。既存のMA特有の監視はいくつかの課題を抱えていました。例えば監視対象のシステムが複数あり、システムによって監視方法が異なり統一されていなかったり、監視ツール自体に課題があったりしました。 これらの課題を解決するため、今回は新たにBigQueryとDatadogを活用した監視の仕組みを構築しました。元々BigQueryは監視のメトリクスのデータソースとして、Datadogは監視ツールとして部署内で活用していたのですが、このふたつを組み合わせた監視の仕組みはまだ存在していませんでした。既存の複数の監視方法の中で、それぞれが別々に活用されている状況でした。この記事では、BigQueryとDatadogを連携するメトリクス送信用アプリケーションによる監視の仕組みをご紹介します。 目次 はじめに 目次 既存の課題 時折通知に失敗する メトリクスを蓄積できない 監視設定のコード管理ができない 監視ツールの選定 監視のアーキテクチャと実装 取り組みの結果 今後の課題 まとめ 既存の課題 既存のMA特有の監視の課題としては以下のようなものがありました。 配信システムによって監視方法が異なる 各監視ツールに課題がある 時折通知に失敗する メトリクスを蓄積できない 監視設定のコード管理ができない 実際に使用していた(試験運用含む)監視ツールは、Redashアラート・Lookerアラート・Digdagで、いずれも本来は監視が主目的のツールではありませんでした。RedashとLookerはBIツールに付随するアラート機能として利用していました。Digdagは定期実行が可能なワークフローエンジンのため、定期的にBigQueryの集計結果が閾値を超えていたら通知するようにして監視代わりにしていました。 時折通知に失敗する 一番の課題として、時折監視の通知に失敗することがありました。RedashはAWS環境でセルフホスティングしていましたが、しばしばワーカーのCeleryのエラーが発生していました。LookerアラートはSaaSでしたが、試験運用中にしばしば通知が失敗してしまい、採用を見送った経緯がありました。 メトリクスを蓄積できない これは元々が監視ツールではないこともあり、いずれのツールでもできていませんでした。蓄積のためには別途自前の仕組みが必要でした。 監視設定のコード管理ができない RedashとLookerではアラート設定をコードで実装できないため、コード管理ができませんでした。Redashアラートは管理画面のフォーム以外の管理方法がありません。LookerはAPIでアラートの設定が可能ですが、別途それ用の実装が必要でした。 監視ツールの選定 こうした背景を踏まえて改めて監視ツールを選定し、Datadogを採用しました。採用理由としては、やはり監視ツールであるがゆえに監視のニーズを満たして既存の課題の解決に最適であったためです。加えて、すでに社内や自分の部署内において多数の運用実績があったのも大きな理由です。 Datadogは過去の運用で既知の通り、高いシステム信頼性で安定稼働していました。メトリクスの蓄積は元々機能として備えており、ダッシュボードで過去の推移を簡単に確認できます。また、監視設定はTerraformで記述可能なためコード管理ができます。 監視のアーキテクチャと実装 本題の監視の仕組みを説明します。 冒頭でも触れた通り、BigQueryのデータを集計し、その集計結果をメトリクスとしてDatadogに送信する監視用アプリケーションを開発しました。言語はGoを採用し、Cloud Runジョブで実行します。Cloud Schedulerでメトリクス送信の実行間隔を設定し、HTTPリクエストをCloud Runジョブに送信します。Datadogの監視はTerraformでコード管理しています。 アプリケーションの設計の要点として、監視対象のシステムや項目が増えてもアプリケーションの修正は不要にしました。新たに監視項目を追加する場合は以下の3つを追加または修正し、GitHub Actionsでそれぞれをデプロイする仕組みになっています。 Cloud Schedulerを管理するYAMLファイル BigQueryのSQLテンプレート Terraformの「datadog_monitor」リソースの定義 Cloud Schedulerを管理するYAMLファイルは以下のようなスキーマで記述します。GitHub ActionsでこのYAMLをパースし、Cloud Schedulerジョブを作成・更新します。 - name : zozo-notification-delivery-error-rate-job description : 配信基盤の配信失敗率 pause : false schedule : "*/10 * * * *" template_path : zozo_notification_delivery/error_rate 例えばこの設定は、配信の失敗率を集計するSQLテンプレートを10分おきに定期実行するジョブを作成します。アプリケーションのCloud Runジョブはひとつのみ作成しますが、監視項目ごとに実行するSQLテンプレートは異なります。そのため、Cloud Schedulerのデプロイ時にHTTPリクエストのボディでファイルパスを渡すようにしています。 Cloud Runジョブの実行時に任意の引数を渡したい場合は、以下のように「containerOverrides」で指定するため、この値にファイルパスを渡しています。また、それ以外に任意の引数を渡すことも可能な実装にしています。 gcloud scheduler jobs deploy http ${JOB_NAME} \ --description " ${DESCRIPTION} " \ --location asia-northeast1 \ --schedule " $( echo " ${SCHEDULE} " | sed -e ' s/\"//g ' ) " \ --time-zone " Asia/Tokyo " \ --uri " https://asia-northeast1-run.googleapis.com/apis/run.googleapis.com/v1/namespaces/ ${PROJECT_ID} /jobs/ ${CLOUD_RUN_JOB} :run " \ --http-method POST \ --message-body " { \" overrides \" :{ \" containerOverrides \" :[{ \" args \" :[' ${MESSAGE_BODY} ']}]}} " \ --max-retry-attempts 3 \ --oauth-service-account-email ${SERVICE_ACCOUNT} \ --project ${PROJECT_ID} アプリケーションの実装においては、SQLの結果をDatadogメトリクスAPIのインタフェースに合わせています。 docs.datadoghq.com 今回は時系列データをポストできる「Submit metrics」エンドポイントを利用しています。このエンドポイントでは、主にメトリクスの「名前」「タイムスタンプ」「値」「タグ」を送信します。 type MetricSeries struct { Metric string `json:"metric"` Points [][] float64 `json:"points"` Type string `json:"type"` Tags [] string `json:"tags"` } func sendToDatadog(metricName string , value float64 , tags [] string ) error { url := fmt.Sprintf( "https://api.datadoghq.com/api/v1/series?api_key=%s" , datadogAPIKey) metric := DatadogMetric{ Series: []MetricSeries{ { Metric: metricName, Points: [][] float64 { { float64 (time.Now().Unix()), value}, }, Type: "gauge" , Tags: tags, }, }, } data, err := json.Marshal(metric) if err != nil { return err } resp, err := http.Post(url, "application/json" , bytes.NewBuffer(data) // 省略 } これに合わせて、BigQueryのSQLテンプレートは「メトリクス名」と「集計した値」を出力するようにしています。例えば先ほどの配信失敗率のSQLテンプレートであれば、このような結果を返します。 [{ " metric_name ": " system1_push ", " value ": 0.0 } , { " metric_name ": " system1_mail ", " value ": 0.01 } , { " metric_name ": " system1_line ", " value ": 0.02 }] この結果に加えて、SQLテンプレート名やその他の管理用の情報をタグとしてメトリクスに追加し、Datadogに送信します。このようにしてSQLの詳細に関わらず、集計結果をメトリクスとして送信できるようにしています。 Terraformでは先ほどのメトリクス名と同じ数の監視を定義します。 resource "datadog_monitor" "zozo_notification_delivery_error_rate" { for_each = toset ( [ "system1_push" , "system1_mail" , "system1_line" , ] ) name = "【Notification Delivery】Error rate of $ { each.key } over the threshold" type = "metric alert" tags = [ "env:$ { local.env } " , "app:zozo_notification_delivery" , "template:error_rate" ] message = <<-EOT $ { local.notification_slack_channel } Error rate of $ { each.key } ({{value}}%) over the threshold {{threshold}}. template: https://github.com/st-tech/ma-datadog-metrics-sender/blob/main/templates/zozo_notification_delivery/error_rate.tmpl {{ #is_alert}} {{#is_exact_match "env" "prd"}} @pagerduty-zozo-ma-alert {{/is_exact_match}} <!subteam^XXXXXXXX|ma-alert-team> {{/is_alert}} {{ #is_no_data}} {{#is_exact_match "env" "prd"}} @pagerduty-zozo-ma-alert {{/is_exact_match}} <!subteam^XXXXXXXX|ma-alert-team> {{/is_no_data}} {{ #is_no_data_recovery}} {{#is_exact_match "env" "prd"}} @pagerduty-zozo-ma-alert {{/is_exact_match}} {{/is_no_data_recovery}} EOT validate = true query = "max(last_5m):bigquery.query.result.zozo_notification_delivery.error_rate.$ { each.key } {env:$ { local.env } } > XXX" notify_no_data = true no_data_timeframe = 25 ## minutes timeout_h = 0 require_full_window = false monitor_thresholds { warning = XXX critical = XXX } } 通知先はSlackとPagerDutyです。監視の閾値を超えた場合だけでなく、一定時間メトリクスの送信がなかった場合も「no_data」の設定によって通知します。「no_data」の設定によって、メトリクス送信アプリケーションに問題があった場合に、それ自体の監視がなくともメトリクス送信の異常を検知できます。 取り組みの結果 現在はこの監視の仕組みを構築し、監視の一部の移行が完了しています。Datadogの安定性とno_dataの設定により、監視の仕組みは安定して運用できています。時折メトリクス送信に失敗しますが、それも検知した上で対応できています。メトリクスの蓄積も既知の通りでしたが、過去の推移を簡単に確認するのに役立っています。監視設定はGitHubで管理しているため、PRレビューをしたり、過去の変更履歴を管理したりできています。Digdagワークフローでは監視項目ごとにアプリケーションコードを実装していましたが、移行後はSQLテンプレートで集計処理が完結するため、コードの実装を減らすことができました。その甲斐もあって移行もコストを抑えてスムーズに行えました。 将来的には複数のツールに点在している監視をこの仕組みに統一することで、拡張性がありつつ運用負荷が低い監視を実現します。 今後の課題 今後の課題としては、残りの監視の移行や不足している監視の追加があります。また、今回の課題とは別問題ですが移行後の監視の見直しを予定しています。見直しの例としては、監視の閾値が低く通知の頻度が多すぎるケースや、監視の集計軸の粒度が大きいために細分化が必要なケースなどです。 まとめ BigQueryとDatadogを活用したMAの監視について紹介しました。メトリクス送信用のアプリケーションを開発することで、BigQueryをデータソースとしてメトリクスを集計し、Datadogで監視できるようにしました。設計の工夫により、この監視の仕組み自体の運用コストを抑えられるようにしました。監視の一事例として参考になれば幸いです。 MA部ではMAの開発やSREを共に推進してくれる方を募集中です。ご興味のある方は下記リンクの採用募集からぜひご応募ください。 corp.zozo.com
はじめに こんにちは、MA部の中原です。 MA部ではZOZOTOWNにおけるメルマガやLINE通知、アプリプッシュ通知、Web広告を配信するためのマーケティングオートメーションシステムを開発・運用しています。本記事では、Web広告について外部SaaSで利用していた機能の内製化と移行についてご紹介します。 目次 はじめに 目次 背景・課題 拡張コンバージョンとは? 拡張コンバージョンの実装 設定方法 環境構築 1. アクセス認証情報の作成 2. API認証ユーザーに付与するGoogle広告のアクセス権限 3. APIリクエストには個人アカウントのリフレッシュトークンを使用する 全体の流れ 1. 運用担当者の広告設定・広告出稿 2. ユーザが広告をクリックし購入 3. ファーストパーティ(自社)の購入データをGoogleに送信 実装 送信データ APIを使用したアップロード 並列処理の方法 1. 対象データ取得 2. バッチグループの作成 3. 並列処理 外部SaaSからの移行 1. テスト用のコンバージョンアクションの作成 2. テスト用のコンバージョンアクションに対して拡張コンバージョンをアップロード 3. テスト用のコンバージョンアクションで拡張コンバージョンが記録されることを確認 4. 本番適用と外部SaaSとの並行稼働 5. 外部SaaSの利用停止 移行前と移行後 まとめ さいごに 背景・課題 ZOZOTOWNでは、GoogleやYahoo!、Metaなどの様々な媒体に広告を出稿しています。広告の運用担当者は、配信した広告のクリック率やコンバージョン率などをみて効果検証し、広告の改善や最適化を行っています。Google広告においてはコンバージョンの計測を補完するために、外部SaaSの機能を活用して拡張コンバージョン(詳細は後述)を導入していました。しかし、外部SaaSに依存しているためエラーが発生した際にリカバリーができず、コンバージョンデータの欠損が発生し効果検証や分析に支障をきたすという課題がありました。そこで、Google Ads APIを用いた拡張コンバージョンの実装を内製化し、課題の解決を図りました。 拡張コンバージョンとは? 従来の方法では、コンバージョンは以下の流れで計測されます。 広告主がコンバージョンページ(注文完了ページなど)にコンバージョンタグを設定 ユーザが広告をクリックするとGCLID(Google Click Identifier)という広告クリックを一意に識別するIDや広告のキャンペーン情報をCookieに保存 ユーザがコンバージョンページに訪問すると、コンバージョンタグが発火し、Cookieの情報を読み取りGoogleに送信 しかし、近年のCookie規制の強化により従来の方法ではコンバージョンの欠損が起きやすくなります。今回紹介する拡張コンバージョンは、ファーストパーティデータ(自社データ)とGoogleアカウントを照合することで、この欠損を補完する機能です。具体的な計測の流れを以下に示します。 自社データのユーザ情報(ハッシュ化した電話番号やメールアドレスなど)やコンバージョン情報(注文日時)をGoogleに送信 Google側で送信されたデータとGoogleアカウントのデータと照合し、広告クリックとコンバージョンの関連付けが行われ計測 拡張コンバージョンの実装 設定方法 拡張コンバージョンの設定方法は3つあります。 table { border-collapse: collapse; width: auto; } th { border: solid 1px #666666; color: #000000; background-color: #FFFFFF; } td { border: solid 1px #666666; color: #000000; background-color: #FFFFFF; } 設定方法 メリット デメリット Googleタグマネージャー ユーザデータを収集するためのタグ設定を行うだけで非エンジニアでも設定可能 コンバージョンページに電話番号やメールアドレスの情報を持たせる必要があり、セキュリティリスクがある Googleタグ Google Ads API 自社から直接ユーザデータを送信するためセキュリティが高い 技術的な知識が不可欠でエンジニアによる開発が必要 今回はセキュリティの観点からGoogle Ads APIを採用しました。詳細は Google広告ヘルプページの拡張コンバージョンに関する説明 をご覧ください。 環境構築 インストールや認証等の設定は以下の公式ドキュメントに従えばスムーズに設定できました。この章では実装するにあたって特に重要なポイントについて説明します。 developers.google.com developers.google.com 1. アクセス認証情報の作成 OAuth 2.0クライアントにはウェブアプリケーションやデスクトップアプリケーションなど、さまざまな種類があります。今回の実装では、APIリクエストに必要なリフレッシュトークンを簡単に発行できるデスクトップアプリケーションとしてOAuth 2.0クライアントを作成しました。なお、サービスアカウントを使用する方法もありますが、これにはGoogle Workspaceドメイン全体の権限を委任する必要があります。この仕組みでは、Google広告の権限を持つユーザーになりすまして認可を受ける形となります。しかし、サービスアカウントに非常に強い権限を付与することになるためセキュリティリスクが高く、この方法は推奨されていません。アクセス認証情報の詳細については以下をご覧ください。 developers.google.com 2. API認証ユーザーに付与するGoogle広告のアクセス権限 APIリクエスト時の認証で使用するユーザは、Google広告にアカウントを追加およびアクセス権限の付与が必要です。主な権限とできることを以下に示します。 権限 できること 読み取り専用 閲覧のみ(広告、キャンペーン、レポートの確認など) 標準 広告運用の全般(キャンペーンや広告の作成、編集、コンバージョンデータのアップロードなど) 管理者 全ての操作 読み取り専用では拡張コンバージョンのアップロードで権限エラーが発生するため、標準以上の権限が必要です。上記以外にもアクセス権限の種類はありますが、詳細は以下をご覧下さい。 support.google.com 3. APIリクエストには個人アカウントのリフレッシュトークンを使用する Google Ads APIではサービスアカウント単体でのリクエストがサポートされていません。そのため個人アカウントの認証情報を使うしかありません。理由は以下の2点です。 Google広告には個人のアカウントしか登録できない Google Ads APIの認証時にAPI発行主がGoogle広告にアカウントと権限があるかが確認される APIのクライアントライブラリではリフレッシュトークンを認証情報として渡します。このリフレッシュトークンは、Google広告に権限があるアカウントで発行したものを使用します。APIリクエスト時にはリフレッシュトークンでアクセストークンを取得し、そのアクセストークンで認証・認可が行われます。リフレッシュトークンは個人アカウントに依存するため、システムをチーム全体で管理する際に不便な場合があります。たとえば、APIリクエストに使用していたメンバーのアカウントが退職などで削除されると、そのアカウントに紐づいたリフレッシュトークンを使用してAPIリクエストができなくなります。一方で、Google広告のアカウントを持つユーザーのリフレッシュトークンを使用することで、Google広告のアカウントを持たない他のメンバーやシステムからもAPIリクエストは可能です。そのためチーム全体で運用する際は、認証に使うアカウントの管理や引き継ぎを考慮する必要があります。個人に紐づくトークンを利用する設計は望ましくないため、将来的にはサービスアカウントをセキュアに利用できる仕組みが導入されることを期待しています。 全体の流れ 実装の説明の前に簡単な全体像を以下に示します。 大まかな流れは次の通りです。 1. 運用担当者の広告設定・広告出稿 運用担当者は広告を作成した後、コンバージョンを計測するためにコンバージョンアクションというものを作成し設定します。コンバージョンアクションとは、サイトでの商品購入やアプリのダウンロードなどユーザの特定の行動です。例えば、ウェブサイトの注文完了ページへのクリックで購入を測定する「購入コンバージョン」などです。サイト内にタグを設置することでクリック時に発火し測定されます。今回は購入コンバージョンの計測を補完しました。 2. ユーザが広告をクリックし購入 ユーザが広告をクリックし注文完了ページに到達すると、コンバージョンアクションのタグが発火し、Googleに以下の情報を送信します。 クリックID(GCLID):広告クリックを一意に識別するID 広告ID:クリックされた広告を特定するための情報 クリック時間データ:広告がユーザーに配信されてからクリックされた時間に関する情報 3. ファーストパーティ(自社)の購入データをGoogleに送信 自社のDBに蓄積される注文データ(ハッシュ化したメールアドレスなどのユーザデータや注文日時など)をGoogleに送信します。Google側でその情報を使用して広告のクリックとコンバージョンを関連付けます。ハッシュ化しているとはいえ、ユーザの個人情報を外部へ送信するため事前に法務部への確認が必要です。 実装 上図の赤の部分を実装しました。定期実行するために、 Digdag を使用しています。Digdagとはオープンソースのワークフローエンジンで、複数のタスクをワークフローとして定義しバッチ処理を行えるものです。自社の注文データをGoogle Ads APIを使用してGoogleに送信するシンプルなアプリケーションを作成しました。送信データとAPIを使用したアップロードの実装について説明します。 送信データ 送信するデータは 公式ドキュメント に記載されています。すべての項目を送信する必要はないため必要最低限の以下の項目を送信しました。 送信項目 説明 order_id コンバージョンのタグで指定する注文ID phone_number ハッシュ化したユーザの電話番号 country_code ISO 3166 の2文字の国名コード email ハッシュ化したユーザのメールアドレス conversion_date_time 注文日時 ハッシュ化する項目は、ハッシュ化する前に以下のように変換する必要があります。 先頭と末尾の空白を取り除く テキストを小文字に変換する E164規格に従って電話番号をフォーマットする メールアドレスのドメイン名の前にあるピリオド(.)をすべて削除する ハッシュ化はBigQueryのクエリで行ってテーブルに書き出し、アップロード時はテーブルのデータを参照するだけの状態にしました。以下にBigQueryでハッシュ化するクエリ例を示します。 SELECT MemberID, to_hex(sha256( lower ( replace ( replace (split(email, ' @ ' )[ 0 ], ' ' , '' ), ' . ' , '' )) || ' @ ' || lower ( replace (split(email, ' @ ' )[ 1 ], ' ' , '' )))) AS email_sha256, to_hex(sha256( ' +81 ' || ltrim ( replace (tel, ' - ' , '' ), ' 0 ' ))) AS tel_sha256 FROM `project.dataset.Member` -- 会員テーブル 送信するデータはファーストパーティのCookieの保管期限の関係で、 ドキュメント に記載の通り、コンバージョン発生から24時間以内のデータだけを送るように注意します。 APIを使用したアップロード Pythonのクライアントライブラリを使用しました。 サンプルコード が公式で用意されており、それを参考に実装しました。APIのリクエストは以下のドキュメントにある通り、1回につき2000件までという制約があります。直列で2000件ずつ処理すると時間がかかるため、今回はAPIリクエストを並列で処理するように工夫して実装しました。 developers.google.com 並列処理の方法 Digdagにはループ処理ができる for_each> オペレータと、並列処理できる _parallel: オプションがあります。 docs.digdag.io これらを使用し、対象データを固定の並列数で2000件ずつ処理するためのグループに分け、ループしながら効率的に処理することにしました。 1. 対象データ取得 DBから過去24時間以内の注文情報を取得して一時テーブルに書き出します。 2. バッチグループの作成 対象データからバッチ処理するグループのリストを作成します。以下はグループ作成のサンプルコードです。引数のcountには対象データの件数を渡します。 _BATCH_SIZE = 2000 # リクエスト1回あたりの最大件数 _PARALLEL_SIZE = 50 # 並列数 def generate_batch_group_list (count: int ) -> List: batch_size = int (count / _BATCH_SIZE) if int (count % _BATCH_SIZE) > 0 : batch_size += 1 batch_number_list = list ( range (batch_size)) return [batch_number_list[i:i + _PARALLEL_SIZE] for i in range ( 0 , len (batch_number_list), _PARALLEL_SIZE)] 例えば対象データ件数が100万件の場合、それぞれの変数とbatch_group_listの中身は以下の通りです。 count: 1000000 batch_size:(1000000 / 2000)= 500 batch_number_list: [0, 1, 2 ... 499] batch_group_list: [[0, 1, 2, ..., 49][50, 51, 52, ..., 99]...[450, 451, ... , 499]] batch_group_listは並列数の50個ずつに区切られた2次元配列となり、中身の数字は、BigQueryに書き出したテーブルから2000件ずつ取得するために基準となる数字です。 3. 並列処理 上記2で作成した batch_group_list をDigdagの変数にセットし、以下のようなDigdagのタスクを定義します。 +upload : for_each> : group_list : ${batch_group_list} _do : for_each> : batch_number : ${group_list} _parallel : true _do : _export : python : /usr/bin/pipenv_run_python docker : image : ${docker_python.image} pull_always : ${docker_python.pull_always} !include : k8s.dig py> : models.google_ads.upload_enhanced_conversions timestamp : ${moment(session_time).local().format("YYYYMMDD_HHmmss")} 外側のループで50個ずつにまとめたグループのリストを取り出し、内側のループでは _parallel : true オプションをつけることで50個を並列で処理します。ここで呼び出しているupload_enhanced_conversionsメソッドでは、batch_numberを基準として、1で書き出した一時テーブルから2000件取得してリクエストします。Google Ads APIのライブラリのクラスやメソッドを使う処理は省略しますが、以下にコード例を示します。 _BATCH_SIZE = 2000 def upload_enhanced_conversions (timestamp: str , batch_number: int ) -> None : start_index = batch_number * _BATCH_SIZE # 対象データをすべて書き出したテーブル table_id = f 'project.dataset.purchase_conversions_{timestamp}' conversions_iterator = bq.list_rows(table_id, start_index, _BATCH_SIZE) # 指定位置から2000件取得 conversions_list = list (conversions_iterator) """ conversions_listをGoogle Ads APIのライブラリのメソッドを使用してアップロード """ def list_rows (table_id: str , start_index: int , batch_size: int ) -> List[bigquery.Row]: client = bigquery.Client(project=config[ "gcp_project" ], credentials=gcp.get_credentials()) table = client.get_table(table_id) row_iterator = client.list_rows(table, start_index=start_index, max_results=batch_size) return list (row_iterator) これで50並列での処理が可能になりました。対象データが100万件の場合、2000件ずつのリクエストが500回となり、50並列で実行するため外側のループは500/50=10回まわることになります。 外部SaaSからの移行 ここまで説明した内容を冒頭でも説明したように外部SaaSを利用して行っていました。ここからは外部SaaSからどのように移行をしたのかについてお話しします。 1. テスト用のコンバージョンアクションの作成 Google広告には開発環境が用意されておらず、本番環境でテスト用のコンバージョンアクションを作成しました。本番運用で使用しているコンバージョンアクションと同じ設定で作成します。本番運用と同じページタグを設置することで、本番運用と同様のコンバージョンを計測でき、本番用のコンバージョンに影響を与えることなく確認できます。 2. テスト用のコンバージョンアクションに対して拡張コンバージョンをアップロード アップロードは、コンバージョンアクションID単位です。このIDはコンバージョンアクション単位で振られます。テスト用のコンバージョンアクションに対してアップロードします。 3. テスト用のコンバージョンアクションで拡張コンバージョンが記録されることを確認 Google広告の管理画面から確認します。以下の画面の「オフラインでのコンバージョン」の詳細ページでアップロードされたかが確認できます。 アップロードのデータに問題があると以下のようにアラートが表示されます。 この時は以下が原因でアラートが表示されました。 24時間より前に購入が発生したデータをアップロードしていた コンバージョン発生から24時間以内のデータが必要とされる テスト用ということで少量データしかアップロードしなかった タグが発火した回数(コンバージョン発生回数)分のコンバージョンデータをアップロードする必要がある 4. 本番適用と外部SaaSとの並行稼働 本番運用で使用しているコンバージョンアクションに対してもテスト用と同様にAPIを使用してアップロードします。アップロードが上手くいかずコンバージョンの計測を補完ができなかった場合、本番運用に影響するため外部SaaSと並行稼働します。 5. 外部SaaSの利用停止 問題なく安定的に本番運用で使用しているコンバージョンアクションに対してアップロードできていることを確認して、外部SaaSの利用を停止します。 移行前と移行後 外部SaaSからの移行前後で拡張コンバージョンのカバレッジが改善され安定するようになりました。コンバージョンが失われている可能性があるとカバレッジの割合は低く表示されるため、高い割合であることが好ましいです。 まとめ 本記事では、Google Ads APIを用いた拡張コンバージョンの実装と外部SaaSからの移行方法について紹介しました。外部SaaSの機能の内製化と移行によって、拡張コンバージョンのカバレッジが安定し、利用コストも削減できました。Google Ads APIを使用して拡張コンバージョンの実装を検討している方の参考になりましたら幸いです。 さいごに ZOZOでは一緒にプロダクトを開発してくれるエンジニアを募集しています。ご興味のある方は下記リンクからぜひご応募ください! corp.zozo.com
Developer Engagementブロックの @ikkou です。ZOZO開発組織の1か月の動向をMonthly Tech Reportとしてお伝えします。 ZOZO TECH BLOG 2024年11月度は7本の記事を公開しました(前月分のMonthly Tech Reportを含む)。中でも次の3つの記事は多くの方に読んでいただきました。 techblog.zozo.com techblog.zozo.com techblog.zozo.com まだお読みでない方はぜひご覧ください。 登壇 ちむぐくる!TOKYO 11月6日に開催されたgusuku Customineのユーザー交流会『 ちむぐくる 』にコーポレートエンジニアリング部の新井が登壇しました。 #CybozuDays 前日📅 サイボウズ東京オフィス・Factoryにて 「ちむぐくるTOKYO」はじまりました🌺🌿 今年の参加者は去年よりもめちゃくちゃ増えて、会場ほぼ🈵状態です👀💫 ありがたい〜〜🔥 #カスタマインちむぐくる #gusuku #Customine #kintone pic.twitter.com/zEhys05zlo — gusuku🎪DX MARKET〜あなたのkintoneに合うトッピングは? (@gusukuSupport) 2024年11月6日 Cybozu Days 2024 11月7日に開催された『 Cybozu Days 2024 』にコーポレートエンジニアリング部の新井が登壇しました。 【登壇のお知らせ】 明日から幕張メッセで開催される #CybozuDays 2024 の1日目にコーポレートエンジニアリング部の新井が『大企業では、kintoneをこう使う!ノーコードツール活用術を全部見せ』というテーマの座談会セッションに登壇します🎙️ https://t.co/AuCHgAARCt #CybozuDaysで会いましょう — ZOZO Developers (@zozotech) 2024年11月6日 経営者・リーダーのためのデータ活用実践フォーラム 11月13日に開催されたオンラインセミナー『 経営者・リーダーのためのデータ活用実践フォーラム 』の基調講演にデータサイエンス部の西山が登壇しました。 オンラインセミナー登壇のお知らせ🎙️👨‍💻 明後日11/13(水) 13時より開催される #日経クロステック 主催『経営者・リーダーのためのデータ活用実践フォーラム』の基調講演にデータサイエンス部の西山が登壇、ZOZOにおける #生成AI の活用事例をご紹介します! https://t.co/fnACYrPrTS — ZOZO Developers (@zozotech) 2024年11月11日 speakerdeck.com JSConf JP 11月23日に開催された『 JSConf JP 』にWEARフロントエンド部 テックリードの冨川( @ssssotaro )が登壇しました。 カンファレンス登壇のお知らせ🎙️ 来週末11/23(土)に開催されるJSConf JP 2024にてWEARフロントエンド部の冨川 @ssssotaro が11:40よりトラックBにて『React CompilerとFine Grained Reactivityと宣言的UIのこれから』というタイトルで登壇します! https://t.co/2UI0kCTq54 #jsconfjp #zozo_engineer — ZOZO Developers (@zozotech) 2024年11月13日 jsconf.jp speakerdeck.com アーキテクチャConference 2024 11月26日に開催された『 アーキテクチャConference 2024 』にCTOの瀬尾( @sonots )が会場限定の講演として登壇しました。 11/26(火) 開催!アーキテクチャConference 2024にCTOの瀬尾 @sonots が『ZOZOTOWNのアーキテクチャ変遷と意思決定の歴史をADRから振り返る』というテーマで登壇します🎙️ 会場限定講演!14:00~14:40に B会場で皆さんのご来場をお待ちしています! https://t.co/30XLLJuuan #アーキテクチャcon_findy — ZOZO Developers (@zozotech) 2024年11月20日 本日登壇した資料こちらに置いておきますね ^^ 『ZOZOTOWNのアーキテクチャ変遷と意思決定の歴史を ADRから振り返る』 https://t.co/sM9SSjOrKM #アーキテクチャcon_findy — そのっつ (Naotoshi Seo) (@sonots) 2024年11月26日 GitHub Universe Recap 東京 後述するGitHub × ZOZOTOWNコラボレーションアイテムの販売にあわせ、11月27日に開催された『 GitHub Universe Recap 東京 』に技術戦略部の諸星( @ikkou )と堀江( @Horie1024 )が登壇しました。 GitHub x ZOZOTOWNコラボグッズ着用時のすがた 先週のイベントで 「GitHub Copilot全社導入のその後とGitHub×ZOZOTOWNコラボレーションの舞台裏」 をお話しされた @Horie1024 さんの良いお写真が撮れたので、許可をいただき掲載。 カジュアルT、GitHub CopilotデザインのライトグレーLサイズです。 pic.twitter.com/Eydp1NF1w4 — GitHub Japan (@GitHubJapan) 2024年12月3日 異色とも言える今回のコラボレーションの裏話と、2023年にGitHub Copilotを全社導入した後の現状をお話ししました。全社導入時の記事とあわせてご覧ください。 speakerdeck.com techblog.zozo.com CloudNative Days Winter 2024 11月29日に開催された『 CloudNative Days Winter 2024 』にSRE部 カート決済SREブロックの横田と同SRE部 プラットフォームSREブロックの亀井が登壇しました。 📰ZOZOエンジニア登壇情報 CloudNative Days Winter 2024の2日目にカート決済SREの横田とプラットフォームSREの亀井が登壇します🎙️ 🖥️システムリプレイスプロジェクト発足から7年、改めてコスト最適化に向き合う 📅 2024/11/29 13:20-14:00 Track B https://t.co/44brxUeEG6 #CNDW2024 #CloudNative — ZOZO Developers (@zozotech) 2024年11月28日 event.cloudnativedays.jp speakerdeck.com 掲載 Software Design 2024年12月号 ZOZOTOWNリプレイスプロジェクトについて全8回で連載中の「 Software Design 2024年12月号 」が11月18日に発売されました。 最終回となる第8回のテーマは「 フロントエンドエンジニアから見るZOZOTOWNリプレイスとまとめ・今後の展望 」です。ぜひご覧ください。 ZOZOTOWNリプレイスプロジェクトについて連載中の「Software Design 2024年12月号」が本日11月18日(月)に発売されました! 第8回のテーマは「フロントエンドエンジニアから見るZOZOTOWNリプレイスとまとめ・今後の展望」です。連載がいよいよ最終回です。お見逃しなく! #zozo_engineer https://t.co/avn09O2zb4 — ZOZO Developers (@zozotech) 2024年11月18日 第7回までの連載は全文を公開しています 。あわせてご覧ください。 日経クロステック(xTECH) ZOZOTOWNのアイテムレビューにおける生成AI活用についての記事が日経クロステック(xTECH)に掲載されました。 xtech.nikkei.com 有料会員限定の記事となりますが、1ページ目はどなたでもご覧いただけます。 また、アイテムレビュー機能そのものについては、関連記事を公開しています。ご興味のある方はあわせてご覧ください。 techblog.zozo.com techblog.zozo.com 日本ネット経済新聞 ZOZOTOWNとWEAR by ZOZOにおける、気温別にアイテムやコーディネイトを提案するコンテンツ・機能について、日本ネット経済新聞に掲載されました。 netkeizai.com ZOZOTOWNは『 FASHION FOR WEATHER 』として特設ページを公開しています。WEAR by ZOZOはiOS/Androidアプリ左上のお天気アイコンから「 コーデ予報 」を確認できます。ぜひ日頃のコーディネートの参考としてください。 東洋経済education×ICT 山田進太郎D&I財団と42 Tokyoによる「 Girls Meet STEM〜ITのお仕事を体験しよう〜 」にZOZOが参画することについて東洋経済education×ICTに掲載されました。 toyokeizai.net 既に参加者の募集は締め切っていますが、「ZOZOのワークショップでITのお仕事を体験!」と題して12月15日に実施します。後日、レポート記事を公開するので、お楽しみに! www.shinfdn.org WAKE Career WAKE Careerの女性エンジニアインタビューとしてバックエンドエンジニアを務める半澤のインタビューが掲載されました。特に女性・ITエンジニアの活躍やキャリアに関心のある方は必見の内容となっています。 wake-career.jp 半澤の過去の取り組みについては、以下のページもあわせてご覧ください。 www.wantedly.com aws.amazon.com イベント案内 12月に開催するイベントをご紹介します。 ZOZO Advent Calendar 2024 ZOZO Advent Calendar 2024は過去最多となる全11シリーズ、275記事を公開予定 冬の風物詩とも言えるアドベントカレンダーに参加中です。今年は 過去最多となる全11シリーズ、275記事を公開予定 です。さまざまなジャンルの記事が公開されますので、ぜひ気になる記事を探してみてください。 qiita.com GitHub Universe 2024 Recap in ZOZO 10月29〜30日の2日間に渡ってサンフランシスコで開催された GitHub Universe 2024 を振り返るRecapイベントをオフラインで開催します。 ZOZOからは2名が登壇し、ゲストとしてニフティ株式会社さん、そして『コード×AIーソフトウェア開発者のための生成AI実践入門』の著者である服部さん( @yuhattor )も登壇します。GitHubに興味をお持ちの方はぜひご参加ください。 zozotech-inc.connpass.com AWS re:Invent 2024 Recap in ZOZO 12/2日から6日の5日間に渡ってラスベガスで開催される AWS re:Invent 2024 を振り返るRecapイベントを12月17日にオフラインで開催します。 ZOZOからはAWS re:Invent 2024参加者4名の他、ゲストとしてAWSの方も登壇します。AWS re:Invent 2024に参加した方も、参加できなかった方も、ぜひご参加ください。 zozotech-inc.connpass.com その他 GitHub × ZOZOTOWNコラボレーション 11月22日(金)12:00より、GitHubとZOZOTOWNのコラボレーションアイテムの受注販売が始まりました。GitHubに関するアイテムは GitHub公式のThe GitHub Shop でも購入できますが、本コラボレーションのアイテムはZOZOTOWNにて期間限定で販売されます。ぜひ 本コラボレーションの特設ページ をご覧ください。 🚨告知🚨 #GitHub × ZOZOTOWN コラボ 世界で最も広く採用されている AIが支援する開発者プラットフォーム 「GitHub」とのコラボレーションがついに...! 11/22㈮正午から発売スタート! お楽しみに👀 ▼特設ページURLはこちら https://t.co/uXzFmrtO36 pic.twitter.com/DVKS7GZMfv — ZOZOTOWN (@zozojp) 2024年11月21日 GitHub x ZOZOTOWNコラボに込めた想いを動画にしました🎥 どうぞご覧ください👇(字幕付き) pic.twitter.com/bz0DTPhbcZ — GitHub Japan (@GitHubJapan) 2024年11月26日 github.blog corp.zozo.com また、このコラボレーションにあわせて GitHub Universe Recap 東京 にてGitHub × ZOZOTOWNのコラボアイテムを展示するブースを設けました。 GitHub × ZOZOTOWN ブース イベント前日までに売り切れてしまい展示のみになったキーキャップセット 私も終日ブースに立っていましたが、まるでショップ店員になった気分でした。とても多くの方にお立ち寄りいただき、ありがとうございました! 受注期間は12月13日(金)11:59までの期間限定です。ぜひこの機会にお求めください! 現場からは以上です! ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com
はじめに 技術評論社様より発刊されている Software Design の2024年5月号より「レガシーシステム攻略のプロセス」と題した全8回の連載が始まりました。 ZOZOTOWNのリプレイスにあたり、検索機能に特化したマイクロサービスが構築されました。複雑に絡み合った既存機能からリプレイスすべき部分を見極め、どのように作業が進められたのでしょうか。その過程と成果を紹介します。 目次 はじめに 目次 はじめに Elasticsearchへのリプレイス 初期の検索機能 Elasticsearchへのリプレイス リプレイスの動機と技術選定 リプレイスに向けた検討事項 検索精度向上の取り組み Elasticserch導入後の課題と対処 リプレイスによる利点 検索機能の参照先をElasticsearchに一本化 課題 新インデクシングシステムの概要 データ基盤の整備 レガシーからモダンへ 検索機能に特化したマイクロサービスの構築 Web機能のリプレイス 既存システムが抱える課題 リプレイス後のアーキテクチャ リプレイス作業 1. 事前調査 2. PoC(Proof of Concept) 3. Step1:小規模なリプレイス 4. Step2:検索結果一覧機能のリプレイス リプレイスの過程で遭遇した困難 作業見積もりの難しさ 不要な機能の削除 二重開発の調整 膨大なビジネスロジック 極力リスクを取らないリリース リプレイスのバックエンド開発で得た教訓 専門チームによるリプレイスをすべき 段階的なリプレイスをすべき おわりに はじめに 本記事は連載の第7回としてZOZOTOWN検索機能のリプレイスについて紹介します。 ZOZOTOWNは2004年のサービス開始以降、IISとClassic ASP(VBScript)、SQL Serverを用いたモノリシックな構成で稼働してきました。初期の検索機能では検索エンジンとしてSQL Serverを採用していましたが、検索機能に対してより柔軟で高いパフォーマンスを求めElasticsearchへリプレイスしました。また検索機能に対する多様な要望とより高い検索精度を実現するため、モノリシックなシステムから機能を切り出し、検索に特化したマイクロサービスを構築しました。さらにWebサイト(IIS+Classic ASP)の検索機能はBFFとマイクロサービスの構成へリプレイスしました(図1)。 図1 検索機能のリプレイス年表 これらのリプレイスで解決した課題や得られた知見を順に紹介します。 Elasticsearchへのリプレイス 初期の検索機能 初期のZOZOTOWNの検索機能は、RDB(SQL Server)と全文検索エンジンであるGSA(Google Search Appliance)で構築されていました。GSAはキーワード検索に使用し、ヒットした商品IDを返却します。そのIDを基にRDBから商品情報を取得し、検索結果として表示していました。GSAの検索結果に対して再度RDBから商品情報を取得していた理由は、インデックス更新によるタイムラグを避け、在庫情報や価格情報などを最新の情報で検索結果として表示するためです。 Elasticsearchへのリプレイス リプレイスの動機と技術選定 2017年、GSAのサポート終了に伴い、RDBとElasticsearchの併用にリプレイスしました。選定の理由は次の2点です。 広範なコミュニティサポートと豊富なドキュメントを持つこと マネージドサービスを提供していること Elasticsearchは広範なコミュニティサポートがあり、問題解決や新しい機能の導入時に迅速に対応できる環境が整っています。また、ドキュメントが豊富で、導入や運用において詳細なガイドラインやベストプラクティスを参照できます。 さらに、Elasticsearchのマネージドサービス「Elastic Cloud」では、マルチAvailability Zone構成をサポートしているため、高可用性と災害復旧の対策が施されています。Elastic社から24時間365日のプラチナサポートが利用可能な点も魅力で、保守管理の負担を軽減できると考えました。 リプレイスに向けた検討事項 Elasticsearchへのリプレイスに向けた具体的な検討を開始しました。既存システムとの整合性を維持しつつ、新しい技術に適応するため、次のステップを実施しました。 Elasticsearchのスキーマ定義の設計 表記揺れ対策やシノニム(同義語)などの日本語処理 検索ログを用いたパフォーマンス試験の目標値設定とキーワードの傾向分析 リプレイス前後の検索結果比較の環境準備と実施 パフォーマンス、バックアップ、メンテナンス時間などの非機能要件、移行計画、運用保守体制の整備 検索精度向上の取り組み Elasticsearchの検索精度向上のために、Mappingの検討、Analyzerの選定、辞書登録を行いました。Mappingはドキュメント内の各フィールドのデータ構造やデータ型を定義するものです *1 。言語処理や検索の方法を細かく制御するために、フィールドごとに適切なAnalyzer(データをどう分割・処理するか)を選定しました。また検索時の表記揺れや同義語の対応のため辞書登録(シノニムやカスタム辞書)を行い精度の高い検索を目指しました。 精度を担保するためリプレイス前後の検索結果を比較し、悪化したキーワードについては、原因調査と改善を繰り返し検索精度の向上に取り組みました。 Elasticserch導入後の課題と対処 Elasticsearch導入後、2つの課題がありました。 1つ目は、バージョン更新対応に伴う作業の多さです。Elasticsearchは頻繁に新しいバージョンがリリースされます。これに対応するためには、既存の設定やプラグインの互換性を確認し、システム全体のテストが必要です。テストを実施する際には、バージョンアップ後の新しいクラスタを準備し、現行クラスタと新クラスタそれぞれのindexを同じ頻度で更新することで、同一の環境を構築し、リグレッションテストを行う方針を取りました。また、突発的な対応が難しいため、EOLから逆算してバージョンアップ計画を立て、必要な工数を見込んだうえで関係者とスケジュール調整を行っています。 2つ目は、シノニム定義とカスタム辞書のindex反映タイミングの課題です。運用中のindexにシノニム定義やカスタム辞書を反映させるためには、indexを再作成し、データを再投入する必要があります。この作業は検索精度に影響を与えるため、慎重に対応する必要がありました。search側ではシノニム定義を即時に反映できる一方、index側への反映は時間がかかるため、リアルタイムでの対応が困難でした。また、indexの再作成は運用負荷が高いため、現在は更新を控えています。その代わりに、シノニムやカスタム辞書を補うため、クエリの意図解釈を用いたしくみを導入し、対応を進めています *2 。 リプレイスによる利点 Elasticsearchへのリプレイスにより、次のような利点が得られました。 クラウドサービスを活用したインフラサポートの充実 スケーリングの柔軟性と効率化 まず、スケーリングやバックアップ、セキュリティパッチの適用などのインフラ運用に関わる重要なタスクがクラウドサービス側で自動化され、チームはアプリケーション開発に集中できるようになりました。また24時間体制のサポートが受けられ、障害時の対応を迅速に行える環境にもなりました。 加えて、Elasticsearchのクラスタは負荷状況に応じて柔軟にスケールを変更できるため、安定したパフォーマンスを維持できます。これにより、リソースの無駄を最小限に抑え、運用コストの削減にも成功しました。 検索機能の参照先をElasticsearchに一本化 2020年に、RDBとElasticsearchの併用からElasticsearchへの完全移行を進めました *3 。この移行の背景には、検索機能のパーソナライズ化を進める全社方針が確定していたことが挙げられます。 それまでZOZOTOWNでは、表示順として「人気順」を用い、データを集計して算出されたスコアを全ユーザーに対して一律に提供していました。しかし、ファッションは年代や性別に加えて、個人の趣向によっても好みが大きく異なる分野であるため、パーソナライズされた検索の実現が求められました。そこで、RDBから検索に特化したElasticsearchへ置き換えることによって、検索のパーソナライズ化を実現することとしました。 課題 既存のシステムには次の2つの課題がありました。 リードタイムの短縮 データの柔軟な受け入れ 既存のElasticsearchを用いた検索では、キーワードにマッチした商品IDのみを取得し、それをキーにRDBから最新の情報を引き当てていました。したがって、品切れや価格等のクリティカルな情報はRDB側で補完できたため、情報の更新は定期的なバッチで行っていました。一方で、RDBの併用を廃止しElasticsearchのみでの検索を実現するにあたり、RDB内マスタテーブルの更新からElasticsearchへ変更を反映するまでのリードタイムに制約が設けられました。この制約を満たすためには、既存のバッチのしくみでは対処が難しく、これに耐え得る新しいしくみが必要でした。 また、パーソナライズ化を進めるにあたり、機械学習に基づくデータやデータ分析に基づく商品特徴量など、複数チームがデータ追加の作業に関与することが想定されました。そのため、どのチームからでも任意のデータを受け入れられるようにして、柔軟に検索可能な状態を目指しました。 新インデクシングシステムの概要 データベースの変更を追跡し、Elasticsearchに反映するためにCDC(Change Data Capture)を使用しました。この機能はテーブル単位の設定が可能で、レコードの追加・変更・削除やテーブルに対するカラム追加・削除の履歴を取得できます。しかし、CDCだけでは変更されたレコードの情報しかなく、検索可能なデータにするためには別のテーブルと結合する必要があります。 当社では以前からDWH(Data Ware House)としてBigQueryを使用しており、本システムに要求される大規模データをすばやく処理するという要件に適していました。そのため、BigQuery上に構築されたデータ基盤のCDCを参照し、そこから得られた情報と、ある時点のスナップショットのテーブルを結合し、最新の情報を構築しています。定期的なデータ更新はGoogle CloudのApp Engine上でバッチ処理として行われ、Elasticsearchに反映されます。 このシステムを用いることで、商品がマスタテーブルに登録されてから、もしくは売り切れ等でステータスが変わった場合でも、平常時約10分以下のリードタイムで商品情報に反映することが可能となりました。 データ基盤の整備 BigQueryに構築されたデータ基盤にリアルタイムでDBデータを連携するしくみについて説明します。 従来のデータ基盤では1日1回の頻度でRDBからデータを抽出し、BigQueryにロードしていました。しかし、このデータをインデックス作成に使うと1日もの遅延が発生してしまうため、そのままでは使用できません。そのため、SQL ServerのChange Tracking機能を使い、変更の発生した行の差分情報のみを高速にBigQueryにロードするシステムを構築しました。なお、Change TrackingはCDCに似てはいるものの厳密にはCDCとは異なる機能ですが、説明の簡素化のためにここでは同一機能として扱います。 差分情報は最近変更のあった行の情報のみしか保持していないため、従来の日次でデータ連携しているデータとマージすることでRDBと同等の情報をBigQueryで再構成しています。当初はこの部分に有料のデータインテグレーションツールを使用していましたが、パフォーマンスが良くない・ソースコードが読めないことによりトラブルシューティングに限界があるなどの問題点があったため、OSSベースのシステムを構築し直しました *4 。OSSのデータコレクターとして有名なFluentd *5 を使いSQL ServerからCDCデータを読み出しています。 この用途で使用できるインプットプラグインは存在しなかったので、自社でインプットプラグインの開発も行いました。インプットプラグインが読み出したCDCデータはFluentdのPub/Subアウトプットプラグインに渡され、その後Pub/Sub→Dataflow→BigQueryという流れでストリーミングインサートされます。RDBでデータの変更が発生してから平均1分以内にそのデータがBigQueryで利用可能になります。 当初は検索のインデクシングのために構築したシステムでしたが、その便利さからか現在では検索以外のZOZOTOWNの機能を裏側から支えることも多くなりました。 レガシーからモダンへ 検索機能に特化したマイクロサービスの構築 RDB(SQL Server)とElasticsearchを併用していた2019年に、VBScriptで実装されていた検索機能は、ZOZOTOWNの参照系機能を提供するモノリスAPIとしてJavaへ一度リプレイスされました。その後、2020年にマイクロサービス化しながらリプレイスする全体方針に変更となり、検索機能のAPIを分離すると同時に、Elasticsearchに一本化し、検索専用のマイクロサービスを構築しました *6 。これは、2つの課題を解決することが目的でした。 1つは、複数チームで1つのコードベースのJava APIを開発しているため、リリースタイミングが調整しにくく、開発生産性が低下していたことです。もう1つは、異なる役割のAPIが同じコードベースに含まれ、トラフィック量にばらつきが生じていたことです。Java APIはSQL Serverを参照し商品やブランド・ショップなどの情報を取得しており、一方で検索機能のAPIはElasitcsearchを参照していました。 Web機能のリプレイス 連載第6回(本誌2024年10月号)で紹介しているBFF(Backends For Frontend)の実装と、Web機能に先行してZOZOTOWNホーム画面のリプレイスがリリース *7 されたことで、Web機能においてもモノリシックなシステムから脱却する道筋ができました。そのため2024年にWebの新品・古着の検索機能についても同様のアーキテクチャをベースにリプレイスを実施しました。 具体的には、Classic ASPでVBScriptを使って実装された検索機能の処理をフロントエンドとバックエンドで分割しました。フロントエンドはReact、バックエンドはNext.js+BFF+マイクロサービス+S3で再実装することで、開発サイクルを分離し、それぞれでデプロイやスケーリングできるようにしました。なお本記事ではバックエンド(マイクロサービス)のリプレイスを中心に紹介します。 既存システムが抱える課題 サービス開始から約20年の歴史を持つプログラムは、長年の機能改修と多数の開発者が関わったことで複雑になり、ほかの機能との依存関係も複雑になっていました。とくにスコープの広い変数やセッションを多用しているロジックの理解には時間がかかり、開発スピードの低下を招くことがありました。当時の設計意図や背景を知らない新しいメンバーにとっては、処理内容が不明瞭で、修正箇所の影響範囲も不透明なため、プログラムの改修や保守に苦労していました。 リプレイスにおける課題の1つは、担当メンバーの多くが既存のアーキテクチャやVBScriptに精通していなかったことです。また、リプレイス後の新しいアーキテクチャについても、経験が不足していました。さらに、ZOZOTOWNのサービスを停止せずに、膨大な検索パターンや多種多様な検索機能をリプレイスする必要がありました。 リプレイス後のアーキテクチャ リプレイス前のWebサーバには、検索条件の構築や検索結果描画用のHTML/JSONの生成をはじめ、リダイレクト処理、メタ情報の構築、関連コンテンツの取得など、さまざまな機能が実装されていました。リプレイス後のアーキテクチャでは、新規のマイクロサービスは構築せず、既存のマイクロサービスにエンドポイントを追加し、VBScriptで実装されていた処理を移行しました。また、Webサーバ上で参照していた設定ファイルについては、更新頻度を調査し、更新頻度が低いものはBFFに内包し、高いものはS3に配置するなど、適切な配置を行いました(図2)。 図2 検索機能のアーキテクチャ遷移 リプレイス作業 マイクロサービスへのリプレイスは、次の流れで実施しました。 事前調査 PoC(Proof of Concept) Step1:小規模なリプレイス Step2:検索結果一覧機能のリプレイス このリプレイスは非常に大規模であると予想されたため、リプレイスに専念できる体制と工期を確保しました。また、既存機能に改修が入ることで、既存システムとリプレイス後のシステムの両方に改修を加える必要が生じないよう、関係各所と事前に案件を調整しました。 1. 事前調査 リプレイス後のアーキテクチャの概観は見えていたため、まずはそのアーキテクチャに対してフィット&ギャップ分析を行い、既存の検索機能の要件が満たせない部分を調査しました。検索機能特有の処理を実装する必要があったため、設計意図や意思決定の経緯を記録するためにADR(Architecture Decision Record)を作成しました。 既存機能を分解するため、まずは既存機能の概要図と機能一覧を作成し、全体像を把握しました。より詳細に把握するため、プログラムを125個の処理単位に分類し、それぞれの実装内容を分析しました。これにより、リプレイス先の候補を選定し、内部の依存関係やロジックの複雑さを理解できました。また、開発メンバーと一緒に分析することで、複雑なロジックに対して理解が浅い部分を補い、プロジェクト全体の理解度を向上させることができました。 2. PoC(Proof of Concept) パフォーマンス要件に対する懸念があったため、リプレイス後のアーキテクチャに最低限の機能を実装し、負荷検証を行いました。これは、規模が大きく不確実性の高い状況で手戻りを防ぎ、プロジェクトのリスクを軽減するための有効な手段でした。また、リプレイス後の新しいアーキテクチャの開発プロセスと技術スタックを経験する貴重な機会となりました。 3. Step1:小規模なリプレイス 検索機能のビッグバンリプレイスを防ぐため、検索機能のリプレイスを2つのステップに分けて実施しました。まず、ユーザーへの影響を最小限に抑えるため、利用頻度が比較的少ない画面からリプレイスを始めました。PoCを経験していたため、このステップではスムーズに開発を進めることができました。またStep2に備え開発方針の整備とレビュー体制の見直しを行いました。 4. Step2:検索結果一覧機能のリプレイス ZOZOTOWNの検索結果一覧画面は機能が多く、リプレイスの規模が大きいため、作業を分割して進めることを検討しました。しかし、画面全体の依存関係が複雑で分割できませんでした。また従来の検索チームだけではリソース不足だったことから、中途採用や協力会社をチームに迎え入れ、開発をスタートしました。 リプレイスの過程で遭遇した困難 作業見積もりの難しさ 通常のシステム開発では、要件定義、設計、実装、テストと進めるのが一般的ですが、リプレイスでは既存システムと同等の要件を満たしつつ、リプレイス先のマイクロサービスアーキテクチャに合わせて設計する必要があります。このため、既存の要件を新しいアーキテクチャに適応させる作業が求められました。 長年にわたり進化してきたプログラムは、設計書が少なく、検索機能以外の機能とも複雑に絡み合っています。そのため、すべてを隅々まで把握するのは困難でした。とくに、検索機能が動作する過程でほかの機能も通過するため、本来リプレイスすべき部分と不要な部分を見極める必要がありました。単純な条件分岐はわかりやすいですが、共通処理として実装された機能で複数の条件が含まれる場合は、注意が必要です。 既存の処理を1つずつ詳細に調査し、リプレイスにかかる開発工数を見積もるのは時間がかかり過ぎるため現実的ではありませんでした。そこで、まずは検索結果一覧画面を6つのコンテンツに分けて作業範囲を明確にしました。コンテンツ単位でできる限り詳細に見積もり、複雑な処理や不確実な部分については課題として認識し、バッファを用意しました。それでも、実装完了までに複数回のスケジュール見直しが必要でした。 不要な機能の削除 検索機能の中には、利用されていない処理が多数存在していました。将来の利用可能性が低いレガシーな部分については、PMや関係者と相談のうえ、積極的にリプレイス作業から除外しました。 検索機能には、URL形式やカテゴリの構造変更に伴い互換性を維持するために多くのリダイレクトが実装されています。長年の運用の中で、リクエストされていないURL形式やクエリパラメータが増えていきました。こうしたケースでは、リクエスト状況を1つずつ確認し、リクエストが極端に少ないものについてはリプレイスしない判断をしました。 二重開発の調整 リプレイス作業に集中するためには、機能開発の一時停止やコードフリーズが理想です。しかし「事業を止めない」というポリシーに従い、優先度の高いビジネス要求については既存システムの改修を行い、その内容をリプレイス側に取り込むことにしました。 リプレイスの進行状況は全社に周知されていたため、検索機能のリプレイスが進行中であることを説明し、対応時期を調整できる機能開発は後回しにしました。これにより、できる限り二重開発を避けるよう努めました。 膨大なビジネスロジック まず、6つに分けたコンテンツごとにBFFと各マイクロサービスのAPI I/F(インターフェース)の設計に取り掛かりました。モノリシックなシステムを、まったく異なる設計思想を持つマイクロサービスとして再構築するには、検索機能に関する深いドメイン知識と、BFFおよび各マイクロサービスの知見が不可欠でした。とくにHTML組み立てに必要な情報が欠落するとバグに直結するため、フロントエンド開発者と密に連携し、API I/Fの各項目を丁寧に決定していきました。また、複雑な描画パターンを持つコンテンツに関しては、処理を整理し、描画パターンを設計書にまとめることで、フロントエンドとの意思疎通を図りました。 モノリシックなシステム内で複雑に絡み合ったVBScriptは、開発するうえでの高いハードルとなりましたが、これを乗り越えるために、デイリーミーティングで課題を共有・相談し、停滞を防ぎ早期解決を目指しました。 テスト工程では、テストの役割を整理し再定義しました。CIに組み込まれた単体テストに加えて、Karate注8を利用したエンドポイントテストを導入し、手動テストを自動化することでデグレを防ぎ、APIの品質を担保しました。結合テストでは、品質管理部門のQAチームと開発チームが協力して、ホワイトボックステストとブラックボックステストを本番同等のパイロット環境で実施し品質を確保しました。 極力リスクを取らないリリース 本番リリース時にはサイトの停止を行わず、ユーザーを既存システムとリプレイス後のシステムに段階的に振り分けるn%リリースを実施しました。1%、20%、50%、100%と徐々にユーザーの割合を増やす計画を立て、その過程で致命的な不具合が見つかった場合には、すぐに0%に戻す対応を取りました。また、事業への影響を最小限に抑えるため、大規模なセールなどのイベント開催時にはn%リリースを一時中断し、すべてのユーザーを既存システムに戻して安全にイベントを進めました。 リプレイスのバックエンド開発で得た教訓 専門チームによるリプレイスをすべき まずマイクロサービスアーキテクチャでは各サービスと結び付いたチームの責務範囲でシステムを構築するため、開発組織がその体制を受け入れられるかどうかはとても重要です。弊社ではマイクロサービスを意識した組織設計がされており、検索機能を専門とする検索チームが主軸になり集中してリプレイスすることで、難易度が高いリプレイスに取り組みました。 段階的なリプレイスをすべき 大規模でモノリシックなレガシーシステムを、設計思想がまったく違うマイクロサービスアーキテクチャで再実装することは、難易度が高く膨大な工数を伴います。そのためZOZOTOWNのような大規模システムのリプレイスにおいては、できる限り細かな単位でリプレイスするアプローチはとくに重要です。 残念ながら、検索機能のリプレイスにおいては段階的なアプローチが十分に取れず、リプレイス作業を2段階に分けるにとどまりました。とくに、Step2のリプレイスは大規模な作業となりました。振り返ると、段階的リプレイスの議論に十分な時間を確保できなかった点は反省点です。 おわりに 検索機能をよりモダンでスケーラブルなアーキテクチャへと進化させるために、機能全体をリプレイスしました。この取り組みにより、パフォーマンスの向上や機能拡張が容易になるなどの成果を上げることができました。リプレイス作業はまだ続きますが、筆者たちの経験が同様の課題に直面している方々の参考になれば幸いです。 本記事は、技術本部 データシステム部 検索技術ブロック ブロック長の可児 友裕と同 検索基盤ブロック ブロック長の渡 雄一郎、そして同 データ基盤ブロック ブロック長 テックリードの塩崎 健弘によって執筆されました。 本記事の初出は、 Software Design 2024年11月号 連載「レガシーシステム攻略のプロセス」の第7回「検索機能リプレイスの裏側」です。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com *1 : Elasticsearchで日本語検索を扱うためのマッピング定義 *2 : ZOZOTOWNのクエリ解釈機能の改善に向けたAPIリプレイスの取り組み *3 : ZOZOTOWNの検索基盤におけるElasticsearch移行で得た知見 *4 : ZOZOTOWNを支えるリアルタイムデータ連携基盤 *5 : https://www.fluentd.org/ *6 : ZOZOTOWN検索機能のマイクロサービス化への取り組み *7 : ZOZOTOWNのWebホーム画面をNext.jsでリプレイスして得た知見
はじめに こんにちは、SRE部プラットフォームSREブロックの石田です。普段はZOZOTOWNのSREを担当しています。 Amazon Aurora MySQL(以降、Aurora MySQL)のv2系の標準サポートが2024年10月31日に終了しました。私たちのチームではZOZOTOWNのID基盤で使用するAurora MySQLをv2系からv3系へアップグレードしました。ユーザ影響を抑えたアップグレードの実現のため、Amazon Aurora Blue/Green Deployments(以降、Blue/Green Deployment)を社内で初めて採用しました。 本記事では、Blue/Green Deploymentを採用する上で直面した課題や、検証方法について具体的な内容をご紹介します。なお、本記事の内容は2024年10月9日時点の情報に基づいています。 目次 はじめに 目次 Amazon Aurora Blue/Green Deployments とは 背景・課題 アップグレードバージョンの決定 Blue/Green Deploymentによるアップグレード検証 アップグレード手順の検証 1. 検証用のAuroraクラスター作成 注意点 2. 新バージョンのDBパラメータグループ作成 3. Blue/Green Deploymentによるアップグレードを実施 4. 再びCloudFormationの管理化 注意点 5. 切り戻し 注意点 API利用確認 検証環境のアップグレード 検証結果 本番環境のアップグレード 注意点 まとめ Amazon Aurora Blue/Green Deployments とは この機能では、稼働中の環境(Blue環境)をもとに論理レプリケーションされた環境(Green環境)を作成できます。これにより稼働中の環境には影響を与えずに、Green環境でデータベースのバージョンアップやパラメータの変更が可能です。準備が整えば、Green環境を新しい稼働環境として昇格させることで、通常1分未満のダウンタイムで安全かつスムーズに移行できます。詳細な仕様や制約、考慮事項については、AWSの 公式ドキュメント をご確認ください。 背景・課題 Aurora MySQLのマイナーアップグレードにおいてはIn-placeアップグレードを採用していました。その際のダウンタイムは1分程度でしたが、今回のメジャーアップグレードにおいて検証した結果、10分程度のダウンタイムが発生する可能性を確認しました。ZOZOTOWNの本番環境において、ID基盤の機能が10分も停止することは、ユーザに大きな影響を与えることになります。そのため、ダウンタイムを最小限に抑える手段としてBlue/Green Deploymentの採用を検討しました。 しかし、Blue/Green Deploymentは 制限事項 に記載されている通り、CloudFormationに対応していません。このため、CloudFormationで管理しているAurora MySQLのバージョン管理に課題がありました。そこで、Blue/Green Deploymentの検証を通して、CloudFormationによるバージョン管理についても確認することにしました。 アップグレードバージョンの決定 はじめに、Aurora MySQL v2系からv3系にアップグレードするバージョンを決定します。ID基盤のAurora MySQLは 5.7.mysql_aurora.2.11.5 のバージョンを使用しており、アップグレードできるバージョンを下記コマンドで確認します。 - > % aws rds describe-db-engine-versions --engine aurora-mysql --engine-version 5 . 7 .mysql_aurora. 2 . 11 . 5 --query ' DBEngineVersions[].ValidUpgradeTarget[].EngineVersion ' [ " 5.7.mysql_aurora.2.11.6 " , " 5.7.mysql_aurora.2.12.0 " , " 5.7.mysql_aurora.2.12.1 " , " 5.7.mysql_aurora.2.12.2 " , " 5.7.mysql_aurora.2.12.3 " , " 8.0.mysql_aurora.3.03.1 " , " 8.0.mysql_aurora.3.03.2 " , " 8.0.mysql_aurora.3.03.3 " , " 8.0.mysql_aurora.3.04.0 " , " 8.0.mysql_aurora.3.04.1 " , " 8.0.mysql_aurora.3.04.2 " , " 8.0.mysql_aurora.3.04.3 " , " 8.0.mysql_aurora.3.05.2 " , " 8.0.mysql_aurora.3.06.0 " , " 8.0.mysql_aurora.3.06.1 " , " 8.0.mysql_aurora.3.07.1 " ] 弊チームでは長期サポート(LTS)が提供されるバージョンの採用を方針としています。当時のLTSバージョンである 3.04.* の中から、最新の 8.0.mysql_aurora.3.04.3 を選定しました。最新のLTSバージョンについてはAWSの 公式ドキュメント をご確認ください。 なお、以前は 2.07.9 を使用していましたが、このバージョンから直接v3系へのアップグレードはできませんでした。そのため、まず 2.11.5 へマイナーバージョンアップを実施し、その後に今回のv3系へのアップグレードを行っています。このように、直接アップグレードができない場合もあるため、事前にアップグレード可能なバージョンを確認しておくことが重要です。サポート期限が迫る前に計画的に対応することで、余裕を持ったアップグレードが可能になります。 Blue/Green Deploymentによるアップグレード検証 ここからはBlue/Green Deploymentによるアップグレード検証の手順をご紹介します。 アップグレード手順の検証 まずはアップグレードの手順を検証するため、CloudFormationで検証用のAuroraクラスターを作成し、想定通りにアップグレードが行えるかを確認します。また、切り戻しが可能かどうかも併せて確認します。 1. 検証用のAuroraクラスター作成 開発環境で使用しているCloudFromationテンプレートを参考にして、Aurora MySQL v2系のクラスターを作成します。下記に作成したテンプレートを記載します。必要なパラメータがわかりやすいように関連のないParametersやResourcesの記載は省略しています。 --- AWSTemplateFormatVersion : "2010-09-09" Description : "[upgrade-test] Aurora" Parameters : GlobalPrefix : Type : String Default : upgrade-test GlobalEnvironment : Type : String Default : dev ServiceName : Type : String Default : upgrade-test RDSEngine : Type : String Default : aurora-mysql RDSEngineVersion : Type : String Default : 5.7.mysql_aurora.2.11.5 RDSParameterGroupFamily : Type : String Default : aurora-mysql5.7 Resources : DBClusterParameterGroupZozoID : Type : "AWS::RDS::DBClusterParameterGroup" Properties : Description : !Sub "cluster parameter group for ${ServiceName}" Family : !Ref RDSParameterGroupFamily Parameters : binlog_format : ROW DBParameterGroupZozoID : Type : "AWS::RDS::DBParameterGroup" Properties : Description : !Sub "parameter group for ${ServiceName}" Family : !Ref RDSParameterGroupFamily DBClusterZozoID : Type : "AWS::RDS::DBCluster" Properties : DBClusterIdentifier : !Sub ${GlobalEnvironment}-${GlobalPrefix}-${ServiceName} DatabaseName : !Ref DBDatabaseName DBClusterParameterGroupName : !Ref DBClusterParameterGroupZozoID Engine : !Ref RDSEngine EngineVersion : !Ref RDSEngineVersion DBInstanceZozoID : Type : "AWS::RDS::DBInstance" Properties : DBClusterIdentifier : !Ref DBClusterZozoID DBInstanceClass : !Ref Instance1DBClass DBParameterGroupName : !Ref DBParameterGroupZozoID Engine : !Ref RDSEngine DBInstanceZozoIDSecond : Type : "AWS::RDS::DBInstance" Properties : DBClusterIdentifier : !Ref DBClusterZozoID DBInstanceClass : !Ref Instance2DBClass DBParameterGroupName : !Ref DBParameterGroupZozoID Engine : !Ref RDSEngine 注意点 Blue/Green DeploymentではBlue環境からGreen環境にレプリケーションするため、バイナリログを使用しています。そのため、実際の稼働環境では、事前にクラスターのDBパラメータグループのバイナリログ(binlog_format)を有効にする必要がある点ご注意ください。なお、複製の不整合のリスクを減らすために ROW の設定が推奨されています。詳細はAWSの 公式ドキュメント をご確認ください。 2. 新バージョンのDBパラメータグループ作成 1で作成したテンプレートに下記を追加して、Aurora MySQL v3系に対応したDBパラメータグループを作成します。作成したDBパラメータグループは次の手順で使用します。 --- AWSTemplateFormatVersion : "2010-09-09" Description : "[upgrade-test] Aurora" Parameters : ServiceName : Type : String Default : upgrade-test RDSParameterGroupFamilyMySQL80 : Type : String Default : aurora-mysql8.0 Resources : DBClusterParameterGroupMySql80ZozoID : Type : "AWS::RDS::DBClusterParameterGroup" Properties : Description : !Sub "cluster parameter group for ${ServiceName}" Family : !Ref RDSParameterGroupFamilyMySQL80 Parameters : binlog_format : ROW DBParameterGroupMySql80ZozoID : Type : "AWS::RDS::DBParameterGroup" Properties : Description : !Sub "parameter group for ${ServiceName}" Family : !Ref RDSParameterGroupFamilyMySQL80 3. Blue/Green Deploymentによるアップグレードを実施 前述した通りBlue/Green DeploymentはCloudFormationに対応していないため手動で作成します。Blue/Green Deploymentを作成する際、Green環境は 8.0.mysql_aurora.3.04.3 のバージョンで作成します。DBパラメータグループは2で作成したものを指定します。作成方法はAWSの 公式ドキュメント をご確認ください。 Blue/Green Deploymentが作成されると 8.0.mysql_aurora.3.04.3 の新バージョンでGreen環境が作成されます。 切り替えを実行し、新バージョンに切り替わることを確認します。 4. 再びCloudFormationの管理化 アップグレード後、CloudFormationで管理しているリソースに差分が生じるため、テンプレートを修正して再適用します。ただし、本来CloudFormation管理下のリソースは、スタック外で変更することを推奨しておらず、CloudFormationとして動作を保証していない点ご留意ください。詳細はAWSの 公式ドキュメント をご確認ください。 以下に修正後のテンプレートを記載します。変更した箇所にコメントを追加しています。 --- AWSTemplateFormatVersion : "2010-09-09" Description : "[upgrade-test] Aurora" Parameters : GlobalPrefix : Type : String Default : upgrade-test GlobalEnvironment : Type : String Default : dev ServiceName : Type : String Default : upgrade-test RDSEngine : Type : String Default : aurora-mysql RDSEngineVersion : Type : String Default : 8.0.mysql_aurora.3.04.3 # 新バージョンに変更 RDSParameterGroupFamilyMySQL80 : Type : String Default : aurora-mysql8.0 Resources : DBClusterParameterGroupMySql80ZozoID : Type : "AWS::RDS::DBClusterParameterGroup" Properties : Description : !Sub "cluster parameter group for ${ServiceName}" Family : !Ref RDSParameterGroupFamilyMySQL80 Parameters : binlog_format : ROW DBParameterGroupMySql80ZozoID : Type : "AWS::RDS::DBParameterGroup" Properties : Description : !Sub "parameter group for ${ServiceName}" Family : !Ref RDSParameterGroupFamilyMySQL80 DBClusterZozoID : Type : "AWS::RDS::DBCluster" Properties : DBClusterIdentifier : !Sub ${GlobalEnvironment}-${GlobalPrefix}-${ServiceName} DatabaseName : !Ref DBDatabaseName DBClusterParameterGroupName : !Ref DBClusterParameterGroupMySql80ZozoID # 新しいDBパラメータグループに変更 Engine : !Ref RDSEngine EngineVersion : !Ref RDSEngineVersion DBInstanceZozoID : Type : "AWS::RDS::DBInstance" Properties : DBClusterIdentifier : !Ref DBClusterZozoID DBInstanceClass : !Ref Instance1DBClass DBParameterGroupName : !Ref DBParameterGroupMySql80ZozoID # 新しいDBパラメータグループに変更 Engine : !Ref RDSEngine DBInstanceZozoIDSecond : Type : "AWS::RDS::DBInstance" Properties : DBClusterIdentifier : !Ref DBClusterZozoID DBInstanceClass : !Ref Instance2DBClass DBParameterGroupName : !Ref DBParameterGroupMySql80ZozoID # 新しいDBパラメータグループに変更 Engine : !Ref RDSEngine 変更セットを作成し、CloudFormationのコンソールよりプロパティレベルの変更の詳細を確認すると以下のような差分が確認できます。 クラスターのプロパティレベルの変更の詳細 インスタンスのプロパティレベルの変更の詳細 CloudFormation上は差分がある状態ですが、このテンプレートを適用することで問題なくCloudFormation管理下に戻すことができました。 なお、テンプレート適用後、ライターインスタンスのイベントログに Finished updating DB parameter group というメッセージが表示されていました。DBパラメータグループの更新が完了したというメッセージになりますが、このメッセージが出力されたからといって、DBパラメータグループの設定内容が実際に更新されたわけではありませんでした。 AWSサポートに問い合わせたところ、ログが検出された原因の特定はできませんでしたが、現在使用されているDBパラメータグループに再度変更するようなAPI呼び出しは実行可能とのことでした。また、変更前に違うDBパラメータグループが適用されていたことやパラメータが実際に変更されたことを直ちに示すものではないとのことです。ただし、テンプレート適用前後で、DBパラメータグループの設定内容に変更がないか確認しておくと安心です。 注意点 テンプレート適用前にインスタンスのDBパラメータグループのステータスを確認してください。ステータスが pending-reboot の場合はテンプレート適用時に再起動する可能性があります。ステータスが 同期中 であれば再起動が発生することはありません。 5. 切り戻し Blue/Green Deploymentには自動的な切り戻し機能はありません。Blue/Green Deployment作成時にはBlue環境からGreen環境にレプリケーションされますが、切り替えたタイミングでレプリケーションは切断されます。そのため、切り戻しは手動でエンドポイントを操作し、新バージョンのクラスターエンドポイントを別の名前に変更後、旧バージョンのクラスターエンドポイントを元に戻す流れで行います。イメージ図のクラスタ名およびインスタンス名はわかりやすくするために簡略化しています。 Blue/Green Deploymentの削除 まず、Blue/Green Deploymentを削除します。これにより、新バージョンと旧バージョンのクラスターの紐付きが解消されます。 新バージョンのクラスター名を変更 新バージョンのクラスター名を別の名前に変更します。ここでは末尾に -new をつけます。これによりエンドポイントが変更されるため、リクエストが全断します。 新バージョンのインスタンス名を変更 新バージョンのクラスター名の変更が完了したら、新バージョンのインスタンス名も別の名前に変更します。ここでは末尾に -new をつけます。 旧バージョンのインスタンス名を変更 次に、旧バージョンのインスタンス名を正規の名前に変更します。 旧バージョンのクラスター名を変更 最後に、旧バージョンのクラスター名を正規の名前に変更します。この変更が完了するとエンドポイントが元に戻ります。そして、旧バージョンのクラスターに対してリクエストが届くようになります。 こちらの手順で実施し、15分ほどで問題なく切り戻すことができました。 注意点 本番環境での実施時には、アップグレード中に問題が発生した場合、データに不整合が生じているかどうかの懸念があります。例えば、部分的なINSERT処理が失敗するなどの不具合が考えられます。そのため、切り戻し後にはデータの整合性を確認し、補正が必要となる場合があります。 API利用確認 開発環境のアップグレードを実施し、新バージョンのAurora MySQLにてID基盤で使用しているAPIが問題なく動作することを確認します。アップグレードは前述の通りの手順で実施します。結果としてアップグレードは問題なく完了し、ID基盤で使用しているAPIも正常に動作しました。 検証環境のアップグレード 検証環境のアップグレードは、商用相当のリクエスト負荷をかけて実施し、エラー発生状況やダウンタイムを確認します。商用相当のリクエストをかける理由は、 切り替えのベストプラクティス で記載されている通り、切り替え時のダウンタイムはリクエスト量に依存するためです。また、負荷をかけるツールは弊チームで開発したOSSツールであるGatling Operatorを使用しています。詳細は以前TECH BLOGに公開された下記記事をご参照ください。 techblog.zozo.com アップグレードは前述の通りの手順で実施します。結果としては想定通りのエラーが発生し、ダウンタイムとしては1分程度でした。なお、ダウンタイムの計測には、Datadog APMのトレース機能を利用しています。アプリケーションエラーを示す trace.http.request.errors メトリクスを可視化することでエラーが発生していた時間を計測しています。以下はDatadog Dashboard上で可視化したグラフです。これより、エラーの発生時間が1分程度であったことを確認できます。 検証結果 ここまでの検証を通して、以下の点を確認できました。 Blue/Green Deploymentを利用して、Aurora MySQL v2系からv3系へのアップグレードが可能であること v3系からv2系への切り戻しが可能であること アップグレード後、再びCloudFormationによる管理が可能であること v3系において、ID基盤としてのアプリケーションが問題なく動作すること ダウンタイムは1分程度であり、許容範囲内であること 本番環境のアップグレード 本番環境でのアップグレードは、ユーザへの影響を最小限に抑えるため、早朝の時間帯に実施します。アップグレードは前述の通りの手順で実施します。結果として、検証時の予測通りにエラーが発生し、ダウンタイムも約50秒と想定内に収まりました。これにより、問題なくアップグレードを完了できました。 注意点 Blue/Green Deploymentは新しいクラスターに切り替わるため、バイナリログを使用したデータの連携をしている場合は注意が必要です。実際に、ID基盤のデータベースではGCPのDatastreamを介してBigQueryにデータを連携していますが、この点を見落としていました。 公式ドキュメント の コンシューマーの親ノードの更新 の項目では、バイナリログコンシューマーがある場合はレプリケーションの継続性を維持するため、切り替え後に親ノードを更新する必要があると記載されています。今回のケースでは、バイナリログコンシューマーがDatastreamにあたります。 切り替え後のライターインスタンスでは、以下のようなバイナリログファイル名( mysql-bin-changelog.000008 )と位置( 536 )を含む情報がイベントログに出力されます。この情報を使用してDatastreamを更新することでデータ連携を再開できます。 この点を見落としていたため、切り替え後にデータ連携が停止し、復旧対応としてbackfill(全量転送)を実施せざるを得ませんでした。こうした事態を防ぐためにも、事前にバイナリログを利用するコンシューマーの存在を確認し、切り替え後に必要となる対応を計画的に準備しておくことが重要です。 まとめ 本記事では、Blue/Green Deploymentによるアップグレードの検証方法や移行に際して直面したコード管理の課題に対する対応について、具体的な取り組みを紹介しました。 入念なアップグレード検証により、ユーザへの影響を最小限に抑えつつ、確実なアップグレードを実現できました。また、CloudFormationで管理しているAmazon Aurora MySQLのバージョン管理においても、スタック外での変更は推奨されないものの、問題なく継続できることを確認できました。 さらに、この取り組みで得られた知見やノウハウは、社内の別マイクロサービスにおけるAuroraのメジャーアップグレードにも活用され、スムーズな移行を支援する結果につながりました。Blue/Green Deploymentによるアップグレードを検討されている方にとって、この記事が参考になれば幸いです。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 hrmos.co corp.zozo.com
はじめに こんにちは、FAANS部バックエンドブロックでFAANSのバックエンドシステムの開発と運用をしている 田島 です。 2021年11月にZOZOTOWNとアパレルのブランド実店舗をつなぐOMOプラットフォーム「ZOZOMO」が始動しました。FAANSは、ZOZOMOで展開するサービスの1つで、ブランド実店舗で働くショップスタッフ専用の販売サポートツールです。FAANSは2022年8月の正式版リリース以来、これまで様々な機能をリリースしてきました。以下はその一部です。 投稿機能 : ショップスタッフが自身で自社のアイテムを着て撮ったコーディネート画像やコーディネート動画といったコンテンツを複数チャネルに同時投稿できる機能。投稿先チャネルとしては、ZOZOTOWNやWEAR、Yahoo!ショッピングといった弊社並びに弊社のグループ会社のWebサイトに加え、ブランド企業の自社ECサイトへの同時投稿も可能。 成果確認機能 : 投稿機能で投稿したコンテンツがどのくらい閲覧されたのか、それをきっかけにEC上の売上にどのくらい貢献できたか、といった様々な切り口の『成果』をアプリ上で確認できる機能。 顧客直送機能 : ブランド実店舗で欠品している商品の在庫が弊社の物流拠点「ZOZOBASE」にある場合、お客様は店頭で決済し、ZOZOBASEからお客様の自宅へ商品を直送できる機能。 ブランド実店舗の在庫取り置き機能 : ZOZOTOWN上で実店舗の在庫取り置きを希望したお客様への対応を、ショップスタッフがFAANS上の簡単操作で完結できる機能。 FAANSではリリースから1年も経っていない段階でSLO(サービス品質目標)を導入し、ここまで試行錯誤を重ねて運用してきました。本記事ではFAANSにおけるSLO運用の実践とそこから得られた知見について紹介します。 目次 はじめに 目次 1 SLO導入の背景 2 FAANSのシステムとそれを支える開発組織 2.1 システムの特徴 2.2 開発組織の体制 3 最初のゴール設定 3.1 バックエンドシステムのみを対象としたSLOを運用に乗せる 3.2 SLOアラートは導入せず、SLOの達成状況の定期的な確認に留める 4 最初のSLI/SLOの選定 4.1 選定方針 4.1.1 対象システムはWeb APIサーバーのみとする 4.1.2 重要機能の単位とシステムコンポーネントの単位の2軸でSLOを定める 4.1.3 レイテンシーと可用性の2つのSLIを選定する 4.1.4 クラウドインフラのSLA未満の水準のSLOを定める 4.1.5 依存している社内の別プロダクトのサービス品質やSLOは一旦考慮しない 4.2 ステークホルダーとの合意 5 「SLO定例」による信頼性チェック 5.1 直近のリリース内容の確認 5.2 直近のアプリケーションエラーやシステム障害の確認 5.3 SLOの達成状況の確認 5.4 データベースのシステムメトリクスの確認 6 SLO運用開始後の工夫と取り組み 6.1 SLI/SLOの設定変更の記録をADR形式で残す「SLO-DR」 6.2 チームで行う継続的学習「SLO Study」 6.3 フルサイクルエンジニアリングチームへの体制移行 6.4 開発組織への定期報告 7 SLOを導入して得られた効果 7.1 明確な判断基準による会議の充実化 7.2 信頼性維持のためのアクション促進 7.3 他チームへの改善依頼の円滑化 7.4 技術的な意思決定の改善 8 SLOの導入は早ければ早いほどよい 8.1 SLO文化を浸透させるハードルが低い 8.2 SLOはソフトウェア設計の意思決定を支援する 8.3 信頼性は事業にとって最初から重要な指標である おわりに 1 SLO導入の背景 スタートアップ事業として立ち上がったFAANSはサービスローンチに向けた立ち上げフェーズを経て、現在は成長フェーズに移行しています。私達はさらなる顧客価値の提供のために、新機能のリリースや既存機能を強化するためのスピーディーな開発を日々行っています。一方で、FAANSではサービス企画段階から「当たり前品質の追求」を掲げてきました。ブランド実店舗で働くショップスタッフは、忙しい実店舗業務の傍らでFAANSを通じてコーディネート画像を投稿したり、FAANSを使ってお客様を接客したりします。そのため、アパレルブランド企業やそのショップスタッフがFAANSを利用する業務において、不満やストレスを感じることのないよう、サービス品質を維持することは極めて重要です。とはいえ限られたエンジニアリングリソースの中で、機能開発によるさらなる価値提供の取り組みとユーザーが不満を抱かないサービス品質維持の取り組みを両立することは、工夫なしでは実現できません。その両立を図るためにSLOを導入するに至りました。 2 FAANSのシステムとそれを支える開発組織 具体的なSLOの導入について話をする前に、まずはその前提知識となるFAANSのシステムの特徴とFAANSの開発組織の体制を概説します。 2.1 システムの特徴 FAANSのシステムは以下のような特徴を有しています。 FAANSのユーザーが利用するクライアントアプリとしてWeb/iOS/Androidに対応しています。 バックエンドシステムのインフラリソースは全てパブリッククラウド環境に存在します。そのほとんどがGoogle Cloudで構築されており、メール配信処理などで一部AWS(Amazon Web Services)を使用しています。 Web APIサーバーは役割の異なる複数種類のシステムコンポーネントが存在します。それらは全てGKE(Google Kubernetes Engine) *1 の1つのKubernetesクラスタ上でPodとしてサービングされています。以下はその一部です。 FAANSのiOS/Android/Webアプリからのリクエストを直接捌く クライアントアプリ用API 。 他のWeb APIサーバーから非同期でオフロードされたジョブを実行する 非同期ジョブ用API 。 アパレルブランド企業の自社ECサイトのシステムから直接アクセスされる 外部システム連携用API 。 ワークフローエンジンとしてKubernetesネイティブなArgo Workflows *2 を採用しており、Web APIサーバーが稼働するGKEクラスタに相乗りしています。スケジュールやイベント駆動で実行されるバッチ処理は基本的には全てArgo Workflowsのワークフローとして稼働しています。 ユーザーのマスタデータなどが管理されているオンライントランザクション向けのデータベースとしてGoogle CloudのCloud SQL(PostgreSQL) *3 を使用しています。このデータベースはWeb APIサーバーやバッチ処理からアクセスされています。 WEARやFulfillment by ZOZOといった弊社の別プロダクトのWeb APIにアクセスするWeb APIやバッチ処理も多く、社内の別チーム管轄の別プロダクトに依存したプロダクトです。 2.2 開発組織の体制 続いて、FAANSの開発組織についてです。以下はFAANSのプロダクト開発・運用に関わるメンバーが所属するブランドソリューション開発本部の組織図です。 ※引用元スライド: https://speakerdeck.com/zozodevelopers/company-deck 弊社ではチームをブロックという単位で編成しています。この中でFAANSのプロダクト開発と運用に中心的に関わるブロックを簡単に説明します。まず、プロダクト戦略部FAANSプロダクトマネジメントブロックにはFAANSのPO(プロダクトオーナー)やPM(プロジェクトマネージャー)といったロールのメンバーが所属しています。FAANSの開発と運用に携わるエンジニアはFAANS部に所属しています。フロントエンドブロックはFAANSのWeb/iOS/Android用のクライアントアプリの開発を、バックエンドブロックがFAANSのバックエンドシステム全体の開発と運用をそれぞれ担当しています。なお、以前はWEARバックエンド部のSREブロックがFAANSとWEARの2つのプロダクトのSRE業務を兼務していました。しかし、現在ではFAANS部バックエンドブロックにその責務は委譲されています。この体制移行の意思決定については後述の「6.3 フルサイクルエンジニアリングチームへの体制移行」で説明します。また、この図には記載していませんが、別の本部に所属するBizDev職メンバーやQA(品質保証)エンジニアもFAANSのプロダクトの開発や運用に深く関わっています。 3 最初のゴール設定 SLOのコアとなるプロセスは極端に単純化すれば以下に集約されます。 ユーザーの満足度に強く関連しているであろう代表指標= SLI(サービス品質指標) を選定し、ユーザーの信頼性を測定可能なものとする。 選定したSLIに対してユーザーが満足しているか、していないかに対応する目標値= SLO を定める。 SLOを満たしていない、つまり エラーバジェット が尽きた場合はSLOを満たすようにする改善を開発タスクより優先して実施する。 一方で、SLOは組織的な取り組みであるため、実用的な運用に乗せるためには戦略的な導入計画を考える必要があります。SLOを導入するにあたって、私達は慎重に「最初のゴール」を設定しました。 3.1 バックエンドシステムのみを対象としたSLOを運用に乗せる 理想的には、SLOはシステム全体、すなわちバックエンドシステムに限らず、クライアントアプリを含めたエンドツーエンドでの監視と運用をすることが望ましいです。ユーザーはクライアントアプリを介してバックエンドシステムを利用するので、バックエンドシステムのサービス品質がいくら高くても、クライアントアプリのサービス品質が悪ければユーザーハピネスに繋がりません。しかし、私達は導入段階で小さく始めることを選び、敢えてバックエンドシステムのみを対象にすることとしました。クライアントアプリは、今後の拡張フェーズでSLOの対象に加える予定です。このように小さなステップから始める理由は、SLOの運用が初期段階では試行錯誤の連続であり、最初からシステム全体に適用すると運用が複雑になりすぎる可能性があるためです。初期段階では対象範囲を絞ることで、SLOの設定や運用フローを確立し、チーム全体がその運用に慣れることを優先しました。この段階での成功体験をもとに、次のステップとして対象範囲を拡大していく方が、最終的な全体最適を図るためにも有効だと判断しました。 3.2 SLOアラートは導入せず、SLOの達成状況の定期的な確認に留める もう1つの重要な決定として、SLOアラートの導入を見送りました。SLOアラートは本来、サービスの品質が低下した際に迅速に対応するための仕組みですが、初期段階でこれを運用に組み込むと、試行錯誤が多い中でアラートが頻発し、かえってノイズとなる恐れがあります。これを防ぐため、まずはアラートを設けずに定期的なSLOの達成状況のチェックに留め、運用に慣れるまで柔軟に改善を重ねる体制を取りました。具体的には、SLOの達成状況を関係者で確認し、現状の目標の達成状況や運用課題を共有する形で進めています。その詳細は「5 『SLO定例』による信頼性チェック」で説明します。 このように、 最初のゴールをあえて小さく設定し、段階的に運用を確立していくことで、無理なく改善を重ねることが可能となる と考えました。 4 最初のSLI/SLOの選定 SLOは、SREのコアとなるプラクティスです。最初のSLI/SLOの設定を考える上で、まずはGoogleのSREチームによって執筆された2冊の書籍の該当章に目を通し、理解を深めました。それが 「SRE サイトリライアビリティエンジニアリング ―Googleの信頼性を支えるエンジニアリングチーム」 と、その副読本である 「サイトリライアビリティワークブック ―SREの実践方法」 です。これらの書籍では、SLI/SLOの概念から実際の運用方法までが体系的に解説されています。なお、私達がSLOを導入したタイミングでは和訳本がまだ存在していなかった 「SLO サービスレベル目標 ―SLI、SLO、エラーバジェット導入の実践ガイド」 という書籍が昨年発刊されました。SLOにまつわるトピックで1冊書かれた貴重な書籍です。これからSLOの導入を検討する方々には、参考文献としてぜひおすすめしたいです。また、これらに加えて各社のSLO導入事例を取り上げたWeb上の記事にも目を通し、他の企業がどのようにSLOを運用しているか、どのような課題に直面したかを学びました。これにより、理論だけでなく現実世界でのSLO導入に伴う具体的な実践方法や成功要因についても理解を深めることができました。 4.1 選定方針 書籍や記事で得たSLOに関する知識をベースにFAANSのシステムの特性を踏まえて考え、FAANSの最初のSLI/SLOを選定する上での方針を以下のように整理しました。 4.1.1 対象システムはWeb APIサーバーのみとする 最初のSLOを選定するにあたって、まずは小さく始めることを意識し、バックエンドシステムの中でもWeb APIサーバーのみを対象とする方針を立てました。この選定には、Web APIサーバーがFAANSにおけるサービスの中核を成しているという理由があります。FAANSのユーザーは、日常的にFAANSのクライアントアプリを通じてサービスを利用します。そして、ユーザーが行うアクションは、すべてWeb APIを介して処理されています。Web APIはユーザー体験の根幹を担っており、可用性やパフォーマンスが低下すれば、即座にユーザー体験へ悪影響を及ぼします。ユーザーであるアパレルブランド企業とそのショップスタッフにとって、Web APIサーバーはFAANSを利用する上で最も重要なシステムコンポーネントです。これを効果的に監視・管理することがSLO運用の初期段階で不可欠だと判断しました。なお、定時実行されるバッチ処理といったその他のシステムコンポーネントの信頼性の追跡に関してはSLO運用の拡大フェーズで検討することとしました。 4.1.2 重要機能の単位とシステムコンポーネントの単位の2軸でSLOを定める SLOを効果的に運用するためには、 システムの属性をうまくカバーできる必要最小限の選択をする ことが重要です。私達は、そのためのアプローチとして、 重要機能の単位 と システムコンポーネントの単位 の2軸でSLI/SLOを定めることにしました。 まず、重要機能の単位のSLO設定において、ユーザー体験の中で最も重要な操作やシナリオを把握するために、簡易的なCUJ(クリティカルユーザージャーニー)を実施しました。CUJとは、ユーザーがサービスを利用する際の主要なステップやアクションを追跡し、業務やサービスの成功に直結するポイントを特定するプロセスです。このプロセスを通じて、どの機能がユーザーにとって欠かせないのか、またその機能に関係するAPIの中で重要なものはどれなのか精査しました。例えば、ECサイトであれば「商品検索」「カートへの追加」「購入手続き」の各ステップがCUJの重要なポイントとなり、それに対応するAPIが重視されるでしょう。このCUJの結果をもとに、具体的な測定対象APIを選定しました。これにより、ユーザー体験の中で本質的な価値を持つ操作に焦点を当てることができ、SLOがユーザーにとって最大限の価値を提供できるような形となると考えています。 次に、システムコンポーネントの単位のSLO設定です。前述の通り、対象システムはWeb APIのみとするため、システムコンポーネントとしてはWeb APIサーバーの種別の単位となります。具体的には、前述のクライアントアプリ用Web APIサーバーや外部システム連携用Web APIサーバーなどです。そして、Web APIサーバーには複数のAPIが実装されていますが、それら全てのAPIを対象としたリクエスト処理に対する総合的な品質に対してSLOを定めることを指します。ただし、ヘルスチェックエンドポイントのようにユーザー体験に直接的な影響を与えないAPIは、サービス品質の測定結果に不要なノイズを生じさせる可能性があるため対象外としています。 このように、重要機能の単位とシステムコンポーネントの単位という2軸でSLOを定めた理由は、 業務の優先度と技術的な健全性の両面をカバーする ためです。システム全体の可用性やパフォーマンスを考慮しつつ、ビジネスにとって重要なユーザージャーニーの品質を確保することが、このアプローチの根幹にあります。SLOは、その設定が業務やサービスに与えるインパクトによって初めて価値を持ちます。もし、設定されたSLOがビジネスの優先度を反映しておらず、業務に影響を与えない指標にばかり焦点を当てていた場合、そのSLOは十分な価値を提供できないでしょう。この点を踏まえ、システムの技術的要素とビジネスの主要な機能の両方をカバーできるバランスを保つことが実用的で効率的なSLO運用のためには重要だと考えました。 4.1.3 レイテンシーと可用性の2つのSLIを選定する 私達は、Web APIサーバーというシステムのユーザーの信頼性を測る指標として レイテンシー と 可用性 の2つにフォーカスすることとしました。 まず、Web APIのレイテンシー、つまり応答時間は、ユーザー体験に直結します。APIのレスポンスがわずかに遅れるだけでも、特にリアルタイム性が求められるアプリケーションでは、ユーザーはフラストレーションを感じ、場合によってはサービスを離脱してしまいます。例えば、モバイルアプリやWebサービスでの検索やデータの読み込みが数秒以上かかると、ユーザーは「遅い」と感じ、その印象がサービス全体の評価に影響を与えることになります。ただし、前述の非同期ジョブ用APIは、そもそも処理に時間がかかることを前提として非同期で行われるためレイテンシーがそれほど重要ではないことから、今回はSLIの対象から除外しました。なお、レイテンシーのSLOは例えば『30日間のローリングウィンドウで99パーセンタイル値のレイテンシーが500ミリ秒以下』といった形で具体的に定義されます。 続いて、可用性(Availability)は、サービスがユーザーに常にアクセス可能な状態であることを示す指標です。可用性が低下するとユーザーはサービスにアクセスできなくなり、エラーやダウンタイムが発生すれば顧客に対する信頼性が著しく損なわれます。現代の多くのサービスは、24時間365日の稼働が当然とされています。Web APIサーバーは、他のシステムやクライアントアプリとの連携を担う中心的な役割を果たしているため、その可用性が低下すれば単にAPI自体の問題に留まらず、サービス全体へ広範な影響が及びます。なお、私達の場合は、Web APIサーバーや各APIの可用性を 成功したリクエスト数/全体のリクエスト数 というイベントベースの稼働率として算出しています。また、可用性のSLOは例えば『30日間のローリングウィンドウで99.5%以上の稼働率』といった形で具体的に定義されます。 私達がSLO運用の初期段階でレイテンシーと可用性にフォーカスしたのは、これらの指標がユーザー体験とサービスの信頼性に最も大きく影響を与えるからです。複雑な指標を多数導入するよりも、まずはWeb APIのようなリクエスト処理システムのSLIにおける2つの基本指標に絞り、運用フローをシンプルかつ効果的にスタートさせることが重要だと考えました。これにより、チーム全体がSLIの監視と改善サイクルに慣れ、徐々に運用の精度を上げていく狙いです。 4.1.4 クラウドインフラのSLA未満の水準のSLOを定める FAANSのバックエンドシステムは前述の通りGoogle CloudやAWSといったパブリッククラウド上に構築されています。そのため、そのシステムのサービス品質はパブリッククラウドのサービス品質に依存することとなります。例えば、FAANSのWeb APIのシステムはDNS、ロードバランサー、サーバーインスタンスやデータベースインスタンスなどのインフラコンポーネントで構成されています。Web APIへのHTTPリクエストがエラーを返すことなく正常に処理されるためには、これらのインフラコンポーネントが正常に稼働している必要があります。パブリッククラウドが提供するプロダクトの中には SLA(サービス品質保証) が定められているものも多いです。これら個々のインフラコンポーネントの役割を担うプロダクトの可用性SLAを調べたところ、最も低い水準だったものはGKE AutopilotのPodの『99.9%以上の稼働率』というSLA *4 でした。よって、FAANSのWeb APIサーバーの可用性SLOを定義する上で、『99.9%以上の稼働率』というインフラのSLA未満の水準で定義する必要があると考えました。 その理由を具体例を用いて説明します。例えば、Web APIのシステムを構成するクラウドインフラの可用性SLAが『99.9%以上の稼働率』だったとします。しかし、実際にしばらく計測してみると安定して『99.99%以上の稼働率』でした。そこで、Web APIの可用性SLOを「30日間のローリングウィンドウで『99.95%以上の稼働率』」と定義しSLOを運用し始めたとします。すると、しばらくの間はSLOを満たした状態が続いていたのですが、ある時Web APIの可用性SLOが未達状態に陥ってしまいました。調べてみると原因はクラウドインフラの可用性の低下であることが判明し、低い時では『99.90%の稼働率』にまで下がっていました。SLOの目標値である『99.95%以上の稼働率』を下回る品質が一定期間続きエラーバジェットを使い切ったので、SLOを満たす状態に戻す改善を早急に実施する必要があります。ところが、この時原因はクラウドインフラの可用性が低下したことなので提供元のクラウドベンダーに相談しても、可用性が改善されるとは限りませんし一般的には期待できません。なぜならば、『99.90%の稼働率』にまで下がっていたとしても『99.9%以上の稼働率』というSLAを満たせているからです。クラウドベンダーは顧客との間で合意済みのサービス品質で依然として提供できているため、顧客のための改善のアクションを取る必要性は基本的にはないのです。 この例からも分かる通り、クラウドインフラのSLAを超える水準のSLOを定めて運用することは現実的とはいえません。ただし、ここで重要なことは、 あくまで現状のインフラ構成のままの場合の制約である ことです。SLOはユーザーの満足度に基づいて決めることが重要です。もし、クラウドインフラのSLAがあるべきSLOの基準を超えていなかった場合、インフラ構成そのものを変えるという選択肢も検討すべきでしょう。SLAの水準が高くなるようにインフラ構成を変えることで、ユーザーの満足度に基づいたSLOがインフラリソースのSLAの水準を超えないように調整できる可能性があるからです。 4.1.5 依存している社内の別プロダクトのサービス品質やSLOは一旦考慮しない 前述の通り、FAANSのバックエンドシステムはWEARなどの社内の別プロダクトのシステムに依存しています。そのため、FAANSのサービス品質は依存先の別プロダクトのサービス品質の影響を受けます。例えば、FAANSのあるWeb APIエンドポイントはAPIの内部処理で社内の別プロダクトのWeb APIに同期的に1回アクセスしているとします。この場合、このFAANSのAPIのレイテンシーの品質は社内の別プロダクトのAPIのレイテンシーの品質を上回ることはありません。そして、別プロダクトのAPIのレイテンシーの品質が悪化すれば、それに引きずられる形でFAANSのAPIのレイテンシーも悪化します。このような依存関係が存在しますが、私達は 依存先プロダクトのサービス品質やSLOを一旦は考慮せず、ユーザー満足度に基づいたSLOを定めて運用し始めてみる こととしました。そのように判断した理由を説明します。ユーザーが満足するか、しないかの基準でFAANSのあるAPIのレイテンシーSLOを考えたときに、例えば『99パーセンタイル値で500ミリ秒以内』という目標値が妥当だという結論に至ったとします。一方で、このAPIの内部処理で同期的に1回アクセスするWEARのAPIのレイテンシー品質が『99パーセンタイル値で700ミリ秒』という実測でした。つまり、依存するWEARのサービス品質の実測に対してこのSLOは無理があるということになります。とはいえ、必ずしもこのSLOを達成まで持っていけないとは限りません。私達が管理していない別プロダクトであっても、同じ会社のその別プロダクトの開発チームに品質改善の相談や依頼をするというアクションはとれます。そして、その際にSLOは他チームに品質改善の必要性を理解をしてもらう上で説得力のある論拠となり得ます。まずは、そのようなアクションをとってみることが重要だと考えました。FAANSのSLOを踏まえて別プロダクトのサービス品質の改善やSLOの見直しを行うといった結果につながるかもしれません。もちろん、このやり方で全てが滞りなく解決されるとは限らず、難しい場面もあるかもしれませんが、その場合はチーム間で協議して現実的な落とし所を探っていけばよいと考えました。ただし、この時 私達が定めたSLOは絶対的に正しい閾値ではない 点には注意する必要があるでしょう。SLOは初期フェーズで適切な閾値を設定することは難しく、運用の中で適切な閾値となるようにイテレーティブに改善していくものです。また、いくら閾値を改善しても、絶対的に正しい閾値には到達し得ないものでもあります。私達はこのような前提を理解した上で、FAANSのSLOを絶対的な論拠とはせず、他チームと協調的に話を進めるべきだと考えています。 4.2 ステークホルダーとの合意 前述の選定方針を踏まえて選定した最初のSLI/SLOを実際に設定して運用を開始するには、関係者との合意が必要です。まず、関係者全員にSLOとは何か、そのメリットや基本的な考え方を丁寧に説明することから始めました。特に非エンジニア向けにSLOの理解を深めてもらうために、山口能迪さんによる 「SLOをもっとカジュアルに活用しよう」 という記事を事前に読んでもらうことを意識しました。この記事は、SLOの要点を非エンジニアにもわかりやすく簡潔に説明しており、初めてSLOに触れる人にとって理解しやすく、導入への前向きな姿勢を促す素晴らしい内容のため重宝させていただいています。なお、関係者ごとにどのような点で合意を取るべきかを明確にすることも重要です。POやPMとは、SLIの選定とSLOの目標値が適切であるかを丁寧にすり合わせ、その基準を下回った場合にエンジニアリングリソースを優先的に修正や改善に投入する必要があることを理解してもらいました。SLOがビジネス価値やユーザー体験に直結することを納得してもらい、リソース配分の重要性を認識してもらうことが大事です。また、エンジニアリングチームとは、エラーバジェットが尽きた際にサービスの安定性回復を優先し、機能開発を一時停止することに合意します。これにより、システムの信頼性がビジネスやユーザー体験にどれほど重要かを共有し、リソース投入のタイミングについて共通認識を持つことができます。 5 「SLO定例」による信頼性チェック 「3 最初のゴール設定」にも設定したように、私達はSLOの運用開始に伴い、SLOの達成状況や運用上の課題を定期的に確認し、必要な改善アクションを迅速に実行するための体制を整える必要があります。そこで、バックエンドシステムの開発・運用に関わるバックエンドブロックとSREブロックの2チームによる SLO定例 という定例会議を立ち上げました。SLO定例は、 FAANSに必要な信頼性を維持すること を目的としており、SLO運用に関連する認識合わせや議論だけでなく、信頼性維持に寄与するその他の情報共有や意思決定も行う場です。ただし、後述の「6.3 フルサイクルエンジニアリングチームへの体制移行」により、SREブロックはFAANSの業務から離れたため、現在はバックエンドブロックのみでこの定例会議を行っています。そのような経緯や試行錯誤を経た最新の会議形式を説明します。 会議の流れは以下のようになっています。 前回のNext Actionの対応状況の確認 直近のリリース内容の確認 直近のアプリケーションエラーやシステム障害の確認 SLOの達成状況の確認 データベースのシステムメトリクスの確認 Next Actionの整理 続いて、これらを個別に説明します。 5.1 直近のリリース内容の確認 GitHubで管理されている前回のSLO定例以降にリリースされたプルリクエスト一覧をチームで確認し、信頼性に影響を与える可能性があるリリースを特定します。信頼性に関わるリリースとは、信頼性向上を目的とした修正だけでなく、大規模な新機能のリリースなど、システム全体のパフォーマンスや可用性に影響を及ぼす可能性のある変更も含まれます。この確認を最初に行うことで、リリースがシステムに与えた影響を理解しやすくなり、後続のログやメトリクスの分析がより効果的なものとなります。 5.2 直近のアプリケーションエラーやシステム障害の確認 私達は、アプリケーションエラーを監視するツールとしてSentryを利用しており、この場では前回のSLO定例から現在までに発生したアプリケーションエラーの一覧をSentryで確認します。エラーが発生すると即座に通知され、迅速にエラー内容の把握と必要なアクションをとる体制を築いています。そのため、この会議の場では原因の深掘りや解決策の策定は行わず、どのエラーがどれだけの頻度で発生したかとそれぞれの対応状況を俯瞰的に確認し、全員で状況を共有するのみに留めています。また、稀に発生する中規模以上のシステム障害に関しては、その発生時期や進行中の再発防止策の状況も確認します。これにより、全員が現状をしっかりと把握し、迅速な対応や適切な判断ができる体制を整えています。さらに、この確認をSLOの達成状況の確認前に行うことで、SLOの改善や悪化時の要因分析がしやすくなります。 5.3 SLOの達成状況の確認 私達は、システム監視ツールとしてDatadogを利用しており、各種システムメトリクスがDatadogで確認できるようになっています。そのため、SLOの達成状況はDatadogのダッシュボードを活用してリアルタイムに監視できるようにしました。このダッシュボードには、例えばAPIごとのエラーレートやレイテンシーが一目で確認できるウィジェットなども配置されており、SLOに変化が生じた際にはその原因を特定しやすい設計となっています。なお、このダッシュボードは 「WEARにおけるSLOを用いた信頼性改善の取り組み」 で紹介されているDatadogのダッシュボードを参考に作っています。エラーバジェットの枯渇状況を確認し、原因を特定して適切な対応策を議論することで、SLOのプロセスが回るようになっています。 5.4 データベースのシステムメトリクスの確認 私達が利用しているCloud SQL(PostgreSQL)の監視として、アラート監視と定期的なメトリクス監視の二重体制を構築しています。まず、アラート監視に関して、データベースのログやメトリクスを対象とした適切なアラート設定を入れて、問題を迅速に検知して対応できるようにすることはデータベースの監視において重要です。また、それとは別で、状況の変化を時系列のメトリクスで管理しキャパシティプランニングや障害の予兆の把握に役立てることを目的とした、定期的なメトリクスの確認も重要です。このようなメトリクス監視もこの会議で行っています。なお、このメトリクス監視で見るべきメトリクスの一覧もダッシュボード化して、容易に確認できるようにしています。 6 SLO運用開始後の工夫と取り組み SLOの運用開始後、運用の過程でさまざまな工夫や取り組みを行ってきました。本章ではその一部をご紹介します。 6.1 SLI/SLOの設定変更の記録をADR形式で残す「SLO-DR」 SLI/SLOは最初に定義した設定を一切変えずに恒久的に使い続けるわけではなく、その設定自体もイテレーティブに見直すものです。例えば、既に運用しているSLOの設定をより適切な閾値となるように調整したり、運用していく中で見出したより適切なSLIに差し替えたり、新機能のリリースに伴い新たなSLI/SLOを盛り込むことがあります。FAANSではSLI/SLOの設定を変更する際にADRの形式で設定変更の意思決定の内容とそのコンテキストをドキュメントに残す規約とし、そのドキュメントを私達は SLO-DR と名付けました。 ADR *5 とは、Architecture Decision Recordの略で、ソフトウェアアーキテクチャに関する意思決定とそのコンテキストを残すドキュメント手法の一種です。FAANSの開発チームでは以前からADRによるドキュメント文化が浸透しており、ADRの形式は慣れ親しんだものでした。また、ADRは個々の意思決定を独立したドキュメントとして作成しますが、一般的には一度作成したドキュメントを廃止することはあっても、内容を更新することはない追記型のスタイルです。この追記型のスタイルはSLOの設定に関する意思決定がどのような変遷を辿ったかというコンテキストが追いやすく、今回の要件に適していました。 例えば、新規で構築したAという名前のWeb APIサーバーがあり、Aの可用性SLOの設定に関して、ある期間に時系列順で以下のタイトルで3つのSLO-DRのドキュメントが作成されたとします。 2024/08/01 新規構築したAのWeb APIサーバー全体の可用性SLOを30日間のローリングウィンドウで『99.9%以上』の稼働率として定義した 2024/10/01 Aの全体の可用性SLOを30日間のローリングウィンドウで『99.9%以上』→『99.5%以上』の稼働率に変更した 2024/12/01 Aの全体の可用性SLOを30日間のローリングウィンドウで『99.5%以上』→『99.8%以上』の稼働率に変更した 最初と最新の状態だけを比べれば『99.9%以上』から『99.8%以上』に目標値を下げたという変化としてまとめられますが、実際にはその過程で一度『99.5%以上』という設定を経由しています。一度『99.5以上』に下げてその後『99.8%以上』に上げたという個々の意思決定とそのコンテキスト、そしてそれらの時系列の変遷という情報には将来の意思決定の際に有用な情報が隠れていることがあります。この例では比較的単純な意思決定の流れを示していますが、実際のより複雑なケースでは、個々の意思決定とそのコンテキストがさらに重要な要素となります。SLO-DRでは、そのような情報が喪失せず、かつ追いやすい形式になっています。一方で、SLI/SLOの設定は頻繁に見直されることがあるわけではないことから、追記型がゆえのドキュメント数の増大に伴う認知負荷に関しては長期的な視点に立っても懸念がないと判断しました。また、最新のSLI/SLO設定の全体像は前述のダッシュボードでも容易に把握できます。私達は、SLI/SLO設定や運用開始日、エラーバジェットポリシーなどの決定内容と、そのコンテキストであるCUJや関係者との協議と合意の記録をまとめたドキュメントとして作成し一覧化して管理しています。 6.2 チームで行う継続的学習「SLO Study」 私達のチームでは、SLOに関する知識を共有し、運用の知見を深めるために SLO Study と名付けた継続的な学習の場を設けています。FAANSにはSLOの運用経験があるメンバーは少なく、SLOに対する関心や知識レベルにもばらつきがあります。そこで、全員がSLOに関する共通の知識を身に付け、チーム全体でSLOの効果的な運用を考えられる体制を築くことを目指して、この取り組みを開始しました。具体的には、事前にSLO Study用のドキュメントに記載の表にSLOに関するWeb上の記事のURLとそこから得た学びや議論したいことを記入しておきます。そして、SLO定例の最後の余った時間を使って記入者がその内容を発表し、定例の参加者で議論します。 SLO運用のあるべき姿は、事業やシステム、さらには組織の特性によって異なるため、一般化された知識だけではすべての現場に適用するのは難しいと感じています。よって、各々の開発組織が自分たちに合ったSLOのあり方を見つけ出すために、独自の試行錯誤を積み重ねていく必要があるでしょう。Google社がSLOのプラクティスを提唱して以来、多くの企業やサービスでSLOの取り組みが行われ、試行錯誤を経てきました。それぞれの現場で得られた実践的な知見を取り入れることは、私達にとって最適な運用方法を見つける上で非常に参考になります。SLO Studyの場では、SLO関連の資料や実際の運用事例をキャッチアップし、学んだことを「FAANSに活かせるか」「FAANSの場合どう適用するか」という視点で議論しています。このような負担の小さく無理のない継続的学習の取り組みを通じて、SLOに対する共通理解を深め、チーム全体での一体感を持ってSLO運用の改善を進めることが目標です。特に、書籍に記載されている一般的な知識だけでなく、各社の具体的な事例を学ぶことで、私達の現場に合った最適なSLOのあり方を模索していきたいと考えています。 6.3 フルサイクルエンジニアリングチームへの体制移行 SLOの運用を進める中で、SREブロックにアサインされていたFAANSの信頼性維持に関するタスクが思うように進まないという課題が浮上しました。この課題に対し、チームトポロジーの考え方を用いることで、状況を客観的に整理できました。 チームトポロジーとは、書籍 「チームトポロジー 価値あるソフトウェアをすばやく届ける適応型組織設計」 で紹介されている、組織のチーム設計において適応型のフレームワークを提供するモデルです。特にチームが担う認知負荷を最適化することを重視しています。この認知負荷が過剰になると、チームのパフォーマンスが低下し、システムの安定性に影響を及ぼすことが指摘されています。 FAANSとWEARは、それぞれ異なるビジネスドメインに属しており、どちらも複雑な要件を持っています。FAANSは新しいサービスながらも急速に成長しており、独自のビジネスニーズと技術的な要件が求められます。一方、WEARは長い歴史を持つ大規模なシステムであり、その運用には深い知識が必要です。これら2つの異なるビジネスドメインに対応することは、SREブロックに大きな認知負荷を強いる要因となっており、これは構造的な課題でもありました。さらに、FAANSとWEARは歴史的な経緯で両者の技術スタックの統一性が低いという事情もあります。そのため、SREブロックは異なる技術基盤の両システムに対応する必要があり、さらに認知負荷が増大していました。現に、SREブロックからは「WEARに手一杯で、FAANSに十分なアテンションを張れない」という声が上がっていました。このアテンションという言葉は、チームが持つ認知負荷に関連する概念で、適切な注意力を割けないことが認知的な過負荷の表れです。このような状況から明らかなのは、問題の本質が単なるリソース不足ではなく、過剰な認知負荷にあるということです。たとえSREブロックの人員を増やしても、認知負荷が軽減されない限り、この問題は解決しません。そこで、私達は チーム間の責任境界の見直し を行い、バックエンドブロックの責任範囲を広げてSREブロックの負担を軽減することによって、両者のフロー効率を改善する道を選びました。 歴史の浅いFAANSのバックエンドシステムは、WEARに比べて小規模なシステムで運用負荷が低いため、バックエンドブロックがSREブロックの責務を引き継いでも認知負荷的に無理はありません。さらに、バックエンドブロックはこれまでもインフラ構成や監視設定の変更、インフラ起因の問題解決に積極的に取り組んできた経験があります。また、バックエンドブロックにはSREブロックが担当していた運用業務の知識やスキルを持つメンバーが複数名いることもあり、体制移行は円滑に進行しました。この結果、現在ではSREブロックがFAANSの運用から離れ、バックエンドシステムの開発と運用はバックエンドブロックで自己完結する体制に移行しました。 バックエンドブロックにとっては責務が拡大したことでやるべき業務が増える一方で、大きなメリットもあります。運用を含む開発ライフサイクル全体を一貫して管理できるフルサイクルエンジニアリングチーム *6 として機能し始め、分業体制でのコミュニケーションコストやサイロ化の問題が解消されます。その結果として、開発ライクサイクル全体へのフィードバックループがより効率的に回りやすくなるのです。ただし、開発ライフサイクルの中でQAに関しては、高い専門性を有したQAエンジニアが複数名おり開発ライフサイクルのボトルネックにはなっていないため、引き続き外部化されたままの体制を維持しています。 6.4 開発組織への定期報告 SLOは、開発組織の全体に共有されるべき重要な指標です。バックエンドシステムを中心にSLO運用を開始していますが、SLOの設定や達成状況、そして取り組んでいる改善アクションは、プロダクト開発・運用に携わる全ての関係者によって把握されている状態が理想です。そこで、私達はFAANSのプロダクト開発のための情報共有や相談の場である週次の定例会議で、SLO運用に関する報告をするようにしました。この場でSLOの状況を定期的に共有することで、機能拡充や新機能開発に偏りがちな意識を、信頼性という側面にも広げ、バランスの取れた価値提供を意識し続けることができると考えています。これは、私達が目指す組織全体へのSLO文化の浸透における重要なステップの1つだと捉えています。特に、信頼性維持に関するタスクを行う際に、そのタスクが今なぜ必要で、どのように全体の価値提供に寄与するのかを組織全体で理解することが重要だと考えています。SLOの定期報告を通じて、信頼性維持の取り組みが単なる技術的メンテナンスにとどまらず、ユーザーやビジネスにとって直結した価値提供の一環であることが明確になります。結果として、組織全体で信頼性に対する意識が強化され、持続的な改善活動につながっていくと考えています。 限られた時間の中で効率的にSLOの情報を共有するため、報告のフォーマットにも工夫を凝らしました。この定例会議は、プロダクト開発に関わる全てのメンバーが集まる唯一の場です。エンジニアだけでなくプロジェクトマネージャーやデザイナー、ビジネスサイドのメンバーも参加しています。そのため、技術的な背景を持たないメンバーにも理解できるシンプルかつ視覚的に分かりやすい形式が求められます。SLOの詳細な分析やアクションの議論は、専用のSLO定例で行うため、定例会議では現状のSLOの達成状況と必要なアクションを簡潔に示すことに集中しています。各SLOの達成状況は 『達成』(=エラーバジェットに余裕がある状態) または 『未達』(=エラーバジェットが枯渇している状態) として色分けし、一目で判断できるようにしました。また、前回の報告からの変化は赤色の文字で記載し、未達状態のSLOに関するアクションプランも簡潔に記載することで、状況の変化や必要な対応を迅速に把握できるようにしています。 具体的には以下の項目を含めるようにしました。 測定日 : 各SLOの測定時点を明記する。測定日はSLO定例の開催日に対応。 SLOの設定内容とその達成状況 : SLOの設定内容と各SLOの『達成』または『未達』という達成状況を色分けして記載。 前回測定時との比較 : 前回測定時の達成状況も併記することで改善や悪化といった変化が読み取れるようにする。 品質改善アクション :『未達』状態のSLOに関しては、『達成』に持っていくためのアクションプランを記載する。 補足情報 : システムコンポーネントやSLO関連の用語の解説を添えることで、背景を理解しやすいようにする。 一方で、具体的なSLIの数値や過去の傾向分析といった細かなデータは報告には敢えて含めていません。それらは前述のSLOダッシュボードで確認でき、SLO定例で議論されるため、開発定例では最小限の情報に絞り込み、報告のスムーズな進行を心掛けています。 具体的には以下のようなフォーマットの資料で報告しています。 7 SLOを導入して得られた効果 SLOの導入によって、私達の開発・運用プロセスにおける意思決定が大きく改善されました。 7.1 明確な判断基準による会議の充実化 SLO導入以前も、バックエンドシステムの運用に関わるエンジニアが集う定例会議の中で、APIのレイテンシーやエラーレートなどのメトリクスを確認してはいました。しかし、それらがどの程度問題であるのか、具体的にどのようなアクションが必要なのか判断することが難しく、会議の進行が散漫になることがありました。メトリクスは収集できていても、問題の有無やアクションの必要性が不明確だったため、何となく会議を終えてしまうケースも少なくありませんでした。 SLO導入後は、その定例会議が前述のSLO定例として生まれ変わりました。各サービスにおいて設定されたSLOに基づいて判断できるようになり、会議でのディスカッションがより具体的かつ建設的なものとなりました。これにより、サービス品質に関する合意形成がスムーズに進み、どのタイミングでアクションを取るべきかが明確になりました。結果として、会議が締まりのあるものに変わり、効果的な意思決定を行う場としての役割を果たせるようになっています。 7.2 信頼性維持のためのアクション促進 SLOを満たせておらずエラーバジェットが尽きた場合には、即座に対策を講じるための具体的な行動に移る習慣が根付いてきました。もちろん、全ての問題が一度に解決されるわけではありませんが、少なくともSLOを基準に優先順位をつけ、後回しにされがちなサービス品質の課題に対しても確実に対応が取られるようになりました。これにより、従来は見過ごされていたような品質問題に対しても、早期に改善のためのアクションが取られるようになり、ユーザー体験の向上につながっています。例えば、APIのエラーレートがSLOを下回ることがあれば、その原因を特定し、必要な修正や最適化を実施する体制が整っています。以前であれば、「重大な問題でなければ後回し」という姿勢が取られていた場面でも、SLOに照らし合わせることで緊急性が明確となり、速やかに改善に取り組むことが可能になりました。 7.3 他チームへの改善依頼の円滑化 SLOの導入により、私達が依存している社内の他プロダクトの開発チームに対して、品質改善を依頼する際の根拠も強化されました。以前は、依存先の他プロダクトのAPIのレイテンシーがなんとなく遅いとは感じつつもどこまで改善すべきかも不明確で、他チームに働きかける具体的なアクションへ繋がりにくい状態でした。しかし、SLOによってその判断基準が明確になったことで、そのようなアクションに繋がりやすくなりました。また、「SLOを満たしていない」という明確な基準を提示できるため、依頼内容が具体的かつ論拠のあるものになり、依頼先にとっても協力してもらいやすいものとなりました。この結果、プロダクト間での協力体制も強化され、全体のサービス品質向上に寄与しています。 7.4 技術的な意思決定の改善 SLOの導入により、技術的な意思決定の際に信頼性への意識が高まりました。システムアーキテクチャの設計やパフォーマンス最適化には、トレードオフを踏まえた判断が求められます。SLOという信頼性基準が明確に定義されたことで、どの程度の信頼性が必要かという具体的な指標が提供され、意思決定の根拠がより明確になりました。例えば、レイテンシーに関するSLOが設定されていることで、負荷テスト実施の際にも目標とすべき性能値の意思決定がスムーズになり、試験結果に基づく判断がより合理的に行えるようになりました。また、SLOが信頼性の指標として組み込まれたことで、アーキテクチャ設計の際にも信頼性を意識した選択がより自然に行われるようになりました。 8 SLOの導入は早ければ早いほどよい SLOを運用していく中で感じたこととして、本記事のタイトルにもなっている SLOの導入は早ければ早いほどよい ということがあります。もちろん、早ければ何でもよいわけではなく、導入のスピードを重視するあまり重要な要素を見落としては本末転倒です。しかし、早いタイミングでSLOを導入することには多くの利点があると感じました。 8.1 SLO文化を浸透させるハードルが低い SLOを効果的に運用するためには、開発組織にその文化を浸透させることが不可欠です。一般的に、組織が大きくなるほど新しい文化を浸透させるのは難しくなります。開発組織が拡大するにつれて、SLO文化の浸透には労力と時間が必要となるでしょう。FAANSは、まだ成長段階にあり、開発組織も比較的小さいです。その点において、SLOの概念を関係者に伝える際の障壁が低く、皆で同じ目標に向かいやすい環境でした。これは信頼性への意識向上とSLOに基づいた意思決定が迅速かつ効果的に行えるようになった一因だと考えています。 8.2 SLOはソフトウェア設計の意思決定を支援する 前述の「7.4 技術的な意思決定の改善」からも明らかなように、サービス品質の基準とはソフトウェア開発の非機能要件の一部であり、ソフトウェア設計において重要な役割を果たします。SLOが早期に設定されていることで、開発プロセスの初期段階から信頼性の基準を考慮した意思決定が促進されます。後から信頼性を確保するよりも、最初からその基準を意識した設計をする方が遥かに効率的です。また、設計や実装のトレードオフを明確に理解し、最適なバランスを追求できるようになります。そして、システム全体の安定性が向上し、開発チームはより自信を持ってプロダクトの成長に貢献できるようになると考えています。 8.3 信頼性は事業にとって最初から重要な指標である そもそもユーザーの信頼性とは事業にとって初期段階から重要な指標です。サービスがローンチされ、ユーザーが実際に利用し始めた瞬間から、信頼性は欠かせない要素となります。ユーザー体験の質を維持するためには、信頼性を継続的に追跡し、改善し続けることが不可欠です。これを怠り、後回しにすることは、事業にとって大きなリスクとなります。 おわりに 本記事では、FAANSにおけるSLOの導入事例と、それによって得られた効果や運用の中での工夫と気付きを紹介しました。SLOの導入は、サービス品質を継続的に改善し、信頼性を維持するための重要なステップでした。導入初期の段階では、SLOに基づくフィードバックループを確立し、運用を通じて改善を重ねることで、一定の成果を達成できたと感じています。 今後の目標は、現在のSLO運用をさらに洗練させ、より効果的なフィードバックループを回し続けることです。その一例として、バーンレートアラートを導入し、エラーバジェットの消費速度をリアルタイムで把握し問題を早期に検知・対応する体制を整備することを検討しています。このような取り組みを通じて、信頼性に対する迅速なアクションをさらに強化し、SLOの達成をより確実にすることを目指します。また、このフェーズで得られた成功体験を基にSLOを定めるシステムの範囲を拡大し、組織全体でSLO文化を深く醸成していきたいと考えています。 本記事が読者の皆さんのSLO導入のきっかけや、導入手順と運用方法の参考となれば幸いです。また、FAANSでは、機能開発と信頼性維持の両方にコミットし、フルサイクルな開発プロセスを実現できるエンジニアを募集しています。私達と共に、信頼性を高めながらプロダクトの価値を最大化していく挑戦をしませんか。 corp.zozo.com *1 : 関連記事 FAANSにおけるCloud RunからGKE Autopilotへのリプレイス事例 *2 : 関連記事 Kubernetesネイティブなワークフローエンジンとは!FAANSでArgo Workflowsを導入した話 *3 : 関連記事 Cloud FirestoreからPostgreSQLへ移行したお話 *4 : なお、このSLAは私達のSLO導入時点におけるものですが、本記事の執筆時点においてもそのSLAに変わりはありません。 Google Cloudの公式ドキュメント *5 : 関連記事 ZOZOFITにおけるADRを利用した意思決定を残す文化作り *6 : 関連記事 Full Cycle Developers at Netflix — Operate What You Build
はじめに こんにちは、SRE部カート決済SREブロックの伊藤( @_itito_ )です。普段はZOZOTOWNのカート決済機能のリプレイス・運用・保守に携わっています。また、DB領域でのテックリードを担っており、データベース(以下DB)周りの運用・保守・構築に関わっています。 現在、ZOZOには DBを専門で扱う部署はありません 。一部メンバーでDatabase Reliability Engineeringのワーキンググループ(以下DBRE-WG)を構成して、DBの信頼性を高めるための活動をしています。 本記事ではZOZOにおけるDBRE-WGの概要と発生していた課題と、いかにして開発メンバーを招き、その結果どのような効果があったかの事例をご紹介します。 はじめに ZOZOにおけるDBRE活動について SRE部で行っているワーキンググループについて DBRE-WGで行っていること DB関連での問い合わせ・レビュー対応 各種案件におけるDB周りのサポート イベント時の監視対応 DB周りでの自動化やパフォーマンス可視化 技術共有会の実施 DBRE活動を進める上で発生した課題 カート決済SREとしてDBと関わることとの違い 課題解決に向けた取り組み 開発チームへの提案 DBAとDBREについて 開発チームとの話し合い メンバー参加後の活動 DBRE-WGに参加してもらった結果 問題となっていたDBのパフォーマンス改善 参加メンバーの自主的な動きによる効果 振り返ってみると 展望 DBのパフォーマンス改善 体制について 最後に ZOZOにおけるDBRE活動について SRE部で行っているワーキンググループについて 現在私が所属しているEC基盤開発本部SRE部は複数のブロック(チーム)で構成されています。このSRE部内の、ブロックを跨いだ活動を促進するための横断組織として複数のWGが存在しており、この中の1つとしてDBRE-WGが存在します。 SRE部内のワーキンググループ構成例 DBRE-WGで行っていること DBRE-WGは「各プロダクトのデータベース健全性を維持向上させる」ことを目的として、以下のような取り組みを実施しています。 DB関連での問い合わせ・レビュー対応 DBの設計や運用に関する質問や、パフォーマンス悪化や予期せぬエラーの発生といった緊急性の高い問い合わせなど、様々な問い合わせに対応しています。作成予定のテーブルの設計が問題ないか、ZOZO内でのDBの開発ガイドラインに沿っているかなどのレビューも行っています。 各種案件におけるDB周りのサポート DB設計が重要な役割を担う場合や、保守期限が迫っていてDB更改をする必要がある場合など状況に応じてDBREが参画します。参考までに、過去にDBREメンバーが主導してリプレイスを実施した記事は以下のとおりです。 techblog.zozo.com イベント時の監視対応 ZOZOTOWNでは1年を通じて様々なセールイベントを開催しており、大きな負荷が想定されるイベントではリアルタイムで監視しています。特に最大級のアクセスが発生する新春セールには最も力を入れており、当日も監視しますが、それに備えた負荷試験においてのDB負荷状況の確認も行っています。 techblog.zozo.com DB周りでの自動化やパフォーマンス可視化 手動運用していた作業の自動化やパフォーマンスの可視化なども進めています。可視化については具体的には以下の記事で紹介しています。 techblog.zozo.com 技術共有会の実施 不定期ではありますが、全社的な技術力向上を目的としてDBに関わる技術共有会を開催しています。 DBRE活動を進める上で発生した課題 上記の活動を進める上でいくつかの課題が生じてきました。その状況を図示したのが次の画像です。 DBRE活動における課題 特に、特定のDBにおいて、パフォーマンスの悪化やトラブル・問い合わせが増加傾向にあり、改善したい課題でした。 クエリタイムアウト起因のエラー数が増加傾向 しかし専任というわけではないので大きく時間を割くことが難しく、問題の原因となるクエリを見つけたとしても、そのサービスに詳しくないため改善のための手順が増えてしまい、改善を進めづらい状況でした。 カート決済SREとしてDBと関わることとの違い 冒頭でも述べた通り、私はカート決済SREとしても活動しています。いわゆるEmbedded SREのような立場であり、開発側を担当しているカート決済部と連携を取りながらDBの運用・保守をしています。 カート決済SREとして活動しているときは前述のような課題を感じず、振り返ってみると立ち位置の違いが大きいように感じました。 カート決済SRE兼DBREとしての立ち位置 問題が起きているDBに対しても、同じような立場で動ける人を増やすことで効率良く改善が進むと考え、そのためのアクションを取ることにしました。 課題解決に向けた取り組み 開発チームへの提案 上記から、パフォーマンス問題が発生しているDBに関わる 開発チームのメンバーから数人、DBRE-WGの活動に参加してもらえないか 提案することを決めました。 SRE側から増やすことも考えましたが、SQLを自在に書ける人材が少なく、1から学習する必要があるため、あまり現実的ではないと判断しました。(組織変更などもあり、2024年11月現在は問題となったDBに大きく関わるDBREメンバーも存在) また、開発側から参画してもらうことで初期学習コストが低い以外にも、下記のようなメリットが考えられました。 開発側の方がサービスの実装に詳しく、直近の問題にも対応できること 開発側でノウハウが蓄積されれば根本から解決できる可能性があること DBREと開発側との連携不足の解消 DBRE-WGの活動へと参加してもらう理由については、双方の連携を向上させるとともに、実際に発生している問題を題材にペアプロすることでスキルの伝達を速やかにできると考えたためです。 DBAとDBREについて 開発側に打診するにあたって、DBAの役割を担える人材を増やしたいという目的を話しています。ここでDBAとDBREの違いについて明確に整理するために、ChatGPTからの回答を引用します。 DBA(Database Administrator) ・役割: データベースの管理、設定、保守を行う。 ・主な業務: データベースのインストール、バックアップ、リカバリ、パフォーマンスのチューニング、セキュリティの管理など。 ・目的: データベースが正常に機能し続けることを保証する。 DBRE(Database Reliability Engineering) ・役割: データベースの信頼性、可用性、スケーラビリティを向上させることに焦点を当てる。 ・主な業務: システム全体のアーキテクチャを考慮し、障害時の復旧手法の設計や、自動化を推進することなど。 ・目的: 高可用性のシステムを実現し、ビジネスニーズに応じたパフォーマンスを維持する。 今回大きく問題となっていたのはパフォーマンス面であり、SRE的な部分までお願いする予定はないため、DBREではなくDBAを増やすことを目的としています。また、元々DBRE-WGはSRE部の中の1つの活動であり、「DBREに参加 ≒ SRE側への引き抜き」のような誤った形で捉えられないようにする意味でもこの辺りを明示しています。 開発チームとの話し合い 実際の話し合いには以下のような内容をまとめた資料を用いてプレゼンを実施しました。 DBRE-WGとは 現状のDBREにおける課題感 開発側メンバーを誘いたい理由 DBのパフォーマンス状況 悪化傾向にあり、エラー数も増加傾向にあることを示すグラフの提示 期待する人物像 懸念点 メンバーjoin時の双方の工数が具体的にどのくらい増えるか DBRE業務をどこまでお願いできるか 結果として、DBに関して同じ課題感を抱えていると認識し合い、ぜひ協力したいという意見をいただきました。 メンバー参加後の活動 上記を経て2名の開発メンバーに参加してもらうことになりました。 参加に伴って問い合わせやテーブル設計のレビューなどをペアプロ形式で行うとともに、DBのパフォーマンス改善に向けた定例会を設けました。 定例会では、以下の例のように最近のDBの状況を確認し、ボトルネックを特定したうえで、それに対する改善策を検討・実施しました。 エラー状況確認からのアクション例 DBRE-WGに参加してもらった結果 問題となっていたDBのパフォーマンス改善 改善活動を行ったことでクエリタイムアウト起因のエラー数が減少し、それに伴うレスポンスタイムの改善を確認できました。 クエリタイムアウト数の減少とレスポンスタイムの改善 参加メンバーの自主的な動きによる効果 上記以外にも、参加してもらったメンバーによる効果が以下のようにありました。 エラー発生時の初動対応の迅速化 背景:エラーが多数発生するとSlack通知が行われ、原因を調査するためのスレッドが立ち上がる 以前まで:調査が行われ、DBが関係していそうならばDBREに問い合わせが行われる メンバー参加後:参加メンバーがエラー発生時から積極的に見てくれるため、早期段階でDBの関係有無の周知とDB側調査が開始される 既存のアプリ改修による運用改善 背景:処理が失敗してしまうとDB全体の動きに影響を与えてしまうバッチアプリが存在する 以前まで:成否が重要なものの、既存アプリだとわかりづらく、運用でのカバーとなっていた メンバー参加後:アプリ自体の改修を実施し、万が一失敗した場合には、重要なアラートが通知されるSlackチャンネルに通知が届くようになった 振り返ってみると 書籍、 データベースリライアビリティエンジニアリング において、DBREの基本原則として以下の記述があります。 1.1.2 周囲を巻き込め 才能あるDBREは、サイトリライアビリティエンジニア(SRE)よりもはるかに希少な存在です。DBREを雇えたとしても、せいぜい1人か2人が関の山でしょう。これはつまり、多くのことを自分たちで、つまり限りあるリソースを最大限に活用して、自給自足で成し遂げる必要があるということです。 意図していたわけではありませんでしたが、開発チームも一緒になって改善を進めるというアプローチで、上記を実現できており、相乗効果を生んでいるように感じています。 展望 DBのパフォーマンス改善 前述のように改善効果があったものの、まだまだ改善すべき点は多くあります。そのため、引き続き改善に向けた活動を継続していきます。 体制について 理想としては次の画像のような体制が望ましいと考えています。 各サービスと近い位置にDBAを担える人がいる体制 サービスの実装に精通したDBAがいることで、問題が発生しても迅速に対応できるほか、有識者によるDB設計やクエリのレビューが行われることで、サービスの品質向上が期待できると考えています。 また、弊社はマイクロサービス化を進めており、それぞれのチームでクラウド管理しているDBの数も増えています。各チームのノウハウや、Aurora MySQLのアップグレードのような定期的な作業なども、個別に調査するのではなく、情報を共有し横展開することで全体の効率化が進むと考えています。 このような体制を目指し、これからもDBの信頼性を高めるための活動を続けていきます。 最後に ZOZOでは、一緒にサービスを作り上げてくれる仲間を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください! corp.zozo.com
はじめに 2024年10月15日に『 ZOZOMETRY 』という計測技術を活用したサービスを正式ローンチしました。今回はZOZOMETRYのサービス概要、計測技術および計測精度について紹介します。 ZOZOMETRYとは ZOZOMETRYとは、事業者の採寸業務を効率化し、採寸が必要な服の売上拡大やコスト削減に貢献する法人向けのサービスです。以前、ZOZOTOWNで提供していた個人向けのサービスでは、ZOZOSUITを着用しての計測が必須でしたが、ZOZOMETRYではZOZOSUITあり、ZOZOSUITなしの異なる計測方法が提供されています。 biz.zozometry.com 計測技術について ZOZO New Zealandのノースと新事業推進本部の西澤です。ZOZOMETRYの計測技術を説明するにあたり、まずはZOZOSUITのローンチ以来、ZOZOの計測サービスがどのように進化してきたかを解説します。 計測サービスの進化 ZOZOSUITは当初、ZOZOプライベートブランドのサイズ推奨用技術として2018年にローンチしました。2020年にはデザインとアルゴリズムをアップグレードしたZOZOSUIT2(現在はZOZOSUITと名称変更)として、パートナー企業募集を実施しました。ZOZOSUITに続き、2019年にはネットでシューズ購入時のサイズ課題を解消させるZOZOMATをローンチしました。その後、ZOZOSUITで使用されている計測技術と、ZOZOMATで足形状の特定に使われるシルエットフィッティング技術の延長として開発されたのが、「ZOZOSUITなし計測」と呼ばれる最新の計測技術となっています。 ZOZOSUITを使った計測は幅広い分野で役立つことが証明され、医療、アパレル、フィットネス、ゲームなど、様々な業界におけるZOZOSUITの応用に関して、多くのクライアントと関わってきました。 また、外部の研究機関との取り組みにより、 脊柱側弯症の検出 や リンパ浮腫の評価 など、今までになかったZOZOSUIT関連プロジェクトの出発点となりました。そのような様々な業界との取り組みの中で、多くの事業者が単に顧客の体をスキャンするだけにとどまらない多様なニーズを持っていることが明らかになりました。 この課題に対して、ZOZOMETRYというBtoBプラットフォームを通じて、事業者が独自の測定ポイントを定義できるようにしました。これにより、業界ごとの専門知識に支えられながら、ビジネスニーズに合わせてカスタマイズできる測定技術を体験することが可能となります。 ZOZOMETRYの計測技術の概要 ZOZOMETRYでは、用途に合わせて「ZOZOSUIT着用あり」または「ZOZOSUIT着用なし」の2つの測定モードを専用アプリで選択できます。 ZOZOSUITは12種類のサイズ展開があり、約2万個の特徴的なフィデューシャルマーカーを使用して体型の3Dモデルを生成します。特殊なアルゴリズムによって高精度な身体測定が可能です。 ZOZOSUITを用いた計測は高精度であるものの、計測方法の手間とZOZOSUIT自体の配送までの待ち時間が課題点となっていました。そのため、ハンズフリーの計測技術である「ZOZOSUITSなし計測」を開発しました。2年間に及んだ開発期間中には、最大450件のZOZOSUITデータと、近年蓄積されたZOZOSUITなし計測のプロトタイプからの追加データ200件の分析が行われました。 ZOZOSUITなし計測を実現させる上で特徴的な問題となったのは、身体上のフィデューシャルマーカーを検出できるZOZOSUITとは異なり、身体を特定するための基準点がなかったことでした。解決策となったのは、ZOZOMATのシルエットフィッティング技術の応用でした。ZOZOSUITの計測技術を活用しながら、ZOZOSUITがない状態を補正します。 様々な背景や肌の色調を考慮できるシルエット検出機能を取り入れることで、ZOZOSUITなしの場合も体型の3Dモデルを構築することが可能となりました。もちろん、計測を成功させるには、iPhoneの前面カメラにアクセスできることと、(正確な測定を確実にするため)ぴったりとした服装を着用することが条件となっています。 ZOZOSUITなし計測の機能は2024年1月に初めてボディマネジメントサービス「ZOZOFIT」に統合されました(現在は米国でのみ利用可能)。以来、ZOZOFITは毎日約800件の計測データを処理しており、これまでに実施された計測データの総数は約5万件となっています。このサービスは、独自のビジネスニーズに合わせて139箇所から測定ポイントを定義できます。今後はZOZOMETRYの高精度なZOZOSUIT計測、または手軽なZOZOSUITなし計測を通して、カスタマイズ可能な身体測定サービスをぜひ世の中の企業様にご体験いただければと考えています。 このサービスは、各企業の特定のニーズに応じて柔軟に対応可能です。ぜひ、これらのサービスを世の中の企業様にご体験いただければと考えています。 ZOZOMETRYの計測精度について 計測プラットフォーム開発本部データ開発ブロックの嶺村と松山です。このセクションではZOZOMETRYのZOZOSUITなし計測およびZOZOSUITあり計測の精度についてご説明します。スキャン後に生成される人体の3Dモデルの精度と、他社の類似サービスとの比較結果についてご紹介します。 3Dモデルの生成精度について 検証内容 3Dモデルの生成精度については、ベンチマークと比較した正確性の精度(accuracy)と、同じ計測対象を複数回計測した場合のばらつきの精度(precision)の検証観点があります。今回の記事では前者の観点での検証結果についてご紹介します。 検証方法としては、第三者メーカー製の3Dボディスキャナー(以下、3Dボディスキャナー)の3Dモデルをベンチマークとし、ZOZOSUITなし計測またはZOZOSUITあり計測で生成された3Dモデルとの比較誤差を算出しています。3Dモデル同士の比較誤差の算出方法は以下の通りです。 人間型のマネキンや人体など、共通の計測対象をZOZOSUITなし計測、ZOZOSUITあり計測および3Dボディスキャナーで計測し、3Dモデルを生成します。 3Dモデル同士を直接比較する前に、ZOZOSUITなし計測、ZOZOSUITあり計測および3Dボディスキャナーの各モデルを移動および回転させ位置合わせします。各計測ツールによって生成された3Dモデルで姿勢(背中の曲がり方や腕、脚の開き具合など)が微妙に異なる可能性があるため、事前にこの方法で3Dモデル同士の姿勢を可能な限り一致させます。 ZOZOSUITなし計測またはZOZOSUITあり計測で生成された3Dモデルの各頂点に対して、3Dボディスキャナーで生成された3Dモデルへの最短の垂直距離を計測します。両者の距離は正の値になる(頂点が外側に位置する場合)ことも、負の値になる(頂点が内側に位置する場合)こともあります。弊社の検証ではいずれのケースでも距離の絶対値を『誤差』として算出しています。この方法で3Dメッシュ全体の頂点の距離を計算し、その平均値を『平均誤差』として計算します。 上記の計算方法で計算したZOZOSUITあり計測およびZOZOSUITなし計測の平均誤差は以下の通りです。 ZOZOSUITなし計測:平均誤差10mm以内 ZOZOSUITあり計測:平均誤差3.7mm以内 スマートフォンアプリを活用したツールでありながら、ZOZOSUITなし計測、ZOZOSUITあり計測のいずれも3Dボディスキャナーの計測結果に近い精度で3Dモデルを生成できています 1 。また、ZOZOSUITあり計測ではより3Dボディスキャナーに近い高精度な3Dモデル生成が可能です。 他社サービスとの比較 検証の概要 ZOZOMETRYと類似した人体計測を提供している他社サービスとの精度比較の結果についてもご紹介します。他社サービスからは3Dモデルへのアクセスが難しかったため、先述の3D同士の比較とは異なり、手計測(以下、ハンドメジャーと呼称)との比較検証を実施しました。検証内容の詳細と検証結果について記載していきます。 ハンドメジャーについて 弊社には、外部の研究機関にて身体計測を専門とする研究者の先生から、ISO規格で定められた国際標準のハンドメジャーの計測方法を学んだ専任のチームが存在します。この専任チームは、プロダクトの精度検証のために定期的にハンドメジャーを実施しているプロフェッショナルチームです。各計測ツールで算出された計測値と比較する正解値としては、このチームで行ったハンドメジャーの値を利用しています。 他社サービスについて 本記事では比較対象として、ZOZOMETRYと同様に携帯端末によりスキャンを行い身体の各箇所の計測値を算出しているA社(仮称)のサービスを取り上げます。A社の公開している計測箇所の一部の定義がZOZOMETRYと同様にISO規格に準拠しているため、同じ定義の計測箇所で比較検証を実施しました。 検証対象データについて 本検証については、以下のデータを収集して比較しています。 データ数 42件の計測データ(ZOZOSUITなし計測、ZOZOSUITあり計測、A社サービスで計測したデータ数がそれぞれ42件あります)。いずれも日本国内に在住している被検者を対象に計測。 対象者の体形レンジ 被験者の身長レンジ: 148cm ~ 186cm 被験者のBMIレンジ: 17.3 ~ 41 計測環境 ZOZOSUITなし計測、ZOZOSUITあり計測ともに照明、背景について当社の想定する理想的な撮影環境下において計測を実施しています。 A社サービスについては、サービス内で表示される注意事項に遵守した環境で計測を実施しています。 計測誤差の算出方法 ZOZOスタッフの計測した各部位のハンドメジャーの値と比較して、ZOZOSUITなし計測、ZOZOSUITあり計測、A社サービスの計測値が相対値で何パーセントずれているかを計算しています。今回対象としたすべてのスキャンデータにおいて、各計測箇所における誤差の平均値および標準偏差を集計しました。 計測精度の比較結果 ※計測箇所の名称はZOZOMETRYサービス内の呼称に準拠 傾向としては以下のようになりました。 平均誤差で見ると、どの計測箇所もZOZOSUITあり計測の精度が最も誤差が小さい結果となりました。特にA社サービスと比較すると、計測箇所によっては半分または三分の一以下に誤差を抑えられています。標準偏差の値もZOZOSUITあり計測の結果が最も小さくなるケースが多く、全体的に誤差が小さく抑えられていることがわかります。 ZOZOSUITなし計測での結果については、ZOZOSUITあり計測の精度には及ばないものの、A社サービスと比較して平均誤差を小さく抑えられています。ZOZOSUITなし計測の標準偏差についても同様に、ZOZOSUITあり計測よりはばらつきが出るものの、A社サービスとの比較ではどの計測箇所においてもばらつきが少ない結果でした。 以上の傾向から、ZOZOSUITあり計測、ZOZOSUITなし計測ともに今回取り上げた計測箇所についてはA社サービスより高精度な計測結果が期待できると考えられます。また、より高い精度が求められるケースにおいては、ZOZOSUITあり計測での計測が望ましいといえます。 なお、正式ローンチ以前よりオーダーメイドバイクスーツ制作のためZOZOMETRYを導入いただいていた南海部品株式会社様からも、計測精度の高さについてはコメントをいただいています。ZOZOMETRY申し込みサイトに掲載されている導入事例の記事もぜひご参照ください。 biz.zozometry.com 今回の検証では、A社のサービスと定義が共通している計測箇所4か所での比較をしました。ZOZOMETRYの正式ローンチ時点では、この4か所を含めて最大139箇所の計測が可能(10月15日現在)で、今後も新たな計測箇所が随時追加されていく予定です。 まとめ 本記事ではZOZOSUITがこれまでどのように使用されZOZOMETRYの開発に至ったのか、またその計測精度について紹介しました。また他にもZOZOMETRYのシステムについていくつか記事を執筆しています。バックエンドなどのシステムについて詳しく知りたい方は、ぜひそちらも合わせてご確認ください。 techblog.zozo.com ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com 本検証に利用した3Dボディスキャナーの計測精度は±0.5%の公称誤差(1000mmの周囲長に対し±5mmの計測誤差が出る精度) ↩