Pepabo Tech Portal
https://tech.pepabo.com/
GMOペパボのエンジニア・デザイナーによる技術情報のポータルサイト
フィード

GA 直後の Amazon S3 Files を SUZURI の本番 EKS に投入し コンテナイメージを 1/20 に圧縮した
Pepabo Tech Portal
はじめに課題:assets が コンテナイメージに焼き込まれる構造的問題 lens/lens2 が扱う assets とは移行前の状況S3 Files という選択肢 当初の候補: EFS + DataSync2026 年 4 月 7 日 GA: Amazon S3 Filesアーキテクチャ設計 移行後の全体像設計のポイント実装: lens2(Rust)で先行検証 EKS への S3 Files マウントPhase 2a: 動作確認(並列マウント)Phase 2b: ASSETS_DIR 切り替えPhase 2c: Dockerfile から assets を除外ハマりどころ: EFS CSI Driver を動かしてわかったこと 1. inline (Ephemeral) CSI volume は非対応2. accessModes: ReadOnlyMany が非対応3. volumeHandle に s3files: プレフィックスが必須4. mountOptions: [iam] の明示指定が必要5. controller SA と node SA に別々の IAM ロールが必要補足: S3 Files 障害時の挙動と監視計測結果 lens2(Rust)本番実測lens(JavaScript)本番実測S3 Files の読み取りレイテンシ(lens 本番実測)lens(JavaScript)へのロールアウト lens ならではの差異: 上書きマウント方式lens2 のノウハウがそのまま活きたまとめ 成果サマリS3 Files を選んでよかった点さいごにはじめにこんにちは、技術部技術基盤グループで SUZURI / minne / カラーミーショップなどのインフラをサービス横断で担当している shibatch です。SUZURI は、オリジナルグッズを手軽に作れる・購入できるサービスです。ユーザーがアップロードした画像とあらかじめ用意した商品テンプレート(assets)を合成して、Tシャツやマグカップの完成イメージを生成する「画像合成サービス」が中核を担っています。この処理を担うのが lens と lens2 という 2 つのサービスです。lens: JavaScript + ImageMagick 製の画像合成サービス(主力)lens2: Rust + Imag
20日前

GitHub Actionsの実行遅延をCloud SchedulerとCloud Workflowsで解消する
Pepabo Tech Portal
こんにちは、技術部データ基盤チームの zaimy です。GitHub Actionsの schedule: トリガーが大幅に遅延する問題を、Cloud SchedulerとCloud Workflowsで解消した話を書きます。最終的に採用した構成だけでなく、検討して棄却した構成と棄却理由も合わせて紹介します。背景検討した構成 案A: Cloud SchedulerからGitHub APIを直接叩く案B: Cloud Schedulerから起動するCloud WorkflowsでGitHub Actionsを置き換える案C (採用): Cloud Schedulerから起動するCloud WorkflowsがGitHub ActionsをdispatchするなぜCloud KMSが必要なのか実装 Cloud KMS asymmetric keyの作成 (terraform)GitHub Appのprivate keyをCloud KMSにimportCloud WorkflowsのYAMLハマりどころ Import methodの選択 (-aes-256 の有無)PEM → PKCS#8 DERへの変換結果その他の構成案まとめ背景データ基盤チームでは、毎日の昼会で「前日のチーム内アップデート」を共有しています。GitHub Projectsのカードを更新者やステータスに基づいてアップデート種別 (例: 話題にすべき / 軽く触れる / ステータス移動) を自動分類してラベル付けするGitHub Actionsを、昼会の少し前に走らせる運用にしていました。このAction自体はラベル付与にClaudeを使う仕組みになっていて中身も面白いのですが、本記事の本題はそこではなく、起動タイミングの話です。このActionは schedule: トリガー (cron) で平日11:55に実行するよう設定していましたが、実際の実行時刻は以下の通り、毎日60分以上の遅延がある状態でした。日付 cron 設定 (UTC) 実際の実行時刻 (UTC) 遅延 4/22 Wed 02:55 03:55 +60min 4/21 Tue 02:55 03:57 +62min 4/20 Mon 02:55 04:02 +67min GitHub Actionsの schedule: はベストエフォ
22日前

データ基盤のワークフロー構成変更によるコスト84%削減とCI 34倍高速化
Pepabo Tech Portal
技術部データ基盤チームの@zaimyです。ペパボの社内データ基盤「Bigfoot」では、2020年にDigdagから移設して以来約5年間にわたりCloud Composer(Managed Apache Airflow)をワークフローエンジンとして運用してきましたが、2026年1Qをもって別構成に移行しました。本記事では、Cloud Composerから3つのマネージドサービスへ移行した経緯と、その効果について紹介します。Cloud Composerで動いていた3つのワークロードなぜ撤退したのか 1. Agentic AI readyになるためにdbtを身軽にしたい2. コストの膨張3. 運用負荷の高さ移行先コンポーネントの紹介 dbt CloudBigQuery Data Transfer Service (DTS)Cloud Workflowsコスト比較開発効率比較運用面の比較エラーレート比較まとめCloud Composerで動いていた3つのワークロードデータ基盤ではCloud Composer上で主に以下の3つのワークロードを動かしていました。dbtによるデータ変換(Cosmos経由でAirflow DAG上で実行)事業DBからBigQueryへの同期dbt以外のデータ変換や機械学習パイプラインなどその他のオーケストレーションなぜ撤退したのか3つの課題が重なったため、2026年1Qでの撤退を決定しました。1. Agentic AI readyになるためにdbtを身軽にしたい技術部の方針「Agent Ready」に基づき、AIエージェント前提の技術基盤づくりを進めるにあたって、dbtでSSoTなデータマートを作った上でAgentic AIによる分析を実現したいと考えています。そのためにdbtをより身軽に使いたいのですが、Airflow上でdbtを実行する構成はオーバーヘッドが大きく、AIを用いた開発運用においてフィードバックループが遅い状態でした。2. コストの膨張Cloud Composer 2からCloud Composer 3への移行や、使用量の増加により、月額コストが数十万円に膨張していました。3. 運用負荷の高さAirflowはPythonで作り込めるがゆえに、5年間の運用を経てコードベースが複雑になっていました。さらにCloud Composer/
22日前

ORDER BY id DESC が招くインデックス誤選択 — 直近のレコードを N 件取り出すクエリを実測 約 658 倍に高速化
Pepabo Tech Portal
はじめに「ある所有者の直近のレコード(注文・投稿・取引など)から、最新の N 件を取りたい」というユースケースは、Web アプリケーションを書いていれば頻出するパターンです。Rails 風に書けば次のような形になります。current_user.orders .where.not(id: @order.id) # 自分自身は除外する .order(id: :desc) # 「直近」を id 降順で表現 .limit(n)このパターンが、minne では特定のユーザーで安定的に MySQL のクエリタイムアウトを起こしていました。Mysql2::Error: Query execution was interrupted, maximum statement execution time exceeded調査の結果、原因は ORDER BY id DESC + LIMIT N の組み合わせによる MySQL optimizer のインデックス誤選択 で、worst case で 2,300 万行をスキャン して max_statement_time を踏み抜いていることが判明しました。ORDER BY のカラムを既存インデックスに合わせて変えるだけで、スキャン推定行数: 23,297,383 行 → 65,024 行(約 358 倍削減)実測実行時間: 56.6ms → 0.086ms(約 658 倍高速化)まで縮みました。本記事では、その原因分析と修正アプローチを EXPLAIN ANALYZE とあわせて紹介します。「直近のレコードを N 件取り出す」クエリを書くすべての人に還元できる知見となることを願っています。はじめに何が起きていたか原因 — ORDER BY id DESC + LIMIT N で optimizer が PRIMARY を選ぶ 「LIMIT を外せば直る」のではない修正 — ORDER BY を既存インデックスのソート順に揃える 補足: id DESC と ordered_at DESC の意味的な差比較サマリ学び参考何が起きていたか問題のあったコードは、ある作家の 直近の注文を N 件取り出す ためのクエリでした。本筋に関係しない部分を削ぎ落とすと、概形は次のようになります。current_user.orders .where.not(i
1ヶ月前

生成AIの従量課金とどう付き合うか?AIサイトエージェント開発で実践した段階的コスト見積もり
Pepabo Tech Portal
はじめにこんにちは。ロリポップ・ムームードメイン事業部でエンジニアリングリードをしています kinosuke01 といいます。生成AIを組み込んだアプリケーションを事業として提供するとき、避けて通れないテーマが コスト です。事業としてやっている以上、売上・利益・粗利率には当然ながら目標値があります。開発者としても「技術的に動けばOK」ではなく、その数字を前提にしたうえで、お金のことも勘案して設計や実装を進める必要があります。ところが生成AIをAPI経由で使う場合、消費トークン数による従量課金となるため、事前にどの程度のコストになるのかを見積もるのが一筋縄ではいきません。とくに生成AIが自律的に判断・分岐するワークフローだと、入力も出力もユーザーの指示内容に大きく依存するため、「このケースで n トークン」と簡単には言い切れません。本記事では、AIサイトエージェント開発プロジェクトで実施した、段階的にコスト試算の精度を上げていくアプローチを紹介します。完璧な見積もりを最初から作ろうとするのではなく、プロジェクトのフェーズごとに見積もり方法を変えていく話です。前提:AI サイトエージェントというサービス私たちが開発している AI サイトエージェント は、「カフェのサイトを作りたい」「フリーランス向けのポートフォリオが欲しい」といった自然言語の指示を投げると、ページ構成・デザインテーマ・コンテンツまでを一括で生成してくれる Web サイト制作サービスです。生成したあとも、チャット越しに「トップのキャッチを変えて」「このセクションの写真を差し替えて」と伝えれば、AI が編集を代行してくれます。内部のワークフローは、決定論的なワークフローをベースに、一部のステップで生成AIが自律的に判断・分岐する 構造になっています。全部を生成AIに任せるのではなく、「ここは構造が決まっている」「ここは自由度を持たせたい」をフェーズごとに使い分けることで、品質と予測可能性を両立させる狙いです。ざっくり図にすると、このような流れです。ラベルに「生成AI:」と書かれているブロックが生成AI呼び出しで、それ以外は決定論的な処理です。たとえば PageAndSectionPlanner は「ページ何枚構成にするか」「各ページにどのセクションを置くか」を、ユーザーの指示に応じて柔軟に決めます。一方で
1ヶ月前

Claude Code Skillでメール障害対応を実施
Pepabo Tech Portal
こんにちは、技術部 技術基盤グループのkmsnです。GMOペパボが運営するECサイト構築サービス「カラーミーショップ」で、Outlook/Hotmail/Live宛メールがブロックされる障害が発生しました。この記事では、Claude Code の Skill を活用して障害の初動対応を効率化した事例を紹介します。結論:Skill で障害対応の初動が変わった先に結論をお伝えします。今回の障害対応で最も効果的だったのは、Claude Code の Skill によって状況把握のスピードが大幅に上がったことです。カラーミーショップのメールサーバーは数十台あります。従来であれば、障害発生時に1台ずつ SSH して sudo postqueue -p を叩き、キューの状態を目視で確認していく必要がありました。台数が多いため状況把握だけで時間がかかり、その間もメールは滞留し続けます。今回は colorme-mailq Skill を使い、「メールキュー確認して」の一言で全台の状態を一括取得しました。サーバー active deferred 合計 server-1 0 3,842 3,842 server-2 12 0 12 ※ 上記は全数十台のうち抜粋特定サーバーの deferred が突出して積み上がっていることが一目でわかり、対応する方針をすぐに決められました。従来の手動確認では状況把握に時間を要していた工程が、Skill によって数秒で完了し、対応方針の決定までの時間を大幅に短縮できました。さらに、この Skill は社内のプライベートリポジトリに登録されているため、自分だけでなくチームの誰でも同じように使えます。個人のスクリプトではなく チーム共有の Skill として整備しておくことで、次に同様の障害が起きたときにも誰でも素早く初動に入れるという点が大きな価値です。何が起きたかカラーミーショップのユーザーが送信したメールが、Outlook/Hotmail/Live宛に届かずブロックされる事象が発生しました。Microsoft(Outlook.com)のような大手プロバイダーは、スパム判定によるブロック時に 5xx 系(主に 550)のエラーを返すことがあります。ただし、Postfix 側の設定や一時的なレスポンスにより deferred キューに滞留するケースもあり、
1ヶ月前

後付け可能な認証を NextAuth で設計する — ログイン機構が決まらないまま、ログイン前提のプロダクトを作った話
Pepabo Tech Portal
はじめにこんにちは。ロリポップ・ムームードメイン事業部でエンジニアリングリードをしています kinosuke01 といいます。「この機能はログインしたユーザーのものとして扱いたい」というのは、ほとんどのプロダクトで当たり前の要件となります。ところが、プロダクト本体の開発を進めたいタイミングで、ログインの仕組みがまだ決まっていないという状況に直面することがあります。後から差し替え可能にしておくというのは一つの手です。しかし「あとで差し替え」を甘く見ていると、いざ差し替えるときに思いのほか大がかりな書き換えが発生してしまう場合もあるのではないでしょうか。この記事では、AIサイトエージェント というプロダクトの開発で実際に直面したこの状況と、そこで取った方針について紹介していきます。要点を先にまとめると、以下の一点になります。決まっていない領域を、差し替え可能なレイヤーに封じ込める。そのレイヤーだけを「本物と同じ形の偽物」で置き、他のコードからは本物と区別できない状態で先に作り切る。具体的には、NextAuth.js を土台にした「本物と同じ形の偽物ログイン」を用意することで、後から本番のログイン機構(OIDC)に NextAuth インスタンスの差し替えだけで移行できるようになりました。以降の節で、この構造を順に分解していきます。前提:AIサイトエージェントとは本題に入る前に、舞台となるプロダクトの輪郭を簡単に共有しておきます。AIサイトエージェントは、「カフェのサイトを作りたい」「フリーランスのポートフォリオが欲しい」といった自然言語の指示を投げると、ページ構成・デザインテーマ・コンテンツまでを一括で生成してくれる Web サイト制作サービスです。生成したあとも、チャット越しに「トップのキャッチを変えて」「このセクションの写真を差し替えて」と伝えれば、AI が編集を代行してくれます。このサービスは、ロリポップ!レンタルサーバー と ムームードメイン のどちらからも利用できるようになっています。ロリポップのユーザーとムームードメインのユーザー、それぞれが AIサイトエージェントのコンパネに入ってサイトを作れる、というのが本番のユースケースとなります。技術スタックこの記事のコード例を読む前提として、プロダクトの技術スタックにも軽く触れておきます。フレームワーク: Next
1ヶ月前

プロンプトのtypoをCIで弾く ── TypeScriptの型でAIへの指示を守る
Pepabo Tech Portal
はじめにこんにちは。ロリポップ・ムームードメイン事業部でエンジニアリングリードをしています kinosuke01 といいます。先日ゴジラ-0.0のティザー映像が解禁されましたね。公開が楽しみです。さて、GMOペパボでは、ユーザーとの対話をもとにWebサイトをまるごと自動生成するAIサイトエージェントを提供しています。ユーザーが「カフェのサイトを作りたい」「コーポレートサイトがほしい」と伝えるだけで、ページ構成からデザインテーマ、コンテンツまでを一括で生成し、すぐに公開できるWebサイトを作り上げます。この仕組みの裏側では、Webサイトの構造をすべてJSONで表現しています。生成AIに対して「このJSONを埋めてください」と指示し、Structured Outputでフォーマットを固定することで、安定した出力を得ています。しかし、JSONの構造を固定するだけでは不十分でした。各プロパティに何を入れるべきかを正しく伝えるために、システムプロンプトでフィールドごとの説明を与えています。ここで問題になったのが、プロンプトに書いたプロパティ名と実際のコードの型定義がズレるリスクです。この記事では、TypeScriptの型システムを活用してプロンプトの正しさをコンパイル時に保証する仕組みを紹介します。サイト生成の仕組みJSONでWebサイトを表現する私たちのシステムでは、Webサイトを「セクション」の組み合わせで表現しています。ヒーローセクション、特徴紹介セクション、料金表セクションなど、複数のセクションを用意しており、それぞれがJSON構造を持ちます。たとえば、ヒーローセクションはこのような構造です。{ "component": "HeroSection", "props": { "headline": { "text": "想いを、かたちに。" }, "title": { "text": "あなたの「やりたい」を実現するために" }, "primaryButton": { "label": "お問い合わせ", "href": "/contact" }, "layout": "split-content-image", "image": { "imageId": "hero-1", "alt": "メインビジュアル" } }}3つのエージェントによる段階的な生成サイト生成は、
2ヶ月前

flaky testの原因は、無関係なファイルの1行にあった
Pepabo Tech Portal
SUZURI Webアプリケーションエンジニアのarumaです。昨日の記事では、SUZURIで遭遇したflaky testの事例をいくつかご紹介しました。本記事では、その中でも特に原因の特定が難しかった1件を深掘りします。発生していた現象手がかり1: 自前のメソッドが呼ばれていない手がかり2: フレームワークに同名メソッドが追加されていた手がかり3: ancestorsチェーンの順序が変わっている手がかり4: トップレベルでのinclude真相おわりに発生していた現象ApplicationHelper に定義された画像表示用のヘルパーメソッド picture_tag のテストが、時々failしていました。fail時のログを確認すると、picture_tag が期待と異なるHTMLを生成していました。CIの実行ログからpass時とfail時それぞれのseed値を確認し、ローカルで同じseedを指定して実行してみると、seed値に応じてpassとfailが再現しました。手がかり1: 自前のメソッドが呼ばれていない試しに ApplicationHelper#picture_tag の中身に raise を加えてそれぞれのseedで実行してみると、passしていたseedで実行 → 例外が発生failしていたseedで実行 → 例外は発生せず、同じようにfailという結果になりました。つまり、failするseedでは、テスト対象 ApplicationHelper#picture_tag がそもそも呼ばれていないということです。では、代わりに何が呼ばれているのでしょうか?手がかり2: フレームワークに同名メソッドが追加されていたpicture_tag で検索してみると、Rails 7.1で ActionView::Helpers::AssetTagHelper に同名の picture_tag メソッドが新規追加されていたことがわかりました。ApplicationHelper#picture_tag とは異なる動作をするメソッドです。fail時の出力は、この AssetTagHelper#picture_tag の動作と一致していました。failするseedでは、ApplicationHelper#picture_tag ではなく AssetTagHelper#pictur
2ヶ月前

シニアエンジニア所信表明 — レバレッジと横断性で事業やサービスが向かうべき先に未来を作る
Pepabo Tech Portal
こんにちは!ロリポップ・ムームードメイン事業部ムームードメイングループのはるおつ(@haruotsu_hy)です。2026年4月1日にシニアエンジニアになりました。このエントリーでは、シニアエンジニアとしてこれから何をしていくのかを、ここに至るまでの経緯とあわせて書きます。所信表明シニアエンジニアとして、レバレッジの効く開発と横断的な行動を武器に、目の前の課題を技術構造ごと解き、事業やサービスが向かうべき先に未来を作る。その成果を全社・業界を動かすレベルまで広げていきます。レバレッジの効く開発とは、一つの技術投資が時間軸・組織軸・技術軸で複数の価値を生む開発です。個別の問題を個別に解くのではなく、構造的に解く。目の前のタスクの背後にある構造を変えることで、まだ見えていない未来の課題まで同時に解決する。横断的な行動とは、価値を届けるために自分から境界を越えに行くということです。技術領域も、組織も、職域も関係ない。価値があるなら自分のチームに閉じさせず、どこまでも持っていく。境界を一つ越えるたびに、同じ投資の効果が倍になっていく。レバレッジで一つの投資の価値を最大化し、横断性でその価値の届く範囲を最大化する。この掛け算で、事業が向かうべき方向に対して技術の力で道を作る。それが私のエンジニアリングです。ペパボのエンジニア職位制度GMOペパボのエンジニア職位制度は立候補制です。「作り上げる力」「先を見通す力」「影響を広げる力」の3軸で評価され、基準を超えた人が立候補資料を提出して昇格の判断を受けます(詳細)。判断は「追認制」、つまりすでに実質その等級として振る舞えている人だけが昇格します。これまで私は2024年4月、新卒年収710万プログラムのエンジニアとしてペパボに入社しました。プログラムの導入背景にあるように、尖った技術力や専門性を幹にしながら複数の枝を生やし、すべてを自分ごと化しながら高いモチベーションと覚悟でチャレンジしていくことが求められる採用枠です。その期待に応えるべく、ペパボの「やっていき・のっていき」文化の「いいじゃん!」に背中を押されながら、同期や先輩パートナーとともに、速度感をもってありとあらゆることに挑戦し時には職種を超えて社内外で注目される活動を重ねてきたように思います。しかし、2025年8月に行った1度目の立候補では、シニアエンジニアになることは叶
2ヶ月前

SUZURIで遭遇したflaky testの事例集
Pepabo Tech Portal
SUZURI Webアプリケーションエンジニアのarumaです。SUZURIにはRSpecによるテストコードが多数ありますが、一時期flaky testが増えてCIが不安定になってしまったことがありました。まとめて調査・対処した際の記録から、いくつかの事例をピックアップしてご紹介します。なお、記事中のコードは説明のために簡略化したものです。事例1: 2026年になると失敗するテスト事例2: 深夜0時ちょうどにのみ失敗するテスト事例3: 保証のない順序に依存していたテスト事例4: 不正なテストデータが作られていたテスト事例5: 偶然の一致で失敗するテスト番外編: そもそも実行されていなかったテストおわりに事例1: 2026年になると失敗するテストこれは厳密にはflaky testというより「ある時点から必ず失敗するようになったテスト」ですが、テストの書き方に共通する教訓があるので紹介します。クレジットカード決済に関するテストのセットアップ内で、カードの有効期限がハードコードされていました。let(:credit_card) do create( :credit_card, expire_year: 2025, expire_month: 12 )endこのコードが書かれたのは2020年。5年間は問題なく動いていましたが、2026年を迎えた途端に有効期限切れの無効なカードとなり、テストの検証対象とは無関係な理由でエラーが発生するようになってしまいました。対処として、有効期限が常に未来の日付となるよう、現在の日付から算出する形に変更しました。let(:credit_card) do create( :credit_card, expire_year: Time.zone.today.year + 1, expire_month: 12 )end似た話として、travel_to で固定した時刻がSQLの now() には反映されないために、テストとDBで時刻がずれていたケースもありました。travel_to はRubyプロセス内の時刻を差し替えるだけなので、DBの now() には効きません。これも月をまたいで初めて顕在化しました。時間の経過でテストの前提が崩れないようにすることが大切です。事例2: 深夜0時ちょうどにのみ失敗するテスト夜間に自動作成されるPRだけなぜか時々
2ヶ月前