footballnext-operation
WIP(現在進行中)
Work In Progress
このプロジェクトで現在進行中の作業と、過去のスナップショットを記録する。
現在の状況
テーマ(2026-06-19〜・起動時広告A/B 実装レビュー round1〜10 完了・sign-off 確定・実装レーン完了/点火前チェックリスト待ち)
実装コードレビュー(gods-talk)を round10 まで回し、App Open / native lifecycle / kill属性 / TTL / 計測契約の全 high/medium を解消。round8 で sign-off 取得 → round9 で R7-#2 revert(render 失敗の過大計上)を medium 差し戻し → round10 で gate 復元(ios 311e3ad)を確認し「round9 medium close・round8 sign-off 回復・新 high/medium ブロッカーなし」を取得。実装レビューレーンは完了。 本番は中立(active=false / v7 / ttl14日)、5.5.3 据え置きで全修正積載。reviewer は通常 native 表示の短い実機再スモークを「望ましいが medium close の必須前提にしない」と明示(gate は sign-off 済み実装へ復帰・A-1 で rendered=true/描画/ILRD/GA4 impression 確認済み・Release 追加差分はコメントのみ)。残るは運用・外部タスクのみ(GA4 2件登録・5.5.3 提出・点火)。 5.5.3 実機スモーク A-0〜A-3 は前段で全PASS済(expire 人工注入 hook -ABNativeSimulateExpire・#if DEBUG 隔離・本番無効)。
点火前チェックリスト(コード外・必須。完了後に active=true)
- GA4 custom 定義の登録 — 完了(2026-06-19・You が管理画面で手登録): 追加2件(
AB Kill Observeddim/kill_observed・AB Applied Interval Minutesmetric/applied_interval_minutes・分)を登録済=全23件。着弾の DebugView 目視は点火後に実施。登録前データは遡及されないため点火前登録の順序は満たした。 - enrollment 窓・判定値 — 確定済(2026-06-19): interval_a=50/interval_b=1440・締切42日/追跡28日/ITT・required_assignment_version=1・min_build=50503(5.5.3+限定)・ttl14日。enrollment窓は窓長42日固定・起点は5.5.3リリース日(+猶予)を点火時セット(旧6/22は過去化で不使用)。詳細は runbook 点火前チェックリスト。
- 初期
experiment_active=falseでの本番 config 取得確認 — 確認済(2026-06-19): 本番ab_test_config.json実取得=200・valid・active=false・v7・全互換値(experiment_id/assignment_version=1/event_schema_version=v1/mp_2026q3_r1/interval 50・1440/ttl14日)が ABProtocol と一致。enrollment窓は旧値だが active=false で無効(点火時 v8 で再設定)。on-device 非参加挙動は round4 実機PASS。 - 点火・停止時刻を記録する運用準備 — 準備済(2026-06-19): runbook に「点火・停止ログ」表を新設(停止境界の正典・GA4アノテーション併用)。現状 v7/active=false を記録、点火/停止行は実施時記入。
→ 点火前タスクの状況(2026-06-19 時点): GA4 2件登録=完了。5.5.3 native スモーク=完了(A-0〜A-3 全PASS)。5.5.3=ビルド準備完了・App Store 申請は2026-06-20 予定(リリース=審査通過後)(点火の enrollment窓起点は store でライブになった日+猶予)。**申請前に実況板バグ(もっと見る→上に戻るでライブカード消失)を修正済(ios
f275fed・実機OK)=5.5.3 に同梱。**残る点火手順: S3 を v8/active=true・enrollment窓(リリース日起点42日)・min_build=50503 で配置+点火ログ記入+同時刻に GA4 アノテーション。 配置は--acl public-read・profile=thomson-ik。v8 雛形ab_test_config.v8.template.json用意済(固定値埋め済・enrollment 2日付けのみ点火時にリリース日から算出)/runbook に生成・配置手順(date→epoch コマンド込み)を新設。 ライブab_test_config.jsonは v7/active=false のまま据え置き。
実装レビュー(gods-talk)round5〜8 の到達(push済)
- round5 ios
0089f65/ opab066e4: R5-#1 native確定destroy(lease制・固定0.6秒撤去・実機スモークPASS)/ #2 pending timeout(30s)+expire / #3 kill属性 snapshot↔applied分離・clear順序。 - round6 ios
3f64ade/ op546dd0a: R6-#1 callback main集約+timeout世代照合 / #2 dequed-expire無効化 / #3 GA4-only整合(kill_observed必須化) / #5 TTL省略可統一。 - round7 ios
31dc22b/ op07aa09f: R7-#1 stale判定をcellForRow前段へ+同一pass再deque / #2 renderable !expired + render成功時のみimpression / #3 self-test fail-hard / #4 kill_observed_at削除。 - round8: 点火 sign-off。残 low(render-false即時再取得なし=fail-closed/self-test範囲/廃止param説明文/runbook commit表記)は全て非ブロッカー。
- round9 ios
311e3ad/ op4e489da: sign-off 後の R7-#2 revert を reviewer が medium 差し戻し(SDK 契約上 false=render失敗で計上不可)。→ impression gate 復元(true 時のみrecordImpression・false 時 fail-closed)+ expire hook を#if DEBUG隔離。Debug/Release 両ビルド 0 errors。 - round10 op(本コミット): gate 復元(
311e3ad)を確認し round9 medium close・round8 sign-off 回復・新 high/medium ブロッカーなし を取得。実行ロジックは31dc22bの R7-#2 と機能的に同等へ復帰。通常 native 表示の短い実機再スモークは「望ましいが medium close の必須前提にしない」と明示。実装レビューレーン完了。 - 検証: 各round sim build 0 errors・改修ファイル新規警告なし・self-test 7/7。limitation: sim は MAX native fill なしで timeout/expire の end-to-end は未実行(destroy判定は純粋関数 self-test+外部レビューで担保)。
5.5.3 実機スモーク結果(2026-06-19・iPhone 実機・Xcode直挿し)
- A-0 self-test: PASS(
-ABNativeSelfTestでdestroy-logic 7/7・abort せず)。 - A-1 描画・ILRD・impression: PASS。
fn_ad_impression(middle_native)が DebugView ライブに出ず調査したが、実はコードバグではなかった: renderNativeAdView は rendered=true(描画成功)で recordImpression→logEvent まで毎回到達し、GA4 Realtime で fn_ad_impression 増加を確認=収集できている。DebugView 非表示は表示/タイミングの癖で、カスタム定義の登録有無とは無関係(DebugView は登録不問で全イベントを出す。登録が効くのは Explore/レポートのみ)。fn_ad_impression は収集OK・slot/format/network も既登録=レポートでも使える=やること無し。- 補足の訂正: 途中で出した「Bool gate が impression を握り潰している」という見立ては誤り(rendered=true なので R7-#2 有無に関係なく元々計上されていた)。ただし R7-#2 revert(ios
5eaf3be)を「無駄&危険な分岐の簡素化」として残す判断をした。 - 【round9 で覆った】この revert は overreach だった。reviewer 指摘: SDK 契約上
renderNativeAdView=false は render 失敗を意味し、観測で false が出なかったことは「false を成功扱いしてよい」根拠にならない(別 network/view 再利用/将来 adapter 差分で false がありうる)。→ impression gate を復元(true 時のみ計上・false 時 fail-closed、ios で revert を差し戻し)。R7-#2 を本アプリの計上判定に使わない、という結論は撤回。
- 補足の訂正: 途中で出した「Bool gate が impression を握り潰している」という見立ては誤り(rendered=true なので R7-#2 有無に関係なく元々計上されていた)。ただし R7-#2 revert(ios
- A-2 lease 負荷: PASS(スクロール往復・pull-to-refresh・More連打でクラッシュ0・空白固まりなし)。
- A-3 expire→再deque: PASS。
-ABNativeSimulateExpire(load 成功 ad を10秒後に人工 expire)で連続 churn 耐久 → クラッシュ0・新 ad 差し替え継続・stock 暴走なし。debug hook 追加(ios777128f・launchArg 限定・本番無効)。
確定した実装仕様(GATE凍結済み)
experiment_id="app_open_interval_2026q3"/assignment_version=1/measurement_protocol_version="mp_2026q3_r1"。- 割当:
SHA256(UTF-8("experiment_id:install_uuid"))先頭8バイト big-endian% 100、[0,50)→A。新 experiment は自動再無作為化。 - enrollment窓: 2026-06-22 0:00〜2026-08-03 0:00 JST(42日)。追跡 trackingDays=28(JST暦日)。TTL=14日。
- 詳細値は正典 SPEC.md「measurement_protocol 凍結値」と
ABProtocol。
詰まっていること・未決事項
- R2-#4 / R2-#3 が点火ブロッカー(上記 残 must-fix)。R2-#3 は実機/TestFlight検証必須。
- ga4 MCP が本セッション未接続=GA4着弾の DataAPI 裏取り不可(点火後検証は接続要 or DebugView 目視)。
- 月次総売上(iOS・FN・対象月)はトムソン提供待ち=解析フェーズ入力(点火ブロッカーではない)。
- S3配置は
--acl public-read必須(footballnextバケットはオブジェクト単位ACL運用・無ACLだと403)。profile=thomson-ik。 - Swift の
SWIFT_ACTIVE_COMPILATION_CONDITIONSに DEBUG 未設定→seed/AB_EVENT検証時は上書き要。seed は実fetchをスキップし決定化済(config_version=999999)。 - 正典 SPEC.md(rev6)が唯一の source of truth。
旧テーマの継続タスク(インフラ・前回まで)
- API Gateway 飛び地の原点直叩き移行 → footballnext API GW 撤去。iOS
OshiraseViewControllerデッドコード整理(削除済みfootballnext-oshirase-v2をOshiraseViewController.swift:30が参照中・据え置き)。rss-crawler の cron 一元化。 - iOS 5.5.1(YouTube WKWebView 化込み)を App Store 提出(実機再生確認は未実施)。footballnext-ios に別件未コミット変更(push通知系・Podfile 等)残置中。
- (継続)xcuserstate 追跡、android 未push、cake_sessions Fatal 放置。
別件で要対応(サーバ側、今回も触らず)
- ⚠️ RDS SG が 3306/tcp を
0.0.0.0/0開放 - ⚠️ DB パスワードが
database.phpに平文コミット - cron EC2 が Amazon Linux AMI 2018.03(EOL)
- リードレプリカ復活 / gp2 → gp3 はコスト追加 NG 方針につき凍結中
前テーマの残タスク(多角レビュー・2026-06-05)
- 改善実装の着手判断(最優先=App Open Ad スロットル復活、広告挿入間隔の緩和)。効果検証はGA4の離脱シグナルで。
- iOS 5.5.1 を Xcode アーカイブ→App Store 提出。レビュー結論と元データは
review-20260605.html。
過去のWIPアーカイブ
(新しい「現在の状況」を書く前に、古いものをここに追記でアーカイブする。新しいものが上)
2026-06-19 14:30 時点のスナップショット(Codex round3 high3+medium4+low1 修正+実機回帰PASS・round4待ち)
点火→緊急停止→round1/2修正→実機A〜D PASS→round3で新high3(判断ミス2件含む)→修正+実機回帰スモークPASS。本番中立(active=false/v7/ttl14日)、5.5.3据え置き。R3-#1/#2 5db2975(decision/show一体claim+placement照合)・#3 5d26ee6(native cap撤去→deinit destroy)・#4-7 ef4d47e・#8 791952f+runbook 0789c73。実機(iPhone14/seed): A native+ILRD / B content_reached / C 群B処置(decision=interval) / D #11 全PASS。次は round4 で R3 確認→5.5.3提出。(→ round5〜8 で native lifecycle/kill/計測の残 high/medium を全解消し round8 で点火 sign-off に到達)
2026-06-19 08:20 時点のスナップショット(点火直前:段階A配置+pre-flight PASS)
テーマ: 点火前チェックリストを全消化。不活性config(active=false)を本番S3配置済み・シミュレータで本番S3取得経路を実証。残りは段階B点火のみ、Youのgo待ち だった。enrollment窓 6/22〜8/3 JST 確定。GA4 custom定義21件登録済。トムソン3項目HTML公開(叩き台確定扱い)。→ この後 点火(active=true)したが Codex実装レビューで重大バグ多数→緊急停止→high/medium修正フェーズへ(上記「現在の状況」)。
2026-06-19 02:32 時点のスナップショット(点火準備フェーズ:BASE実装+GATE凍結+GA4登録完了・段階A配置前)
テーマ: 起動時広告 削減A/B は実装・GATE値凍結・GA4 custom定義登録(21件)まで完了。S3配置用 ab_test_config.json(active=false)+ ab_test_deploy_runbook.md 作成済。当時の残タスク=①トムソン通知 ②enrollment期間決定 ③S3配置→点火 ④Data API検証。本番影響は中立。→ この後 08:20 までに トムソン3項目HTML公開(叩き台確定扱い)・enrollment窓確定(6/22〜8/3 JST)・段階A=不活性config配置・シミュレータpre-flight PASS を完了(上記「現在の状況」)。残るは段階B点火のみ。
2026-06-19 01:03 時点のスナップショット(BASE A〜F 実装完了・点火準備着手前)
テーマ: 起動時広告A/B の計測基盤 BASE A〜F を実コードで実装完了(ios commit 4ebdeb6)。新規4ファイル+既存14ファイル配線。フルビルド成功・シミュレータ実機で experiment_enrolled→target_launch→app_open_ad_decision→content_reached 発火確認。本番影響は中立。当時の次タスク=GATE値確定・S3 config配置・GA4登録・native refactor。→ この後 02:32 までに GATE値叩き台凍結・config/runbook作成・native lifecycle修正・GA4 custom定義 全21件登録 を完了(上記「現在の状況」)。
2026-06-19 01:00 時点のスナップショット(実装計画フェーズ完了・実装着手前)
テーマ: トムソン承認後、実装計画HTMLを作成し Codex(gods-talk)5ラウンドで設計レビュー完了。実装計画 rev6 と正典 SPEC.md rev6 が同期済み。この時点でコード変更はまだ無し(資料・正典・レビュー往復のみ)。確定事項: assignment/activation/enrollment の3分離(3 artifact+版番号)、ForegroundCycle/TargetLaunchContext 2層+60秒gate、可視判定は状態機械、kill/crash対策に write-ahead OpenContextRecord+CAS、GA4送信 at-most-once、収益 canonical fn_ad_revenue 一本化、円/人 固定コホート、暦日 Asia/Tokyo。実装は3レーン(BASE着手可 / BASE契約5点 / GATE-値)。→ この後 2026-06-19 にBASE A〜F を実装完了(上記「現在の状況」)。
2026-06-18 23:55 時点のスナップショット(提案書フェーズ完了・承認前)
- 起動時広告 削減A/B の提案書フェーズが完了していた状態。提案書
起動時広告A-Bテスト_提案書_20260618.htmlをthomsonsS3 に公開(URL:https://thomsons.s3.ap-northeast-1.amazonaws.com/起動時広告A-Bテスト_提案書_20260618.html)、Codex提案書レビュー2ラウンド(.gods-talk/ab-test-proposal-review-*)反映済み。 - この時点の確定設計: 群A=現状(50分)/群B=24h rolling、ITT、GA4のみ(BigQuery不使用)、起動時は群別
didPayRevenue実額・それ以外は混合eCPM概算、残り具合=コンテンツ未到達離脱率+セッション/人+D7/D14。正典AB_TEST_MEASUREMENT_SPEC.md初版。 - 次アクションは「トムソンの承認待ち+計測仕様TODO消化」だった。→ その後承認が出て実装計画フェーズへ移行(上記「現在の状況」)。提案書時点のTODO(rolling24hのabsバグ・固定コホート・収益式の為替/対象範囲・設定取得SLA・事前テスト合格基準)は実装計画 rev6 で大部分が確定仕様化された。
2026-06-18 10:32 時点のスナップショット
テーマ: 起動時広告 削減A/B の測定設計(机上)。50/50無作為A/Bで実測判断する設計を、コードの現状確認込みで詰めた。当時の骨子(※この後の議論で更新済み・最新は「現在の状況」と SPEC 参照): 群A=現状(毎回と誤認)/群B=1日1回、判断②に launch_bounce、3パターン即採用、勝った間隔を S3 展開。コードの現状: 起動時広告は AppDelegate.swift:241 で明示 showAdIfReady()(ifで群分け可)、appOpenAdInterval スロットル+GA4配線済み、間隔は S3(Const.baseS3Url/201811)→UserData.setAppSettings で変更可、広告はパッチワーク(MAX外に AdG/Zucks/LINE、didPayRevenue 空)。当時の次アクション=提案資料化。
2026-06-13 00:42 時点のスナップショット
直近の割り込み対応(完了): iOS YouTube プレイヤーの WKWebView 化。ナレッジ ios-wkwebview-youtube-inline-playback(7channel 由来)を YoutubePlayerView.swift に適用。UIWebView 廃止・baseURL/origin 一致(Const.baseURLString)・https 化・自動再生オフ(タップで再生)。arm64 シミュレータビルド成功。コミット a94aa84(この1ファイルのみ選択コミット)→ push 済み。You が 5.5.1 申請予定(実機再生確認は未実施)。footballnext-ios に別件の未コミット変更(push通知系・Podfile 等)残置中。
テーマ: 今後のアプデ前の足場固め=定期実行の一元化(cron 専用 EC2 へ寄せる)+ AWS Lambda 大掃除。
どこまで進んだか: TFN_cron を cron サーバーへ移管完了(8エンドポイントを crontab 化・Lambda 本体+ルール削除・~/crontab_backup_20260606-073553.txt 退避)。Lambda 大掃除完了 42関数 → 9関数。teiki/article API 経路解析済み(API GW → Dynamo キャッシュ Lambda → CakePHP api4 の三段、execute-api 経由は飛び地数本)。
次にやること(当時): API Gateway 飛び地の原点直叩き移行 → footballnext API GW 撤去。iOS OshiraseViewController デッドコード整理。rss-crawler の cron 一元化。多角レビューの改善実装着手判断(最優先=App Open Ad スロットル復活)。
詰まっていたこと: 削除済み footballnext-oshirase-v2 を iOS が参照中(デッド画面と判断し据え置き)。
2026-06-06 17:32 時点のスナップショット
テーマ: 今後のアプデ前の足場固め。定期実行の一元化(cron 専用 EC2 へ寄せる)+ AWS Lambda の大掃除。
どこまで進んだか: TFN_cron を cron サーバーへ移管完了(8エンドポイントを crontab 化・Lambda 本体+ルール削除・~/crontab_backup_20260606-073553.txt 退避)。Lambda 大掃除完了 42関数 → 9関数(残9は全て現役 or 保守ツール)。teiki/article の API 経路解析済み(API GW → Dynamo キャッシュ Lambda → CakePHP api4 の三段、execute-api 経由は飛び地数本のみ)。
次にやること(当時): API Gateway 飛び地の原点直叩き移行 → footballnext API GW 撤去。iOS OshiraseViewController のデッドコード整理。rss-crawler の cron 一元化。
詰まっていたこと: 削除済み footballnext-oshirase-v2 を iOS が参照中(デッド画面と判断し据え置き)。CloudWatch 長期間1バケット集計の過少値問題は7日日次合算で回避。
2026-06-05 19:50 時点のスナップショット
テーマ: Football NEXT の多角サービスレビュー(large-scale-service-review 試運転)。広告・収益・行動・コードの複数ソースを診断し review-20260605.html に出力。GA4 新規接続。
どこまで進んだか: レビュー完了・HTML出力済み(6ソース三角測量)。GA4 接続済み(MCP ga4 / property 211701453 / ADC流用)。元データ一式が repo ルートに存在。スキル改善済み。
レビュー結論: 広告過多によるデススパイラル(広告売上 2019¥17.5M→2025¥5.4M −69%、レビュー星 2022→2023 で 3.0→1.6 崩落+広告言及率93%、GA4 MAU 約2年−32%、全て時系列一致)。最優先=App Open Ad スロットル復活、次いで不快クリエイティブのブロック・ストア評価運用、中位=有料広告なし課金(StoreKit)・計測基盤。
詰まっていたこと: AdMob/AppLovin MAX が別管理者で面別広告収益・eCPM取得不可。iOS OneSignal 2.12.0 が arm64 simulator 非対応。
2026-06-03 17:15 時点のスナップショット
テーマ: 試合詳細に「得点者」表示を追加するフルスタック機能(サーバ:events_json 保存+V2 で scorers 配信+過去試合バックフィル用 updateFixture / iOS:得点者カード 5.5.1)。本番反映・トムソン確認OK・iOS タグ 5.5.1 push 済み。
どこまで進んだか:
- サーバ(footballnext-server)完了・デプロイ済み:
fnlive_fixtures.events_json追加(ALTER)。Cron で events 保存、V2Controller::fixture()がscorers(得点時間/選手/アシスト/PK/OG/home-away)を配信。終了試合も埋めるupdateFixture($id)(/cron/updateFixture/{id})新設。AfterInstall でモデルキャッシュ自動クリア。直近10試合バックフィル済み(全件 scorers=得点数 一致)。 - iOS(footballnext-ios)完了・push/タグ済み: 5.5.1。得点者カードを記事テーブルのヘッダー(
LiveFixtureScorersView)。コミットa3793be、タグ5.5.1。 - footballnext-android: 変化なし(Play Console アップロード待ち)。RDS/サーバ基盤: 平常運用。
次に何をするか: iOS 5.5.1 を Xcode アーカイブ→App Store 提出。任意で得点者まわりの dead code 掃除。過去試合の得点者は updateFixture で個別バックフィル。
詰まっていること: iOS dead code(未使用 xib + 空セル)。xcuserstate 追跡継続、android 7コミット未push、cake_sessions Fatal 放置。
2026-05-30 20:00 時点のスナップショット
テーマ: footballnext-ios の近代化(広告SDK全面更新・Zucks/Moloco 追加・GMA13 移行)完了+GitHub リポジトリ運用の正常化。
どこまで進んだか:
- footballnext-ios 近代化(完了・push 28f5fe7): Xcode 26.5 ビルド復旧、Zucks/Moloco 追加、広告SDK一斉最新化、GMA13/iOS13 化、Liftoff+SKAdNetwork 補完。
- リポジトリ正常化: operation 配下を GitHub クローン(master / Pods gitignore)へ置換、旧 CodeCommit クローンは
~/Downloads/footballnext-ios-codecommit-backupに退避。 - footballnext-android: 16KB対応 AAB(3.0.7 / vc17)生成済み、Play Console アップロード待ち。
- RDS/サーバ: 平常運用。
次に何をするか: 旧 CodeCommit バックアップ削除、実機で MAX Mediation Debugger 再確認、xcuserstate 追跡解除、android AAB アップロード、android 7コミット push 判断。
詰まっていること: GMA 13.3 止まり、dSYM 警告(無害)、xcuserstate 追跡継続、android 7コミット未push、cake_sessions Fatal 放置。
2026-05-21 19:35 時点のスナップショット
テーマ: footballnext-android の 16KB 対応リリース対応 ── コード作業は完了・実機検証済み、Play Console アップロード待ち。RDS/サーバ側は平常運用継続。
どこまで進んだか:
- footballnext-android 16KB対応(対応期限 2026-05-31): 原因=Realm の native lib のみ。Realm 7.0.8→10.19.0(16KB対応版)で解消。
- 4年分のツールチェーン近代化(Gradle 8.9 / AGP 8.7.3 / Kotlin 2.1 / targetSdk 35 / minSdk 24)、ViewBinding 全面移行(43ファイル)、依存刷新(OneSignal 5 / AdMob 23 / okhttp 4)、nend撤去、YouTubeプレイヤー差し替え。
- 実機テストでクラッシュ4件を発見・修正。署名済みリリースAAB(versionCode 17 / 3.0.7)生成、16KBアラインメント検証OK。
- footballnext-android リポジトリに7コミット(ローカル、origin 未push)。
次に何をするか:
- AAB を Play Console にアップロード(内部テスト→製品版推奨)、動画/コメント/設定画面の手動確認、OneSignalプッシュ実配信テスト、ADG/i-mobileネイティブ広告の実機表示確認、7コミットの origin push 判断。
詰まっていること・未決事項:
- footballnext-android の7コミットが未push。cake_sessions Fatal の許容度(放置・頻度上がったら見直し)。
2026-05-19 09:34 時点のスナップショット
テーマ: 平常運用フェーズ(RDS チューニング + baseballnext 統合は完了、エラーログスパイクは原因特定済み・対応保留)
どこまで進んだか:
- footballnext RDS チューニング(performance_schema 不発弾撤去 / old_articles・impressions 空化 / articles OPTIMIZE / index A 群 DROP)→ DB 9.1GB → 4.1GB、ReadIOPS -61〜70%
- baseballnext 統合(5/9 完了、5/11・5/19 観察で問題なし、buffer_pool ヒット率 99.99% 維持)
- エラーログスパイクアラート(5/16・5/17・5/19)の原因特定 = cake_sessions Duplicate entry / bot のパススキャン由来
次に何をするか:
- 即時アクションなし。観察モード
- cake_sessions Fatal の頻発が続くようなら対応(A: 攻撃パス拒否 / B: Session→File or Redis)
詰まっていること・未決事項:
- cake_sessions Fatal の許容度(今は放置、頻度上がったら見直し)
2026-05-09 19:00 時点のスナップショット
テーマ: RDS (footballnext) のチューニング → baseballnext 統合受け入れ準備フェーズへ
どこまで進んだか:
performance_schema不発弾撤去(pending-reboot 解消)old_articles空化(約 2GB 解放)articlesのOPTIMIZE TABLE(2.8GB → 0.76GB、index 75%減)articlesインデックス A 群 4 個を Step 1+2 一気に実行(INVISIBLE → 即 DROP、index 499 → 369MB / -130MB)- Step 2 効果検証完了: ReadIOPS -61〜70%(FreeableMemory はほぼ変化なし)
impressionsの機能廃止(INSERT コメントアウト+デプロイ+TRUNCATE で 730MB 解放)- DB 全体: 9.1GB → 4.1GB(-54%)
- baseballnext RDS 実態調査(2.24GB / 90 テーブル / 現役稼働 / SELECT 5.8/秒)
次に何をするか:
- baseballnext 統合作業は
baseballnext-operationプロジェクト側 で進行中。こちらは受け入れ側として現状維持 - 統合作業中・直後の footballnext RDS のメトリクス観察(FreeableMemory・CPU・IOPS・ヒット率)
- 統合後にあらためて buffer_pool / インスタンスサイズの判断(Step 3 / medium 昇格 / 現状維持)
2026-05-08 02:24 時点のスナップショット
テーマ: RDS (footballnext / db.t4g.small) の FreeableMemory カイゼン
どこまで進んだか:
performance_schema不発弾撤去(pending-reboot 解消)old_articles空化(約 2GB 解放)articlesのOPTIMIZE TABLE完了(2.8GB → 0.76GB、特に index 75%減)- DB 全体 9.1GB → 5.0GB、FreeStorageSpace +2.66GB
hot_keyword_rankingsの利用パターン調査(最新dir top2 のみ参照、過去データは死蔵、書き込みも実質停止)- 19-20時 FreeableMemory スパイクは「自分の調査クエリ起因の人為的事象」と切り分け完了
次に何をするか(優先順):
- インスタンス昇格 db.t4g.small → medium の段取り(メモリ問題の本命解、+$30/月)
- CloudWatch アラーム新設(FreeableMemory < 100MB)
hot_keyword_rankingsのデータ整理(直近 N 日分残し or TRUNCATE、実行可否は別途判断)articlesのインデックス整理 A 群 4 個(優先度下がった、追加効果は数百MB)
別件で要対応: RDS SG 0.0.0.0/0 開放、DB パスワード平文、cron EC2 AMI 2018.03 EOL、インスタンス昇格
2026-05-07 21:47 時点のスナップショット
テーマ: RDS (footballnext / db.t4g.small) の FreeableMemory カイゼン
どこまで進んだか:
- 不発弾
performance_schema = 1 (pending-reboot)を engine-default (0) に戻した(再起動時の事故防止) old_articlesを TRUNCATE して約 2GB 解放(phpMyAdmin 経由)- 現状把握: buffer_pool 1024MB / ヒット率 99.996% / working set < 1GB
articlesの断片化(data_free 1.1GB)と過剰インデックス(22個 / 2GB)を特定
次に何をするか(優先順):
articlesのインデックス A 群 4 個(article_site_id/image_width/title/del)をINVISIBLEに切り替え → 1〜数日観察 → 問題なければDROP INDEX→OPTIMIZE TABLE(約 1〜1.5GB 圧縮見込み)- CloudWatch アラーム新設(FreeableMemory < 100MB)
- 余裕があれば B 群(
replicated/comment_update_unixtime)も同手順で
別件で要対応: RDS SG 0.0.0.0/0 開放、DB パスワード平文、cron EC2 AMI 2018.03 EOL、インスタンス昇格
ROADMAP(計画)
ロードマップ
今週
- RDS
performance_schemaの pending-reboot 解消(2026-05-07 完了) -
old_articlesのデータ廃棄(2026-05-07 完了、約 2GB 解放) -
articlesの OPTIMIZE TABLE(2026-05-08 完了、2.8GB → 0.76GB、index 75%減) -
articlesのインデックス整理 A 群 4 個(2026-05-08 完了、INVISIBLE → 即 DROP、index -130MB / ReadIOPS -61〜70%) -
impressionsの機能廃止と空化(2026-05-08 完了、730MB 解放) - 19-20 時 FreeableMemory スパイク調査(2026-05-08 完了、人為的と確定)
- Step 2(DROP INDEX)効果検証(2026-05-09 完了、dev-timer #14 経由)
- baseballnext 統合作業(baseballnext-operation 側で進行中、こちらは受け入れ準備状態を維持)
- footballnext-android: 16KB対応版(v3.0.7 / versionCode 17)を Play Console にアップロード(AAB 生成済み)
- footballnext-ios: 旧 CodeCommit バックアップ(
~/Downloads/footballnext-ios-codecommit-backup)の削除(新環境ビルド確認済み) - footballnext-ios: 実機で MAX Mediation Debugger 再確認(Liftoff が Completed か、各SDK が LATEST 表示か)
- footballnext-ios 5.5.1(試合詳細の得点者表示 + YouTube プレイヤー WKWebView 化・自動再生オフ)を Xcode アーカイブ → App Store 提出(You が申請作業中)
- 【マネタイズ改善・最優先】起動時広告 削減A/B のトムソン提案資料化(2026-06-18 完了)。提案書HTML作成→Codex2ラウンド反映→
thomsonsS3に公開アップロード。→トムソン承認済み - 起動時広告A/B の実装計画HTML作成+Codex 5ラウンド設計レビュー(2026-06-18 完了)。
起動時広告A-Bテスト_実装計画_20260618.htmlrev6 + 正典AB_TEST_MEASUREMENT_SPEC.mdrev6 同期。round5で設計レビュー完了判定。 - 起動時広告A/B 実装フェーズ(BASE A〜F)(2026-06-19 完了)。
footballnext-iosコミット4ebdeb6。新規4ファイル(ABTest/ABLaunchTracker/ABAdCollector/ABConfigLoader)+既存14ファイル配線。BASE契約5点(OpenContextRecord/CAS・at-most-once・3 artifact+版番号・canonicalfn_ad_revenue一本化・MAX枠collector接続)実装。フルビルド成功・シミュレータ実機で全ファネル発火確認。本番影響は中立(S3 config 未配置の間は現行挙動) - 起動時広告A/B GATE-値を measurement_protocol として凍結(2026-06-19 完了・叩き台)。割当ハッシュ/clock_anomaly/fail-safe/maturation/円人コホート/residual/収益正規化/事前テスト合格しきい値①〜⑧。要トムソン最終確認は3項目(コホート定義・residual/正規化・合格しきい値)
- 起動時広告A/B GA4 custom定義 登録(2026-06-19 完了)。ディメンション18(イベント16+ユーザー2)+指標3=計21件、実コードと一致(スクショ検証済)
- 起動時広告A/B native loader-ad lifecycle 修正(2026-06-19 完了・ios
8dc3b5a)。loader を ad とペアで retain。検証用 seed/NSLog は二重ガードのため残置 - 起動時広告A/B 点火準備(2026-06-19 完了): トムソン3項目HTML公開・enrollment窓確定・段階A配置・pre-flight PASS。
- 起動時広告A/B 一度点火→Codex実装レビューで重大バグ→緊急停止(2026-06-19)。active=false / v7 / ttl14日 へ戻し中立化。5.5.3 据え置き。
- 起動時広告A/B Codex実装レビュー round1(high8) / round2(high4) の修正大半(2026-06-19): #1状態分離・R2-#1 context世代snapshot+TTL14日・#5#7 callback・#6/R2-#m6 impression・#3 build・#11 群A・#9a・#10/#8/R2-#m2/#m4/#m5/#m1/#m3。すべて ios master へ push。
- 起動時広告A/B R2-#4 起動overlay gap 修正(2026-06-19
bcc9950):LaunchManager連鎖に boot covering token。負の確認(早期発火しない)済、正の確認は実機へ。 - 起動時広告A/B R2-#3 native loader handle方式(2026-06-19
55fbe06・compile確認のみ)。実機で native描画+ILRD 検証必須(A/B点火ブロッカー)。 - 起動時広告A/B 実機検証(You・Xcode→実機): 手順書
ab_test_device_verification.mdの A〜D。TestFlight必須でなく実機直挿しで足りる。 - (任意)Codex round3(R2修正群の最終確認)/R2 low(build符号化文書/fail-close・DEBUG定義+XCTest)。
- 起動時広告A/B 実装レビュー(gods-talk)round5〜8 完了・点火 sign-off 取得(2026-06-19)。native lifecycle(lease/確定destroy/timeout/expire/同一pass再deque)・kill属性 snapshot↔applied分離・GA4-only整合・TTL統一を全解消。round8 で「点火前チェックリスト完了後 active=true 可・high/medium ブロッカーゼロ」。ios
0089f65→3f64ade→31dc22b、opab066e4→546dd0a→07aa09f。 - 起動時広告A/B 点火前チェックリスト(コード外・必須。完了後に active=true): ①GA4 custom定義の実登録(
kill_observed/applied_interval_minutes追加分) ②enrollment窓・判定値の確定 ③初期active=falseでの本番config取得確認 ④点火/停止時刻の運用記録。 - 起動時広告A/B 5.5.3 実機で MAX native の表示・expire/reload・impression スモーク(reviewer 優先#2。lease本体は round5 実機PASS済、expire/reload は未観測)。
- 起動時広告A/B 実機スモーク+点火前チェックリスト合格 → 5.5.3 提出→承認後リリース→ S3 再点火(active=true・config_version+1・enrollment窓を実リリース日に調整)→ GA4着弾を Data API 検証(要 ga4 MCP 接続)+事前テスト①〜⑧。
- 起動時広告A/B low(任意・非ブロッカー): build符号化の検証/文書化、通常DebugにDEBUG定義+fake loader/entry で timeout/expire/render false の XCTest整備、render false後の安全なrow reload、runbook の実コードcommit表記更新。
- 起動時広告A/B のトムソン依頼を握る: ①GA4 Viewer→Editor 権限 ②「iOS・Football NEXT・対象月」の月次総売上。S3
app_open_ad_interval=50(群A基準)は実測確認済み。処置差(50分→24h の抑制割合)は事前テストで実測 - 不快クリエイティブのブロック設定(成人/マッチング/ショッキングをメディエーション管理画面でブロック)
- 広告挿入間隔の緩和(トップ一覧10件中2枠・コメント/動画5件ごと → 間隔拡大)
今月
有料「広告なし/サポーター」課金の新設(StoreKit) ─ 月額¥300–500 or 買い切り。収益多様化+ロイヤル層の受け皿(review-20260605.html 由来)
ストア評価の底上げ運用(広告改善とセットでレビュー依頼+ストア返信)
リテンション/広告影響の計測基盤(GA4の screen 命名整備・
ad_impression送信・面別計測)footballnext-ios: OneSignal を arm64 simulator 対応の XCFramework(5.x) へ更新(シミュレータ起動不可=QA阻害の解消)
API Gateway 飛び地の原点直叩き移行:
/article/{id}(footballnext-article)+/footballnext-teiki-notification-articles/{id}をクライアント側でhttps://fnext.thomsonsapp.com/api4/...直叩きに差し替え → 浸透後に Lambda+ルート削除 → 最終的に footballnext API Gateway(7isghft565) を撤去。論点は DynamoDB READキャッシュの代替(必要なら CakePHP 側キャッシュ)。原点は HTTPS 200・iOS は ATS 無効で障壁なしiOS
OshiraseViewControllerのデッドコード整理(削除済みfootballnext-oshirase-v2参照/oshirase-v2/{id}の除去。90日invocation 0 のデッド画面)baseballnext 統合後の footballnext RDS メトリクス観察(FreeableMemory・CPU・IOPS・ヒット率)
CloudWatch アラーム新設: FreeableMemory < 100MB
RDS Security Group
sg-083bace7c7ee9e450の 3306/tcp0.0.0.0/0開放を是正DB 認証情報を
database.phpから SSM Parameter Store / Secrets Manager に移管footballnext-android: リリース後の確認(動画/コメント/設定画面の手動確認、OneSignalプッシュ実配信テスト、ADG/i-mobileネイティブ広告の実機表示確認)
footballnext-android: 7コミットを origin へ push するか判断
footballnext-ios: xcuserstate の追跡解除(.gitignore 済みだが過去コミットで追跡継続)
得点者まわりの dead code 掃除(方針転換で残った未使用
LiveFixtureScorersCell.xib+ 空セル)
今四半期
- baseballnext 統合後の最終チューニング判断(buffer_pool 維持 / 縮小 / medium 昇格)
-
articlesのインデックス整理 B 群(replicated/comment_update_unixtime、優先度低) - cron EC2 (Amazon Linux AMI 2018.03 / EOL) の刷新
- rss-crawler の cron 一元化:
rss-crawler-notice-prod-crawlArticle(RSS発見→SQS投入の実ロジック・毎分稼働)をサーバ側エンドポイント化して cron から叩く。curl ラッパーではないため移行に設計が必要(残存する数少ない定期実行 Lambda の最後の砦) -
hot_keyword_rankings書き込み停止(最新 2026-04-09)の真因調査(cron 停止 / 別マシン稼働 / 廃止意図) -
hot_keyword_rankingsの古いデータ整理判断 - footballnext-ios: Google AdMob を 13.4 へ(Google 製 InMobi アダプタが GMA 13.4 に対応したら)
いつか
- articles の本文格納方式の見直し(body カラムが varchar(512) で空のまま、本文はどこ?を整理)
- hot_keyword_rankings / search_logs / temp_rss_load_times のリテンションポリシー策定
- CakePHP 2 系の依存からの脱却検討
- gp2 → gp3 ストレージへ移行(コスト追加 NG 方針につき凍結中、必要が出たら再開)
- リードレプリカ復活(コスト追加 NG 方針につき凍結中)
- cake_sessions Duplicate entry Fatal の対応(bot パススキャン由来、頻度上がったら格上げ)。候補: ALB/Apache で攻撃パス拒否 / Session 保存先を File or Redis に移行 / try-catch 握り潰し
- footballnext-ios: AdAttributionKit (iOS17.4+) 対応(全ネットワーク横断、現状は SKAdNetwork のみ)
- 過去試合の得点者バックフィル拡大(必要に応じ
updateFixtureで個別。全件一括はクォータ的に非現実的)
DECISIONS(意思決定)
意思決定記録
このプロジェクトで下した重要な意思決定を記録する。 最新が上に来る。
2026-06-19: 起動時広告A/B 点火前の判定値を確定(GA4追加2件・enrollment窓はリリース起点・min_build=50503)
背景: 実装レビュー sign-off 後、点火前チェックリストの「GA4登録」「enrollment窓・判定値の確定」を詰める段。GA4 Admin MCP は未接続のため登録は手作業、窓は 5.5.3 リリース日に依存。
決定:
- GA4 追加登録は2件のみ(既登録21件に対し):
kill_observed(event-scoped dimension)/applied_interval_minutes(custom metric・分)。登録後 DebugView でapp_open_ad_decisionに乗るか確認。 - enrollment 窓は窓長42日固定・起点は 5.5.3 が store ライブになった日(+反映猶予 数日)を点火時セット。旧叩き台 2026-06-22 起点は 5.5.3 未提出で過去化のため流用しない。
end = start + 42日。 - build gating
min_build=50503(5.5.3+ 限定)。round1〜8 の全修正は 5.5.3 積載のため旧ビルドを実験から除外。max_buildなし。 - interval_a=50 / interval_b=1440 / 締切42日 / 追跡28日 / ITT / required_assignment_version=1 / ttl 14日 は既凍結のまま。
理由: GA4 は登録前データが遡及されないため点火前必須だが差分は小さい。窓をリリース日に固定するのは「ビルドが store に出る前に窓が開くと初日コホートが空/旧ビルド混入で歪む」ため。5.5.3+ 限定は未修正コードの native 収益欠損・decision 矛盾を実験に混ぜないため。
2026-06-19: 起動時広告A/B native expire/lease lifecycle の最終形を確定・kill_observed_at 廃止・実装レビューで点火 sign-off
背景: 実装レビュー round5〜8 で、flag外の MAX native lifecycle(lease/timeout/expire)と kill 計測属性に残 medium が連続して出た。固定遅延 destroy・stock限定 expire・puts での無効化はいずれも段階的に不十分で、reviewer が更に詰めた。native は experiment flag 外で全ユーザーに作用するため収益欠損(無効広告の計上/枠消失)を出さない fail-closed が必須。
決定:
- native の destroy は「
released(row利用終了) orexpired」かつ「displayLeases==0(表示中cell無し)」のときだけ(固定秒数廃止)。expire 済みはloader(for:)を nil にして render/impression を厳密に止める。 - stale MAX ad の判定は
CoreTableView.cellForRowAtの deque/nib選択より前に置き、同一 pass で新 stock を再deque(puts まで遅らせない)。manager→row 逆参照は持たず、Any?型非依存 API で共通CoreTableViewを AppLovin 非依存に保つ。 - impression は
renderNativeAdView==trueのときだけ計上(render失敗の過大計上を排除)。callback は全て main 集約+timeout 世代照合。 kill_observed_atを廃止。GA4-only では未登録 param を事後参照できないため、停止後区間の除外は必須 dimensionkill_observed、global 停止境界は S3 でexperiment_active=falseにした時刻の運用記録で扱う。- 実装レーンは round8 sign-off をもって実質完了とし、以降は点火前チェックリスト(GA4登録・値確定・実機スモーク・点火/停止時刻記録)の運用準備へ移行。
理由: 逆参照を持たない一方向設計(destroy→loader nil→cellForRow前段でstale再deque)は CoreTableView の汎用性を保ちつつ実装が小さく、main 集約で race を避けられる。固定遅延 destroy は表示終了を保証せず、stock限定 expire は deque後4h超の再表示を取りこぼし、puts での無効化は再deque を保証できなかったため、いずれも棄却した。kill_observed_at は GA4-only 契約と整合せず実用価値が無いため削除。
2026-06-19: 起動時広告A/B 実コードのCodexレビューを必須化・点火を撤回して修正優先・5.5.3据え置き
背景: 提案書・実装計画の机上Codexレビューを通過し点火(active=true)まで実施したが、実装コードのCodexレビュー(ab-test-implementation-review)round1/2 で high/medium 多数が判明。特に「非参加者へのB処置漏れ(policy/measurement未分離)」と「session config固定+短TTLで日次ユーザーが永久非参加」は実験を不成立にする重大バグだった。
決定:
- 点火を撤回し緊急停止(S3 config を active=false+config_version+1)。本番中立を最優先し、実装が正しいと検証できるまで再点火しない。
- 実装コードのCodexレビューを必須プロセス化(机上レビューだけでは出ないバグがあるため)。round1→round2と回し、round3まで継続。
- config snapshot は session全体固定でなく context世代方式(beginTargetContextで last-known-good を1回読み record に固定、fetch結果は次context有効)。session固定は日次ユーザーを壊すため却下。TTLは6h→14日(launch間隔より十分長く)。
- policy/tracking は canEnroll でなく「既参加(enrolled)+追跡窓内(JST暦日<28)+active/fresh|cached」で判定(isParticipating)。非参加者へのB漏れと締切後の追跡停止を両方解消。
- native loader 修正は点火ブロッカー(当初「A/B非交絡・収益別経路」で先送り判断したが、
MANativeAdLoader.revenueDelegateが weak=誤対応付けでILRD欠損しうる、とのCodex指摘で撤回)。実機/TestFlight検証必須。 - バージョンは 5.5.3 据え置き(未提出のため番号再利用。全修正を5.5.3に積み、検証完了後に提出)。
理由: A/Bの主指標(1人あたり収益)と GATE(未到達率・収益再現・全枠callback品質)を満たすには、処置と計測の母集団一致・config世代の決定性・収益callbackの完全性が前提。これらが崩れたまま点火するとデータが汚れ無管理に減収する。机上で5ラウンド見ても実コードのTTL相互作用・delegate weak・状態未分離は出なかったため、実装レビューを独立工程として必須化した。
2026-06-19: 起動時広告A/B の enrollment 窓を確定・点火は段階A/Bの2段運用でトムソン確認は叩き台確定扱い
背景: 点火前の残作業(トムソン要確認3項目・enrollment期間・S3配置)を処理する段で、事前登録の作法と本番の不可逆性をどう両立させるかを決める必要があった。
決定:
- enrollment窓 = 6週間(叩き台どおり)。
enrollment_start_at=1782054000(2026-06-22 月 0:00 JST)/enrollment_end_at=1785682800(2026-08-03 月 0:00 JST)。開始をJST深夜0時の月曜境界に置く(enrollment_dayのコホート日が揃い解析がきれい)。canEnrollは now ∈ [start, end)。 - トムソン要確認3項目は叩き台のまま確定扱い。Youより「自分が任されているのでトムソンへの確認は不要」と判断。通知物はHTML化して
thomsonsS3 に公開済(事前登録の記録として残す)が、合意待ちで点火をブロックしない。 - 点火を段階A/Bの2段運用に分離: 段階A=不活性config(active=false)を本番S3へ先行配置(中立・挙動不変、本番S3取得経路の確認用)。段階B=
experiment_active=true+config_version=2で再アップロード(実点火)。今回は段階Aまで実行し段階Bは保留(Youの明示goで実行)。
理由: enrollment窓開始を未来日(6/22)に置くと、段階Bを今点火しても窓開始まで誰もenrollしない=S3配置の機械的タイミングと解析窓の開始を分離でき、週末をpre-flight期間にできる。段階A/B分離は、本番S3への書き込み・パース健全性を実ユーザー挙動を変えずに先行検証するため。トムソン確認は事業判断としてYouに委譲されており、叩き台で十分動くため点火ブロッカーから外した。
2026-06-19: 起動時広告A/B の割当ハッシュ定数を凍結・計測モジュールは Foundation-only で実装
背景: BASE 実装着手にあたり、群割当の具体定数(GATE-値の一部)を決める必要があった。また、計測ロジックは本番起動パス・広告収益コードに深く絡むため、検証可能性を担保したかった。
決定:
- 割当ハッシュを凍結:
input=UTF-8("experiment_id:install_uuid")→ SHA256 → 先頭8バイトを big-endian UInt64 として% 100→ bucket_map[0,50)→A / [50,100)→B。experiment_id="app_open_interval_2026q3"、assignment_version=1、measurement_protocol_version="mp_2026q3_r1"。テストベクトルをABTest.verifyTestVectors()に固定。ABProtocol(リポジトリ凍結=measurement_protocol 相当)に集約。 - 新 experiment では再無作為化を採用(hash 入力に experiment_id を含むため、同一 install_uuid でも別 experiment では別 bucket になる)。bucket 再利用案は採らない。
- 計測コア(
ABTest.swift/ABLaunchTracker.swift)は Foundation のみに依存させ、GA4送信は emit クロージャ注入で外出し。→ アプリ全体をビルドせず standalone swiftc で状態機械を単体検証できる構成にする。広告SDK/Firebase に触る部分(ABAdCollector/ABConfigLoader/AppDelegate配線)は別ファイルへ分離。
理由: GATE-値のうち「割当の決定規則」は実装と同時にテストベクトル付きで固めないと検証できないため先行確定。再無作為化は実装が最も単純(hash入力にIDを含めるだけ)で、フェーズ2の experiment 切り替え時にも公平性が保てる。Foundation-only 分離は、本番クリティカルパスのロジックをフルビルド・実機なしで反復検証可能にし、安全に段階実装するための土台。
2026-06-18: 起動時広告A/B 実装計画を Codex 5ラウンドで設計レビュー完了(配送は at-most-once に転換)
背景: トムソン承認後、実装着手の前に実装計画HTMLを作り、Codex(gods-talk)で繰り返しレビューした。ローカルコードを直接読ませたことで、机上設計では見えなかった穴(delegate未配線・write-ahead保存の必要性・収益event二重計上・総売上母集団の非識別性など)が判明した。
決定:
- 実装計画 rev6 と正典
AB_TEST_MEASUREMENT_SPEC.mdrev6 をもって 設計レビューを完了とする。以降は再レビューラウンドを設けず、実装PRの受入チェックで確認する(round5でCodexが「追加の大規模レビューは不要」と判定)。 - 実装は 3レーンで進める: BASE(着手可)/BASE契約(永続model・collector・logger API 確定前に潰す5点)/GATE-値(schema凍結前に measurement_protocol として固定)。
- GA4への計測 event 配送は at-most-once(
pending→attemptedを先に確定して一度だけ送信、欠損は成熟後残差率に残す)。収益 event はfn_ad_revenueに一本化(app_open_ad_paid廃止)。設定は 3 artifact 分割(assignment_manifest / activation_config / measurement_protocol)。 - 確定本文は 正典 SPEC.md を唯一の source of truthとし、実装計画HTMLはその投影と位置づける。
理由: GA4-onlyかつ launch_id を出さず BigQuery 不使用の制約では exactly-once も at-least-once(dedupe不能)も成立しないため、二重計上を防ぐ at-most-once を選択。代替(at-least-once+GA4 dedupe)は本構成では実現できないと判断。収益の二重計上回避のため canonical event を1つに固定。レビューは5ラウンドで指摘の質が逓減し収束したため、これ以上の往復より実装で詰める方が有効と判断。
2026-06-18: 起動時広告A/B 提案書を確定(Codex2ラウンド反映で設計を精緻化・同日の設計を一部上書き)
背景: 同日午前の設計(下のエントリ)を提案書化する過程で、議論と Codex(gods-talk)2ラウンドのレビューにより、いくつかの前提が誤り・脆弱と判明した。実装と解析の正典を固める必要があった。
決定(同日午前の設計を以下に更新):
- 群Bは「1日1回」→「24時間に1回(rolling)」。群Aは「毎回」ではなく現状=最短50分に1回(本番S3
app_open_ad_interval=50を実測確認)。 - データ基盤は GA4のみ・BigQuery不使用(無料運用)。
ab_group集計のため GA4 を Viewer→Editor へ権限変更をトムソンに依頼。 - 判断は統計的有意差でなく 「事前固定した実務閾値での記述的判断」。主指標1(1人あたり売上)+ガードレール(継続率・クラッシュ率)+終了日 の最小骨子だけ事前固定、閾値は別紙で合意し計測誤差内は判定保留、最終採否はトムソン。「微減+良化=即採用」は撤回し回収試算の上で判断。
- 解析は ITT(広告表示の有無に関わらず割当全員)。
- お金: 起動時は群別
didPayRevenue実額合計を主値(表示回数×eCPMは検算)、それ以外は混合平均eCPMで概算。月次総売上は「iOS・Football NEXT・対象月」に範囲を揃える(Android/旧版混在は不可)。 - 残り具合:
launch_bounceを 「コンテンツ未到達離脱率」 に改称。判定は起動秒数でなく「最初のバックグラウンドまでにコンテンツ接触ゼロか」、content_ready(描画)≠content_visible(広告の背後でなく実際に見えた)を分ける。D7/D14 起点=実験参加日。 - 「起動時広告 vs 記事内広告 どちらが主因か」は本実験では因果確定せず(変えるのは起動時頻度のみ)→ 中立な表現に。
- 計測の正典を
.devnotes/AB_TEST_MEASUREMENT_SPEC.mdに集約。提案書と矛盾したら SPEC を優先。
理由: BigQueryは Owner 権限(info@thomsons.jp)と課金が要り Sandbox でブロック=無料の GA4 のみが現実的。GA4集計だけでは厳密な有意差が出せないので表現を実務閾値判断に落とすのが誠実。群A実測が50分だったため処置差は想定より小さく、期間・最大減収はMAUでなく事前テストで再試算が必要。content_ready は全画面広告の背後で発火し群Aを過小評価しうるため content_visible 必須。
2026-06-18: 起動時広告A/B 提案書を thomsons S3バケットに公開アップロード
背景: 提案書(HTML)をトムソンに共有する必要。既存の review-20260605.html 等を置いている thomsons バケットがある。
決定: 提案書を thomsons バケット(profile thomson-ik/ap-northeast-1)に object ACL public-read で公開アップロード(既存HTMLと同方式)。URLは https://thomsons.s3.ap-northeast-1.amazonaws.com/起動時広告A-Bテスト_提案書_20260618.html。
理由: 同バケットは公開HTML共有の前例があり BPA無し・ACL公開可。アップ前に秘密情報スキャンを実施し、メール/APIキー/GA4 property ID/広告ユニットID 等が含まれないことを確認済み。URLを知れば誰でも閲覧可なので取り扱いは注意。
2026-06-18: 起動時広告の削減は「全消し」でなく「50/50無作為A/Bで実測判断」する
背景: 多角レビューの最優先施策は「広告UX是正」。起動時広告は総売上の約25%(トムソンヒヤリング)で、減らすと収益の即時下落が確実な一方、リテンション改善は遅く不確実という非対称な賭け。トムソンは「満足度を測れない」と削減に消極的。決定権はトムソン側にあり、提案を通せる形にする必要があった。
決定:
- 起動時広告を全消しせず、頻度を絞るダイヤルとして扱い、50/50 の無作為A/B(群A=現状/群B=1日1回)で得失を実測してから判断する。
- 「満足度」ではなく行動指標で測る:①ユーザーあたり広告収益(impression×eCPM)、②離脱=
launch_bounce・週次セッション・D7/D14継続。 - 収益測定はトムソンの日次データに依存せず自前のimpressionカウントを土台にし、金額は月次総売上1個から2バケツ(起動時=MAX実額/それ以外=混合平均eCPM)に割る方式とする。
- 採否は3パターン判断(同等以上→即採用/微減だが残り良化→採用/残り不変→戻す)。勝った間隔は S3 設定で全員展開。
理由:
- 群Aに現状を半分残すので、最悪でも全体の半分は売上を維持=トムソンが許可しやすい(賭け額が見える)。前後比較では自然減(MAU −15%/年)に埋もれるが、無作為A/Bなら自然減が両群に等しくかかり相殺され因果がクリーン。
launch_bounceは「率」で自然減に埋もれず、起動時広告に最も因果がタイト。これがトムソンの「記事内広告が主因」説への反証材料にもなる。- 広告構成がパッチワーク(MAX外に AdG/Zucks/LINE)で MAX の
didPayRevenueだけでは収益を捕捉しきれないため、全SDK共通で取れる impression カウントを測定の土台に据えるのが堅い。didPayRevenue は MAX スライスの実額+月次の答え合わせに用途を限定。
2026-06-13: iOS の YouTube 動画は自動再生せず、タップで再生開始にする
背景: YouTube プレイヤーの WKWebView 化(ナレッジ ios-wkwebview-youtube-inline-playback 適用)に伴い、2015年以来の autoplay=1(動画詳細を開いた瞬間に自動再生)を維持するか選ぶ必要があった。
決定: embed URL を autoplay=0 にし、ユーザーのタップで再生開始する挙動に変更する。
理由: You の明示指示。意図しない音出し・通信を避け、ナレッジの推奨値(7channel 実績構成)とも一致する。
背景: 定期実行の呼び出し元が「cron 専用 EC2」と「AWS Lambda(EventBridge)」に二分。Lambda 側の TFN_cron 等は fnext.thomsonsapp.com を叩くだけの curl ラッパーで、Baseball は同等処理を既に cron 上で実行済みだった。
決定: curl ラッパー型の定期実行は cron 専用サーバーに寄せ、対応する Lambda + EventBridge ルールは削除する。実ロジックを持つ Lambda(rss-crawler-notice-prod-crawlArticle 等)は当面残す。
理由: ラッパーは cron の1行で完全等価に置換でき、二重管理・nodejs8.10 廃止ランタイム・不要コストを解消できる。実ロジック型は移行に設計(サーバ側エンドポイント化)が要るため別扱いとした。
2026-06-06: 稼働ゼロ Lambda の削除は「invocation実測+トリガー+キャッシュ有無」で判定する
背景: 大量の旧 Lambda(TSP系・sports-paper系・test系)が残存。削除可否を安全に判断する必要があった。
決定: ①直近7日/90日の invocation を日次バケット合算で実測(長期間1バケットは過少値になるため不可)②resource-policy/EventSourceでトリガー有無を確認③API Gateway 経由はステージのキャッシュ無効を確認して「0回」が本物だと裏取り——の3点が揃えば削除。コードは毎回バックアップしてから実行。共有リソース(IAMロール ichirokisanuki_lambda、SNS IchiroKisanukiAlert)は触らない。結果 42→9関数。
理由: API キャッシュが効いていると invocation 0 でも現役の可能性がある。三点確認で誤削除リスクを排した。実際 footballnext-oshirase-v2(90日0)は iOS にコード参照が残っていたが、キャッシュ無効=真に未到達と確認できたため据え置き判断ができた。
2026-06-06: 削除済み footballnext-oshirase-v2 は復元しない(デッド画面として次回整理)
背景: 90日invocation 0 を根拠に削除した footballnext-oshirase-v2 を、iOS OshiraseViewController.swift:30 がまだ参照していると後から判明。
決定: 復元せず据え置き。次回アプリ改修で OshiraseViewController ごとデッドコードを除去する。
理由: ステージのAPIキャッシュ無効を確認済みで、90日ゼロ=実際に誰もこの画面に到達していない(到達不能 or 廃止導線)と判断。バックアップ(/tmp/lambda_cleanup3/footballnext-oshirase-v2.zip)は保持しているため、万一必要になれば復元可能。
2026-06-05: Football NEXT のマネタイズ方針 ─ 広告密度を「最大化」から「最適化」へ転換する
背景: 広告ネットワーク売上が2019ピークから2025で−69%、下落が加速。原因を多角レビュー(広告売上・全レビュー3,987件・GA4行動データ・Codecの広告実装解析の6ソース)で診断したところ、広告増量・評価崩落(平均星3.0→1.33)・MAU縮小(−32%)・売上下落が時系列で一致し、広告過多による「デススパイラル」と判定された。
決定: 広告impression最大化路線をやめ、離脱を止めて母数を維持する「最適化」路線へ転換する。具体的には ①App Open Ad のスロットル(AppDelegate でコメントアウト中の頻度ゲート)を復活、②不快クリエイティブのブロック、③広告挿入間隔の緩和を最優先とし、中期で有料広告なし課金(StoreKit)を新設して収益を多様化する。効果検証は AdMob面別データが取れないため GA4 の離脱シグナル(MAU/セッション/継続率)で代替する。
理由: 広告増量は短期impressionを稼ぐが、レビュー・GA4の両面でロイヤル層の離脱を招き、母数減でかえって総売上を押し下げている蓋然性が高い。残存ユーザーはエンゲージ率90%・月35セッションの高価値層で、広告で焼くより保全+課金する方が中長期の期待値が高い。
2026-06-05: GA4 を MCP として接続し、行動データをレビューの常用ソースにする
背景: 当初レビューは継続率・MAU等の行動データが無く代理指標で補っていた。Football NEXT は Firebase Analytics を実装済みで GA4 にデータがあると判明。
決定: Google公式 google-analytics-mcp(pipx) を claude mcp add ga4 -s user で登録(property 211701453=footballnext-bca77、認証は既存ADC流用、property固定)。今後の footballnext 分析で GA4 を常用ソースにする。
理由: Firebase連携の GA4 が既にデータを持っており、サービスアカウント新規発行より既存ADC流用が最短。user スコープにして他プロジェクトからも再利用可能にした。AWESOME FOOTBALL(366217595) は無関係なので除外。
2026-06-03: 得点者データは fnlive_fixtures.events_json に生 events を保存し、整形は配信API側で行う
背景: 試合詳細に得点者(得点時間・選手・アシスト・PK/OG)を表示するため、データの保存先と整形場所を決める必要があった。
決定: 専用テーブルは作らず、既存 scores_json と同パターンで fnlive_fixtures.events_json(TEXT)に API-Football の events 配列を生のまま保存。表示用の scorers 整形(Goal 抽出・home/away 判定・PK/OG・アシスト)は配信API V2Controller::fixture() に集約。OG は API が「得点が入る側」に team を割り当てているため team.id を反転せず素直にマップ(実APIで検証しスコア一致)。
理由: 試合あたりの events は高々数十件で常にセット取得=JSON カラムで十分(新テーブル/関連は過剰)。生保存なら将来カード/交代表示に流用可。整形を1箇所(既存の status/score 整形と同じ場所)に集約すると保守が楽。単一試合レスポンスに events が同梱されるので追加 API コールも不要。
2026-06-03: iOS の得点者カードは記事テーブルの tableHeaderView に手動レイアウトで載せる
背景: 試合詳細はスコアボード用テーブル(高さ固定 184・自己サイズ非対応セル)+記事テーブルの2段構成。得点者カードをどこに置くか。
決定: スコアボード用テーブルには手を入れず、LiveFixtureScorersView(手動フレーム+sizeThatFits で高さ確定)を記事テーブルの tableHeaderView に設定する方式を採用。当初スコアボードテーブルに section 追加+動的高さで実装したが、固定 184 で見切れ・estimatedRowHeight=0 でスコアボードセルが崩壊した。
理由: スコアボードセルが自己サイズ非対応のため同テーブルでの自己サイズ運用は破綻しやすい。記事テーブルのヘッダーに手動レイアウトで載せる方式は自己サイズの曖昧さに依存せず確実で、視覚的にもスコアボード直下・記事の上で仕上がりイメージ通り。
2026-06-03: 過去試合の得点者は updateFixture で個別バックフィル(全件一括はしない)
背景: アプリは過去試合も履歴から閲覧できるため、デプロイ前に終了済みの試合(events_json が空)にも得点者を出したい。一方 RapidAPI にはクォータ制限がある。
決定: 全履歴の一括バックフィルはせず、updateFixture($id)(終了試合もスキップせず強制更新)で必要分を個別に埋める。今回は直近10試合のみ実施。今後終わる試合は ovserveFixtures が終了時に自動で events_json を保存するため対応不要。
理由: 何千試合もの一括取得はクォータを食い潰し非現実的。閲覧されうる直近分だけ個別に埋めれば実用上十分でコストを抑えられる。lazy 取得(閲覧時に API)はリクエスト経路に外部呼び出しが入るため見送り。
2026-05-30: footballnext-ios は GitHub クローンで作業し、Pods はコミットしない
背景: operation 配下の footballnext-ios が、GitHub 移管前の CodeCommit クローンのままだった(remote=CodeCommit、Pods を丸ごとコミットする旧運用)。GitHub 側は移管時に履歴を書き換えて>50MBバイナリを除去し Pods/ を .gitignore 済み。push しようとして発覚(CodeCommit 認証切れ)。
決定: GitHub ichirokisanuki/footballnext-ios(master) を正とし、operation 配下を GitHub クローンに置換。Pods は コミットしない(.gitignore、pod install で都度復元)。今回のソース変更のみを移植して push(28f5fe7)。旧 CodeCommit クローンは ~/Downloads/footballnext-ios-codecommit-backup へ退避。両リポジトリは履歴分岐のため相互 push/force は禁止。
理由: GitHub は100MB超ファイルを拒否し、今回 Pangle バイナリが 124MB=そもそも Pods は載せられない。Pods 非コミット+pod install 運用が唯一現実的で、移管時の方針とも一致する。CodeCommit は実質終了済み。
2026-05-30: footballnext-ios の最小 iOS を 12→13 に引き上げ(iOS12 サポート終了)
背景: MAX の広告SDKを最新化する中で Google Mobile Ads を 12→13 に上げる必要があり、GMA 13 は最小 iOS 13 が必須。
決定: デプロイメントターゲットを iOS 13 に統一し、iOS 12 サポートを終了。これにより GMA 13.3 / InMobi 11.3 等の最新アダプタ群を採用する。
理由: GMA 13 だけでなく InMobi 最新アダプタも GMA 13 を要求するため、最新化するなら iOS13 化は不可避。iOS 12 は2026年では実利用ほぼゼロで実害なし。API 破壊はほぼ無く(GADNativeAd→NativeAd の3箇所のみ)移行コストは小さかった。
2026-05-21: footballnext-android は全面近代化して16KB対応リリースする
背景: Google Play から「16KBメモリページサイズ非対応」の指摘(対応期限 2026-05-31)。footballnext-android の前回リリースは2021年10月で、ビルド環境は4年前のまま。
決定: 16KB対応(Realm 7.0.8→10.19.0)だけでなく、ツールチェーンを全面近代化する。Gradle 6.7→8.9 / AGP 4.2→8.7.3 / Kotlin 1.3→2.1 / JDK 17+ / targetSdk 30→35 / minSdk 19→24。kotlin-android-extensions 廃止に伴い ViewBinding へ全面移行。nend SDK は撤去(サービス終了済み)、広告は AdMob/ADG/i-mobile を維持。YouTubeプレイヤーは旧APIの提供終了により android-youtube-player に差し替え。
理由: 16KB対応の native lib(Realm 10.19.0)と Play 要件の targetSdk 35 を載せるには AGP 8系が必須で、それが Gradle/Kotlin/JDK 更新を芋づる式に強制する。jcenter 閉鎖で旧構成はそもそもビルド不能であり、16KBだけの部分対応は不可能。minSdk 24 は Android 6以下(世界シェア1%未満・2026時点で実害なし)を切る判断で、ネイティブmultidex等でビルドが安定する副次効果がある。
2026-05-21: OneSignal は 5.1.38 に固定(最新5.x系を使わない)
背景: footballnext-android の OneSignal を 3.x から 5.x へ移行する際、バージョンをレンジ指定すると最新の 5.9.2 に解決された。
決定: com.onesignal:OneSignal:5.1.38 にバージョンを固定する。
理由: OneSignal 5.8以降は OpenTelemetry / okhttp5 / wire を推移的に引き込み、kotlin-stdlib を 2.2系へ強制昇格させてしまう。プロジェクトの Kotlin コンパイラ整合とAPKサイズの観点から、これらを引き込まない最後の軽量系列 5.1.x の最新(5.1.38)に固定するのが筋がよい。
2026-05-19: cake_sessions Duplicate entry の PHP Fatal は当面放置
背景: 5/16・5/17・5/19 に CloudWatch 経由でエラーログスパイクのアラートメールが来ていた。調査の結果、原因は fnext.thomsonsapp.com(本番 API ドメイン)に対する bot のパススキャン由来。bot が同一セッション Cookie を使い回して並列リクエストを打つため、CakePHP の DatabaseSession::write() が cake_sessions.PRIMARY の race condition で Duplicate entry を起こして Fatal になる。
決定: 即時対応はせず保留。データ実害なし・RDS への影響軽微・正規アクセスへの影響なしの判断。頭の片隅に置いておき、頻度が上がるか実害が出始めたら対応する。
理由: 攻撃トラフィック自体はどこのサービスでも常時発生しており、いま手を動かす緊急性がない。対策案 A〜E(攻撃パス拒否 / Session→Redis / try-catch 握り潰し / アラート緩和 / WAF 導入)はいずれもコストか副作用があるので、観察モードのまま維持する方が筋がよい。中長期で Session 保存先を File or Redis に移す案は副次効果(DB 負荷減)が大きいので、別タイミングで検討する価値あり。
2026-05-09: Step 3 (innodb_buffer_pool_size 1024→768MB 縮小) は保留
背景: 当初は FreeableMemory 直接対策として buffer_pool 縮小を計画(Step 1+2 のインデックス整理に続く 3 段目として)。しかし baseballnext を同 RDS に統合する計画があることが判明し、統合後はホット working set が 1.25-1.5GB 規模に増える見込み。
決定: Step 3 は実施せず保留。baseballnext 統合シナリオが確定したあとに「buffer_pool 1024MB 維持 / 縮小 / medium 昇格」を再評価する。
理由: 統合前にメモリを絞ると、統合後にすぐ戻すか medium 昇格する二度手間になる。Step 1+2 の効果(ReadIOPS -61〜70%)で I/O 余裕は得られたので、メモリ周りは統合計画の解像度が上がるまで動かさない方が筋がよい。
2026-05-08: articles インデックス A 群は INVISIBLE 観察期間をスキップして即 DROP
背景: 通常は INVISIBLE 化で 1〜数日観察してから DROP INDEX する保守的フローを取るが、A 群 4 個(article_site_id / image_width / title / del)はいずれも他複合 index の完全 prefix で、戻す可能性が極めて低い。
決定: INVISIBLE 化の直後に同セッション内で DROP INDEX まで実行(Online DDL / ALGORITHM=INPLACE / LOCK=NONE)。
理由: A 群は機能的に完全な上位互換が存在するため、観察してもクエリ計画の変動はほぼ起こり得ないと予測。万一戻すとしても CREATE INDEX で再構築可能(数分、Online)。Step 3 の判断材料を早く揃えるためにも観察期間の機会損失を避けた。実測では ReadIOPS -61〜70% で副作用なしを確認。
2026-05-08: impressions テーブルの機能を廃止
背景: 730 MB / 6.98M 行のテーブル。INSERT は V1Controller.php:296 1 箇所のみ、読み取りは管理画面 PortalController の集計用 2 箇所のみで、エンドユーザー API からは未参照。
決定: ユーザーが INSERT 行をコメントアウトしてデプロイ → TRUNCATE で全データ廃棄。テーブル定義は当面残置(ストレージ影響軽微)。
理由: 機能としての価値が低く、毎秒 0.7 INSERT 程度の継続的な書き込みコストを払う意味がない。管理画面の article_from_twitter 集計が空になる影響はあるが、ユーザー判断で許容。
2026-05-08: 19-20 時の FreeableMemory スパイクは cron 由来ではなく人為的と確定
背景: 5/7 19:20 に FreeableMemory が 1 分以内に 148 → 55 MB へ急落するスパイクを観測。当初は cron 起動による定常パターンを疑った。
決定: 過去 7 日同時間帯と比較した結果、5/7 のみ突出した急落。同時刻に走らせていた articles + old_articles への AVG(LENGTH(body)) のフルスキャン集計クエリが原因と特定。cron 由来ではない・調査クローズとして扱う。
理由: 過去 6 日の同時間帯は min 119-151 MB のベースライン揺らぎ範囲で、特異なスパイクなし。「ほかプロジェクト談」で話題になった FreeableMemory 課題は、特定時刻スパイクではなく構造的な低空飛行(avg 141 MB / min 115 MB あたり)の方であり、本命の対策は インスタンス昇格 に帰着するという理解。
2026-05-08: hot_keyword_rankings の古いデータ整理は判断保留
背景: 7.4 年分・約 915 万行の死蔵データが堆積。アプリは「最新 dir の rank top2」しか参照していないため、過去データは完全に不要と判断できる。一方で書き込み自体が 2026-04-09 で止まっており、cron 停止か別マシン稼働かが切り分け未済。
決定: データ削除は今セッションでは実行しない。判断材料を蓄積した上で別途検討する。
理由: 削除自体に技術的リスクは低いが、書き込み停止の真因が不明なまま削除すると「機能の生死」を誤判定する可能性がある。書き込み停止が意図的なのか障害なのかを先に切り分けたい。
2026-05-07: footballnext-operation はサブプロジェクト3つを .gitignore で除外
背景: footballnext-ios / footballnext-android / footballnext-server はそれぞれ独立した Git リポジトリで管理されている。footballnext-operation はそれらを跨いで運用情報をまとめる「指揮塔」用途。
決定: 3 サブプロジェクトのディレクトリを本リポの .gitignore に追加して、追跡対象から外す。
理由: ネスト Git の二重管理を避け、本リポは運用メモ・ドキュメント・スクリプト類に集中させる。
2026-05-07: RDS の performance_schema は OFF を維持
背景: カスタムパラメータグループ general-mysql84 で performance_schema = 1 が user-set かつ pending-reboot 状態で残っていた。再起動すると有効化される「不発弾」の状態。db.t4g.small (2GB) の FreeableMemory は Min 55MB / Avg 141MB と既に黄信号。
決定: reset-db-parameter-group で user override を削除し、engine-default (0) に戻す。
理由: Performance Schema は db.t4g.small クラスでは 200〜400MB 追加で消費する。現状すでにメモリが厳しく、有効化するとさらに悪化する。観測性を取り戻したくなったらインスタンス昇格とセットで再検討。
2026-05-07: old_articles テーブルのデータを廃棄
背景: old_articles は articles 2.0GB 規模のアーカイブテーブル。footballnext-server 内に参照コードはゼロ、過去のアーカイブ運用の名残として DB にだけ残っていた。
決定: phpMyAdmin から TRUNCATE してデータを全廃棄。テーブル本体は当面残置。
理由: アプリ参照ゼロかつ運用も止まっているため、データ保持コストが純粋な無駄。テーブル定義(とインデックス枠)まで消すのは物理影響が軽微なので後回しでよい。
2026-05-07: articles のインデックス整理は調査結果に留めて実行は保留
背景: 22個のインデックスのうち、4個(article_site_id / image_width / title / del)は他複合 index の prefix で明確に冗長。ただし本番運用中のテーブルでロックを伴う変更は慎重に進めたい。
決定: 削除候補リストと推奨手順(INVISIBLE → 観察 → DROP → OPTIMIZE)の作成までで一旦保留。次セッションで実行する。
理由: ロールバック容易性を最大化したい。MySQL 8 の ALTER INDEX ... INVISIBLE を使えば Online でオプティマイザから外せて、問題があれば瞬時に戻せる。今日は調査メモとして残し、メンテ時間帯を意識して実行する。
DEVLOG(作業ログ)
開発日誌
このプロジェクトでの作業を時系列で記録する。 最新のエントリが上に来る。
2026-06-20
実況板(LiveHorizontalListCell)が「もっと見る→上に戻る」で壊れる不具合を修正(5.5.3 申請前の単発バグ潰し)
テーマ: 5.5.3 申請前に、AB テストとは無関係の UI バグを1件修正。新着タブ先頭の実況板(横スクロールのライブカード)が、記事一覧の「もっと見る」(offset>0 load)を一度してから上に戻ると、ライブカードが消えて緑の「過去 実況版一覧へ」だけが左端に残る壊れ方をしていた(ios f275fed)。
真因: TopArticleTableView.importContents が offset 問わず毎回 realm.delete(realm.objects(Live.self))→content["lives"] から再追加していた。もっと見る(offset>0)のレスポンスは lives が空なので全削除だけ走り Live が DB から消滅。一方、先頭の LiveHorizontalListRow(offset==0 で生成)は古い liveIds を保持し続け、LiveHorizontalListCell.puts は毎回 DB.objects(Live.self).byID(liveId) で引き直すため nil → カード0枚。row.liveIds.count > 0 は保たれるので moreView だけ offsetX=0 に出ていた。
やったこと:
- 修正①(根本): Live の更新(delete+再追加)を
offset==0(フル取得/プルリフレッシュ)時のみに限定。もっと見るでは Live を消さない。 - 修正②(防御):
putsで moreView を「カードが1枚以上描画できたとき(offsetX>0)」だけ出す。あわせて再利用時クリーンアップをLiveListView+LiveListMoreView両方 remove に拡張し、横スクロール位置を.zeroリセット(累積・残留の予防)。
検証: 実機テストで「もっと見る→上に戻る」でカードが正しく残ることを確認(You)。Debug build 0 errors・改修2ファイル新規警告なし。最初 cell 側(moreView 累積)だけ直して的外れ→スクショで真因がデータ側(Live 全削除)と判明し②から①へ。
気づき/判断: importContents は LiveArticleId / LogoLink / Enquete も同様に offset 問わず delete-all している(もっと見るで一時的に消え得る潜在バグ)。今回は報告のあった Live のみ修正。残り3つは表示が DB 引き直しでないか別経路のため即時破綻はしていないが、いずれ offset==0 ゲートで揃えるのが望ましい。5.5.3 申請に本修正を含める。
23:00 - 起動時広告A/B 実装レビュー round10: round9 medium close・sign-off 回復・実装レビューレーン完了
テーマ: round9 で差し戻された medium(R7-#2 revert による render 失敗の過大計上)の修正(gate 復元+#if DEBUG 化、ios 311e3ad / op 4e489da)を gods-talk round10 でレビュー。clean close。
round10 feedback(ab-test-implementation-review-010):
- [high] 該当なし / [medium] 該当なし。
renderNativeAdViewの戻り値を保持し true 時のみrecordImpression→ round9 medium「render 失敗の過大計上」は解消。false 経路(releaseCurrentLease→releaseAd→adObject=nil→hidden)は main 直列で race・二重 destroy・負 lease・retain cycle なし。loader(for:)の!expired維持で「live entry render 失敗=Bool gate」「expire 済み再render=loader 取得失敗」両方が閉じる。実行ロジックは31dc22bの R7-#2 と機能的に同等へ復帰(差分はコメント+expire hook の Debug 限定化のみ)。round9 medium は close。 - [low] expire hook の
#if DEBUG隔離 完了 — release に残る参照経路なし・Release build 成功で確認。round9 low 推奨を満たす。 - [low] round8 残 low(render false 即時 reload なし/timeout・render false の XCTest 未整備/
kill_observed_at説明文)は非ブロッカーの将来課題。 - 点火可否:
311e3adで round9 medium 解消・round8 sign-off 回復。現行コードで 5.5.3 提出可、点火前チェックリスト完了後に S3active=trueで開始可。新 high/medium ブロッカーなし。通常 native 表示の短い実機再スモークは望ましいが medium close の必須前提にしない(gate は sign-off 済み実装へ復帰・A-1 で rendered=true/描画/ILRD/GA4 impression 確認済み・Release 追加差分はコメントのみ)。
気づき/判断: round9 で私が入れた誤った revert を round10 で正式に巻き戻し close できた。reviewer が実機再スモークを必須前提から外したため、コードレビュー上のブロッカーはゼロ=実装レビューレーン完了。残るは純粋に運用・外部タスク(GA4 2件登録・5.5.3 提出・点火)。
現在地: 実装レビュー round1〜10 完了・sign-off 確定。本番中立(active=false/v7/ttl14日)、5.5.3 据え置きで全修正積載。
次回やること: 5.5.3 提出/リリース(任意で提出前に通常 native 表示の短い実機スモーク)→ GA4 custom定義 2件を手登録 → S3 を v8/active=true・enrollment窓(リリース日起点42日)・min_build=50503 で点火+点火ログ記入。
22:30 - 起動時広告A/B 実装レビュー round9: R7-#2 revert を差し戻し(impression gate 復元)+expire hook を #if DEBUG 化
テーマ: round8 sign-off 後に入れた2変更(R7-#2 revert・expire 人工注入 hook)を gods-talk round9 でレビュー。reviewer が R7-#2 revert を medium 点火ブロッカーとして差し戻し。指摘どおり gate を復元し、hook を #if DEBUG 隔離。Debug/Release 両ビルド 0 errors を確認。
round9 feedback(ab-test-implementation-review-009):
- [medium] R7-#2 revert は「render 失敗の過大計上」を再導入=点火ブロッカー。MAX SDK 13.6.2
MANativeAdLoader.hはrenderNativeAdView:withAd:の戻り値を「正常 render されたら YES」と明記。revert 後は戻り値を捨てて常時recordImpressionするため、SDK が false を返した render 失敗も実表示として計上される。「実機で false が観測されなかった」は「false を成功扱いしてよい」根拠にならない(別 network/view 再利用/将来 adapter 差分で false がありうる)。loader(for:)の!expiredガードは expire 後 render を防ぐが live entry の render 失敗は扱わない。回復性(false 時 hide)と計上 gate(true 時のみ)は分離可能で、最低限 gate を復元すべき。私の「churn リスク」懸念は false 頻発前提で的外れ(churn でなく失敗 ad の回復処理)。 - [low] expire hook は安全だが Release 除外推奨(
#if DEBUG)。[weak self]・main 直列化で leak/race/二重 destroy なし。実 SDKdidExpireと同経路の代理として妥当。単独ブロッカーではない。 - [low] round8 残 low: low1(render false 即時再取得なし)は false 分岐削除で表面上消えたが上記 medium に置換=解消ではない。low2(self-test 範囲)は expire 経路を hook で実機 end-to-end 確認した分前進。low3(
kill_observed_at説明文)は現行契約に誤って含めておらず現状で OK。 - 点火可否: 現行
777128fのままでは sign-off 維持されず提出・点火を承認しない。gate 復元+Debug/Release build+通常 native 表示の短い再スモークで medium は閉じる。
やったこと(この会話で修正):
- ① impression gate 復元
MiddleAppLovinMaxNativeAdCell.swift:let rendered = loader.renderNativeAdView(...)の戻り値を保持しif rendered { recordImpression } else { releaseCurrentLease()+releaseAd()+adObject=nil+hidden+return }(round8 の R7-#2 を実質復元・fail-closed)。コメントに revert が誤りだった旨と論点を明記。 - ② expire hook を
#if DEBUG隔離AppDelegate.swift(launchArg 設定)+MiddleAppLovinNativeAd.swift(debugSimulateExpire宣言・asyncAfter 発火の2箇所)。全6参照が#if DEBUG内に収まることを grep 確認。
検証: XcodeBuildMCP(iPhone 17 / iOS 26.5)で Debug build SUCCEEDED 0 errors・Release build SUCCEEDED 0 errors。改修3ファイルに新規警告なし。Release で debugSimulateExpire が #if DEBUG 除外され参照漏れなしを確認。
気づき/判断: 私の R7-#2 revert は overreach だった。核心は「観測で false が出なかった」と「false は安全」の混同で、SDK 契約上 false は render 失敗を意味するので観測の有無に関係なく計上すべきでない。reviewer 指摘は妥当・却下なし。修正は round8 の sign-off 済み状態への復帰に等しいため round10 再レビューは任意(reviewer が close 条件を明示済み)。
現在地: round9 medium をコードで解消・両ビルド 0 errors。残るは通常 native 表示の短い実機再スモーク(You)。点火前チェックリストは GA4 2件手登録・5.5.3 提出が未。
次回やること: 通常 native 表示の実機再スモーク → 5.5.3 提出/リリース → GA4 2件登録 → S3 を v8/active=true・enrollment窓(当日起点)・min_build=50503 で点火。任意で round10 close 確認。
15:45 - 起動時広告A/B 実装レビュー round5〜8 完了・点火 sign-off 取得
テーマ: gods-talk 実装レビューを round5→8 まで回し、native lifecycle / kill属性 / 計測契約の残 high/medium を全解消。round8 で「5.5.3 提出・点火前チェックリスト完了後に S3 active=true で A/B 開始可・high/medium ブロッカーゼロ」の最終 sign-off を取得。実装レーンは実質完了し、以降は運用準備へ移行。
round5(medium3) ios 0089f65 / op ab066e4:
- R5-#1 native 確定destroy: entry を class化し
released(row利用終了)×displayLeases(描画中cell数)、両成立で destroy(固定0.6秒撤去)。cell が puts で lease 取得・gone/prepareForReuse/deinit で解放。実機スモーク PASS(scroll往復/pull-to-refresh/More連打: クラッシュ0・描画失敗0・loader nil 0)。 - R5-#2 pending timeout(30s)+
didExpireNativeAd(stock限定の失効・補充)。 - R5-#3 kill属性:
effective_interval_minutes(snapshot)を保ちapplied_interval_minutes分離・user property clear を SDK初期化guard前へ。
round6(medium3+low2) ios 3f64ade / op 546dd0a:
- R6-#1 callback を
onMain集約+didLoadで pendingHandles 在籍世代照合(timeout済み遅延成功は即destroy・cap超過防止)。SDKヘッダで didLoad/didFail のthread保証なしが根拠。 - R6-#2
didExpireを deque済み含む全entryへ・destroy条件(released||expired)&&leases==0・cell puts で loader nil 検出→row無効化(4h超の無効広告 render/計上を停止)。 - R6-#3/#5 GA4-only整合(
kill_observed必須dimension化)+TTL省略可統一。launchArg self-test(-ABNativeSelfTest)導入 7/7。
round7(medium2+low2) ios 31dc22b / op 07aa09f:
- R7-#1 stale判定を
CoreTableView.cellForRowAtの deque/nib選択前へ移動+同一pass再deque(Any?型非依存API で共通table を AppLovin非依存に)。 - R7-#2
loader(for:)を!expired必須化+renderNativeAdView==true時のみ impression(render失敗の過大計上を解消)。 - R7-#3 self-test を
preconditionで fail-hard 化。R7-#4kill_observed_at削除(GA4-only で事後参照不能)。
round8 → 点火 sign-off:
- high/medium ブロッカーゼロ。残 low(render-false 即時再取得なし=fail-closed/self-test範囲/廃止param名の説明文/runbook commit表記)は全て非ブロッカー。
- 点火前チェックリスト(コード外・必須): ①GA4 custom定義の実登録(
kill_observed/applied_interval_minutes含む)②enrollment窓・判定値の確定 ③初期 active=false での本番config取得確認 ④点火/停止時刻の運用記録。
検証: 各round sim build 0 errors・改修ファイル新規警告なし。self-test 7/7(fail-hard後も通過)。R5-#1 lease は実機スモークPASS。limitation: sim は MAX native fill なしのため timeout/expire の end-to-end は未実行(destroy判定は純粋関数 self-test+外部レビューで担保)。
気づき/判断: round を追うごとに残課題が native expire 経路へ収束。私の R5-#2(stock限定)→R6-#2(puts無効化) は段階的に甘く、reviewer が cellForRow前段stale判定・renderable !expired・render Bool gate へ正した(毎回妥当・却下なし)。native は experiment flag 外で全ユーザーに作用するため fail-closed が必須。実装は完了、以降は GA4登録・値確定・実機スモークの運用準備へ。
14:30 - 起動時広告A/B Codex round3 の high3+medium4+low1 を修正+実機回帰スモークPASS
テーマ: Codex 実装レビュー round3 が新たに high3/medium6/low2 を指摘(私の判断ミス2件含む)。点火可否=「3 highが残ブロッカー」との総括。high3+medium4+low1 を修正し、実機で回帰スモーク(decision/native)を確認。
Codexの訂正(私の取りこぼし):
- R3-#1: 私の decision 修正(完了ハンドラ再評価・
d103ad5)が不完全だった。完了時は普通 ready=false で not_ready が latch → 後で ready になり show すると「not_ready なのに displayed」の同型矛盾が再発。 - R3-#2: 「attempt紐付けは原理的に不可」は誤り。AppLovin SDK に
showAdForPlacement:がありMAAd.placementで照合できる(=未実装だっただけ)。 - R3-#3: R2-#3 の native cap(
entries.count>20無条件 destroy)を残置していた=表示中ad破棄・全ユーザー作用。
やったこと(high):
- R3-#1/#2
5db2975: decision と show attempt を context 単位で一体・一度だけ claim。emitAdDecisionFinal/noteAdDecisionPending/finalizePendingAdDecision に再設計(非eligible→即final / eligible&ready→claim+show+final(shown) / eligible¬_ready→保留しcontext閉鎖時にnot_readyをfinal / SDK未init→保留)。claimAdAttempt()で show を context 1回に限定。placement に token(=launchId)を載せshow(forPlacement:)、callback はad.placement一致(isCurrentAttempt)時のみ funnel へ。完了ハンドラ再評価は app active 時のみ。→ decision と実表示が常に整合・旧attempt遅延callbackの誤帰属を排除。 - R3-#3
5d26ee6: native cap 撤去。releaseAd(_:)を新設し NativeAdRow.deinit(row利用終了)で生成元loaderへdestroy。表示中ad破棄・上スクロール再表示時のloader(for:)nilを解消。entriesはlive row数で自然有界。 (medium/low): - R3-#4/#5/#6/#7
ef4d47e: kill-switchをshow直前に最新last-known-goodで確認(isKillSwitchActive・open contextにも反映)/strictIntの2^53飽和回避・ttl上限60日・config_version上限・samePolicyのttl比較Double化/applyConfigをmainへ集約(thread race排除)/時計巻き戻しfail-close(isWithinTrackingWindow)。 - R3-#8
791952f+runbook0789c73: build符号化を成分数・範囲でfail-close(衝突回避)+符号化規則/数値上限/kill-switch反映時間をrunbookに明記。 - R3-#9(DEBUG定義+XCTest)は test基盤の別作業として先送り。
実機回帰スモーク(iPhone 14・seed): decision=interval が実結果と整合(群B 24h以内→起動時広告抑止・矛盾なし)・content_reached発火・native middle_native の imp/rev 健在(スクロール往復で APPLOVIN_EXCHANGE/Liftoff)・クラッシュ0・描画失敗0。R3 変更の回帰なし。
気づき/判断: 実装レビューは round を重ねるごとに「実機/エッジでしか出ない」層(SDK初期化タイミング・placement照合・table cell lifecycle・thread race・時計異常)まで掘られた。私の誤判断(#2不可・decision修正の不完全・native cap残置)を round3 が捕捉。Codexの指摘は妥当で却下なし。
現在地: Codex round3 の点火ブロッカー(high3)+medium4+low1 を修正・push 済み。実機回帰スモークPASS。残るは Codex round4(R3修正の確認・推奨)か、R3が挙げた個別エッジ(not_ready→ready/21件超native/kill-switch mid-context)の的を絞った実機再検証、その後 5.5.3 提出。
次回やること: Codex round4 で R3 修正を確認 → 必要なら個別エッジ実機検証 → 5.5.3 提出→リリース→点火。R3-#9(DEBUG/XCTest基盤)は別途。
13:30 - 起動時広告A/B 実機検証(iPhone 14・iOS26.5)A〜D 全PASS+実機で発見したdecision矛盾を修正
テーマ: Xcode→実機直挿し(devicectl・Debug+DEBUG override・--console でNSLog捕捉・seed/-FIRDebugEnabled)で検証A〜Dを実施。全PASS。検証中に新たなdecisionバグを1件発見し修正・再検証した。
やったこと/結果:
- 検証A(native描画+ILRD・最優先)PASS: 記事一覧スクロールで
fn_ad_impression/fn_ad_revenue(slot=middle_native・ad_unit=562ca14298b68765・network=Liftoff Monetize・precision=exact)発火=R2-#3 handle方式で native ILRD 欠損なし。footer_banner も発火。クラッシュ0。観測用に ABAdCollector へ #if DEBUG NSLog 追加(d103ad5)。 - 検証B(content_reached・R2-#4)PASS: ATT表示中で止めた時点で content_reached 0件(連鎖中抑止=負の確認)。プロンプト完了後のコールド再起動で content_reached 1件・time_to_content=1(正の確認)。途中の launch_no_content は「content前に背景化(Mac切替)」の正常動作と確定。
- 検証C(群B処置)PASS: この端末は群Bバケット。decision_reason=interval・policy_eligible=false・effective_interval_minutes=1440・app_open非表示=24h間隔の処置が実機で動作。
- 検証D(#11・更新直後)PASS:
-ABSimulateUpdaterState(旧キー=30分前・新キー=0 を注入)で移行発火を確認。updater_migration old=… new=(同値)=新キーへ旧値引き継ぎ成功、decision=interval・app_open非表示=アップデート直後の追加表示なし(fd913dd)。 - 実機で発見→修正したdecision矛盾(
d103ad5): コールド起動の最初の didBecomeActive は SDK未初期化のため、R2-#9aのsdk_not_initializeddecision が emitAdDecisionOnce(R2-#m1)の「1回」を消費し、SDK初期化後の本当のdecision(interval/shown)を抑止していた(app_open_ad_displayedが出るのにdecision=sdk_not_initialized の矛盾)。→ SDK未初期化時はdecisionを出さず保留し、ALSdk初期化完了ハンドラで showAdIfReady を再評価して本当のdecisionを1回送る方式に変更。再検証で decision_reason=interval を確認。
気づき:
- TestFlightは検証に不要(Youの指摘どおり)。広告SDKがdevice-onlyなだけで、Xcode→実機直挿し(Debug+実広告配信・ILRD発火)で完結。devicectl の app 引数は
--区切りが必要(-ABSeedTestConfigが短縮フラグ列に誤解釈され-tで失敗する)。 - 実機検証はコールド起動の SDK 初期化タイミングという、sim では出ない条件を踏んで decision バグを炙り出した=実機検証の価値。
- seed は同一version・enrollmentStartArt相対値のため2回目以降は same-version-immutability(R2-#m5)で reject され cached 継続(テスト専用の挙動・本番影響なし)。
現在地: 実機検証 A〜D 全PASS・decisionバグ修正済み。S3 config は中立(active=false/v7/ttl14日)。5.5.3 据え置きで全修正+検証完了=5.5.3 提出可能な状態。残るは任意の Codex round3/R2 low。
次回やること: (任意)Codex round3 で R2修正群+実機発見のdecision修正を最終確認 → 5.5.3 を App Store 提出 → 承認後リリース → 普及を見て S3 点火(active=true・config_version+1・enrollment窓を実リリース日に調整)→ GA4着弾を Data API 検証+事前テスト①〜⑧。
12:30 - 起動時広告A/B Codex round2 残りの must-fix(R2-#4 / R2-#3)完了+実機検証手順を整備
テーマ: 11:30 の続き。Codex round2 の残 must-fix だった R2-#4(起動プロンプト連鎖の content_reached 誤確定) と R2-#3(native loader 再構成) を実装し、実機検証手順書を作成。これで round2 の must-fix を全消化。
やったこと:
- R2-#4
bcc9950: 起動プロンプト連鎖(Splash→EULA→RSS→TopReload→Push→ATT)を単一 boot covering 化。bootSequenceActiveを onDidFinishLaunching でON、終端InitialATTAction解決(ATT非提示)または ATT VC dismiss でbootSequenceEnded()→content再評価。プロンプト中・隙間での content_reached 早期/群依存確定を抑止。sim(初回=プロンプト出っぱなし)で早期発火しないこと(負の確認)済。returning-user 経路の正の発火は実機検証へ。 - R2-#3
55fbe06: native を 1 load = 1 delegate handle(NativeAdLoadHandle)方式へ。各 loader を自分の callback に紐付け、並行 load の誤対応付け・revenueDelegateweak によるILRD欠損・誤 destroy を解消。loader(for:)/deque/didPayRevenue/cap destroy は維持、owner weak で循環断ち。compile確認のみ(sim で MAX native load 不可)→ 実機検証が必須関門。 - 実機検証手順書
ab_test_device_verification.md(op81fc50f): 検証A native描画+ILRD(収益クリティカル・最優先)/B オンボーディング完走後content_reached/C AB funnel全体(seed or 一時active)/D 群A=現行どおり(#11)。観測は GA4 DebugView(-FIRDebugEnabled)。
気づき/判断:
- 「TestFlight 必須」は誤り(Youの指摘)。広告SDKが device-only なだけで、Xcode→実機直挿し(Debug でも実広告配信・ILRD発火)で native 検証は完結。Release 構成で実機ビルドすれば本番経路(seedなし・最適化ON・DEBUG除外)も同時確認できる。TestFlight は最終配布の関門であって修正検証の必須要件ではない。
- R2-#3 は revenue クリティカルでブラインド改修リスク高。描画は
loader(for:)経由(MiddleAppLovinMaxNativeAdCell.swift:57)で最新.loaderは不使用と確認したうえで実装。検証A 不合格ならリリース不可。
現在地: Codex round2 の must-fix を全 code-complete(ios master へ push 済み)。S3 config は緊急停止のまま(active=false / v7 / ttl14日)=本番中立。5.5.3 据え置きで全修正積載。残るは実機検証(You が Xcode→実機)と、任意の Codex round3 / R2 low。
次回やること: 実機検証(手順書 ab_test_device_verification.md の A〜D)→ 合格で 5.5.3 提出→承認→リリース→ S3 点火(active=true・config_version+1・enrollment窓を実リリース日に調整)→ ga4 MCP 接続のうえ GA4 着弾検証+事前テスト①〜⑧。任意で Codex round3(R2修正群の最終確認)。
11:30 - 起動時広告A/B 点火→Codex実装レビューで重大バグ発覚→緊急停止→high/medium修正群(5.5.3据え置き)
テーマ: enrollment窓を決めて**点火(active=true)**まで実施した後、トムソン通知HTML公開・5.5.3バージョンバンプ・実装コードのCodexレビュー(gods-talk ab-test-implementation-review)を回したところ、実コードに重大バグが多数見つかり、緊急停止して順次修正した。本番は中立(active=false)に戻し、5.5.3 提出も検証完了まで保留。
時系列でやったこと:
- 点火(段階B):
ab_test_config.jsonを active=true / config_version=2 にしてs3://footballnext/ab_test_config.jsonへ配置(op851ee7a)。enrollment窓6/22開始なので週末は挙動不変の設計。 - トムソン要確認3項目を HTML化→
thomsonsS3 公開(起動時広告A-Bテスト_点火前最終確認_20260619.html)。Youより「確認は任されている」=叩き台確定扱い。 - iOS版状況の確認(App Store Connect MCP): 現行公開は 5.5.2(6/12ビルド・AB計測コード未収録)。master HEAD にAB実装あり。番号が5.5.2のままだったので 5.5.3 へバンプ(ios
39fee19)。※5.5.3は未提出=番号据え置きで再利用可。 - シミュレータ pre-flight(段階A健全性): 本番S3取得→パース→保存を実証済(前エントリ)。さらに seed test(
-ABSeedTestConfig/-FIRDebugEnabled・要SWIFT_ACTIVE_COMPILATION_CONDITIONS=DEBUG)で experiment_enrolled→target_launch→content_reached→app_open_ad_decision の GA4 送信(Analytics.logEvent)到達を確認。ga4 MCP は本セッション未接続でDataAPI裏取り不可→DebugView目視はYou側、と整理。 - Codex 実装レビュー round1(
ab-test-implementation-review-001): high 8 / medium 5 / low 2。最重要は #1「showAdIfReadyがcanEnroll/窓を見ずB処置を適用=非参加者にB漏れ・点火済みconfigと相互作用」。 - 緊急停止: 指摘#1を受け S3 を active=false+config_version+1 に戻す(op
…、最終 v6)。5.5.3 提出も保留。 - high/medium 修正(実機検証なしで安全な範囲を実装・各 seed 回帰確認):
- #1 状態分離(isParticipating=既参加&&追跡窓内&&active/fresh|cached・trackingDays=28・beginTargetContextを既参加追跡/新規enrollに分離)
3df1b59 - #2 config単一snapshot+cached実装
fb79e31(※後述 R2-#1 で再設計) - #5/#7 callbackをadStateガード+hidden/failed emitを context クローズ前に・watchdogをcontext-scoped
4b5316e - #6 impression冪等キーを creative→広告instance×slot
63824d4 - #3 build範囲 fail-close+dotted版パース、#11 群A旧キー移行、#9(a) SDK未初期化decision補完
6020850 - #10 割当再作成防止、#8 config検証強化(schema互換/version>0/min<=max)
5f6f589 - native loader再構成(#6/#7 native)・#9(b) は当初「先送り」とした。
- #1 状態分離(isParticipating=既参加&&追跡窓内&&active/fresh|cached・trackingDays=28・beginTargetContextを既参加追跡/新規enrollに分離)
- Codex round2(
ab-test-implementation-review-002): round1修正を確認しつつ 新規 high 4 / medium 6 / low 2。最重要 R2-#1「#2のsession全体固定+TTL6hで、cold launch間隔>6hの日次ユーザーが永久に非参加=実験不成立」。また R2-#3 で「revenueDelegateはweak=loader誤対応付けでILRD欠損しうる→nativeは先送り不可」と私のrationale誤りを訂正。R2-#4「起動overlay間gapでcontent_reachedが広告有無=群依存で誤確定(GATE交絡)」。 - R2 修正群(実装・seed検証・push):
- R2-#1 snapshotを session固定→context世代方式 に再設計(beginTargetContextがlast-known-goodを1回読みrecordに固定・fetchは次context有効)+既定TTL 6h→14日・seed時は実fetchスキップで決定化
4a9c516、config ttl 14日化2115035 - R2-#m2 追跡窓をJST暦日(daysSince<trackingDays)へ・R2-#m4 assignmentVersion fail-close・R2-#m5 厳密JSON型検証(strictBool/Int/Double)+同一version不変性
835e91f - R2-#m1 decisionをcontext単位1回(emitAdDecisionOnce)・R2-#m3 非参加化時にuser property clear
f32fe12 - R2-#m6 impression冪等を weak identity box
3e7fac2 - R2-#2 達成可能分は adStateガードで対応済(残差=MAX callbackがtokenを返さない原理的制約として記録)
- R2-#1 snapshotを session固定→context世代方式 に再設計(beginTargetContextがlast-known-goodを1回読みrecordに固定・fetchは次context有効)+既定TTL 6h→14日・seed時は実fetchスキップで決定化
気づき/判断:
- 提案書・実装計画の机上Codexレビューを5ラウンド通しても、**実コードでしか出ない重大バグ(policy/measurementの状態未分離、TTLとsnapshotの相互作用)**が出た。実装レビューは別物として必須。
- 私の #2(session全体固定)は筋が悪く R2-#1 で日次ユーザーを壊した。context世代方式(record固定+fetchは次context)が正解。
- native loader は「A/B妥当性に非影響・収益別経路」と判断したが、revenueDelegate weak の指摘で収益欠損あり=点火前ブロッカーへ格上げ。実機検証必須。
現在地: S3 config は active=false / v7 / ttl14日(中立)。5.5.3 据え置きで全修正を積載。残る must-fix は R2-#4(起動overlay gap・オンボーディング連鎖の covering token 化) と R2-#3(native loader再構成・実機/TestFlight検証必須)。
次回やること: R2-#4 を慎重に実装(LaunchManager連鎖に covering token)→ R2-#3 を実機検証とセットで → Codex round3 → 全通過で 5.5.3 提出→承認後リリース→ S3 再点火(active=true・config_version+1)。
08:20 - 起動時広告A/B 点火準備の続き(トムソン確認3項目HTML公開/enrollment窓確定/段階A=不活性config配置/シミュレータpre-flight PASS)
テーマ: BASE実装・GATE凍結・GA4登録の完了を受け、点火に向けた残作業を進めた。トムソン要確認3項目の通知物をHTML化してS3公開、enrollment期間を確定してconfigに設定、不活性config(active=false)を本番S3へ先行配置(段階A)、シミュレータで本番S3取得経路を実証(段階B=点火は保留)。
やったこと:
- トムソン要確認3項目をHTML化→
thomsonsS3に公開(起動時広告A-Bテスト_点火前最終確認_20260619.html)。3項目=①円/人コホート(締切6週/追跡28日/ITT)②収益正規化・residual(月平均eCPM・感度0.5–1.5×・普及率70%/旧版上限30%・月次総売上提供依頼)③事前テスト合格しきい値①〜⑧。提案書と同体裁、事業判断レベルに噛み砕き。URL:https://thomsons.s3.ap-northeast-1.amazonaws.com/起動時広告A-Bテスト_点火前最終確認_20260619.html(HTTP 200確認)。Youより「トムソン確認は任されているので不要」と判断 → 叩き台のまま確定扱い。 - enrollment窓を確定(叩き台6週間):
enrollment_start_at=1782054000(2026-06-22 月 0:00 JST)/enrollment_end_at=1785682800(2026-08-03 月 0:00 JST・+42日)。JST深夜0時の月曜境界=コホート日が揃う。ab_test_config.jsonに2キー追加。解析データ成熟の目安は最終enroll日2026-08-02+追跡28日+maturation3日=〜2026-09-02。 - 段階A(中立)= 不活性config を本番S3へ先行配置:
s3://footballnext/ab_test_config.json(バケットfootballnextルート=Const.baseS3Url)。experiment_active=false=全員非参加で挙動不変。初回アップロードはpublic-read ACL未付与で403→--acl public-read付与で再アップロード→HTTP 200取得可に。配置先プロファイルはthomson-ik(footballnextバケットにアクセス可)。 - シミュレータ pre-flight(段階Aの健全性確認)= PASS: iPhone 17 Pro sim でフルビルド→install→launch。
ABConfigLoader.fetch()(AppDelegate:129)が本番S3を取得→パース→検証→UserDefaults保存まで通すことを、保存キーab_config_app_open_interval_2026q3の中身で実証(取得1秒)。全項目一致、enrollment窓epochがNSDate(reference-date 2001)として 1782054000/1785682800 にビット一致デコード。active=false なので canEnroll=false=enroll/AB event は出ず挙動不変も確認。これまで未実証だった本番S3取得経路を初めて実証(過去検証は seed 経路のみ)。
気づき/判断:
footballnextバケットはオブジェクト単位 public-read ACL運用(teiki等は200・新規アップロードは無ACLだと403)。S3配置時は--acl public-read必須。- 段階Aで確認できるのは「本番S3取得+パース+保存」まで。GA4のAB funnel(experiment_enrolled等)は active=false では出ない(点火か seed test が必要)。
- 点火しても enrollment窓開始(6/22 0:00 JST)まで誰もenrollしない設計(
canEnrollが窓開始前 false)=今点火しても週末は挙動不変、月曜の最初の対象起動から群Bが24h間隔に切替。
点火前チェックリストの到達: ①GATE値叩き台✅ ②GA4 custom定義✅ ③トムソン3項目(叩き台確定・通知物公開)✅ ④enrollment期間決定✅ ⑤段階A配置・pre-flight✅。残: ⑥段階B=点火(experiment_active=true+config_version=2で再アップロード)のみ。Youのgo待ち。
次回やること: 段階B点火(Youのgo時)。点火後 GA4 dimension にデータが乗るか Data API 検証。緊急停止は active=false+版+1 で即可能。
02:32 - 起動時広告A/B 点火準備(GATE値叩き台凍結/S3 config正本/runbook/GA4 custom定義 登録完了)
テーマ: BASE 実装完了後、点火に向けた準備物を整備。GATE値の叩き台を凍結し、S3 配置用 config と手順書を作り、GA4 の custom 定義を全件登録(管理画面操作は You が実施)。
やったこと:
- 残りGATE値の叩き台を確定・凍結(commit ios
2bae96d/ op8b9fb4c):- client 実行時値 →
ABProtocol: 許容clock skew=300s・未来clamp=400日・既定TTL=6h。showAdIfReadyの時計判定を skew許容+未来clamp回復で精緻化。ABConfigLoaderの ttl_seconds を任意化。 - 解析側値 → 正典 measurement_protocol(mp_2026q3_r1): maturation=3日/円人コホート(締切42日・追跡28日・分母=割当全員ITT)/D7D14単日(JST)/residual(月別平均eCPM・感度0.5–1.5×・旧版売上比率上限30%/普及率≥70%)/収益正規化(gross/USD/対象版・月)/事前テスト合格しきい値①〜⑧/GA4 Cohort再現手順。要トムソン最終確認は3項目(コホート定義・residual/正規化・合格しきい値=事前登録として握るだけ、実装は既定値で動く)。
- client 実行時値 →
- S3配置用
ab_test_config.json(正本)とab_test_deploy_runbook.md(点火/緊急停止/版番号規則/GA4登録ワークシート/wire format)を新設(commit opb35bf2f)。config 初期値experiment_active=false=配置しても誤発進しない。ABConfigLoaderのパース+検証通過を standalone で確認。 - MAX native の loader-ad lifecycle を修正(commit ios
8dc3b5a): load毎に差し替わる loader を ad とペアで retain(weak delegate で過去adのrender/impression/revenue callback欠損を解消)。cell はloader(for: ad)で生成元loaderを使う。destroyAd→Swiftdestroy(_:)改名対応。フルビルド成功。 - GA4 custom定義の登録ワークシートを実コードに整合(commit op
22720b0): 実送信 param キーと1対1照合。未送信のconfig_age_hoursは登録不要と明記(spec §4 訂正)。 - GA4 custom定義を全件登録完了(GA4 Editor権限 取得済→You が管理画面で実施・スクショで検証): カスタムディメンション18件(イベント範囲16+ユーザー範囲2=
assigned_group(user)/app_open_ad_interval(user))+カスタム指標3件(time_to_content秒/revenue_usd標準/effective_interval_minutes分)。param名・スコープ・単位すべて一致=計21件 過不足なし。
気づき/判断:
- GA4 custom定義の作成は接続中の
ga4MCP(Data API=読み取り専用)とADC未設定では自動実行不可。登録ワークシートを用意し You が手動登録、私は Data API で事後検証する分担に。 - 「トムソン合意が要る」を再整理: 実ブロッカーは GA4 Editor 権限(取得済)だけ。しきい値・コホート定義は事前登録の作法上トムソンに通知/握るが、実装は叩き台既定値で動く。月次総売上は解析フェーズ入力で点火ブロッカーではない。
- 検証用 seed/NSLog(
#if DEBUG+-ABSeedTestConfig二重ガード)は実ビルドに混入せず S3 なしで funnel 再検証できる唯一の手段なので残す。
点火前チェックリストの到達: ①GATE値叩き台 ✅/②GA4 custom定義登録 ✅。残: ③トムソンへ要確認3項目を通知 ④enrollment期間を決め config に設定 ⑤ab_test_config.json を S3 配置→experiment_active=true+config_version+1 で点火。
次回やること: トムソンへの通知文面づくり/enrollment 期間の決定/S3 配置・点火。点火後 or seed DebugView で GA4 dimension にデータが乗るか Data API 検証。
01:03 - 起動時広告A/B 計測基盤 BASE A〜F をフル実装+シミュレータ検証+コミット
テーマ: 実装計画 rev6/正典 SPEC.md rev6 を元に、起動時広告 削減A/Bテストの計測基盤を実コードで一気に実装(BASE A〜F)。footballnext-ios(GitHub/master)に実装し、フルビルド成功・シミュレータ実機で全ファネル発火を確認・コミット/push まで完了。
やったこと:
- 新規4ファイル(
footballnext-ios/FootballNEXT2015/):ABTest.swift(BASE-A/F core): 群割当SHA256("experiment_id:install_uuid")先頭8バイト big-endian% 100→bucket_map([0,50)→A)、永続install UUID(IDFA不使用)、experiment_id単位の原子保存・再計算禁止、enrollment追記、型付きABActivationConfigの全項目検証+last-known-good+canEnroll/config_status+fail-safe interval、JST暦日(cohortDay/daysSince)、verifyTestVectors()。ABLaunchTracker.swift(BASE-C): ForegroundCycle/TargetLaunchContext、write-aheadOpenContextRecord+terminal CAS、at-most-once送信(emission_state pending→attempted)、可視状態機械+content provider前面チェック、presentation watchdog、kill/crash時previous_launch_unresolved(元属性で送信)。Foundation-only(emit注入式)で standalone テスト可能。ABAdCollector.swift(BASE-E): 全MAX枠 共通fn_ad_impression/fn_ad_revenue(冪等・ILRD一本化、experiment共通paramのみ)。ABConfigLoader.swift(BASE-F): S3ab_test_config.jsonの非同期取得→手動パース→検証付き applyConfig(fail-safe)。
- 既存14ファイル配線: AppDelegate(lifecycle 4種・群別頻度制御の時計安全化(abs廃止/clock_anomaly)・
app_open_ad_decision(BASE-D)・delegate/revenueDelegate配線)、UserData(didDisplay起点の永続キー)、ViewController(content前面provider)、SplashViewFadeoutAction(content可視)、EULA/RSS/Push/ATTの4モーダルVC(overlay covering)、AdView/MaxByAppLovinBannerAd/AppLovinUnderArticleBannerAd/MiddleAppLovinNativeAd(+Cell)(collector接続)。 - pbxproj に4新規ファイルを手動登録(
plutil -lintOK)。 - 検証: ①ABTest+ABLaunchTracker を standalone swiftc でコンパイル+状態機械テスト全PASS(非参加/enroll+target_launch/content_reached/launch_no_content/kill→unresolved/at-most-once/CAS/割当ベクトル)。②フルビルド成功(iPhone 17 Pro sim・Debug)。③シミュレータ実機起動で
experiment_enrolled→target_launch→app_open_ad_decision(not_ready)→content_reachedの実発火をログ確認。 - 正典
AB_TEST_MEASUREMENT_SPEC.mdの未決GATEから「SHA256→bucket定数・テストベクトル・新experiment時の再無作為化」を確定へ移動(ABProtocolへ凍結)。 footballnext-iosにコミット4ebdeb6(18ファイル/+1262 −63)→ GitHub master へ push。
気づき:
- このプロジェクトは Swift の
SWIFT_ACTIVE_COMPILATION_CONDITIONSに DEBUG が未設定(GCC側のみ DEBUG=1)。→ Swift の#if DEBUGは通常 Debug ビルドでも全除外。検証用 seed/NSLog は#if DEBUG+起動引数-ABSeedTestConfigの二重ガードで、通常ビルドには絶対混入しない(今回はSWIFT_ACTIVE_COMPILATION_CONDITIONS=DEBUG上書きで検証)。副作用としてverifyTestVectors()の起動時自己チェックも通常は走らない(standaloneで検証済みのため実害なし)。
本番影響: S3 に ab_test_config.json を置くまで全context非参加=AB event 不発・広告挙動は現行どおり(中立)。config 配置が実験開始トリガー。
次回やること: GATE-値(トムソン合意要の数値含む)を measurement_protocol として凍結 → ab_test_config.json 作成・S3配置 → GA4 custom定義登録 → native loader-ad lifecycle の深いrefactor(pre-test ③/⑦で検証)→ 検証用 seed/NSLog の最終扱い判断。
2026-06-18
23:55 - 起動時広告A/B 実装計画HTMLの作成+Codex 5ラウンドレビュー(gods-talk)→ rev6 で設計レビュー完了(〜06-19 00:00)
テーマ: トムソン承認が出た提案書を受けて、**実装計画(HTML)**を作成し、Codex(gods-talk)で繰り返しレビュー。rev1→rev6 まで反映し、正典 AB_TEST_MEASUREMENT_SPEC.md も同期。コード変更なし(資料・正典・レビュー往復のみ)。
やったこと:
- 実装計画
起動時広告A-Bテスト_実装計画_20260618.htmlを新規作成。実際のiOS実装(AppDelegate.swiftshowAdIfReady()/広告delegate、UserData.setAppSettings()のS3配信、Firebase=GA4、永続UUID未実装)を読んで具体化。 - gods-talk 新トピック
ab-test-implementation-plan-reviewで 5ラウンドのレビューを往復(依頼書5本+feedback5本)。各ラウンドで指摘を rev に反映:- round1(high9): 可視判定をdidHide限定にしない状態機械化、
appOpenAd.delegate/revenueDelegate未配線、LaunchContext一意化、割当原子保存、fail-safe ITT保持、未確定検出、GA4 ad_impression衝突回避、円/人の固定コホート、混合eCPMの層別。 - round2(high7): 正典が旧仕様のまま→同期、assignment/activation/enrollment 3分離、型付きconfig原子保存、ForegroundCycle/TargetLaunchの60秒gate、presentation pending、unresolvedを成熟後残差率に格下げ、総売上1個ではslot別点推定不可。
- round3(high7): experiment_id同梱manifest互換規則、context単位config snapshot、背景化outcome確定、
target_cohort_day、fallback param配線、収益範囲条件復活、全MAX枠ILRD。→「条件付きGO」。 - round4(high4): write-ahead OpenContextRecord、3 manifest分割+版番号、canonical revenue event一本化、総売上母集団の演算式(感度変数化)。→「限定付きGO・追加ラウンド不要」。
- round5(high1): 配送を at-least-once→at-most-once に方針転換(GA4-onlyでexactly-once不可・logEventにack無し)、per-event emission state、medium4件の内部矛盾同期。→「設計レビュー完了。実装PRの受入チェックで確認できる範囲」。
- round1(high9): 可視判定をdidHide限定にしない状態機械化、
- HTMLに 3レーンの実装ゲート(BASE着手可 / BASE契約=API確定前に潰す5点 / GATE-値=schema凍結前に固定)を新設。
- 正典
.devnotes/AB_TEST_MEASUREMENT_SPEC.mdを rev6 まで全面同期(write-ahead/at-most-once配送・3 artifact・canonical event・総売上母集団・enroll許可config/kill-switch・param出所分離・MAX native lifecycle・暦日TZ Asia/Tokyo)。
決めたこと(DECISIONS にも転記):
- 設計レビューはここで完了。次は実装フェーズ(BASE着手+GATE値を measurement_protocol として凍結)。再レビューは必須にせず実装PRの受入チェックで確認。
- 配送意味論は at-most-once(pending→attempted を先に確定して一度だけ送信、欠損は残差率に残す)。
気づき:
- Codexはローカルコードを直接読むため、
appOpenAd.delegate=self未配線のような「コードを見ないと出ない」指摘が出た。提案書段階の机上設計では見えなかった穴。 - 5ラウンドで指摘の質が「設計の穴→境界/原子性→配線漏れ→収束仕上げ」と逓減し、自然に収束した。
次回やること:
- 実装着手(BASE)。GATE-値(skew/TTL/maturation日数/暦日具体値/residual単価根拠 等)を measurement_protocol として凍結してから本番 event schema を確定。
13:38 - 起動時広告A/B 提案書の作成・Codex2ラウンドレビュー反映・S3公開アップロード
テーマ: 10:32 の測定設計を、トムソンに出せる**提案書(HTML)**に仕上げる。議論で設計を精緻化し、別LLM(Codex)に2ラウンドのレビューを受けて反映、最後に公開アップロードまで。コード変更なし(資料・メモのみ)。
やったこと:
- 提案書
起動時広告A-Bテスト_提案書_20260618.htmlを新規作成(既存 review-20260605.html と同じ体裁)。md版は破棄しHTMLのみ。 - 議論で固めた設計の更新点(提案書に反映):
- 群B「1日1回」→「24時間に1回」(実装のローリング挙動に一致)。
- BigQuery不使用=GA4のみ・無料に決定。
ab_group集計のため GA4 を Viewer→Editor へ権限変更をトムソンに依頼、と明記。 - 起動時広告の枠を実コードで棚卸し(フォーマット4種+ネイティブ生存5系統。Movie/HotKeyword/SnsMovie は休眠。全ネイティブは
CoreTableView.dequeAd()集約)。 - 表示カウントは「起動時/それ以外」の2系統。ネット内訳は送らず generic +1。
- Codex レビュー(gods-talk,
.gods-talk/ab-test-proposal-review-*)2ラウンドを実施・反映:- R1: 2バケツeCPMの単価構成仮定/
launch_bounceの交絡・起動秒数で群A機械的不利/GA4のみで有意差は出せない(→実務閾値判断に降格)/imp定義 等。 - R2: 群Aは「毎回」でなく現行50分間隔(S3実測
app_open_ad_interval=50確認)/3パターンに「判定保留」追加/content_ready→content_visible(広告の背後発火問題)/主因の因果主張を弱める/即停止表現を現実化/売上範囲を iOS・対象アプリ・対象月に限定/ITT明記/D7D14起点=実験参加日 等。 - 指標名を
launch_bounce→「コンテンツ未到達離脱率」に改称。
- R1: 2バケツeCPMの単価構成仮定/
- 提案書を
thomsonsS3バケットに公開アップロード(profile thomson-ik / ap-northeast-1 / object ACL public-read、既存HTMLと同方式)。秘密情報スキャン済み。URL:https://thomsons.s3.ap-northeast-1.amazonaws.com/起動時広告A-Bテスト_提案書_20260618.html - 計測仕様の正典
.devnotes/AB_TEST_MEASUREMENT_SPEC.mdを新規作成(コンテンツ未到達離脱率の計測フル+確定前提+残りTODO)。
気づき/確定事実:
- 本番S3
app_open_ad_interval = 50(群Aの基準は「毎回」ではない)。処置差は想定より小さく、期間・最大減収は事前テストで再試算が必要。 - Firebase/GCP プロジェクトは
footballnext-bca77(Owner=info@thomsons.jp、You は Editor)。GA4 property は Viewer のみ=カスタムディメンション登録不可。BQエクスポート有効化は Owner 権限が要り Sandbox(課金なし)でブロック → GA4のみ方針の裏付け。
次回やること:
.devnotes/AB_TEST_MEASUREMENT_SPEC.mdの TODO(willDisplay計測・App Openコールバック・rolling24hのabsバグ・固定コホート・収益式・設定取得SLA 等)を埋める。- トムソンの提案レスポンス待ち → 承認後に実装着手。
10:32 - 起動時広告 削減A/Bの測定設計(トムソン提案に向けた机上検討)
テーマ: review-20260605.html(多角レビュー)を起点に、トムソン側との議論をどう前に進めるかを検討。起動時広告(App Open Ad、総売上の約25%=ヒヤリング値)を「どこまで減らせるか/減らすと得か損か」を実測で判断する50/50無作為A/Bの測定設計を、コードの現状確認込みで詰めた。コード変更はなし(設計のみ)。
前提(トムソンとのやりとりで判明):
- トムソンは「記事内広告がウザがられてる」認識が強い。You は「起動時広告への不満も大きい」と主張。→ どちらが主因かは片面ずつ絞って実測すれば決着がつく。
- トムソンは「起動時広告を減らして満足度を測れない」と渋る。→ You が「離脱率(行動)で測れる」と返した。これが今回の設計の出発点。
- 起動時広告の売上 ≈ 総売上の25%(ヒヤリング済み)。全消しではなく「絞るダイヤル」として扱う方針。
- You は一開発者(決定権はトムソン)だが仕様提案は可能。提案は「測定設計込みのパッケージ」にして通しやすくする。
コードの現状確認(footballnext-ios / FootballNEXT2015):
- ①パターン確定: 起動時広告は
AppDelegate.swift:241でshowAdIfReady()をアプリが明示的に呼ぶ(SDK自動表示ではない)→ifで囲むだけでA/B群分けが成立。 - 時間間隔スロットルは既に実装済み:
showAdIfReady()(:546)内にappOpenAdInterval(分)による表示間隔ガード+直近60s background復帰の抑制あり。0で実質毎回、1440で1日1回。 - GA4イベント配線済み:
app_open_ad_show {interval_minutes}/app_open_ad_suppress {reason: interval/recent_background/not_ready}/ user propertyapp_open_ad_interval。 - サーバ設定チャネルが存在: 起動時に S3 JSON(
Const.baseS3Url/201811)→app_setting→UserData.setAppSettings(UserData.swift:184)で UserDefaults に流し込む作り。app_open_ad_intervalはキー名整合あり=全ユーザー一律ならアプリ更新なしで間隔変更可能(ただし無作為A/Bではなく前後コホート比較になる)。「ハードコード」認識は半分だけ正しかった。 - 広告構成はパッチワーク: 起動時広告・記事下バナー(
AdView)は AppLovin MAX(MAAdRevenueDelegate.didPayRevenueで表示単位の実収益が取れる)。だが記事一覧の差し込みは自前ローテ(NativeAdManager+AdNativeRateで AdG[Supership AdGeneration]/Zucks/LINE系 default を切替)=MAX外の直SDK。impression本数が一番多い面がMAX外なので、MAXのdidPayRevenueだけでは収益の大半を捕捉できない。 didPayRevenueの実装は空(MiddleAppLovinNativeAd.swift:61等)。ad.revenueを拾えば実額が取れるのに今は何も記録していない。
測定設計(合意した骨子):
- 方式: 50/50 無作為A/B。バケットをアプリに仕込む(永続UUID→%2)。群A=現状(毎回)/群B=起動時1日1回。
ab_groupを GA4 user property+各イベントに付与。 - 判断軸①「お金」: 土台は全SDK共通の impression カウント(AdG/Zucks/MAX全部、表示の瞬間は必ずコードを通るので自前で数えられる)。金額換算は2バケツの eCPM —「起動時(MAX実額)」と「それ以外(=月次総売上−起動時実額 を それ以外impで割った混合平均eCPM)」。各社ダッシュボードを漁らずトムソンの月次総売上1個で導出可能。
- eCPMが割り算で出る理由=出るのは混合平均だが、A/Bで使うのは群間のimpression“差”の金額で、差の中身の構成比はローテ配分が群無関係ゆえ全体平均と一致 → 平均を掛けても近似で正しい(数値例で確認)。偏りが気になればネット別imp計測+ネット別月次売上で精密化。
didPayRevenueは「全部の実額が取れる」ではなく「MAXスライスの実額+月次の答え合わせ」に役割降格。
- 判断軸②「残り具合」:
launch_bounce(起動Xs以内・コンテンツ0で終了)↓ / 週次セッション/人 ↑ / D7・D14継続 ↑。launch_bounce は「率」なので自然減(MAU −15%/年)に埋もれない=起動時広告への一番タイトな指標。 - 判断ロジック(3パターン): 群Bの[お金/残り] が ①同じ以上/良化→即採用 ②微減/良化→残る分で回収するので採用 ③減/不変→効果なし戻す。最適 interval は多腕 or 階段で後追い。勝った値は S3 で全員展開(再リリース不要)。
仕込む実体(次セッション以降の実装対象):
- A/Bバケット割当(永続UUID→%2、
ab_group付与) launch_bounceイベント新設- 全面・全ネット別の impression カウント(群タグ付き)
didPayRevenueの空メソッドを埋める(MAX実額・補助)/起動時の出し分けはAppDelegate.swift:241をifで囲む
未確認(次セッションで詰める):
- S3
app_settingにapp_open_ad_intervalが現在入っているか/現在値(群Aの基準値の確定)。Const.baseS3Url実体の中身確認。 - AdG/Zucks 等の自前ローテ面に「表示された」確実なフック(didDisplay相当 or deque直後)があるか=全面impカウントの可否。
- 月次総売上をトムソンからどの粒度でもらえるか(総額1個 or ネット別)。
次回: この設計をトムソンへの提案資料にまとめる(You が別セッションで継続予定)。
2026-06-13
00:39 - YouTube プレイヤーの WKWebView 化(割り込み対応・申請前の最終変更)
テーマ: 7channel 由来のナレッジ ios-wkwebview-youtube-inline-playback を footballnext-ios の動画詳細プレイヤーに適用。deprecated な UIWebView 実装(2015年製)を WKWebView 構成に置き換えた。
やったこと:
FootballNEXT2015/YoutubePlayerView.swiftを全面書き換え(変更はこの1ファイルのみ)- UIWebView 継承 → WKWebView を内包する UIView(既存
SnsMoviePlayerViewと同じプロジェクト内イディオム)。xib(素の<view>+ customClass)と呼び出し側load()/clear()API は互換のため他ファイル変更なし - インライン再生の必須2点セット:
allowsInlineMediaPlayback = true+mediaTypesRequiringUserActionForPlayback = [] - ナレッジ最大のハマりポイント対応:
baseURL: nilをやめ、Const.baseURLString(https://fnext.thomsonsapp.com)をbaseURLと embed URL のorigin=で一致させる構成に - embed URL を
http://→https://化、enablejsapi=1/allow="autoplay; encrypted-media; fullscreen"/allowfullscreen追加 target="_blank"リンク無反応対策としてWKUIDelegate.createWebViewWithで同一 WebView ロード- 自動再生をやめた(
autoplay=1→autoplay=0、ユーザー指示)。タップで再生開始に変更
- UIWebView 継承 → WKWebView を内包する UIView(既存
- arm64 シミュレータビルドで BUILD SUCCEEDED を確認(
DEVELOPER_DIRに Xcode 26.5 指定) - footballnext-ios へ選択コミット(
a94aa84、YoutubePlayerView.swift のみ)→ origin/master へ push 済み。別件の未コミット変更(push通知系・Podfile 等)は混ぜずに残置
詰まったこと / 気づき:
baseURLStringはAppではなくConstのメンバー(どちらもApp.swift内に定義があり紛らわしい)。最初App.baseURLStringと書いてコンパイルエラー- ナレッジ第4項(埋め込み不可動画のフォールバック、サーバー側
status.embeddableフラグ)はサーバー改修を伴うため今回は見送り
次回やること:
- You がこの変更を含めて App Store 申請(5.5.1 アーカイブ)
- 実機での動画詳細画面の再生確認(ビルド確認のみ実施済み)
2026-06-06
17:32 - 定期実行の一元化 + Lambda 大掃除(今後のアプデ前の足場固め)
テーマ: 定期実行の呼び出し元が「cron 専用 EC2」と「AWS Lambda(EventBridge)」に二分されている状態を整理。cron 側へ寄せつつ、稼働実態のない Lambda を一掃した(AWSアカウント thomson-ik / 865107862340 / ap-northeast-1)。
やったこと:
- TFN_cron(毎分の curl ラッパー Lambda)を cron 専用サーバーへ移管
- 中身は
fnext.thomsonsapp.comの8エンドポイントを叩くだけ(Baseball は既に cron 上で同等処理を実行済み)。 - 移管手順: 8エンドポイントの200疎通確認(cronサーバーから)→ 実 crontab に8行追加(
sleepでずらし配置、FootballNEXTセクションの movieUseSqs 直後)→ cronログで全8ジョブの起動確認(07:36:02)→ EventBridge ルールTFN_cronを disable。 - サーバー上に
~/crontab_backup_20260606-073553.txtを退避。後日 Lambda 本体+ルールも削除済み。
- 中身は
- 死んだ/不要な Lambda を段階的に削除(コードは毎回 /tmp にバックアップ後に実行)
TFN_cronUpdateOldCurrentData(2016 current データ用・起動0・トリガー無し)TSP_*5関数(TSP_cron/TSP_crawlArticle/TSP_cron10/TSP_uploadToS3OfIchimens/TSP_sendScheduledNews)+ 対応 EventBridge ルール4本。叩く先spp.thomsonsapp.com(The Sports Paper)が HTTP 000=死亡を確認。sports-paper-*4関数 + API Gatewaysports-paper(c2770nol99) + テスト残骸hello-dev-hello/testDetach/lbTest+test-rule。- 孤児 EventBridge ルール
Per10Minutes/sposhin-rss-crawler-notice-crawl-article(2019 temp.json 指し)。 - 残存関数の稼働精査(7日/90日 invocation、APIキャッシュ無効も確認=「0回」は本物)→ 完全ゼロの19関数 + 捨て API Gateway 4本(prod-test-crawler / prod-rss-crawler / custom-runtime-php71-demo2 / rss-clawert)を削除。本番 football/baseball の API GW(7isghft565 / a0s3exiz21)と rss-crawler-notice API は現役なので残置。
- 結果: Lambda 42関数 → 9関数(残9は全て現役か保守ツール)。
- teiki / article エンドポイントの呼び出し経路を解析
footballnext-teiki-notification-articles(24h 897 / 7d 7,095 / 30d 29,322・エラー0・平均162ms)とfootballnext-article(24h 126 / 7d 547)は、いずれも API Gateway(7isghft565) → Lambda(DynamoDB READキャッシュ) → MISS時のみhttp://fnext.thomsonsapp.com/api4/...(CakePHP Api4Controller) の三段構成。- 呼び出し元はアプリ本体(iOS
ArticleViewController/TeikiArticleSuiteTableView、AndroidArticleDetailActivity/GogaiListViewModel/TeikiNotificationSuiteModel)。teiki は「定期プッシュ通知タップ → 記事一覧」の導線。 - アプリは既に大半のAPIを
https://fnext.thomsonsapp.comに直叩きしており、execute-api 経由は/article/{id}と/footballnext-teiki-notification-articles/{id}の2つ(iOSは加えて/oshirase-v2/{id})の飛び地のみ。原点は HTTPS 200・iOS は ATS 無効なので、直叩き移行の障壁は無し。
詰まったこと / 気づき:
- CloudWatch GetMetricStatistics の「30日を1バケット集計」は高頻度関数で過少値を返す(rss-crawler が 24h 4320 なのに 30d 10 等)。7日を日次バケットで合算する方式に切替えて正確化。
- API Gateway の
delete-rest-apiは連続実行でレート制限(429 / 約30秒に1回)。バックオフ再試行で対応。 - 削除した
footballnext-oshirase-v2(90日invocation 0)を iOSOshiraseViewController.swift:30がまだ参照していることを後から発見。90日ゼロ=到達不能のデッド画面と判断し、復元せず据え置き(次回アプリ改修でコードごと整理)。
次回やること:
- API Gateway 飛び地(
/article/{id}+/footballnext-teiki-notification-articles/{id})をhttps://fnext.thomsonsapp.com/api4/...直叩きへ移行 → 最終的に footballnext API Gateway を撤去。論点は Dynamo READキャッシュの代替(必要なら CakePHP 側キャッシュ)。 - iOS
OshiraseViewControllerのデッドコード整理(削除済みfootballnext-oshirase-v2参照の除去)。 - rss-crawler の cron 一元化(RSS発見ロジックのサーバ側エンドポイント化)は引き続き宿題。
2026-06-05
19:50 - large-scale-service-review の試運転(Football NEXT 多角レビュー)+ GA4 接続 + Codex UXオーディット
やったこと:
large-scale-service-reviewスキルを Football NEXT で試運転。複数ソースを集めて多角レビューを実施し、結果をreview-20260605.html(HTMLダッシュボード、チャート付き)に出力。- データソースを6つ集約して三角測量: ①広告売上推移
FootballNext売上推移.html(13年分、Youが提供)②全レビュー3,987件..._AppStore_20260605.json(Youが提供)③ASCレビュー/売上 ④GA4行動データ ⑤GA4コホート ⑥Codexによる広告実装の静的解析。 - GA4 を接続: Football NEXT は Firebase Analytics 実装済→ GA4 property
211701453(footballnext-bca77) を特定。Google公式google-analytics-mcp(pipx) をclaude mcp add ga4 -s userで登録(ADC流用・property固定)。MAU/セッション/コホート/クラッシュ等を取得。 - Codex の Build iOS Apps に UXオーディットを依頼(指示書
codex-ios-ux-audit_指示書.md)。実機起動は OneSignal 2.12.0 の arm64 simulator 非対応で失敗したが、静的コード解析で広告挿入ロジックを file:line 付きで特定(findingscodex-ios-ux-findings.md)。 - スキル本体を改善(
~/.claude/skills/large-scale-service-review/SKILL.md): 収益モデル先行確定・ローカル成果物の能動スキャン・複数ソース三角測量・カバレッジ可視化を追加。
主要な発見(レビュー結論):
- 広告ネットワーク売上は 2019ピーク¥17.5M → 2025 ¥5.4M(−69%)、下落が加速(2023 −23%/2024 −29%/2025 −34%)。直近月¥332k。
- 全レビュー分析: 平均星が2022→2023で崩落(3.0→1.6→1.33)、「広告」言及率 20%→93%。売上下落と時系列一致=広告過多によるデススパイラル。
- GA4: MAU 26,076→17,609(約2年−32%)、セッション−41%。クラッシュ影響は1%未満で安定性は無問題。94%iOS・ほぼ100%日本。エンゲージ率90%・月35セッションでロイヤル層はヘビー。
- Codex: App Open Ad のスロットル(頻度ゲート)がコメントアウトされている=広告抑制が外された物証。一覧10件中2枠・コメント5件ごと等の密度も判明。
気づき:
- 広告型アプリは ASC の proceeds=0 が正常で、実収益はメディエーション/ローカル集計にある。最初に収益モデルを確定しないとデータの在処を外す。
- AdMob/MAX は別管理者で面別収益は取得不可。効果検証はGA4の離脱シグナルで代替する方針。
次回やること:
- 改善実装の着手判断(最優先=App Open Ad のスロットル復活)。
- 任意: OneSignal を監査用ビルドでスタブ化して Codex に実機UX再検証させる。
2026-06-03
17:11 - 試合詳細に「得点者」表示を追加(iOS 5.5.1 + サーバ/フルスタック機能追加)
やったこと
- サーバ(footballnext-server):
- API-Football v3 の単一試合レスポンスに
events(得点・カード・交代)が同梱されることを実APIで確認 → 追加の RapidAPI コール無しで得点者が取れる。CronController::ovserveFixturesの更新にevents_json保存を追加し、取得&更新の本体を_updateFixtureFromApi($id)に共通化。 - 終了試合も強制更新できる
updateFixture($id)を新設(/cron/updateFixture/{id}、Match Finished スキップ無し)=過去試合のバックフィル手段。 V2Controller::fixture()(iOS が叩く/v2/fixture/{id}の実体)にscorers整形を追加:type=Goal抽出、team.idで home/away 判定、OG は反転せず素直にマップ、PK/OG 種別、アシスト。当初 V1Controller を誤編集→スモークテストで気づき V2 へ移して V1 は撤回。AfterInstall.shにモデルスキーマキャッシュ自動クリア(rm -f app/tmp/cache/models/*)を追加。- DB(master)の
fnlive_fixturesにevents_json TEXT NULLを追加(ALTER)。 - デプロイ後、
/v2/fixtureで scorers キー・500なし、/cron/ovserveFixtures200無エラーを確認。直近10試合をupdateFixtureでバックフィル(全件 scorers=得点数 一致)。
- API-Football v3 の単一試合レスポンスに
- iOS(footballnext-ios / 5.5.1):
LiveFixtureにscorers(表示用 computed property 付き)を追加。- 得点者カードを
LiveFixtureScorersView(記事テーブルの tableHeaderView・手動レイアウト)で実装:タイトル外出し+白い角丸カード、ホーム左/アウェイ右、live_ball画像インライン、PK/OG、アシストはボール幅インデント、行間調整。 - バージョン 5.5.0→5.5.1、MAX Mediation Debugger OFF を同梱。コミット
a3793be・push・タグ5.5.1(規則v…_byXcode…とは別の素タグ、ユーザー指定)。トムソン確認OK。
詰まったこと / 気づき
- エンドポイント取り違え:
/v2/fixtureは V1Controller ではなく V2Controller。スモークテスト(scorers キー欠落)で発覚。手を入れる前に「実際に叩かれるルート」を確認すべし。 - CakePHP 2.x スキーマキャッシュ: カラム追加後、
app/tmp/cache/models/を消さないとsave()が新カラムを黙って捨てる(エラーも出ない)→ AfterInstall で自動クリア。 - レイアウト試行錯誤: スコアボード用テーブルは storyboard で高さ固定 184・自己サイズ非対応。section 追加や
estimatedRowHeight=0で見切れ・崩壊。最終的にスコアボード側は触らず、記事テーブルのヘッダーに手動レイアウトで載せて解決。 - OG の team 帰属: API-Football は OG を「得点が入る側(受益チーム)」に既に割り当てている(ブラジル6-2パナマで実検証、home6/away2 がスコアと一致)→ 反転不要。
- 起動経路:
ovserveFixturesはリポジトリ内から呼ばれず、cron 専用サーバの*/2 * * * * curl http://fnext.thomsonsapp.com/cron/ovserveFixtures(公開URL→web サーバで実行)で回っていた。
次回やること
- iOS 5.5.1 を Xcode アーカイブ → App Store 提出。
- 任意: 方針転換で残った未使用
LiveFixtureScorersCell.xib+ 空セルの掃除。
2026-05-30
20:00 - footballnext-ios の近代化(広告SDK全面更新・Zucks/Moloco追加・GMA13移行・GitHubリポジトリ正常化)
やったこと
- Xcode 26.5 でビルド不可を解消: Reachability pod 3.7.6→3.7.7(
pod update Reachability --no-repo-update)。新SDKで private 扱いになった#import <netinet6/in6.h>が 3.7.7 で除去済みのため解決。Ads-Global のスクリプト失敗はビルド中断の巻き添えで、Reachability 修正後に消えた。 - Zucks ネイティブ広告セルを追加: 記事枠(
ZucksNativeAd/ZucksNativeAdCell+xib)とミドル枠(MiddleZucks*)を既存パターン(CoreNativeAd 派生ローダー → NativeAdRow → セル)に倣って実装。AdNativeType.zucks(=15) /AdMiddleNativeType.zucks(=7) 追加、各 NativeAdManager と CoreRow に配線。frameId は記事_f7758a97de/ ミドル_02f98424fe。 - Moloco を MAX に追加: Podfile に
AppLovinMediationMolocoAdapter(→4.6.1.1、MolocoSDKiOS 4.6.1)。アプリ側コード不要(MAX が自動ロード)。Moloco の SKAdNetwork ID は既存で網羅済みだった。 - 広告SDK/アダプタ一斉最新化(MAX Mediation Debugger 指摘分)をリスク順(低→中→高)に段階更新:
- AppLovin 13.6.2 / Mintegral 8.1.4 / Pangle 8.1.0.6 / Amazon 5.6.2 / Line(FiveAd) 3.0.1 / Google Mobile Ads 13.3 / InMobi 11.3。
- FiveAd 2→3 はメジャーだが旧APIが deprecated 維持+警告非エラー設定でコード変更不要。
pod 'FiveAd'が直書きロックされていたためpod update AppLovinMediationLineAdapter FiveAdで同時更新。 - GMA 12→13 は最小 iOS 13 必須のため iOS12→13 化(プロジェクト/Pods/post_install を 13.0 統一)。
GADNativeAd→NativeAd(NS_SWIFT_NAME 化)で CoreRow の 3 箇所を修正。削除バナー関数等は能動使用なしで API 破壊ほぼ無し。 - GMA は 13.3 止まり(
GoogleMobileAdsMediationInMobi 11.3.0.0がGMA ~> 13.3.0を要求。13.4 は Google 製 InMobi アダプタの追従待ち)。InMobi の AppDelegate 使用(IMSdkinit/setLogLevel)は 11.3 でも互換。
- Liftoff(Vungle): アダプタ 7.6.2.0→7.7.3.0(VungleAds 7.7.3)。「SKAdNetwork Incomplete」は公式XML(155件)と差分し不足2件(
apzhy3va96/ln5gz23vtd)を Info.plist に補完。 - アーカイブ時の「Upload Symbols Failed」警告(AppLovin/DTBiOS/InMobi/Moloco の dSYM 無し)は、ベンダーが dSYM 非同梱のため無害・除去不可と判断(クラッシュ症号化のみ影響、配信に支障なし)。
重大な気づき / 是正
- operation 配下の footballnext-ios が「GitHub移管前の CodeCommit クローン」だった。GitHub 側は移管時に履歴を書き換えて>50MBバイナリを除去し
Pods/を .gitignore 済みなのに、ローカルは remote=CodeCommit のまま・Pods を丸ごとコミットする旧運用。push しようとして発覚(CodeCommit 認証切れ)。 - 是正: GitHub
ichirokisanuki/footballnext-ios(master) を新規clone → 今回のソース変更のみ(Pods 除外、17ファイル)をパッチ移植 →pod install→ push(28f5fe7)。GitHub クローンを operation 配下に再配置し、旧 CodeCommit クローンは~/Downloads/footballnext-ios-codecommit-backupへ退避。 - 今回 Pangle バイナリが 124MB=GitHub の 100MB 制限超で、Pods は載せられない/載せない設計の妥当性を再確認。
次回やること
- 旧 CodeCommit バックアップ(
~/Downloads/footballnext-ios-codecommit-backup)の削除(新環境のビルド確認済み)。 - 実機で MAX Mediation Debugger 再確認(Liftoff が Completed になるか、各SDKが LATEST 表示か)。
- xcuserstate の追跡解除(.gitignore 済みなのに過去コミットで追跡され続けている)。
2026-05-21
19:33 - footballnext-android の 16KB 対応リリース対応(全面近代化)
やったこと
- 背景: Google Play から「16KBメモリページサイズ非対応」の指摘(対応期限 2026-05-31)。footballnext-android の前回リリースは2021年10月。
- 16KB問題の診断: アプリ自体にネイティブコードは無く、
.soを持ち込むのは Realm のみと特定。librealm-jni.soが唯一のネイティブlib。Realm Java 10.19.0 が「16KB ELF packaging」対応版(Realm Java は EOL、これが事実上の最終対応版)。 - ツールチェーン全面近代化: Gradle 6.7.1→8.9、AGP 4.2.0→8.7.3、Kotlin 1.3.50→2.1.0、JDK 8→17+(Android Studio同梱JBR 21使用)、jcenter 除去。compileSdk/targetSdk 30→35、minSdk 19→24。
- Realm 7.0.8→10.19.0(16KB対応の本丸)。
- ViewBinding 全面移行:
kotlin-android-extensions廃止に伴い、synthetic を使う43ファイルを ViewBinding 化。 - 依存刷新: OneSignal 3→5(5.1.38固定)、AdMob 19→23、okhttp 3→4、AndroidX各種、Firebase BoM。
- 撤去・差し替え: nend SDK 撤去(サービス終了済み)、旧 YouTubeAndroidPlayerApi.jar(提供終了)を android-youtube-player に差し替え。
- 署名鍵の確定: Dropbox の
footballNEXT_key2.jksを keytool 検証。端末上の現行版アプリの証明書 SHA-256 と完全一致 → 正しい署名鍵と確定(Play App Signing 未使用、この鍵で直接署名)。keystore.properties 方式で署名設定を整備。 - 実機テスト(Huawei YAL-L21 / Android 10)でクラッシュ4件を発見・修正:
- Realm 10 の UIスレッド書き込み禁止 →
allowWritesOnUiThread(true) - SplashActivity の DialogFragment.show() が onSaveInstanceState 後に落ちる → state ガード
- クリック連打防止の窓が3500ms と長く、記事一覧のタップが1回で効かない(既存バグ)→ 600ms
- SiteListFragment / TvProgramListFragment がコンストラクタ引数を持ち、復元時に InstantiationException(既存バグ)→ 引数なし+arguments方式
- Realm 10 の UIスレッド書き込み禁止 →
- 署名済みリリースAAB(versionCode 17 / versionName 3.0.7)生成。16KBアラインメント検証OK(全4ABIで
.soLOADセグメント align=16KB、APK/AAB両方で zipalign -P16 整合)。 - footballnext-android リポジトリに7コミット。
気づき
- 16KB対応そのものは Realm のバージョン更新のみ。だが「新規リリースを出す」には4年分の近代化が芋づる式に必要だった(jcenter閉鎖で旧構成はビルド不能、AGP8がGradle/Kotlin/JDK更新を強制)。
- OneSignal 5.8+ は OpenTelemetry/okhttp5/wire を引き込み kotlin-stdlib を2.x系へ強制昇格させる → 5.1.x系最新(5.1.38)に固定して回避。
- クラッシュ2件(連打防止・コンストラクタ引数Fragment)は2021年版から潜在していた既存バグ。モダンな Fragment ライブラリで顕在化した。
次回やること
- You:
footballnext-android/app/build/outputs/bundle/release/app-release.aabを Play Console にアップロード(内部テスト→製品版推奨)。 - 動画/コメント/設定画面の手動確認、OneSignalプッシュの実配信テスト、ADG/i-mobileネイティブ広告の実機表示確認。
- footballnext-android の7コミットを origin へ push するか判断。
2026-05-19
09:33 - エラーログスパイクアラートの原因特定(cake_sessions Duplicate entry)
やったこと
- 直近のエラーログスパイクアラートメール 3 件(5/16 16:57 / 5/17 07:48 / 5/19 04:51)の原因調査
- 各時刻の RDS メトリクス確認 → CPU・接続数・IOPS とも正常範囲、5/17 のみ FreeableMemory min 74MB とやや低めだが他は通常
- CloudWatch Alarms 履歴を辿ってアラート源を特定:
footballnext-apache-error-log-all-spike(LogMetrics 名前空間、Apache error log のスパイク監視)- 3 件とも同じアラームが ALARM 遷移していた
- メトリクスフィルタからロググループ
FootballNEXT/Apache/error_logを特定 - 各時刻 ±5 分の Apache error_log を取得 → 3 件とも完全に同じパターン:
PHP Fatal: PDOException SQLSTATE[23000] Integrity constraint violation: 1062 Duplicate entry 'xxx' for key 'cake_sessions.PRIMARY'- 短時間に数十件発生 → アラート発火
- referer 解析: すべて
fnext.thomsonsapp.com/<不審パス>から/dwcc/,/lottery/,/api.php/,/Public/,/qiantaizhuanyon_chaxun/,/iexchange/webtrader/,/melody/,/appx/,/syn/,/djacgkjs/ほか- 中国語パス・賭博/抽選/取引系・admin パネル探索・PHP scanner の典型パターン
- ユーザー確認:
fnext.thomsonsapp.comは 本番 API ドメイン(捨てるわけにはいかない) - 構造の理解: bot が API ドメインに対してパススキャンを並列で打ってきて、同一セッション Cookie を使い回すので CakePHP の
DatabaseSession::write()でINSERT INTO cake_sessionsが race condition で衝突 → Fatal
決めたこと
- 緊急性なしと判断しいったん放置。攻撃トラフィック由来でデータ実害なし・RDS への影響も軽微、ただし将来の対応候補として頭の片隅に置く
- 対応するなら候補:
- A. ALB or Apache で攻撃パス拒否(短期、即効)
- B. CakePHP Session 保存先を File or Redis に変更(恒久、副次効果大)
- C. cake_sessions 書き込みを try-catch で握り潰し(症状対症療法)
- D. CloudWatch アラームしきい値緩和
- E. AWS WAF / Cloudflare 導入
詰まったこと / 気づき
- 「エラーログのスパイク」と聞くと RDS や Web サーバの本格的な異常を疑いがちだが、実態は bot 攻撃由来の Fatal 連発だった
fnext.thomsonsapp.comというドメインを当初「正体不明の第三者ドメイン」と勘違い。本番 API ドメインを user が運用してることを最初に確認すべきだった(聞いてしまえば 30 秒だが、聞かないと方向を間違える)- CakePHP の
DatabaseSessionは race condition に弱い設計(INSERT を素直に投げる、ON DUPLICATE KEY UPDATE 等を使わない)。普通のサービスでも稀に Duplicate が発生する可能性
次回やること
- もし cake_sessions Fatal が頻発するようなら ROADMAP の「いつか」項目を「今月」「今四半期」に格上げする
- baseballnext 統合後の RDS 状況は引き続き問題なし(5/11 dev-timer #15 経過、本日 5/19 朝の確認でも安定)
2026-05-09
17:45 - dev-timer #14 経由で Step 2 効果検証 / baseballnext 統合方針
やったこと
- dev-timer #14(5/9 16:00 設定)の通知を受けて作業再開
articlesインデックス DROP(5/8 02:54 実施分)の Before/After を CloudWatch で比較- FreeableMemory: min 55 → 63 MB / avg 137 → 133 MB(ほぼ変化なし、想定通り)
- CPU avg: 5.4 → 4.9% / max: 60.5 → 58.6%(軽微な改善)
- ReadIOPS avg: 5.9 → 2.3(-61%) / max: 2193 → 649(-70%) ← 本質的な効果
- スロークエリ・体感異常なし
Step 3(buffer_pool 1024→768MB 縮小) は baseballnext 統合計画の存在により 保留確定
決めたこと
- DROP した 4 個(
article_site_id/image_width/title/del)は実際に冗長で、本番影響ゼロ。Step 2 成功判定 - buffer_pool 縮小は baseballnext 統合シナリオ確定後に再評価。今は触らない
- baseballnext 統合作業は
baseballnext-operationプロジェクト側で進行(footballnext-operation はサーバー受け入れ準備状態を維持)
詰まったこと / 気づき
- 「ReadIOPS が大幅に減る」のはインデックス整理の主効果として理解しておく価値あり。FreeableMemory 改善を期待すると外す
- gp2 100 IOPS ベースの環境では IOPS 削減はメモリ削減と同じくらい価値がある
- baseballnext 統合計画の存在で「buffer_pool 縮小」が筋が悪くなったように、設計判断は前提条件次第で逆転する(メモリ縮減 vs 統合余地確保が対立する)
次回やること
- baseballnext 統合作業の進捗待ち(baseballnext-operation 側)
- 統合作業中・直後の footballnext RDS のメトリクス観察(FreeableMemory・CPU・IOPS)
- 統合後にあらためて Step 3 の判断(buffer_pool 1024MB 維持 / 縮小 / medium 昇格)
2026-05-08
02:54 - articles インデックス Step 1+2 一気実行 / impressions 廃止 / baseballnext 統合計画把握
やったこと
articlesインデックス整理 Step 1 (INVISIBLE) を実施- A 群 4 個(
article_site_id/image_width/title/del)をALTER INDEX ... INVISIBLEで隠す - 即時切替・即時リバート可能の保険フェーズ
- A 群 4 個(
- 通常は INVISIBLE で 1〜数日観察の流れだが、A 群が冗長 prefix 確定で戻す可能性が極めて低いため 観察期間をスキップして即 Step 2 (DROP INDEX) に進む判断
ALTER TABLE articles DROP INDEX ... ALGORITHM=INPLACE, LOCK=NONEで 4 個を 1 文で物理削除(実時間 0.27 秒)- 結果: index 499 → 369 MB(-130MB / -26%)、インデックス数 22 → 18
impressionsテーブルの利用調査- INSERT は
V1Controller.php:2961 箇所のみ(他はコメントアウト済み) - 読み取りは管理画面 PortalController の集計用 2 箇所のみ(エンドユーザー API からは未参照)
- ユーザーが INSERT をコメントアウトしてデプロイ → 自分で TRUNCATE 実行
- 結果: 6,982,221 行 / 730 MB → 0 / 0.03 MB(約 730 MB 解放)
- INSERT は
- DB 全体: 5,006 → 4,145 MB(開始時 9,102 MB から -54%)
- baseballnext を footballnext RDS に統合する計画があることをユーザーから共有
- baseballnext RDS(db.t4g.micro / MySQL 8.4.8 / 20GB / Single-AZ)の実態調査
- 合計 2.24 GB / 90 テーブル
- articles 632 MB(5/8 08:43 更新 = 現役稼働)、old_articles 522 MB、sns_movie_tweets 472 MB、movies 146 MB ほか
- SELECT 5.8/秒、INSERT 0.73/秒、UPDATE 0.24/秒、buffer_pool ヒット率 99.775%
- 「アイドル」ではなく現役稼働中のサービス
- 統合シナリオ B(t4g.small 維持)で約 +$15/月削減、ただしホット working set が 1.25-1.5GB 規模になりヒット率は要観察
- C(medium 昇格セット)も選択肢、コスト的には現状(micro+small)と大差なし
- baseballnext RDS(db.t4g.micro / MySQL 8.4.8 / 20GB / Single-AZ)の実態調査
- セッション内で起きた誤認の訂正
hot_keyword_rankingsの古いデータ削除を「buffer_pool 効率↑」と説明したのは雑だった- 正しくは「読まれていないデータは buffer_pool に乗らない → 削除しても FreeableMemory は変わらない」。ストレージ(FreeStorageSpace)には効くが、メモリには効かない
- 同様に
old_articles/impressionsの TRUNCATE もメモリには効かなかった(実測でも変化なし)
決めたこと
articlesインデックス A 群は INVISIBLE 観察期間をスキップして即 DROP(戻す可能性 ≒ 0、CREATE INDEX で再構築可能)impressionsの機能を廃止(INSERT 停止 + 全データ廃棄)Step 3(buffer_pool 1024→768MB 縮小) は 保留。baseballnext 統合計画と相性が悪い- baseballnext 統合の実作業は
baseballnext-operationプロジェクト側で進める(こちら footballnext-operation は受け入れ側として現状維持) - メモリに「直接効く / 間接的に効く / 効かない」の切り分けを今後は混同しない
詰まったこと / 気づき
- 「アイドルに見えた baseballnext が実は現役」だったように、CloudWatch DatabaseConnections avg だけで稼働判断するのは危険(短時間接続はサンプリングで漏れる)
- インデックス整理は「メモリには効かないが ReadIOPS には大きく効く」の代表例
- 設計判断は前提条件(baseballnext 統合の有無)で容易に反転する
次回やること
- 5/9 16:00 dev-timer #14 で Step 2 効果検証
- baseballnext 統合計画の進捗(baseballnext-operation 側で進行)と footballnext RDS への影響観察
- 統合後にあらためて buffer_pool / インスタンスサイズの判断
02:24 - articles OPTIMIZE 結果検証 / hot_keyword_rankings 調査 / メモリスパイク切り分け
やったこと
- 深夜にユーザーが phpMyAdmin で実行した
OPTIMIZE TABLE articlesの結果検証- data 812 → 263 MB(-549 MB / -68%)
- index 2010 → 499 MB(-1511 MB / -75%)← インデックス側のページ充填率がかなり悪かった
- total 2822 → 762 MB(-2060 MB / -73%)
- data_free 1101 → 7 MB(断片化解消)
- avg_row_bytes 1448 → 426(妥当な値に戻った)
- DB 全体 9102 → 5006 MB(-4096 MB / -45%、
old_articles空化分と合算) - CloudWatch FreeStorageSpace: 5.86 GB → 8.53 GB(+2.66 GB)
- テーブル削減 4GB に対して free 増分が小さいのは InnoDB の共有テーブルスペースや一時ファイルが OS に即返却されないため
- FreeableMemory はほぼ変わらず(OPTIMIZE 後も avg 110-126 MB)
- buffer_pool ヒット率 99.996% で working set が小さい以上、テーブルが縮んでもメモリ消費は同じ
hot_keyword_rankingsテーブルの利用調査- 7.4 年分(最古 2018-11-25 / 最新 2026-04-09)、全 9,153,398 行のうち直近 1 ヶ月の追加が 495 行のみ(事実上書き込み停止)
dir = YYYYMMDD_HHMMフォーマット(10分ごとのスナップショット)、distinct 386,983 件- コード参照は すべて「最新 HotKeywordHistory.dir に紐づく rank top2」のみ(V1Controller / S3UploaderController / CronController / CurrentDataComponent / Portal)
- 過去データは完全に死蔵
- 19-20 時の FreeableMemory スパイク(min 55MB)の原因切り分け
- 過去 7 日同時間帯の min は 119-151 MB(特異なパターンなし)
- 5/7 19:20 のみ 1 分ピンポイントで 148 → 55 MB に急落
- 同時刻に流していたクエリ:
articles+old_articlesへのAVG(LENGTH(body))などフルスキャン集計 ×2 - 結論: 自分の調査クエリが buffer_pool を瞬間的に奪った人為的スパイク。cron 由来の定常パターンではない
決めたこと
hot_keyword_rankingsの古いデータ整理は判断保留(実行は別途)- 19-20 時スパイクは「人為的・cron 由来ではない」と確定。深追いせずクローズ
- メモリ問題の本質は構造的(db.t4g.small 2GB に MySQL 8.4 + 9GB データ)であり、根本対策はインスタンス昇格に帰着するという再確認
詰まったこと / 気づき
- 本番 RDS への
AVG(LENGTH(...)) FROM 大きいテーブルは瞬間的にメモリを圧迫する。今後は EXPLAIN で I/O 規模を確認するか、リードレプリカ復活後にそちらで実行する OPTIMIZE TABLEは data_free(断片化)だけでなく インデックス側の充填率改善も大きい。articles の index 75% 削減は予想以上だった- ストレージ削減は FreeableMemory には直接効かない(buffer_pool ヒット率がもともと高い場合)
次回やること
- インスタンス昇格 db.t4g.small → medium の段取りを詰める
- CloudWatch アラーム新設(FreeableMemory < 100MB)
articlesのインデックス整理は OPTIMIZE で index も縮んだため優先度を下げる(追加効果は数百MB 程度)hot_keyword_rankingsのデータ整理(実行可否は別途判断)
2026-05-07
21:47 - RDS (footballnext) FreeableMemory カイゼン調査
やったこと
.gitignoreにサブプロジェクト3つ (footballnext-ios/,footballnext-android/,footballnext-server/) を追加。3者はそれぞれ独立した Git リポジトリで管理されているため、本リポは指揮塔ポジションとして除外する形にした- AWS CLI で RDS の現状把握
- インスタンス: db.t4g.small / MySQL 8.4.8 / gp2 20GB / Single-AZ / リードレプリカなし
- パラメータグループ:
general-mysql84(custom)、ステータスが pending-reboot - SG:
sg-083bace7c7ee9e450で 3306/tcp が0.0.0.0/0開放(PubliclyAccessible=False で実害なしだが要対応)
- CloudWatch で FreeableMemory 7日間の数値把握
- Min 55 MB / Avg 141 MB / Max 173 MB ← 黄信号〜赤信号ゾーン
- パラメータグループの中身を確認し、
performance_schema = 1が user-set かつ pending-reboot 状態で残っていることを発見- エンジンデフォルトは
0、過去7日間に再起動イベントなし → 実効値は0のまま - このまま再起動すると Performance Schema が起動して 200〜400MB 追加で食う「不発弾」状態
- エンジンデフォルトは
reset-db-parameter-groupでperformance_schemaを engine-default に戻した(即実行)- EC2 (cron インスタンス、52.194.87.168) 経由で RDS に SSH トンネル張って実調査
- footballnext ユーザーから見える DB は
footballnext1個のみ、9.1 GB database.phpに書かれているkowai/search_articleは実体なし- 大きいテーブル:
articles2.8GB、old_articles2.0GB、hot_keyword_rankings1.2GB - buffer_pool ヒット率 99.996%(read_requests 152億 vs disk reads 60万)→ working set < 1GB
articlesの data_free 1.1 GB / data 0.8 GB(135% スカスカ=激しい断片化)articlesのインデックス: 22個・合計 2.0 GB、4個は明確に冗長 prefix
- footballnext ユーザーから見える DB は
articlesの自動アーカイブ・期間削除処理をfootballnext-server/で grep → 存在しない(コメントアウトのみ)old_articlesを空化(ユーザーが phpMyAdmin で TRUNCATE 実行)→ 2,036 MB → 0.34 MB、約 2 GB 解放articlesのインデックス削除候補を分析(A群4個:article_site_id/image_width/title/del、いずれも他複合 index の prefix)→ 実行は保留
決めたこと
- サブプロジェクト 3 リポは本リポから .gitignore で除外
performance_schema = 0を維持old_articlesのデータは廃棄、テーブル本体は当面残置(容量影響軽微)articlesのインデックス整理は今日は実行せず、調査結果のみ記録
詰まったこと / 気づき
- 「old_articles の方が行数 3 倍なのに半分のサイズ」謎の答えは InnoDB のページ充填率の差。articles は INSERT/UPDATE/DELETE が活発で data_free が data 本体を上回るほど断片化していた
- 接続不要で取れる情報(CloudWatch / parameter group)の段階で「performance_schema 不発弾撤去」までできたのは収穫
- buffer_pool ヒット率が異常に高い → メモリ問題の正解はチューニング側ではなくインスタンス昇格 or DB ダイエット側にある可能性が高い
次回やること
articlesのインデックス整理(A 群を INVISIBLE → 様子見 → DROP → OPTIMIZE)- CloudWatch アラーム新設(FreeableMemory < 100MB)
- old_articles 空化後の CloudWatch FreeStorageSpace 反映確認
最近のコミット
- 0894967 起動時広告A/B 点火: ab_test_config を v8/active=true で本番S3配置・runbook点火ログ記入 2026/6/22
- 93d7929 iOS画面表示集計_アクセスログ_20260622.md を追加(EC2アクセスログからの画面表示集計) 2026/6/22
- 9309360 実況板バグfix(ios f275fed)を記録: もっと見る→上に戻るでライブカード消失。importContentsのLive全削除をoffset==0限定に+puts防御 2026/6/20
- 3c6b1f6 起動時広告A/B 点火用 config(v8) 雛形を用意+runbookに生成・配置手順を新設 2026/6/20
- dfe91c9 起動時広告A/B WIP訂正: 5.5.3はビルド準備完了・申請は2026-06-20予定(リリースは審査通過後)。点火のenrollment窓起点はライブ日依存 2026/6/19
- e51577d 起動時広告A/B 点火前チェックリスト: GA4 custom定義2件 登録完了を反映 2026/6/19
- 049b822 起動時広告A/B 実装レビュー round10: round9 medium close・sign-off 回復・実装レビューレーン完了 2026/6/19
- 4e489da 起動時広告A/B 実装レビュー round9+feedback対応記録: R7-#2 revert 差し戻し(gate復元)+expire hook #if DEBUG化 2026/6/19
- 2bc9791 WIP訂正: fn_ad_impressionのDebugView非表示はコードバグでなく表示の癖(カスタム定義登録とは無関係・Realtimeで収集確認済)。Bool gate握り潰し説は誤りだったと明記 2026/6/19
- 78dc901 起動時広告A/B WIP更新: 5.5.3実機スモークA-0〜A-3全PASS・R7-#2 revert(実機発見)・expire人工注入hook追加を記録 2026/6/19
README
footballnext-operation
概要
(記入予定)
セットアップ
(記入予定)
使い方
(記入予定)