7channel-operation
WIP(現在進行中)
Work In Progress
このプロジェクトで現在進行中の作業と、過去のスナップショットを記録する。
現在の状況
関連付け=サーバ同居 codex(gpt-5.6-luna)・5分毎で cast/device とも「自動書き込み+物申す」体制(2026-08-08 本番稼働)
判定エンジンの恒久策が決着し、同日中に cast・device の両方が同型の自動学習ループになった。
- 判定基盤: サーバ
~/7channel-matcher/(cron*/5flock付き・JUDGE_BACKEND=codex・BASE=http://127.0.0.1:8000・CODEX_BIN=/usr/local/bin/codex)。Mac stopgap は退役。事前dry-run(48本)で現行sonnet比 F1 0.96・約10倍速。反映ラグ最大1時間→5分。 - cast: mcr 自動書き込み(サーバ側テキスト存在ガード)→ メール
🟢/🔴 出演者の自動関連付け(名前/タイトル/物申すリンク/YouTube URL)→/cast-feedbackでタグ解除+指摘→codexがcast_matching_hints.mdへ自動追記。 - device: 既存機種マッチは
movies.device_id自動書き込み(device_id=0のみ・上書きしない)→ メール🟢/🔴 機種の自動関連付け→/device-feedbackで「外す/付け替え(全マスタdatalist補完)/指摘→device_matching_hints.md追記」。新機種提案のみ🟢/🔴 機種の確認(新機種あり)の登録フォーム人間ゲート温存。判断根拠=承認実績99.6%(799/802)で承認形骸化+テキストガードは略称多用で71%しか通らず流用不可。滞留pending102件は「いいえ」一括処理済み(新機種40件は登録フォーム待ち残置)。 - 実装:
matcher_feedback.py(cast/device共通・cast_feedback.pyから改名)+ cron/cast_match.py・cron/device_match.py・cast_matcher.py(notify_mail)・main.py・local_matcher.py(confidence出力)。 - 今日の実データ対応: ガンダムSEEDクライマックス(8/7に登録フォーム経由でパチンコがスロット扱い混入)→ is_pachinko=1 修正+SEED系hints追記+新機種71台目視(混入は1台のみ)。
- 運用監視(08-09整備・08-10 JST修正): ①🚨失敗アラート=判定コール失敗で throttle 付きメール(初回即時+3hごと+✅復旧通知。
.envのNOTIFY_V3_DIRで有効)②📊トークン日報=コールごとにlogs/token_usage.jsonl記録→cron 0:00 JST に前日分をメール(0トークン=停止のサイン)。08-10: cronデーモンがUTCのまま3ヶ月動いていたのを発見→再起動でJST化(既存の時刻指定cron4本も9時間遅れだったのを副次修復)。コードも Asia/Tokyo 明示。 - トークン削減(08-10): 実測 約93万/日(夜18-21時台が8割)→ 解剖の結果、最大の無駄は device 全マスタ810台の毎回同梱(先祖返り)。縮小マスタ復活(
load_devices_recent=3年以内 or 180日実使用・定番機は自動残留・外れた機種は全マスタ名前照合の番人で既存IDへ振替)で device 30.2k→17.4k/コール(−43%)。②まとめ待ち(発火を8本or15分まで待つ・さらに−60〜70%可)はラグとのトレードでYou判断により見送り。 - codex 認証(08-09恒久化): auth.json の Mac 共有は refresh token 競合で約1日で失効した(8/9 12:15-17:52 の401停止・滞留は自動消化済み)→ サーバ専用 ChatGPT ログインセッションに切替(
codex login+ssh -L 1455トンネル+Macブラウザで1回ログイン)。以後 Mac と独立。再失効時も同手順(またはcodex login --device-auth)。
次にやること:
- 明日0:00の📊日報で縮小マスタの削減効果を確認(見込み: 100万→70万前後/日。足りなければ②まとめ待ちが控え)。
- 新体制の初期観察: 🔴メールの的中感・新機種確認メールの頻度・自動書き込みの精度・hints の育ち方。
- サーバ hints(cast/device 両方)の定期 scp 回収(自動追記で git と乖離。deploy 時に knowledge/ を上書きしない)。
iOS 8.5.0(2026-06アプデ)を App Store 審査へ提出(2026-07-05・審査結果待ち)
T1〜T4実装(2026-06-29)+サーバ配信キー投入まで済んでいた 2026-06アプデを、ASOメタデータ全面刷新と合わせて提出まで到達。ASC状態=WAITING_FOR_REVIEW(現公開は8.4.0)。
- ASOメタデータ確定(promo-expert レビュー2往復で作り込み。コピペ用は
pr-manager/products/7channel/asc_paste_2026-06-29.md):- サブタイトル:
機種・出演者・番組で、まとめて楽しむ(前半=索引価値のある構造語を温存、後半=App名「サクサク検索」と重複させず集約USPを便益で) - キーワード88字(旧
7チャンネル,無料,まどマギ…を2026機種+一般語へ全面刷新)。「実践」はキーワードに残し、概要は「実戦」表記=表記違いで両検索を拾う。 - 概要=完全リライト(冒頭2文でフック完結・YouTube語は出さず「あちこちに散らばる〜」で婉曲回収・実戦表記)/プロモ/リリースノート差し替え。
- サブタイトル:
- スクリーンショット:
7channel-ios/app_store_screenshots/に 6.7(1290×2796)・6.9(1320×2868) を制作済み。ASCの枠が 6.5インチ(1284×2778 か 1242×2688 のみ許容) で寸法エラー→6.5/に 1284×2778 の4枚を sips 変換で生成して解決(量/鮮度/検索/記事の4枚。差別化の5枚目は未制作)。 - リリース方式:
AFTER_APPROVAL(承認後自動公開)のまま提出。手動リリースにしたければ審査待ちの今のうちにASCで変更可。
次にやること:
- 審査結果を待つ(リジェクト時の想定理由: キーワードの商標グレー/UGCの通報・ブロック不足)。
- 承認・公開後、本番実データで T1再生安定・T2レビュー導線(新規レビューが付き始めるか)・GA4/Crashlytics を観測。
- 実機QA(T1機内モード/T2レビューシート/T4キャッシュ削除)は未実施のまま提出=公開後に本番確認でも可。
- (任意)差別化5枚目スクショの制作+次回 Product Page Optimization の仮説決め。
並行して運用中: 出演者/機種の関連付けは 2026-08-08 にサーバ同居 codex(gpt-5.6-luna)・5分毎へ恒久移行済み(最上部セクション参照。Mac stopgap は退役)。
採用・却下の通知メールを停止(2026-08-11)
動画取り込み(採用・不採用)の結果メール「7ch: 採用N件 却下N件」を You 要望で停止(cron/curator.py と旧 curate.py の send_notification をコメントアウト、fab1739・本番反映済み)。処理本体と他のメール(出演者付与・機種・エラーアラート・日報)は従来どおり。結果確認は管理画面で。復活はコメント解除+deploy のみ。
iOSアプデ product打ち手(T1〜T4)実装完了・サーバ配信キー本番投入済み(2026-06-29)
2026-06アプデ(PR担当のASOレビュー起点)の product側コード実装 が一通り完了し、サーバ配信の土台まで本番に乗った段階。アプリ提出はまだ。
- 実装(7channel-ios・CodeCommit push済
9a7117b..c3e2619): T1 再生失敗時のエラーUI+リトライ+報告(WKWebView失敗デリゲート+YouTube IFrame APIのonError+15秒タイムアウト)/T2 アプリ内レビュー導線ReviewPromptGate(視聴成功3本+静かな瞬間にSKStoreReviewController)/T3 レビュー直前の広告抑制/T4 設定にキャッシュ削除(SDWebImage+WKWebViewのみ・cached.realm不可侵)。実装はCodex、レビュー・検証はClaude。 - 品質: コードレビューで致命バグなし。ビルド SUCCEEDED・エラー0。シミュレータ(iPhone17/iOS26.5)で起動・遷移・T1プレイヤー正常初期化・T4設定行の描画を確認。指示書は
7channel-ios/CODEX_アプデ実装指示_2026-06-29.md。 - サーバ配信(本番投入済):
app_settingsに5キー(id 10-14)player_load_timeout_sec/review_prompt_enabled/review_min_watch_count(3)/review_min_interval_days(120)/review_block_after_ad_sec(90) を投入し/S3Uploader/current201609でS3再生成。SQLは7channel-server-v3/sql/app_settings_2026-06_アプデ.sql(GitHub80ba533)。初期値=iOS fallback同値なので現挙動は不変、狙いは更新なしの遠隔チューニング土台。
次にやること(明日以降・ASCや実機が必要):
- 実機/TestFlight QA — T1エラーUI(機内モードで再生失敗)/T2レビューシート(視聴3本後・本番でないと出にくい)/T4キャッシュ削除アラート実行。
- ASOメタデータ反映 —
release_checklist_2026-06-29.mdSTEP1〜6(サブタイトル/キーワード/概要差し替え+スクショ5枚制作+ビルド紐付け+審査提出)。スクショはscreenshot_design_2026-06-29.mdに指示済み。 - 必要になれば
review_*をサーバ値で調整(早すぎ→review_min_watch_count↑、緊急停止→review_prompt_enabled=false)。
未確認/留意: 出演者/機種判定(下記アーカイブのGemini毎分)は引き続き運用フェーズ。バージョンは8.4.0(App Open導入時に上げた値のまま)。
出演者/機種判定を「サーバ側Gemini・毎分」へ再移行・本番稼働(2026-06-29)
判定主体を クラウドのClaude定期実行(毎時) → サーバ側の毎分cron+Gemini判定 に作り替え。反映ラグ 最大1時間→最大~1分。
- エンジン: Google Gemini 無料枠。
gemini.py(generateContent+スキーマ変換)、/cron/castMatch/run・/cron/deviceMatch/run(内部のみ・nginx /cron deny で外部遮断)、crontab* * * * *2本。前日のクラウドroutine 2本はenabled:false。 - コスト構造: 未判定0本ならAPIを叩かず即終了(空振り¥0)。コストは新着本数で決まる=頻度に依存しない。実判定のみ無料枠を消費(実質¥0想定)。
- 精度(cast自動書き込み): ①テキスト存在ガード=名前/別名が title+概要 に実出現する出演者のみ採用(幻覚/似名誤マッチを確定遮断。1文字名「嵐」対応済)。②モデル=flash(dry-run60件で過剰タグ0)。device は機種マスタを66台に縮小してGeminiへ、確定照合は全767台+メール承認が安全網。
- 未判定管理: movies の flag列
cast_matched/device_matched(前日のマイグレーション流用)。失敗時はフラグ立てず復旧後に自動再判定(取りこぼし無し)。 - 無料枠の役割分担: マッチャー=flash(250 RPD)、週次監査=flash-lite(別枠1,000 RPD)+6秒ペーシングで分離。
- 週次監査:
/cron/castMatch/weeklyAudit(月9:00 JST)。直近タグをGeminiで再チェックし「概要末尾の予告/関連/お便り言及だけ」の誤タグをメール通知(無ければ無音)。ガードで防げない型の最終安全網。 - Gemini失敗通知: throttle付き(最初+3hごと+復旧)。今日の429で実地に発火確認。
運用上の留意点:
- 無料枠は有限(flash 250/日・flash-lite 1,000/日、RPD はmidnight Pacificリセット)。テスト実走で枠を食わない。今日はdry-run連発で一時429(本番実負荷~80/日は250に収まる)。
- device提案メール承認フロー(/device-approve)は従来どおり温存。
- 旧クラウドroutine(trig_…cast/device)は無効化して残置。
.envのGEMINI_API_KEY/MATCHER_API_TOKENはサーバのみ(git非追跡)。
次にやるなら: しばらく運用し、週次監査メールの内容と device 提案精度を観察。誤タグ傾向が見えたら hints/プロンプトを追記。routines/README は旧クラウド前提のままなので実態(サーバGemini)へ更新したい。
直近コミットの安全レビュー完了+.envバックアップのgitignore漏れを修正(2026-06-28)
別セッションが「定期実行リストアップ」依頼でコミット&pushまで走っていた件を受け、今日入っていた2コミット(a7f6733 判定ヒント拡充+cast削除UI/eba2295 device_matcherのフォールバック照合)をレビュー → いずれも内容は妥当・問題なし(cast_list.htmlのmovie_countは両ルートでSELECT済み、device_matcherの参照キーはload_devicesと整合、秘密情報コミットなし)。
その過程で 7channel-server-v3/.env.bak.local.20260626195610(取り込みインシデント時の.env退避)がgitignore対象外だった潜在リスクを発見。7channel-server-v3/.gitignore に .env.* を追加し !.env.example で除外解除(コミット12679af・push済み)。これで.env系バックアップの誤コミットは予防済み。
メモ: 読み取り依頼セッションでコミット&pushまで走るのは行き過ぎ挙動。今後この種の自動化は注意(実害は出ていない)。下記の取り込み/display_title/実戦機種は引き続き運用フェーズ継続中。
取り込みのYouTube API呼び出しを半減+停止インシデント収束(2026-06-26)
display_titleデプロイ直後、取り込みが18:10で停止(Youが気づいた)。原因は①デプロイ復旧時に本番.envをローカルで上書きし無効なYOUTUBE_API_KEY_3を混入→detail(movieUploadsDetail)が400で全失敗、②復元後フォールバック先YOUTUBE_API_KEY_2が当日quota切れ。応急でサーバー.envにYOUTUBE_API_KEY_3=メインキーを一時設定して復旧(滞留17件消化・movies再開)。ローカル.envの無効KEY_3も削除済(再発防止)。
根治として cron/youtube_uploads.py を改修(コミット2140018・本番稼働中):
movieUploadsClassicが RSSのtitle/説明/公開日を保存(RSS descriptionは全文と実測検証済=cast/deviceマッチに影響なし)。_is_open_to_public→_fetch_public_and_durationに置換し 1回のvideos.listで「公開判定+長さ」を両取り。- 結果 動画あたりAPI 2回→1回。今回詰まったKEY_3/KEY_2経路が新規取り込みのクリティカルパスから外れた。
未処理/様子見: サーバー.envの stopgap YOUTUBE_API_KEY_3=メインキー は detail がほぼ走らなくなり実害なし。気になれば外してKEY_2フォールバックに戻せる。注意: 次に ./deploy.sh するとサーバー.envがローカル(KEY_3行なし)で上書きされるが、新フローではdetailが非クリティカルなので問題なし。
参考: YouTube APIキー使用は全7箇所(youtube.py5=メインキー/cron/youtube_uploads.py2)。YOUTUBE_API_KEY_2は直接参照なし(KEY_3フォールバック先)。RSS(キーレス)でtitle/説明/公開日/再生数/サムネは取得可、API必須は「長さ」と「公開/ライブ判定」のみ。
動画一覧用 display_title 導入:本番デプロイ完了・運用フェーズ(2026-06-26)
生 YouTube タイトルから出演者/機種/ジャンルタグ/媒体署名/末尾ハッシュタグを落とした表示専用タイトル movies.display_title をルールベースで生成。本番デプロイ済み・iOSアプリに clean タイトル配信中(アプリ無改修)。
構成: ロジックは title_cleaner.py(DB非依存)、CLIは display_title_builder.py(dry-run / --write バックフィル)。API は api/common.py:movie_fields_sql() で COALESCE(NULLIF(display_title,''), title) AS title を返し、空ならtitleにフォールバック。取り込みは cron/curator.py 採用時に自動生成。生 title は温存(検索/マッチ/WP用)。
ルール: ①末尾ハッシュタグ(裸/[#x]、【番組#96】カウンタは誤食しない)②先頭の機種/ジャンル/カテゴリタグ(キャッチコピー・複数語見出しは残す)③末尾括弧=ジャンル語/媒体署名/機種名(マスタ正規化+接頭辞L/P/e/CR/SLOT)//区切り/既知名 を除去。直近5000件で約62%カバー・誤爆ほぼゼロ。
重要前提: device_id がほぼ未設定(全体49%が0・直近はほぼ全0、機種承認がメール制のため)なので per-movie 機種照合は使わず、機種マスタ全体+ストップリスト+機種タイプ接頭辞で照合している。
DB状態: display_title varchar(256) utf8mb4 追加済み、全件バックフィル(134,796中61,845件)適用済み。FULLTEXTのため ALTER は sql_mode 緩めて COPY 実行。
運用での育て方 / 残タスク:
- ロングテール(マスタ未登録の短い機種名・3文字未満機種・
〜編集部以外の番組署名)はtitle_cleaner.pyのGENRE_WORDS/CATEGORY_TAGS/SIGNATURE_REや機種マスタ別名に1行足して回収。 - 表記が気になる動画は
display_title_builder.py(dry-run)で before/after を確認 → 必要なら--write --limit Nで再生成。 - どうしても残る曖昧分はLLM後段(cast_matcher流用)を将来検討。
- 過去動画の遡及はバックフィル済み。新規は curator が自動付与。
注意: deploy.sh の FILES に title_cleaner.py/display_title_builder.py を追加済み。インラインの scp で $FILES を使うと zsh では分割されないので、手動デプロイ時はファイル明示 or ./deploy.sh を使う。
実戦機種の関連付け:メール承認方式で本番稼働開始・運用フェーズへ(2026-06-25)
device_id 未設定の新着を Codex が判定し、メールの「はい」/「いいえ」/「登録フォーム」で人が確定して初めて movies に紐付ける方式。Youフィードバックを反映して判定を作り込み、運用開始した。現在は「しばらく運用してみる」フェーズ。
フロー: device_matcher.py(Mac/launchd com.7channel.device-matcher 30分毎) が新着を Codex 判定 → device_match_proposals に候補保存+承認リンク付きメール送信。main.py 公開エンドポイントがリンク処理:
/device-approve(はい)=全既存機種を device_id/other_device_id に書込(WHERE device_id=0ガードで手動付与は不上書き)//device-reject(いいえ)=却下記録//device-new(登録フォーム)=新機種を作成して紐付け。- 認証境界:
/device-*公開3本は admin認証の外=メールから踏める(トークンガード)。/admin/devices/newは admin(401)内。
判定ルール(Youフィードバック反映済み):
- パチンコは対象外(devicesマスタから
is_pachinko=0だけCodexに渡す=740台)。 - 2026年前提で旧同名マスタにマッチさせない:ミリオンゴッド/化物語/吉宗/カバネリ等は最新スマスロ版を新機種登録として提案(例「スマスロ ミリオンゴッド-神々の軌跡-」「L 東京喰種」)。ジャグラー/ハナハナ等の定番現役機は例外でマスタにマッチ。
- 出力は
machines配列(主→副 最大2台/各 既存or新機種)。1動画で新機種2台まで登録対応。 - 「■実戦台」セクション最優先+500字超の後方抽出。誤判定は「いいえ」で人が却下する設計。
実装の肝: 提案テーブルに suggested_name2/english2、登録フォーム2スロット化。_insert_device は get-or-create(同名機種が既にあれば再利用=跨り動画でも重複登録しない)。英名は32字・機種名64字に丸め(devices.english_name varchar(32) 制約)。トークンは secrets.token_urlsafe(16)、再クリック冪等。
稼働状態: 本番(Lightsail micro 35.73.116.227)デプロイ済み。watermark は最新IDで前進中。立ち上げ期は新機種登録が多いが、一度登録すれば以降は既存マッチ(はい一発)になる一回限りのコスト。Mac起動中+Codex認証(~/.codex)依存(cast_matcher と同条件)。
次にやること / 様子見ポイント:
- しばらく運用して誤判定の傾向・メール頻度・3台案件(144258型は上限2台で1台こぼれ)の扱いを観察。
- 気になる癖は
knowledge/device_matching_hints.mdに1行追記して育てる。 - 任意動画の確認は
device_matcher.py --movie-ids <ids>(dry-run・DB/メール/watermark不変)。 - (将来)過去の device_id=0 約66k(全体49%)の遡及は今回対象外。欲しくなったら同じ提案+メール承認に流す形で別途。
iOSデザイン全面刷新に着手(2026-06-23・基盤完成+codex-qa-loop足場生成)
ゴール(You指定): 収益最大化とサービス永続化の観点で iOS アプリのデザイン・機能を大幅改善する。今回の主軸はデザイン全面刷新。進め方は「基盤→旗艦画面のUIKit現代化」。レビューは codex-qa-loop(Codexがシミュ実機で触ってUX/デザイン課題を起票→Claudeが修正)で回す。旗艦画面は Home(トップ)。
前提診断: 7channel-ios は 100% UIKit + xib(SwiftUI 0%)、iOS15/Swift5、ダークモード・Dynamic Type・SafeArea ほぼ未対応。色は xib/インラインのハードコードが主体。既存の総合レビューは review-20260606.md(収益=広告再建が最大レバー)。
① デザインシステム基盤を構築(完成・ビルド検証OK・未コミット)
Channel7/App.swift に DS 名前空間を新設。DS.Color(ライト/ダーク2値の動的カラー)・DS.Font(Dynamic Type追従)・DS.Spacing/DS.Radius/DS.Shadow。既存 Const 色を DS.Color に再ポイント(ライト値は完全踏襲=見た目不変、ダーク値だけ併設)。掃き出し完了まで Channel7/Info.plist に UIUserInterfaceStyle=Light をピン留め(半端なダークをユーザーに見せない安全ロールアウト)。
② codex-qa-loop 足場生成(完成・未コミット)
7channel-ios(別repo=CodeCommit)直下に .codex-review/findings.md・response.md・AGENTS.md(Home重点のQA規約)を生成。次の手番は You が Codex デスクトップで 7channel-ios を開き、Home を触って findings 起票 → 「findings処理して」で Claude が修正。
次にやること:
- (You)Codex で現状Homeを触って findings.md に起票
- findings を Claude が修正(高severity優先)→ コミット&push(
AWS_PROFILE=ichirokisanuki git push origin master) - Home の見た目刷新(DSトークン適用・カード/セル現代化)を Codex 指摘と合わせて進める
- ダーク色の画面別掃き出し(白ハードコード22箇所+xib)→ 完了後 Light ピン留め解除
記事日付バグ修正+記事一覧レイアウト改善を反映し iOS 8.3.2 審査提出(審査結果待ち)
① iOS 8.3.2 審査提出(2026-06-18・結果待ち)
8.3.1 以降の蓄積をまとめて 8.3.2 を App Store 審査に提出。リリースノートは「・視聴履歴機能を追加 / ・アプリの安定性を向上しました」。含まれる主な変更: 視聴履歴タブ追加・記事一覧の画像なしレイアウト改善・番組0件チャンネルのタップ直行・YouTube埋め込み自動再生オフ・try?化のクラッシュ対策。審査結果待ち。 ※7channel-ios は CodeCommit(master)管理、push は AWS_PROFILE=ichirokisanuki git push origin master。
② 記事日付の1970/01/01バグ修正(2026-06-18・サーバ側で全ユーザー即時反映済み)
記事一覧で日付が 1970/01/01 の記事が上位に出る不具合を修正。真因は API のシリアライズ形式(isoformat()=T+ハイフン)と iOS パーサ(yyyy/MM/dd HH:mm:ss)の不一致で、パース失敗→Realm既定値エポックに落ちていた(DBは正常)。api/common.py serialize_dates を %Y/%m/%d %H:%M:%S 形式へ修正+cron/article_crawl.py のフォールバックをエポック→datetime.now() に変更。本番反映済み(アプリ更新不要で全ユーザーに効く)。movies の upload_date も同経路で是正。
③ 出演者関連付け自動化+mcr一本化(2026-06-16 完成・稼働中)
cast_matcher.py が Codex CLI で出演者IDを判定し movie_cast_relations(mcr) へ書き込み。launchd 30分毎(Mac実行・13308トンネル経由)。watermark方式(cast_matcher_state.json)で新着のみ判定。⚠️Codex認証がMac側のみ=Mac停止中は新着にキャスト付かない(次回launchdで復旧)。関連テーブルは cmr→mcr に一本化(段1〜3完了、UNIQUE(movie_id,cast_id)付与済み)、段4(cmr DROP)は観察期間後で未実施。
④ 継続中(前セッションから)
- AdMob 制限解除の審査結果待ち(〜6/19頃)。解除後 rate 見直し(バナー/ネイティブをAdMob優先に戻すか)。
- コンテンツ大量拡充(稼働121ch)後の様子見。消滅疑い3ch(id12/36/98)・del=1番組を指す生存動画1,563本の棚卸し判断。
次にやること:
- iOS 8.3.2 の審査結果確認+本番実データ確認(視聴履歴の利用・Crashlytics・記事日付が直ったか)
- サーバー側 Codex 化(取り込み直後リアルタイム発火=Mac依存解消)
cast_movie_relationsの DROP(観察後・段4)knowledge/cast_matching_hints.mdの育成 / (継続)AdMob審査結果・棚卸し
v3 のデプロイ方法(メモ): scp -i ~/.ssh/lightsail-7channel.pem <files> ubuntu@35.73.116.227:~/7channel-server-v3/ → sudo systemctl restart 7channel-web(cron/ や api/ はパス指定でscp。repoの deploy.sh は古いので使わない)
過去のWIPアーカイブ
2026-08-08 20:36 時点のスナップショット(同日中の中間状態: cast のみ物申す対応・device は承認フローのままだった数時間)
18:48 の移行直後は「cast=自動書き込み+物申すリンク/device=従来の提案→はい・いいえ承認メール」の非対称構成だった。メールはまとめ形式(cast は「N件を新規付与」1通・device は「機種の確認N件」1通)。同日夜に You 判断でメール刷新(動画ごと1通・自信度絵文字・YouTube URL)と device の自動書き込み化(承認率99.6%実測が根拠)まで進み、この中間状態は数時間で解消。
2026-08-08 18:48 時点のスナップショット(サーバcodex移行前の判定エンジン変遷: caffeinate化・qwen調査・Mac緊急移行・Gemini枯渇)
ローカルmatcherを caffeinate 化:スリープ割り込みによるタイムアウト滞留を解消(2026-07-05 夕)
「最近関連付けがおかしい・毎時動いてるか微妙」の調査対応。Mac ローカル stopgap 判定ランナーのみ変更、本番サーバは無関係。
- 原因: launchd(
com.7channel.local-matcher)は稼働自体は正常だが、①StartInterval 3600はMacスリープ中は非発火=夜間は14〜16h空く(既知の宿命)②走り出した判定の途中でMacが再スリープに入り、claude呼び出しのawake累積が240sに達してタイムアウト→cast/deviceその回丸ごとスキップ→pendingが滞留(22→28本)。「反映が遅い」の正体は主に②。 - 対処:
run_matcher.shの python 実行を/usr/bin/caffeinate -iで包む(1行のみ)。実行中の数分だけ idle スリープを抑止=起床時に発火した回を完走させる。Macを起こし続けはしない。plist未変更=unload/load不要。「スリープ明けに実行」は元々StartIntervalが起床直後に1回発火するので実現済み、今回は「その回を完走させる」補強。 - 状態: 手動で1回消化し pending 0 を確認・push済み。次のスリープ→起床で240sタイムアウトが再発しないか数日ログを観察して安定判定するのが次の確認事項(
local-matcher/logs/matcher.log)。 - 留意: これはあくまで stopgap の延命。恒久策(下記qwen調査の分岐=Haiku生API等)は引き続き別途検討。
判定エンジンの恒久策を検討中:ローカルLLM評価→qwen 7b不可・保留(2026-07-05)
現行の判定は変わらず local_matcher.py(claude -p sonnet・毎時 launchd)の stopgap で稼働中(下記2026-07-02の移行のまま)。本番は今回未変更。今回は「恒久策」を探る調査スパイク。
- わかった前提: Maxサブスク≠生API認証(生APIはAPIキー=トークン従量)/Codex CLIはChatGPTサブスクで動くが自動cronはToSグレー+レート上限/「毎時縛り」はClaudeの制約でなく実装都合/
local_matcher.pyは判定がclaude_judge(prompt)1関数に隔離済み=バックエンド差し替えは容易。 - 実測結果: Ollama+
qwen2.5:7bを DB正解付きフィクスチャ(40本/マスタ245人)で評価 → cast判定 F1 12%(IDを捏造・実用不可)、22秒/8本(2026機GPU)。2019 Intel(CPUのみ)では速度も厳しい見込み=精度・速度の両方が向かい風。 - 保留中の分岐(次に決める): ①qwen2.5:14b試す ②作り替え(名前出力+候補REGEXP絞り込み) ③Haiku生API(月$2・フロンティア品質・2019速度不問)。
- 再開メモ: 実験スクリプト/フィクスチャは session scratchpad(揮発)。再開時は再生成orリポジトリ移設。13308トンネルは上げたまま。
関連付け判定を「ローカルMacのClaude・毎時」へ緊急移行(2026-07-02)
flash-lite 移行後も Gemini API の関連付けが失敗しまくる(無料枠が実運用に耐えない)ため、You判断で判定主体を Mac ローカルの claude -p(毎時 launchd) に一時退避(stopgap)。サーバは無改修——2026-06-28 にクラウドroutine用に作った /matcher/cast/*・/matcher/device/*(Bearer認証)エンドポイントをそのまま流用する。
- 構成:
local-matcher/(7channel-server-v3 の外=deploy対象外・git追跡、ただし.env/logs/は非追跡)。local_matcher.py… Pythonが/matcher/*/pending取得→CHUNK(8)分割→claude -p(model=sonnet)で判定JSON取得→/matcher/*/resultsにPOST。HTTP入出力は決定的、知的判定だけClaudeに委譲。プロンプトはroutines/*.mdのルールを移植。run_matcher.sh… cast→device順に実行しログをlogs/matcher.logに追記。com.7channel.local-matcher.plist… 毎時(StartInterval 3600)。~/Library/LaunchAgents/に配置・load済み。
- サーバ側停止: crontab の Gemini 3本(
castMatch/run・deviceMatch/run・weeklyAudit)を#GEMINI停止(ローカルClaude移行)付きでコメントアウト(バックアップ/tmp/ct.bak)。gemini.py 等コードは残置(復帰可能)。 - 実地確認(2026-07-02 16:20): フル1回=cast 30本→39件付与・device 30本→19件提案(約11.5分)。エンドツーエンドOK。滞留バックログあり=毎時で数時間かけ消化。速度不足なら
.envのLIMITを上げる。 - 制約/留意: Macがスリープ/電源オフ中は発火しない(ローカル実行の宿命=あくまでstopgap)。判定失敗はフラグを立てず次回自動再試行(取りこぼし無し)。トークン消費はYouのClaudeサブスク側。恒久策は要検討(例: Gemini paid tier復帰/クラウドroutine再開+egress許可)。
Gemini無料枠改定でflash枯渇→flash-lite全面移行で復旧(2026-06-29 夜)
関連付けの429失敗が頻発。原因は Google が無料枠を改定し gemini-2.5-flash が 20 RPD/project/model まで激減していたこと(旧 250 RPD 前提が崩壊)。cast/device が同一キー・同一 flash を共有し毎分叩くため、実負荷~80コール/日に対し20回で即日枯渇→以後ずっと429(GenerateRequestsPerDayPerProjectPerModel-FreeTier quotaValue:20 を実測確認)。失敗時はフラグを立てない設計なので取りこぼしは無し(枠回復で自動消化)。
- 対処(You判断=¥0維持): マッチャーを flash-lite へ全面移行。本番
.envにGEMINI_MODEL=gemini-2.5-flash-liteを追記して restart(即時復旧)。リポジトリ側config.pyの既定値も flash→flash-lite に変更しコメント更新。 - 検証: 切替後は 429 が消え 503(一時高負荷)に変化→数リトライで cast/device とも pending=0 まで消化を実地確認(device は5件処理・3件提案メール送信)。
- 留意: マッチャーと週次監査が flash-lite の枠を共有することになったが、監査は週次・小規模/マッチャー~80/日 で RPD に収まる。精度はテキスト存在ガードで幻覚遮断済み。flash の精度優位を取り戻したい場合は Google Cloud billing 有効化(paid tier・月数円規模)で flash 復帰が次の選択肢。flash-lite の実枠が思ったより小さければ要再検討。
2026-06-29 16:00 時点のスナップショット(クラウドClaude定期実行・毎時版=同日中に毎分Geminiへ置換)
判定主体を Mac/Codex/launchd → クラウドの Claude 定期実行 routine に移行完了(サーバはデータ授受+人間承認のみ)。
- エンドポイント
/matcher/cast|device/{pending,results}(Bearer認証)。判定本体は旧 cast_matcher.py/device_matcher.py の Codex非依存ヘルパーを import 再利用。 - routine: cast=毎時:15 / device=毎時:20(
/scheduleクラウド、Maxサブスク枠、sonnet-4-6)。未判定管理は movies flag列。Mac launchd 2本は unload 済み。 - 留意: 反映ラグ最大~1時間(cloud cron最短1時間)/実行環境のネットワークポリシーに
7channel.ikapps.com許可必須(外すと403)。 - ※ この「毎時クラウド」構成は反映が遅く、2026-06-29 に「サーバ側Gemini・毎分」へ置換(routineは無効化して残置)。
2026-06-18 06:00 時点アーカイブ(出演者自動化稼働・mcr一本化 / 8.3.2提出前)
出演者関連付けの自動化(Codex判定)稼働+関連テーブルを movie_cast_relations に一本化
① 出演者関連付け自動化(2026-06-16 完成・本番反映)
cast_matcher.py が Codex CLI で動画タイトル/概要+出演者マスタ(名前/別名 other_matching_name/除外 no_matching_name)から出演者IDを判定し movie_cast_relations へ書き込む。launchd で30分毎(Mac実行・13308トンネル経由、com.7channel.cast-matcher)。
- watermark方式(
cast_matcher_state.json, Mac local)で新着のみ古い順に1回ずつ判定。(なし)動画も前進させ再判定ループ無し。判別=「movie.id<=watermark なら判定済み(出演者有無は別)」。 - 取り込み時(autoEntry)の番組ルール固定キャスト付与を撤去→出演者は Codex が唯一の決定者。
- 誤判定対策の知識は
knowledge/cast_matching_hints.md(人間がメンテ)に1行追記すると次回から反映=実質の学習。 - ⚠️トレードオフ: Codex認証がMac側のみ=Mac停止中は新着にキャスト付かない(次回launchd起動で復旧)。最終ゴールはサーバー側Codex化でリアルタイム発火。
② 関連テーブルを mcr に一本化(段1〜3完了・段4未)
cast_movie_relations(cmr)/movie_cast_relations(mcr) の二重持ち(同じmovie↔cast情報, キーがyoutube_code vs movie_id)を mcr に統一。readers(api/common.py, s3_uploader.py)・writers(autoEntry, database.py, admin, cast_matcher)・relation_sync を全部 mcr へ。mcr に UNIQUE(movie_id,cast_id) 付与済み。cmr は孤立・凍結、段4(cmr DROP)は観察期間後。旧PHP(7channel-server)は休眠なので影響なし。
③ 継続中(前セッションから)
- AdMob 制限解除の審査結果待ち(〜6/19頃)。解除後 rate 見直し(バナー/ネイティブをAdMob優先に戻すか)。
- コンテンツ大量拡充(稼働121ch)後の様子見。消滅疑い3ch(id12/36/98)・del=1番組を指す生存動画1,563本の棚卸し判断。
- iOS 8.3.1 本番実データ確認(ATT許諾率・Crashlytics・Zucks収益・GA4 screen_view)。
次にやること:
- サーバー側 Codex 化(取り込み直後リアルタイム発火=Mac依存解消)
cast_movie_relationsの DROP(観察後・段4)knowledge/cast_matching_hints.mdの育成(誤判定を見つけたら1行追記)- (継続)AdMob審査結果・大量追加後の様子見・棚卸し・ドキュメント実態合わせ
2026-06-16 12:17 時点アーカイブ(コンテンツ拡充投入・admin保護・AdMob審査待ち / 出演者自動化の前)
コンテンツ大量拡充完了(121チャンネル・movies +16,749本/日)+ admin 認証保護 + AdMob 審査結果待ち
① コンテンツ拡充(2026-06-13 実運用投入完了)
チャンネル管理(前日完成)を実運用に投入。YouTube 検索で発掘した未登録候補102件からユーザーが大量追加し、稼働121チャンネル・movies は1日で+16,749本。パイプラインはバックログ0で健康。運用中に出た課題も全部修正済み:
- 番組編集の動画検索をチャンネル内に限定
- 過去動画の遡り上限 2000→1万本(5,071本を37秒で実測)、Nginx /admin タイムアウト300s
- 管理画面の動画検索を FULLTEXT→LIKE(「ミリオン★タッグ」OR検索化問題の根治)
- チャンネルアイコンのリンク切れを日次cron
/cron/refreshChannelImages(4:15 JST)で自動修復。初回99件修復 - ⚠️ チャンネル消滅疑い3件(id12 打ってけ!TV / id36 D'STATION TV / id98 キクヤチャンネル)→ 棚卸し対象
② admin セキュリティ保護(2026-06-13・サーバー側のみ)
/admin が認証ゼロの完全公開だったのを発見し保護:
- Nginx Basic 認証(ユーザー名
7ch、htpasswd/etc/nginx/.htpasswd-admin、パスワードはパスワードマネージャー保管) - cron系(/cron・/crawl・/api3/updateHotMovies・/api2/beginBroadcast)外部403遮断、crontab は 127.0.0.1 直叩きに統一
- iOS API は無影響を検証済み。Nginx バックアップ:
7channel.conf.bak-20260612
③ iOS 8.3.1 公開済み・AdMob 制限解除の審査中(〜6/19頃)
8.3.1 は App Store 公開済み(2026-06-12 承認)。AdMob 制限解除の審査リクエスト提出済み。広告は全面 Zucks 直接配信中(バナー _50d0640137 / ネイティブ _cc69bfb840 / レクタングル _6ba8332444、インタースティシャル廃止)。
次にやること:
- AdMob 審査結果待ち。解除されたら rate 見直し(ネイティブ/バナーを AdMob 優先に戻すか。レクタングルは AdMob 禁止枠なので Zucks のまま)
- 大量追加後の様子見: curate 採用通知のノイズ、program_id=0 動画の量、アプリの見え方
- 消滅疑い3チャンネルの棚卸し(チャンネル・動画ごと論理削除するか判断)。関連: del=1 の番組を指したまま生存している動画1,563本(program1 閉店くんが行く=1265 / program132 打ってけ!TV=298)の扱いを判断(2026-06-16 発見・別件)
- ※2026-06-16 完了: is_single_program チャンネルの同名番組を全21件撤廃し動画3,688本を program_id=0 に(動画詳細の冗長表示を解消・サーバ/本番データ/iOS。詳細はDEVLOG)
- 8.3.1 本番の実データ確認: ATT 許諾率・Crashlytics クラッシュ・Zucks 収益数値・GA4 screen_view
- 旧v2 Lightsailインスタンス(13.230.63.19)の棚卸し・削除 →【誤認だった・訂正済み】13.230.63.19 は現役の多目的サーバー(mydb.ikapps.com)本体。削除厳禁(下記⚠️訂正参照)
- ドキュメント実態合わせ(
multi-purpose-lightsail-server1README / v3deploy.sh)
v3 のデプロイ方法(メモ): scp -i ~/.ssh/lightsail-7channel.pem <files> ubuntu@35.73.116.227:~/7channel-server-v3/ → sudo systemctl restart 7channel-web
2026-06-13 07:08 時点アーカイブ(チャンネル管理完成直後・実運用投入前)
v3 admin にチャンネル管理を新設(2026-06-12)。/admin/channels/new は YouTube の URL/@ハンドル/動画URLを貼るだけでチャンネル登録(API自動解決)。チャンネル編集画面から過去動画をチャンネル内検索して取り込み(program_id=0)→ 番組編集画面で割り当ての2段構え。upload_unixtime=実投稿日時の修正済み。初の運用実績は「スロぱちの隣駅」(id=112)。
2026-06-12 18:07 時点のスナップショット(8.3.1 公開直後・チャンネル管理実装前)
iOSアプリ 8.3.1 は App Store 公開済み(2026-06-12 承認)。広告クラッシュ・ポリシー違反・Zucks直接化・ATT・YouTube自動再生オフをすべて含む。同日、AdMob ポリシーセンターから制限解除の審査リクエストを提出済み(問題=「サイトの動作: ナビゲーション」、修正=インタースティシャル完全削除、審査は通常1週間程度)。広告配信状態: バナー Zucks _50d0640137(需要割り当て待ち)、ネイティブ Zucks _cc69bfb840、レクタングル Zucks _6ba8332444、インタースティシャル廃止、AdMob は解除審査中。
2026-06-12 時点のスナップショット(8.3.1 提出直前)
iOSアプリ 8.3.1 の実装は全完了、Archive → App Store 提出待ちの状態。実装内容: AdMob XIBクラス名修正(GADUnifiedNativeAdView→GADNativeAdView)、スクロールクラッシュ修正(topViewController→viewControllers.first)、インタースティシャル廃止、SKAdNetworkItems 50エントリ、ATT許可プロンプト、Crashlytics dSYM自動アップロード、バナー/ネイティブ/レクタングルのZucks直接化、YouTube自動再生オフ、バージョン8.3.1統一。提出後に AdMob「審査プロセスを開始」を実行する段取りだった。
2026-06-11 時点のスナップショット(8.3.0リリース直後・広告リワーク着手前)
プロジェクト概要
7channel の運用を管理するモノレポ的な構成。
7channel-ios/— iOSアプリ(別gitリポ=AWS CodeCommit、このリポからはgitignore。git操作はAWS_PROFILE=ichirokisanuki必須)7channel-server/— レガシー(PHP 5.3 + CakePHP)。現在は休眠(本番は v3)7channel-server-v3/— 本番。iOSアプリAPI + cron系を Python(FastAPI) で実装、Lightsail Micro 1台に集約
現在の状態(2大トピック)
① iOSアプリを Xcode 26 向けに近代化 → App Store 審査提出済み(2026-06-07・結果待ち)
塩漬け(2020年構成)で最新Xcodeでビルド不能だったのを全面近代化し、実機起動 → アップロード → 年齢制限/輸出コンプライアンス/リリースノートを片付けて審査提出まで完了。あとは Apple の審査結果待ち。年齢制限は新方式で 18+(疑似ギャンブル頻繁のため。実ギャンブルは「いいえ」)。詳細は DEVLOG 2026-06-07。
- 依存全面更新: AdMob 7.62→13.4 / Firebase 6→12 / Alamofire 4→5 / Realm 5→20 / OneSignal 2→5、deployment target iOS 15
- 広告をAdMobに集約(終了済みの nend / i-mobile 撤去)=レビュー改善案①
- 画面計測を復活(
sendScreenView→Firebasescreen_view、GA4の(not set)解消)=改善案② - バージョン 8.3.0、本番Team JFBRCYHN24(Thomson Inc.) 署名でアップロード済み
- ⚠️ アカウント移管(HV2598SQ7Y→JFBRCYHN24)に伴うキーチェーン警告あり=既存ユーザーは過去の自分のタグ/コメントを編集/削除不可になる(実害小・回避不能)
- コミット: iOSリポ
master09a98bf まで push 済み
② サーバー = Lightsail Micro 1台に完全集約(2026-05-04 完了・安定稼働中)
本番構成 iPhone → 7channel.ikapps.com → Lightsail Micro (Nginx 443 SSL → FastAPI v3 + MySQL同居)、月約 $7($106→$7、年-$1,188削減)。cron は Micro内に統合。詳細は下記アーカイブ+ DEVLOG 2026-05-04。
- 管理画面URL(現行)=
https://7channel.ikapps.com/admin(HTTPS)。旧7ch.ikapps.com(HTTP) はDNS消滅で到達不能(ドキュメント側が古いだけ)。 - ⚠️**【2026-06-15 訂正】
13.230.63.19(静的IP7channel-v2-ip)は放置インスタンスではなく現役の多目的サーバー本体**。mydb.ikapps.com(phpMyAdmin)+ footballnext/baseball/7channel の SSHトンネル+他プロジェクトのサービスが稼働中。ssh -i ~/.ssh/lightsail-multipurpose ubuntu@13.230.63.19。削除・静的IPリリース厳禁(やると多目的サーバーごと全滅)。7channel本体は別インスタンスの Micro35.73.116.227。- 7channel DB を phpMyAdmin で見る経路 = mydb 上の systemd
phpmyadmin-tunnel-ch7.service(127.0.0.1:13308 → ssh Micro → 127.0.0.1:3306)。2026-06-15 にこのトンネルを旧経路(削除済みkizuna RDS / 終了済み踏み台ch7 EC2)から Micro 直結へ張り直して復旧。
- 7channel DB を phpMyAdmin で見る経路 = mydb 上の systemd
③ iOS: YouTube埋め込みをアプリ内インライン再生化(2026-06-10)
動画詳細のYouTube埋め込みがタップで外部YouTubeアプリに飛ぶ問題を修正。AdvancedWebView.swift(WKWebViewにallowsInlineMediaPlayback=true)+YoutubePlayerView.swift(playsinline=1、baseURL/originを自前ドメイン7channel.ikapps.comで一致)で解決。エラー153→152→成功の経緯はDEVLOG 2026-06-10参照。iOSリポ未push分はこのセッションでコミット予定。
次にやること
- App Store 審査結果を待つ(リジェクトされたら理由に対応。UGCの通報/ブロック不足を突かれる可能性に留意)
- 承認後: 本番での広告表示・画面計測(GA4 screen_view)・クラッシュ(Firebase Crashlytics)が正しく動いているか実データで確認
- iOS フォローアップ(ROADMAP参照): ネイティブ広告XIBの旧クラス名(
GADUnifiedNativeAdView)確認、OneSignal通知拡張のリッチ通知再統合、ATT許可プロンプト実装 - レビュー(
review-20260606)の高優先: 広告収益化の実数把握(面別eCPM)、広告除去サブスク検討 - 旧v2インスタンス(13.230.63.19)の棚卸し・削除 →【誤認・2026-06-15訂正済み】13.230.63.19 は現役の多目的サーバー(mydb.ikapps.com)本体。削除厳禁(上の⚠️訂正参照)
- ドキュメント実態合わせ(lightsailサーバーリポのREADME/.devnotes、v3 deploy.sh の古いURL/IP修正)
リバート手順(サーバー)
- 何かあれば Lightsail のスナップショットから復元 or サーバー内で問題切り分け
- v3 のコード変更でロールバックしたい場合は git で前バージョンに戻して rsync デプロイ
- iOS をロールバックしたい場合は CodeCommit で前コミットに戻して再アーカイブ
過去のWIPアーカイブ
2026-06-07 時点のスナップショット(サーバー集約完了・iOS近代化前)
サーバーは Lightsail Micro 1台に完全集約済みで安定稼働(月$7)。DB設定(utf8mb4化・JST化・YouTube APIキー3本分散)、SNS通知(SMS_Alert を Ichiro IAM User で publish、subscriber は goda/ichirokisanuki.apple)、ローカル開発(MariaDB + Docker PHP)など詳細は DEVLOG 2026-05-04 群を参照。積み残しだった CloudFront ECQ9QLPK0563T 削除・RDS最終スナップ kizuna-final-before-delete-20260504 削除は dev-timer #12/#13 で対応予定だった(要最終確認)。この時点までサーバー側が主戦場で、以降は iOS アプリ近代化に移行。
2026-05-04 19:30 時点のスナップショット
v2 (7channel-server-v2) を v3 に完全吸収完了。cron/curator.py を FastAPI ルーター /cron/curate として新規実装し、Lightsail Micro の crontab に登録。SNS publish も Ichiro IAM User で動作確認([TEST] メール到着確認)。多目的Lightsail から v2 cron / systemd / ディレクトリ / Nginx 設定 / Route53 A レコードまで全部撤去。ローカルも git rm 済み。7channel = Lightsail Micro 1台に完全集約。
2026-05-04 18:50 時点のスナップショット
完全別鯖から叩いていた v3 cron 16 行を 7channel-server-micro の ubuntu crontab に移植。MAILTO抑止+ >/dev/null 2>&1 付与。syslog/access.log で発火確認、別鯖側は停止済み(旧別鯖自体は別用途で継続稼働)。これで 7channel 関連 cron は Lightsail 2 台(Micro が v3 cron / 多目的が v2 curate.py)に集約完了。
2026-05-04 18:30 時点のスナップショット
cutover後に潜んでいた隠れバグを総ざらいで対処。① v3 cron が MySQL strict mode で500連発していたのを4ファイル修正、② YouTube quota 超過で「全動画削除済み」誤判定の連鎖を新APIキー(別プロジェクト)+コード防御で解消、③ ローカル PHP テスト用 MariaDB 立ち上げ+スキーマ流し込み、④ movie_uploads/movies を utf8mb4 化(絵文字対応)、⑤ Lightsail OS+MySQL を JST 化+ publishedAt の JST 変換、⑥ v2 curate.py の DB_HOST が削除済み kizuna のままで沈黙していたのを修正。これで完全復活。
2026-05-04 17:30 時点のスナップショット
最終ゴール達成済み(同日 13:40 のスナップショット)に追加で、AWSアカウントに残っていた古いEBSスナップショット 12個(96GB)と AMI 11個を全削除。13年前の旧サービス(FootballNEXT / bizpice / news / haiena / 初代7channel等)の置き土産まで完全クリーンアップ。月-$4.8。RDS最終スナップ 9GB は保険として1週間後(5/11)に削除予定、dev-timer #13 で予約。
2026-05-04 13:40 時点のスナップショット
最終ゴール達成。RDSを Lightsail内部のMySQLに移行(kizuna削除)、CloudFront廃止して Lightsail で Let's Encrypt直接HTTPS化。月コスト$106→$7、年-$1,188削減。CloudFrontは Security Bundle 課金で月末まで削除不可、disable状態で放置(影響なし、6/1にリマインド設定)。
2026-05-04 05:50 時点のスナップショット
段階3 完遂。CloudFront origin を Lightsail に切替成功、iOSアプリ動作確認、B-6 (beginBroadcast/S3Uploader) 緊急実装、/v1/healthcheck/HEAD対応、EC2-2 terminate、old_movie_imps DROP、phpmyadmin の RDS接続を直接接続に変更(副次対応)。月コスト$106→$45(-$61/月、年-$732)。
2026-05-03 22:00 時点のスナップショット
Phase D 着手。Lightsail Nano 7channel-server 立ち上げ・v3 デプロイ完了・Nginx+systemd稼働。RDS Public化+SG許可で接続確立、外部疎通テストも PHP本番と一致確認。CloudFront origin 切替を試みたが「明日に進めたい」とユーザー判断で revert。明日 cutover 予定。
2026-05-03 19:50 時点のスナップショット
段階3 着手。v3 新規ディレクトリで Phase A〜C 完了(21メソッド実装+admin動作確認)。iOS API 9個 + cron 12個全部 PHP本番と動作一致をローカル検証済み。次は Phase D(Lightsail Smallにデプロイ)。B-6 (beginBroadcast) は保留。
2026-04-28 20:30 時点のスナップショット
段階2完了! CLB廃止・CloudFront経由HTTPS化・Lambda/EventBridge大掃除・EC2-1終了。月$33-37削減。残構成は EC2-2 + RDS のみ。STRAIGHT_JOIN による14秒問題はロールバックでデプロイ済み。次は段階3(RDS移行とアプリリプレース)。
2026-04-27 21:00 時点のスナップショット
スロークエリ第3弾(device_id等の絞り込みパターン)も対処完了。movies に3つの複合インデックス追加(device/channel/del_upload_unix)と CastMovieRelation::getRows の STRAIGHT_JOIN 書き換えをデプロイ。最大7.6秒のクエリが62msに。dev-timer明日11:00セット。
2026-04-27 17:00 時点のスナップショット
スロークエリ第2弾(新ワースト1〜3)も全部インデックス追加で解消。articles.url、movies.idx_program_del_upload、movies.idx_del_upload の3つを追加。元のワースト1〜3は前セッションのデプロイで完全消滅を確認済み。次は明日5:00の cleanMovieImps 初回実行確認と SG クリーンアップ。
2026-04-27 11:30 時点のスナップショット
段階1(EC2-2単体稼働)24h観察クリア。RDS/PHP最適化を一気に実施: スロークエリログ有効化、movies/articles/cast_imps のインデックス整備、cast_imps INSERT停止、movie_imps を直近3ヶ月にswap、自動クリーンアップcron追加、ワースト1〜3クエリ全部対処してデプロイ完了。次は数時間後にスロークエリログ確認。
2026-04-26 13:30 時点のスナップショット
7channel-server-v2移行・curate.py cron・SNSアラートメール全て稼働中。残課題は旧Lightsail廃止検討と 7ch.ikapps.com のHTTPS化。
2026-04-25 02:10 時点のスナップショット
7channel-server-v2/ 統合完了、lightsail-claude-avatar/ は破棄、curate.py cron復旧済み。残課題は旧Lightsail廃止検討と 7ch.ikapps.com のHTTPS化。
2026-04-24 移行完了直後のスナップショット
リポジトリ初期化・GitHub連携済み。7channel-server-v2 は多目的サーバーに移行完了。7channel-server-v2/ と lightsail-claude-avatar/ はローカルに存在するが未コミット(untracked)。次は両ディレクトリのコミット管理方針を決めること。
2026-04-24 時点のスナップショット
(以下、ディレクトリ構成からの推測)
git init したばかり。.gitignore に 7channel-ios/ と 7channel-server/ を除外設定済み。7channel-server-v2/ と lightsail-claude-avatar/ は未コミット状態。初回コミット & GitHub push、7channel-server-v2/ の整理が次のステップ。
(新しい「現在の状況」を書く前に、古いものをここに追記でアーカイブする。新しいものが上)
ROADMAP(計画)
ロードマップ
今週
- 判定エンジンをサーバ同居 codex(gpt-5.6-luna) へ恒久移行(2026-08-08。
~/7channel-matcher/cron 5分毎・サブスク枠・dry-run F1 0.96 で品質確認済み。Mac stopgap 退役・反映ラグ最大1時間→5分) - 「物申す」フィードバック自動学習フローを新設(cast付与メールの動画別リンク→
/cast-feedback(HMACトークン公開URL)→誤タグ解除+自由記述を codex が汎用ルール化して hints へ自動追記。監査ログ付き。実地テスト全パス) - メールを新フォーマット化(2026-08-08夜。動画ごと1通・件名に自信度絵文字🟢/🔴・本文=名前(機種名)/タイトル/リンク/YouTube動画URL。confidence は Luna の判定出力に追加)
- 機種関連付けも「自動書き込み+物申す」へ同型化(2026-08-08夜。既存機種は movies へ直接書き込み・新機種のみ登録フォーム人間ゲート温存・物申すは外す/付け替え(datalist補完)/指摘→hints。滞留pending102件は「いいえ」一括処理。根拠=承認率99.6%実測)
- パチンコ混入1台を修正(ガンダムSEEDクライマックス is_pachinko=1+SEED系hints追記+登録フォーム経由の新機種71台目視で他は混入なし)
- 実際の付与メールで「物申す」リンクの導線を実地確認(受信確認済み・機種側はE2Eでフォーム操作まで確認)
- 📊トークン日報(2026-08-09。判定コールごとに記録→毎日0:00に前日分をメール。初日実測 約5万トークン/日)
- 🚨失敗アラート(2026-08-09。初回即時+3hごと+✅復旧通知の throttle 型。疑似障害でフルサイクル検証済み)
- codex auth の恒久化(2026-08-09。Mac との auth.json 共有が約1日で401失効→サーバ専用 ChatGPT ログインセッションに切替。DECISIONS.md 参照)
- 📊日報のJST化(2026-08-10。cronデーモンがUTCのまま3ヶ月動いていた真因を発見→再起動でJST化・検証発火確認・既存の時刻指定cron4本も本来の早朝JSTに復帰・コードにも Asia/Tokyo 明示)
- トークン使用量の見直し=縮小マスタ復活(2026-08-10。device 全マスタ810→144台・1コール30.2k→17.4k(−43%)実測。まとめ待ち等は見送り、DECISIONS.md 参照)
- 縮小マスタの削減効果を📊日報で確認(見込み100万→70万前後/日。不足なら「まとめ待ち」=発火を8本or15分まで待つ案が控え・−60〜70%まで到達可)
- 新体制の初期観察(🔴メールの的中感・新機種確認メールの頻度・自動書き込みの精度・hints の育ち方・📊日報の数字感)+サーバ hints(cast/device)の定期 scp 回収運用
- 2026-06アプデ product打ち手(T1〜T4)実装(T1再生エラーUI/T2レビュー導線
ReviewPromptGate/T3レビュー直前の広告抑制/T4キャッシュ削除。Codex実装→Claudeレビュー→ビルド0エラー→シミュレータ検証。CodeCommit push済9a7117b..c3e2619) - アプデ用サーバ配信キーを本番投入(
app_settings5キー id10-14+S3再生成。初期値=iOS fallback同値で挙動不変・遠隔チューニング土台) - ASOメタデータ反映+iOS 8.5.0 審査提出(サブタイトル
機種・出演者・番組で、まとめて楽しむ/キーワード88字刷新/概要リライト(YouTube婉曲・実戦表記)/プロモ/RN差し替え+スクショ差し替え+ビルド紐付け。コピペ用=pr-manager/products/7channel/asc_paste_2026-06-29.md)→ 提出完了・WAITING_FOR_REVIEW - iOS 8.5.0 審査結果の確認(リジェクト時の想定=キーワード商標グレー/UGC通報・ブロック不足。承認・公開後は T1再生安定・T2レビュー導線・GA4/Crashlytics を本番実データで観測)
- (任意)実機/TestFlight QA(T1エラーUI=機内モード/T2レビューシート=視聴3本後/T4キャッシュ削除)※未実施のまま提出済み・公開後の本番確認でも可
- (任意)差別化スクショ5枚目の制作+次回 Product Page Optimization の仮説決め(サブタイトル2案/1枚目「量」vs「探す手間ゼロ」)
- 出演者関連付けの自動化(cast_matcher.py + Codex CLI判定 + launchd 30分毎・watermark方式)
- 取り込み時の固定キャスト付与を撤去(autoEntry)→ 出演者は Codex が唯一の決定者に
- 関連テーブルを movie_cast_relations に一本化(段1〜3: readers/writers/UNIQUE付与/relation_sync退役)
- 判定知見ファイル導入(knowledge/cast_matching_hints.md をプロンプトへ注入)
- 実戦機種の自動関連付け(device_matcher.py + Codex判定 + launchd 30分毎・watermark方式)→ メール承認方式で本番稼働
- 機種=メール承認方式(device_match_proposals に候補→メールの はい/いいえ/登録フォーム で確定。自動書込みしない)
- 機種判定ルール作り込み(パチンコ除外・最新スマスロ優先・1動画最大2新機種・get-or-create・英名32字制限)
- 動画一覧用 display_title 導入(title_cleaner ルールベース+display_titleカラム+API alias でiOS無改修配信。全件バックフィル61845件・本番デプロイ済み)
- 取り込みのYouTube API呼び出しを半減(RSSでtitle/説明/公開日取得+videos.list 1回で公開判定+長さ両取り。動画あたり2回→1回・quota堅牢化)
- display_title のロングテール追い込み(マスタ未登録の短い機種名・番組署名ブロックを GENRE_WORDS/CATEGORY_TAGS/SIGNATURE_RE/機種別名 で運用追記)
- (任意)quotaリセット後にサーバー .env の stopgap
YOUTUBE_API_KEY_3=メインキーを外し KEY_2 フォールバックに戻す - 機種関連付けをしばらく運用し、誤判定傾向・メール頻度・3台案件(上限2台こぼれ)を観察→ hints 育成
- (将来)過去の device_id=0 約66k(全体49%)の遡及バックフィル(同じ提案+メール承認に流す形で)
- Mac依存の解消+ニアリアルタイム判定(最終形=サーバ側の毎分cron→新着があればGemini判定。
/cron/*Match/run+movies flag列管理)→ 反映ラグ最大1時間→最大1分を達成。Codex/launchd/トンネル全廃。経緯: ①Mac/Codex→②クラウドClaude毎時(cron最短1時間でリアルタイム未達)→③サーバGemini毎分(本達成)。cast はテキスト存在ガードで誤タグ遮断、無料枠はマッチャー=flash/監査=flash-liteで分離- しばらく運用して週次監査メールの内容・device提案精度を観察し hints/プロンプト追記
-
7channel-server-v3/routines/README.mdを旧クラウドroutine前提→現行(サーバGemini毎分)へ更新
- 段4: cast_movie_relations の DROP(観察期間後・cmrは現在孤立凍結)
- iOSアプリを Xcode 26 向けに全面近代化(依存更新・AdMob集約・画面計測復活)→ 実機起動&App Storeアップロード完了
- App Store 審査提出(年齢制限18+・輸出コンプライアンス・リリースノート記入)→ 提出完了・審査結果待ち
- App Store 審査結果の確認 → 8.3.0 承認済み(2026-06-09)
- 8.3.1 Archive → App Store 提出 → 審査通過・公開完了(2026-06-12)
- AdMob コンソールから制限解除の審査リクエスト提出(2026-06-12・結果は〜1週間)
- AdMob 制限解除の審査結果確認(解除されたら rate 見直しへ)
- Zucksバナー枠
_50d0640137の配信開始確認(新枠のため需要割り当て待ち) - 8.3.1 本番実データ確認(ATT許諾率・Crashlytics・Zucks収益・GA4 screen_view)
- 記事日付の1970/01/01バグ修正(API serialize_dates をスラッシュ式に統一+crawlフォールバックをepoch廃止/サーバ側で全ユーザー即時反映)
- iOS: 記事一覧で画像なし記事はサムネイル領域を畳んでタイトルを右端まで拡大
- iOS 8.3.2 審査提出(視聴履歴タブ・記事一覧改善・try?化クラッシュ対策ほか/RN「視聴履歴機能を追加/安定性向上」)→ 提出完了・審査結果待ち
- iOS 8.3.2 審査結果の確認+本番実データ確認(視聴履歴の利用・記事日付が直ったか)
- admin にチャンネル管理を追加(YouTube URL貼るだけで登録・番組自動作成・優先クロール)
- チャンネル編集画面に過去動画の検索・取り込み機能を追加(uploadsプレイリスト遡り・program_id=0登録)
- 自動キュレーションの upload_unixtime を実投稿日時に修正(新着フィード占拠バグ)
- チャンネル追加の実運用 → YouTube検索で候補102件発掘・大量追加完了(稼働121ch・movies +16,749本/日)
- 番組編集の動画検索をチャンネル内に限定
- 過去動画の遡り上限を1万本に拡大(Nginx /admin タイムアウト300s)
- 管理画面の動画検索を FULLTEXT→LIKE に修正(記号入り検索語のOR化問題)
- チャンネルアイコンのリンク切れ自動修復 cron(日次4:15、初回99件修復)
- admin に Basic 認証+cron系エンドポイントの外部遮断(完全公開状態だったのを保護)
- 消滅疑い3チャンネル(打ってけ!TV / D'STATION TV / キクヤチャンネル)の棚卸し・論理削除判断
- AdMob制限解除後のrate見直し(ネイティブ/バナーをAdMob優先に戻すかの判断)
- iOS: YouTube埋め込みのアプリ内インライン再生化(baseURL/originを自前ドメインで揃えてエラー153/152を解消)
- 旧v2インスタンス
13.230.63.19の棚卸し・削除 →【2026-06-15 誤認と判明・対応不要】13.230.63.19(静的IP7channel-v2-ip)は現役の多目的サーバー(mydb.ikapps.com)本体。削除厳禁(消すと phpMyAdmin・各DBトンネル等が全滅)。7channel本体は別の Micro35.73.116.227 - ドキュメント実態合わせ:
multi-purpose-lightsail-server1のREADME/.devnotes と v3deploy.shの古いURL/IP(7ch.ikapps.com・13.192.166.254・7channel-server-v2)を現行(7channel.ikapps.com/HTTPS)に修正 - iOS: ネイティブ広告XIBの旧クラス名(
GADUnifiedNativeAdView→GADNativeAdView)修正+実機表示確認済み - AWS残置スナップショット大掃除(古AMI 11個 deregister + EBSスナップ 12個 / 96GB 削除、月-$4.8)
- 5/11 (dev-timer #13) で RDS最終スナップショット
kizuna-final-before-delete-20260504削除(月-$0.86) - cutover後の隠れバグ総ざらい(v3 cron strict mode / YouTube quota / utf8mb3 / TZ / v2 DB_HOST)を完全復旧
- 完全別鯖から叩いていた v3 cron を Lightsail Micro の ubuntu crontab に統合
- v2 (
7channel-server-v2) を v3 に完全吸収(curate.py → /cron/curate、systemd/Nginx/Route53 撤去、ローカル削除) -
7channel-server-v2/とlightsail-claude-avatar/をこのリポジトリでコミット管理するか方針を決める(v2は統合、avatarは破棄) - AWSコスト削減 段階1: ALB配下のEC2を2→1台に減らす(EC2-1停止、AppNameタグ削除)
- cron頻度調整(
movieUploadsClassic5分毎、removeNotNeedMovieUploads30分毎) - スロークエリログ有効化(mysql84 / long_query_time=1)
-
cast_impsにcast_idインデックス追加 -
moviesの重複/無価値インデックス4つ削除 -
articles.done_image_scrapingインデックス追加(ワースト1対策) -
cast_impsへの INSERT 停止(読まれていないため) -
movie_impsを直近3ヶ月swap、old_movie_impsに旧データ退避 -
cleanMovieImpscron 追加(毎日5:00自動削除) - HotMonthMovie cron
/api3/updateHotMovies/3復活 - スロークエリ ワースト2対処(
updateCastMovieCountをfind('count', recursive=-1)に) - スロークエリ ワースト3対処(
ORDER BY DATEDIFF→ORDER BY upload_date DESC3箇所) - スロークエリログ再確認(数時間後)→ 旧ワースト1〜3 完全消滅を確認
- スロークエリ第2弾: 新ワースト1対処(
movies.idx_program_del_upload追加 → 5.3秒 → 8ms) - スロークエリ第2弾: 新ワースト2対処(
movies.idx_del_upload追加 → 1.7秒 → 21ms) - スロークエリ第2弾: 新ワースト3対処(
articles.idx_url追加 → 約2秒 → 2.7ms) - スロークエリ第3弾: device_id絞り込み対処(
movies.idx_device_del_upload→ 7.3秒 → 62ms) - スロークエリ第3弾: channel_id予防インデックス(
movies.idx_channel_del_upload) - スロークエリ第3弾: cast起点パターン対処(
movies.idx_del_upload_unix追加 +CastMovieRelation::getRowsSTRAIGHT_JOIN化、デプロイ済み) - スロークエリ第3弾: 効果確認+STRAIGHT_JOINで14秒問題発覚→自然JOINにロールバック・デプロイ
-
cleanMovieImpsの初回実行確認 - SSH SGクリーンアップ
- AWSコスト削減 段階2: CLB廃止 + CloudFront経由HTTPS(-$33-37/月、当初の-$35見込みと同水準)
- CMN_*Lambda 4個削除、EventBridge 9ルール削除
- EC2-1 terminate
今月
- 判定エンジンの恒久策を決める → 2026-08-08 決着: サーバ同居 codex(gpt-5.6-luna・サブスク枠・cron 5分毎)。候補だった qwen14b/REGEXP作り替え/Haiku生API は不採用(DECISIONS.md 参照)
- 7channel iOSアプリの本番動作の継続観察
- iOS: OneSignal 通知拡張のリッチ通知(画像付きプッシュ)を再統合(
OneSignalExtensionを拡張ターゲットに正式リンク) - iOS: ATT 許可プロンプト実装(実機確認済み・8.3.1に含む)
- レビュー改善(高): 広告収益の実数把握(AdMob面別 eCPM/fill/impression)→ 収益化の目標値設定
- レビュー改善(高): 広告除去サブスク/買い切りの最小導入を検討(「広告ウザい」固定客向け第2の柱)
- レビュー改善(中): ASO刷新+アプリ内レビュー依頼(
SKStoreReviewController)復活(新規流入の底上げ) - 外形監視を UptimeRobot に移行(任意・優先度低)
今四半期
- PHP 5.3 + 旧CakePHP製の本番アプリのリプレース方針決定 → FastAPI/Python に移植(v3 新規実装で v2 を吸収する方針)
- 段階3 Phase A: iOS API 9メソッドを v3 で実装、PHP本番と動作一致確認
- 段階3 Phase B-1〜B-5: cron系 12メソッド実装(YouTube/RSS/scraping含む)
- 段階3 Phase C: v2 admin 機能を v3 で動作確認
- 段階3 Phase B-6:
/api2/beginBroadcastの S3アップロード処理(保留可) - 段階3 Phase D: Lightsail Nano($5/月)で
7channel-server立ち上げ + v3 デプロイ + Nginx + systemd 稼働 - 段階3 Phase D: kizuna RDS Public化 + Lightsail static IP を SG許可(VPC peering 不可のため)
- 段階3 Phase D: Route53
lightsail.7channel.ikapps.comAレコード作成 - 段階3 Phase D: CloudFront のオリジンを
lightsail.7channel.ikapps.comに切替(cutover完了) - 段階3 Phase D: B-6 (beginBroadcast/S3Uploader) を緊急実装(保留できない設計だった)
- 段階3 Phase D:
/v1/healthcheck追加、HEAD method対応 middleware - 段階3 Phase D: EC2-2 terminate / Elastic IP 解放 /
old_movie_impsDROP - 段階3 Phase D: phpmyadmin の kizuna接続を直接接続に変更(EC2-2 terminate の副次対応)
- 段階3 最終: RDS テーブル整理(cast_imps / article_imps / kabutan / twitter_follow_* DROP、約313MB削減)
- 段階3 最終: Lightsail Nano → Micro ($7) アップサイズ(MySQL同居でメモリ余裕確保)
- 段階3 最終: Lightsail に MySQL 8 セットアップ + kizuna RDS データ移行(mysqldump 経由)
- 段階3 最終: phpmyadmin の接続も Lightsail MySQL(Lightsail内部VPC 172.26.8.107)に切替
- 段階3 最終: kizuna RDS 削除(最終スナップショット作成→Deletion Protection OFF→delete)
- 段階3 最終: CloudFront 廃止 → Let's Encrypt + Nginx 直接HTTPS化(DNS-01 challenge)
- 段階3 最終: Route53 7channel.ikapps.com を CloudFront → Lightsail static IP に切替
- 6/1 (dev-timer #12) で CloudFront ECQ9QLPK0563T 削除再挑戦(Security Bundle が月末まで残る制約)
- iOSアプリの JSON デコード型確認(PHP=string と v3=int の差で問題ないか)
いつか
- Lightsail micro のスナップショット定期バックアップ運用(任意)
- 外形監視を UptimeRobot に移行(任意・優先度低)
- admin: 取りこぼし確認ビュー(program_id=0 で movies に入った動画の一覧→その場で番組割り当て)
- admin: チャンネル発見支援(YouTube Search API でキーワードから未登録チャンネルをサジェスト→ワンクリック登録)
- 広告除去サブスク/買い切りの導入(近い将来はやらない・頭の片隅ストック)
DECISIONS(意思決定)
意思決定記録
このプロジェクトで下した重要な意思決定を記録する。 最新が上に来る。
2026-08-11: 採用・却下の結果通知メールを停止(削除せずコメントアウト方式)
背景: 動画取り込みの採用・不採用処理が動くたびに結果メール「7ch: 採用N件 却下N件」が届いていたが、You から送らないようにしたいと要望があった。
決定: メール送信部だけをコメントアウトで停止。現役の cron/curator.py に加え、cron 停止済みでも deploy.sh の配布対象に残る旧 curate.py も同様に停止。処理本体と他のメール(出演者付与・機種・エラーアラート・日報)は維持。
理由: 処理結果は管理画面で確認でき、正常系の結果メールは不要。コメントアウトなら復活はコメント解除+deploy だけで済む。旧 curate.py も止めておけば、万一再有効化されてもメールだけ復活する事故がない。
背景: トークン日報の実測で判定コストが約93万トークン/日(実効14k/動画・8割超が毎回同梱の固定部)と判明し、You から「無駄に使われている気がするから見直したい」と要望。解剖の結果、無駄の最大要因は①deviceの全マスタ810台同梱(Gemini時代の縮小マスタ最適化が現行経路に未適用の先祖返り)②5分毎の小口発火(平均3.2本/回)だった。
決定: ①のみ実施。判定器に見せる機種マスタを load_devices_recent(3年以内追加 or 直近180日実使用=144台)に縮小する。②「まとめ待ち」(8本たまるか15分経過まで発火を待つ)は見送り。③castマスタの活動者絞り・④cast/device統合1コール化も費用対効果が薄く見送り。
理由: ①は実質1行の先祖返り修正でリスクなし(ジャグラー/ハナハナ等の定番機は「180日実使用」条件で自動残留=手動例外リスト不要。縮小で外れた機種も全マスタ名前照合の番人が既存IDへ振替するのでマッチ不能にならない。Gemini時代に66台まで縮小して実証済み)。実測 device 30.2k→17.4k/コール(−43%)。②は反映ラグ最大5分→15分の劣化との交換になるため、①の効果を日報で見てから判断すれば足りる(サブスク枠なのでコスト増ではなくレート枠消費の問題)。
2026-08-09: サーバの codex 認証は「auth.json コピー共有」をやめ「サーバ専用ログインセッション」にする
背景: サーバ移行時は Mac の ~/.codex/auth.json をコピーして認証していたが、移行から約1日で「refresh token was already used」の401で判定が全滅(8/9 12:15〜17:52)。refresh token は使い捨てローテーションのため、同一トークンを Mac と共有すると先に更新した側が勝ち、もう片方が確実に失効する構造だった。
決定: サーバで codex login を実行し(ssh -L 1455:127.0.0.1:1455 トンネル+Mac ブラウザで1回ログイン)、サーバ専用の ChatGPT セッションを持たせる。auth.json のコピーは今後行わない(非常用の応急手段のみ)。合わせて失効を最長5分で検知できるよう 🚨失敗アラート(初回即時+3hごと+✅復旧・throttle付き)と 📊トークン日報(0トークン=停止サイン)を整備。
理由: ①複数端末ログインは ChatGPT アカウントとして正規の使い方で、セッションが独立すれば競合が構造的に消える。②コピー運用は Mac で Codex を日常使用している限り約1日で確実に壊れる実測が出た(「まれに失効」ではなかった)。③API キー方式はトークン従量課金になりサブスク枠運用の意義が消えるため不採用。
2026-08-08: 機種関連付けも「自動書き込み+物申す」へ(承認フロー廃止・新機種のみ人間ゲート温存)
背景: cast を自動書き込み+物申す化した後、device も同型にしたいという You の要望。ただし device には cast のようなテキスト存在ガードが張れるか・新機種登録をどうするかの論点があった。
決定: ①既存機種マッチ(kind=0)は movies.device_id へ自動書き込み(WHERE device_id=0 ガード=手動・既存設定は上書きしない)し、メールは「物申すリンク」型に変更。②新機種を含む提案(kind=1)だけは従来の登録フォームメール(人間ゲート)を温存。③物申すフォームは「外す」だけでなく正しい機種への付け替えまで対応(全マスタの datalist 入力補完+完全一致解決)。④滞留していた既存機種 pending 102件は自動消化せず「いいえ」状態に一括更新。
理由: ①実測で承認実績 799承認/3却下=99.6% と「はい」クリックが完全に形骸化しており、しかも102件が承認待ちで滞留=承認フローが情報を足さずボトルネックだけ生んでいた。②cast型テキスト存在ガードは実測71%しか通らない(動画側は「マイジャグ5」「SAO2」「北斗転生」等の略称・別名で書くため)→ ハード遮断は recall を3割殺すので採用せず、「誤り0.4%は物申すで事後修正+🔴絵文字を目視の目印にする」設計を選択。③新機種のマスタ自動作成は前日のガンダムSEEDクライマックス混入(パチンコがスロット扱いで登録)が示すとおりリスク実証済みなので、ここだけ人間ゲートの価値が残る。④pending102件の扱いは You 判断(古い滞留分を今さら自動反映しない)。
2026-08-08: 判定エンジンの恒久策 = サーバ同居 codex CLI(gpt-5.6-luna・ChatGPTサブスク枠・codex exec)
背景: 関連付け判定は Mac ローカル claude -p 毎時の stopgap が1ヶ月続いており(スリープ非発火・Youのサブスク消費という宿命)、恒久策の候補として qwen2.5:14b/名前出力+REGEXP作り替え/Haiku生API(月$2規模)を保留していた。You から「サーバーで codex の AppServer なら(ChatGPT)サブスク枠でできる」という提案があった。
決定: 判定主体を Lightsail Micro 同居の codex CLI・gpt-5.6-luna・cron 5分毎 にする。ただし常駐の app-server は使わず codex exec(都度起動・--ephemeral --ignore-user-config reasoning=low) を採用。サブスク枠での無人cronが ToS グレーである点は You が許容して進めると判断。Haiku生API等の従量課金案は採らない。
理由: ①事前dry-run(直近48本・現行sonnet判定を正解に)で F1 0.96・完全一致44/48・約10倍速 と品質が実証でき、追加コスト0(サブスク枠内・pending 0 の回はLLM不発火)で Haiku生API より安い。②app-server は experimental な JSON-RPC でバージョン破壊リスク+1GB機での常駐RAMが不利、毎時→5分毎のバッチに常駐の利点が無い。③local_matcher.py の判定が1関数に隔離済みで JUDGE_BACKEND 切替により Mac claude 復帰の退路も残る。④サーバ実行で Macスリープ問題が消え、反映ラグも最大1時間→5分に短縮。
2026-08-08: 誤判定フィードバックは「トークン公開リンク+完全自動でhints追記」方式にする
背景: cast 付与メールに対し You が「物申したい」とき、リンク→フォーム→テキスト入力→codex が解釈して判定知見(cast_matching_hints.md)へ追記する仕組みを新設するにあたり、認証方式と自動追記の安全度を決める必要があった。
決定: ①リンクは admin Basic認証内ではなく device-approve 式のトークン付き公開URL(HMAC(MATCHER_API_TOKEN, "castfb:") のステートレストークン・テーブル追加なし)。②codex が生成した知見は プレビュー確認を挟まず完全自動で追記。③同フォームで誤タグの解除(mcr DELETE)もできるようにする。
理由: ①iPhone のメールから頻繁に踏む導線なので Basic認証の摩擦を排除(実績ある device 承認フローと同型・movie_id 固定トークンなので冪等で何度でも使える)。②完全自動のリスク(プロンプト注入される hints の汚染)は、(a)「一般化に適さない指摘は追記しない」判断を codex 自身にさせる、(b)原文+生成ヒントを cast_feedback_log.md に監査ログとして残し巻き戻し可能にする、の2段で緩和。You が速度を優先して選択。
2026-07-05: iOS 8.5.0 の ASOメタデータ方針(サブタイトル/概要のコピー・表記の使い分け・YouTube婉曲・自動公開)
背景: 2026-06アプデ提出にあたり、新規DLが月8件前後まで枯れた発見性の回復を狙ってメタデータを全面刷新する必要があった。promo-expert のレビュー2往復で、App名との重複・索引価値・商標リスクを踏まえて確定させた。
決定:
- サブタイトル =
機種・出演者・番組で、まとめて楽しむ。前半「機種・出演者・番組」はApp名にもキーワード欄にも無い唯一の索引場所なので温存し、後半はApp名の「サクサク検索」と重複させず集約USPを便益で表現(「サクサク探せる」「次の実践まですぐ」は不採用)。 - 表記の使い分け: 見えるコピー(概要・プロモ)は「実戦」、キーワード欄は「実践」を残す。索引はフィールド横断なので、表記違いにすることで「実戦/実践」両方の検索を拾う。
- 概要でYouTube語は明示しない(婉曲)。「あちこちに散らばる〜をまとめて」で事実記述に留め、断定比較(「より見やすい」等)は避ける(審査/ポリシーの競合便乗リスク回避+★1「YouTubeで見れるじゃん」への回答を両立)。
- リリース方式は
AFTER_APPROVAL(承認後自動公開) のまま提出(手動リリースの推奨はあったが、自動で進める)。
理由: ①索引はフィールド横断で重複排除されるため、App名で売り切った語をサブタイトルで繰り返すと2行目がゼロ寄与になる=貴重な30字を便益に回すのが最適。②表記ゆれを逆手に取ると1枠で2表記をカバーでき、無料でキーワード網を広げられる。③機種名/出演者名は第三者商標のグレー枠で、一般語9個で土台を固めた上の余り枠という位置づけ。断定比較まで踏み込むと審査リスクが上がるため事実記述で回収。④発見性の根因はサブタイトルではなく検索順位+6年凍結レビューなので、効くレバーの重い順は A.レビュー導線(T2) → B.再生安定(T1) → メタデータ、という前提でメタデータは「土台の底上げ」と割り切る。
2026-07-05: ローカルmatcherの安定化は「スリープ中の定期起動」ではなく「実行中のみ caffeinate」で行う
背景: stopgap のローカル判定ランナー(launchd 毎時)が、夜間はスリープで発火せず、昼も判定の途中でMacが再スリープに入り claude 呼び出しが 240s タイムアウト→丸ごとスキップ→pending 滞留、という目詰まりを起こしていた。Youの要望は「スリープ中に無理に起こさなくてよい。起きたら確実に走ればよい」。
決定: pmset 等でスリープ中に定期 wake させる案は採らず、run_matcher.sh の実行を caffeinate -i で包み、ジョブ実行中の数分だけ idle スリープを抑止する方式にする。「スリープ明けに実行」は StartInterval の起床時1回発火で既に満たされているため、追加するのは「発火した回を完走させる」補強のみ。
理由: ①電力・意図(夜間は静かにしたい)を尊重しつつ、実害だったタイムアウト滞留を直接潰せる。②plist 変更不要で unload/load もいらず、変更面が run_matcher.sh 1行に収まりロールバックが容易。③恒久策(Haiku生API等)に踏むまでの延命として最小コスト。クラムシェル中は効かない等の限界はあるが stopgap には許容。
2026-06-29: 2026-06アプデ product打ち手の実装方針(cached.realm不可侵・広告はコード追加せず設定値運用・再生失敗ログはクライアント完結・AppSeed=fallback同値)
背景: PR担当のASOレビューが挙げた product側打ち手A〜D(レビュー導線/再生安定化/広告バランス/キャッシュ削除)を実装するにあたり、コードベース調査で判明した制約を踏まえて方針を固める必要があった。
決定:
- D キャッシュ削除は
cached.realmに触らない。クリア対象はSDWebImage画像+WKWebViewディスクキャッシュのみ。Cookie/LocalStorageも残す。 - C 広告バランスはコードをほぼ足さない。頻度ロジック(
InterstitialGate/AppOpenGate)は十分洗練済みなので、実装はレビュー導線との競合回避(review_block_after_ad_sec)だけ。頻度調整は今後サーバapp_settings値で運用。 - B 再生失敗ログはクライアント完結。専用サーバエンドポイントは新設せず、エラーUI+報告メール導線で完結(解析が要れば既存Crashlyticsのnon-fatalを任意で)。
- 新規
app_settingsキーは初期値=iOS側fallbackと同値で本番投入する。
理由:
cached.realmは現役メインDB(Movie等格納)で、実行中削除はクラッシュ+全コンテンツ消失+再DLコスト。容量削減はSDWebImage+WKWebViewで十分足りるため、リスクを取る理由がない。- 広告制御は既存実装が要件を満たしており、頻度をコードに焼くより遠隔調整できる設定値運用の方が将来の自由度が高い。「広告ウザい」対策は実装よりチューニングの問題。
- サーバAPI新設はサーバ側(v3)の作業と運用負荷を増やす。クライアント完結なら7channel-iosだけで閉じる(Youの選択)。
- 投入値をfallback同値にすると挙動が一切変わらず安全に「更新なしで遠隔チューニングできる土台」だけを先行して本番に置ける。
2026-06-29: 判定を「サーバ側Gemini・毎分」に再移行。マッチャー=flash/週次監査=flash-lite と無料枠で役割分担し、cast はテキスト存在ガードを必須にする
背景: 前日に組んだ「クラウドのClaude定期実行(毎時)」は反映に最大1時間かかる。本当の要望は毎分(ニアリアルタイム)反映だが、クラウドroutineはcron最短1時間で不可能。
決定:
- 判定主体をサーバ側の毎分cron→新着があればGemini判定に作り替える(クラウドroutineは無効化して残置)。空振り(未判定0)はAPIを叩かず終了=コストは新着本数で決まり頻度に依存しない。
- LLMは Google Gemini 無料枠。毎分マッチャー=gemini-2.5-flash(精度優先)、週次監査=gemini-2.5-flash-lite(別枠)に分ける。
- cast(自動書き込み)はテキスト存在ガード必須=出演者の名前/別名が title+概要 に実出現する場合のみ採用。
- 誤タグの最終安全網として週次監査(直近タグをGeminiで再チェックしメール通知)を追加。
理由:
- 毎分にできるのはクラウドroutine(1時間下限)では無理で、routineを外部から起動する手段(webhook/OAuth)もサーバに無い → サーバ側でLLM APIを直接叩くのが唯一の道。
- Geminiを選んだのは無料枠で従量課金ゼロを狙えるため(DeepSeek/Qwenより¥0に寄せやすい)。
- モデル分け: 実テストで flash は過剰タグ0、flash-lite+ガードは過剰タグ5(/60)で精度が劣る → 自動書き込みは flash。ただし flash は無料枠が狭い(250 RPD/10 RPM vs flash-lite 1,000/15)。マッチャー実負荷~80/日は250に収まるので flash 維持、burst が出る週次監査だけ別枠の flash-lite に逃がして枠の干渉と RPM 超過を回避。
- テキスト存在ガード: 判定はテキストしか見ないので正しいタグは必ず本文に名前がある。本文に無い人物のタグ(幻覚・似名誤マッチ=flash-liteで実害確認)を、正解を落とさず確定的に弾ける。device はメール承認があるため対象外。
- 反省として記録: 精度検証の dry-run を無料枠の予算を考えず連発して 429 を踏んだ。検証は最小回数(各モデル1回)で結論を出すべきだった。
2026-06-28: 出演者/実戦機種の自動判定を「クラウドのClaude定期実行」に移行(B案)。サーバAPI直叩きやMac継続は採らない
背景: cast/device の判定は Mac の Codex CLI バッチ(launchd 30分)で動いていたが、Codex認証(~/.codex)がMacにしか無く「Mac起動中しか動かない」stopgapだった。Mac依存を外したい。
決定: 判定主体をクラウドの Claude 定期実行 routine(/schedule)に移す。サーバ(FastAPI v3)は Bearer 認証付きの pending/results エンドポイント(/matcher/*)だけ持ち、判定本体はClaude routineが「pending取得→Claude自身が判定→results送信」で行う。未判定管理は movies の flag列(cast_matched/device_matched)。device の人間承認(提案→メール)は温存。間隔は cast=毎時:15 / device=毎時:20。
理由:
- 判定するのがClaude自身なので「サーバが外部AIをAPIで叩く」連携コードもCodex CLIも不要。クラウドで動くのでMac起動・~/.codex認証・launchdスリープ問題が全部消える。クラウドに置く秘密はBearerトークン1個だけ(DB認証もSSH鍵も置かない)。
- 代替案①「サーバがClaude APIを直接叩く(5分間隔・空振り無料)」は従量課金が発生するため、Maxサブスク枠で無料の毎時cloud routineを優先して見送り。代替案②「Mac方式継続」はMac依存が残るので不採用。
- 制約として cloud routineのcron最短は1時間(10/30分は不可)でリアルタイム化は不可。新着反映に最大~1時間ラグを許容する。
- 副次の発見: nginx
/crondeny all を避けエンドポイントは/matcher配下に置く。クラウド実行環境のegress許可リストに7channel.ikapps.comを追加しないと403で到達できない。
2026-06-28: .env のバックアップ等(.env.*)はすべて gitignore 対象。例外は .env.example のみ
背景: 取り込みインシデント対応で本番.envを退避した 7channel-server-v3/.env.bak.local.20260626195610 が、v3の.gitignoreが.envしか除外していなかったため追跡対象外として素通りしていた。git add -A 系で誤ってステージ→秘密情報コミットの恐れがあった。
決定: 7channel-server-v3/.gitignore に .env.* を追加し、追跡継続したい雛形 !.env.example で除外解除する。今後 .env の派生(.env.bak/.env.local/日付サフィックス付き退避など)は名前を問わず無視される。
理由: 秘密情報は「デフォルト無視・必要なものだけ明示追跡」が安全側。.env.example(ダミー値の雛形)だけは共有したいので ! で除外解除する最小構成にした。コミット 12679af。
2026-06-26: 取り込みの動画詳細は「RSS(title/説明/公開日)+videos.list 1回(公開判定+長さ)」にして API を動画あたり1回に集約する
背景: デプロイ事故で取り込みが停止した際の調査で、定期取り込みが「RSSで新着検知 → _is_open_to_public(API) → 詳細は movieUploadsDetail(API)でtitle/長さ/説明を再取得」と、動画あたりYouTube API 2回叩いていたと判明。YouTube quota切れが停止の一因でもあった。RSSにtitle/説明/公開日が含まれており(descriptionは全文=実測でAPIと一致、cast/deviceマッチに必要な「■実戦台」も保持)、API再取得は冗長だった。
決定:
movieUploadsClassicで RSS の title・説明・公開日をそのまま保存する(API再取得しない)。_is_open_to_publicを_fetch_public_and_durationに置換し、1回の videos.list(part=snippet,contentDetails,status) で「公開/ライブ判定」と「長さ(秒)」を同時取得する。movieUploadsDetailは新規では実質不要化(title='' のレガシー補完フォールバックとして残す)。- スクレイピングは採用しない(ToSグレー・脆い)。RSS+公式APIのみ。
理由:
- 動画あたり API 2回→1回でquota消費を半減。今回停止の原因だったKEY_3/KEY_2経路を新規取り込みのクリティカルパスから外せる(堅牢化)。
- RSSのdescriptionは全文と実測検証済みなので、説明をRSS由来にしてもcast/device判定の精度は落ちない。
- 長さ(ショート除外)と公開/ライブ判定だけはRSSに無くAPI必須だが、公開判定の既存API呼び出しに長さ取得を相乗りさせれば追加コストゼロで両取りできる。
付随(インシデント): デプロイ復旧時に本番 .env をローカルで上書きし無効な YOUTUBE_API_KEY_3 を混入させたのが停止の直接原因。教訓として、.env を含む手動デプロイは慎重に(deploy.shは #!/bin/bash で正常、インラインscpの $FILES はzshで非分割の罠)。
2026-06-26: 動画一覧の表示タイトルは別カラム display_title + ルールベース + API alias で実現する
背景: YouTube生タイトルに出演者・実戦機種・ジャンルタグ・媒体署名・末尾ハッシュタグが冗長に含まれ、一覧表示に不向き。clean タイトルを出したいが、生 title は FULLTEXT検索・cast/device マッチ・WP同期が依存しており壊せない。表示先は iOS アプリ(App Store申請サイクルが重い)。
決定:
- 生 title は温存し、別カラム
movies.display_title(utf8mb4) を新設。 空なら表示側で title にフォールバック。 - 生成はルールベース+事前計算(LLM不使用)。ロジックは
title_cleaner.pyに分離し、バックフィルCLI(display_title_builder.py)と取り込みフック(cron/curator.py)で共用。 - iOSへは API の SELECT で
COALESCE(NULLIF(display_title,''), title) AS titleとして既存titleキーに載せる=アプリ無改修・申請不要で反映。 - 機種照合は per-movie ではなく機種マスタ全体+ストップリスト+機種タイプ接頭辞で行う。
- 「番組名+話数」マーカーだけに切り詰める案は採用しない(先頭見出し・本文は残し、末尾のノイズだけ落とす)。
理由:
- 別カラムにすることで検索/マッチ/WPを一切壊さず、結果を目視点検・部分修正でき、全件やり直しも可能(cast_matcher と同じ運用思想)。実行時計算はレンダリング毎の無駄&オーバーライド不可なので事前計算。
- ルールベースは無料・即時・決定的。dry-run反復で直近5000件約62%カバー・誤爆ほぼゼロを確認でき、LLMのコスト/非決定性を避けられた。残りはロングテールで運用追記 or 将来LLM後段で足せる。
- API alias は iOS の
titleキーをそのまま使えるため、アプリ改修・App Store申請が不要で即日反映できる(最大の利点)。 - per-movie照合を捨てたのは、実データで
device_idがほぼ未設定(全体49%が0、機種承認がメール制でほぼ確定しない)と判明したため。マスタ全体照合なら生成タイミングがマッチ処理に依存しない副次利点もある。 - 話数マーカー切り詰めは You の確認で「やり過ぎ(本文も残すべき)」となり撤去。
2026-06-25: 実戦機種の関連付けは「Codex判定→メール承認」方式とし、パチンコ除外・最新スマスロ優先で運用する
背景: 出演者(cast_matcher)同様に実戦機種も自動関連付けしたいが、機種は表記揺れ・新旧スマスロの世代差・コラボ番組のロスター羅列など誤りやすい。当初は Codex 判定で movies.device_id/other_device_id を自動書込する案で稼働させたが、Youが「自動で書かず、メールで確認してから紐付けたい」「新機種は登録提案してほしい」と要望。さらにメール内容を見て具体的な現場ルールが判明した。
決定:
- 自動書込みをやめ、メール承認方式にする。 device_matcher は候補を
device_match_proposalsに貯め、メールの「はい/いいえ/登録フォーム」リンク(公開・トークンガード)を人が押して初めて movies に書く。 - パチンコ機種は関連付け対象外。 devices マスタから
is_pachinko=0(スロット)だけを Codex に渡す。 - 2026年前提で、旧同名マスタ機種にはマッチさせず最新スマスロ版を新機種登録として提案する。 ただしジャグラー/ハナハナ等の定番現役機は例外でマスタにマッチ。
- 1動画あたり主・副の最大2機種(既存/新機種の混在可)。新機種が複数動画に跨る場合は get-or-create で重複登録せず再利用。
理由: 機種判定はキャストより誤りやすく、収益動線(アプリの機種別導線)に直結するので、自動全書込みより人手の最終確認を1クリック挟む方が安全(押さなければ未設定のまま=無害)。マスタには旧台のベース名エントリが多く、現在打たれているのは別物のスマスロ版なので「既存優先」だと旧台へ誤紐付けが量産される——その実態を踏まえ「新台は新機種提案」を既定にした。立ち上げ期は新機種登録が増えるが、登録が進むほど既存マッチ(はい一発)に収束する一回限りのコスト。
2026-06-18: 日付のAPI配信形式は yyyy/MM/dd HH:mm:ss(スラッシュ式)を正準とし、修正はサーバ側で行う
背景: 記事一覧の日付が 1970/01/01 になる不具合の真因が、v3 API の serialize_dates が isoformat()(Tセパレータ+ハイフン)を返す一方、iOS の既存パーサ(PrimitiveObject.init(json:) / UtilExtension.stringToDate)が yyyy/MM/dd HH:mm:ss しか受け付けず、パース失敗→Realm既定値エポックに落ちていたことだった。iOS側を直す案もあった。
決定: API の日付配信形式を %Y/%m/%d %H:%M:%S(date型は %Y/%m/%d 00:00:00)に統一し、サーバ側で修正する。iOSパーサ側は変更しない。
理由: ①サーバ修正なら全ユーザーにアプリ更新なしで即時反映できる(iOS修正は審査・普及待ちで取り残しが出る)。②旧PHP版API のレスポンスがスラッシュ区切りで、iOSパーサはそれに合わせて作られていた=スラッシュ式が歴史的正準。v3移植時に isoformat() にしたのが逸脱だった。今後 v3 API で日付を返す箇所は全てこの形式に揃える。
2026-06-16: 動画への出演者付与は Codex CLI による個別判定にする
背景: 従来の出演者付与は「番組ルール固定キャスト(auto_entry_programs / cast_program_relations)を動画の中身に関係なく機械的に付ける」方式で、ゲスト出演やレギュラー欠席回がズレていた。動画ごとにタイトル/概要を見て出演者を当てたい。
決定: cast_matcher.py が Codex CLI(codex exec)に出演者マスタ+動画タイトル/概要を渡して出演者IDを判定する。サーバーに Anthropic SDK を組み込む案は採らず、既にローカル認証済みの Codex を使う。cast_matcher は cast_program_relations(番組レギュラー)は参照しない。取り込み時(autoEntry)の固定キャスト付与は撤去し、Codex を唯一の決定者にする。
理由: Codex 認証(ChatGPT定額)が手元にあり追加コストが小さい。番組レギュラー注入は精度の足しになるが「番組固定を脱したい」目的に反するので不採用。固定付与を残すと Codex は追加しかできず誤付与を消せないため撤去。トレードオフ(Mac停止中は新着にキャスト付かない)は許容し、将来サーバー側Codex化で解消する。
2026-06-16: 出演者の関連付けは movie_cast_relations に一本化する
背景: cast_movie_relations(youtube_code基準)と movie_cast_relations(movie_id基準)が同じ movie↔cast 情報を二重に持ち、relation_sync cron で正→従にミラー(ラグ+取りこぼしバグ)していた。
決定: mcr を正テーブルに一本化。readers/writers/未付与判定を全て mcr に寄せ、mcr に UNIQUE(movie_id,cast_id) を付与、relation_sync を退役。cmr は孤立・凍結し、観察期間後に DROP(段4)。
理由: 二重持ちは v3 の書き込み経路では必然性が無くラグ・取りこぼし・二重ソースの保守コストだけ残っていた。旧PHPは休眠で cmr を読む生きた経路は無いため廃止可能。DROP だけは不可逆なので即時はやらず観察を挟む。
2026-06-16: cast_matcher の処理済み判別は watermark 方式(位置ベース)にする
背景: 出演者ゼロ(なし)の動画は mcr 行が付かないため「未付与」のままで、毎回再判定される無駄(ループ)があった。「この動画は判定済みか」を持つ必要があった。
決定: 処理済み最大 movie.id を cast_matcher_state.json(Mac local)に持ち、id>watermark を古い順に1回ずつ判定→(なし)動画も含めて前進。movies へのフラグ列追加(per-movie)は採らない。
理由: 「今後追加される動画だけ」というスコープに watermark がぴったりで、本番 movies テーブルへのスキーマ変更+バックフィルを避けられる。位置ベースの限界(ID順前提)は本用途では許容範囲。
2026-06-13: admin は Nginx Basic 認証で保護、cron 系エンドポイントは localhost 限定
背景: https://7channel.ikapps.com/admin が認証ゼロの完全公開で、チャンネル/番組/出演者の CRUD が誰でも実行できた。cron 系(/cron, /crawl 等)も公開で、連打されると YouTube API quota を浪費させられる状態だった。
決定: Nginx の /admin に Basic 認証(アプリレベルのログイン画面や IP 制限は採用しない)。cron 専用エンドポイントは Nginx で外部403遮断し、crontab は 127.0.0.1:8000 直叩きに統一。
理由: Basic 認証は15分で導入でき iPhone Safari でも運用に支障がない。アプリレベル認証は工数の割に防御力が大差なく、IP 制限はスマホ回線の変動IPと相性が悪い。cron は外部から叩く理由が一切ないので遮断が正解。
2026-06-13: 管理画面の動画タイトル検索は FULLTEXT ではなく LIKE を使う
背景: 「ミリオン★タッグ」のような記号入り検索語で、FULLTEXT(BOOLEAN MODE) のトークンが記号で割れて OR 検索になり無関係なタイトルがヒット。フレーズ化("...")すると今度は記号の位置情報ズレで実データ204件あるのに0件になった。
決定: 番組編集・出演者編集の動画検索は title LIKE '%q%' の厳密部分一致に変更。
理由: 管理画面の用途は「入力文字列をそのまま含むタイトル」が期待値。LIKE は18万行全体でも0.4秒・チャンネル内なら0.1秒で実用十分。FULLTEXT は記号入り日本語との相性が根本的に悪い。
2026-06-13: チャンネルアイコンのリンク切れは日次cronでURL再取得して直す
背景: チャンネル運営者がアバターを変更すると、保存済みの yt3.ggpht.com URL が無効になり画像が表示されなくなる。
決定: /cron/refreshChannelImages で全チャンネルの画像URLを YouTube API から日次で取り直す。S3 への画像キャッシュ(自前ホスティング)はしない。API がデータを返さないチャンネル(削除/BAN疑い)は自動削除せず報告のみ。
理由: channels.list は50件/1unit で121チャンネルでも1日3unit とタダ同然。S3 キャッシュは確実だが取得・配信の実装が重く、日次更新で実用上十分。誤BANからの復活があり得るので自動削除はリスク。
2026-06-12: 過去動画の取り込みは「チャンネル画面で program_id=0 登録 → 番組画面で割り当て」の2段構え
背景: チャンネルの過去動画をYouTube APIで検索して取り込む機能の置き場所。当初は番組編集画面に「検索→番組ID付きで取り込み」を作りかけた。
決定: チャンネル編集画面に「チャンネル内検索→取り込み(program_id=0)」を実装。番組への割り当ては番組編集画面の既存「過去の動画に番組を割り当て」(ローカルmovies検索)を使う。
理由: 既存機能と責務が重複しない(取り込み=チャンネルの仕事、割り当て=番組の仕事)。1チャンネルに複数番組があるケースで、一度取り込んだ動画を複数番組へ柔軟に振り分けられる。ユーザー指示による方針。
2026-06-12: movies の upload_unixtime は常に実投稿日時を使う(現在時刻は使わない)
背景: curate / autoEntryMovie が upload_unixtime=現在時刻 で登録していたため、新規チャンネル追加時にRSSで拾った過去動画(最大15本)が「たった今投稿された」扱いでアプリ新着フィードの上位を占拠した。
決定: 自動キュレーション・手動取り込みとも、movie_uploads / YouTube API の実投稿日時(JST)から unixtime を算出する。
理由: 新着フィードの時系列が正しく保たれる。通常運用(投稿から数分でクロール)では現在時刻とほぼ同じなので副作用なし。新規チャンネル追加時だけ正しく過去に並ぶ。
2026-06-12: 過去動画の遡りは search.list ではなく playlistItems を使う
背景: チャンネル内のタイトル検索に YouTube Data API の search.list(100 units/50本)を使うと quota を大量消費する。過去に quota 超過で全動画誤削除の事故もあった。
決定: uploads プレイリスト(UCxxx→UUxxx)を playlistItems(1 unit/50本)で全件取得し、タイトル絞り込みはサーバー側で行う。上限は40ページ=2000本。
理由: 2000本遡っても40 units と search.list 1回未満のコスト。タイトルの部分一致もYouTube検索の曖昧マッチより制御しやすい。
2026-06-11: YouTube動画詳細は自動再生しない(タップで再生開始)
背景: 動画詳細画面を開いた瞬間にYouTube動画が自動再生されていた。
決定: autoplay=1 → autoplay=0。ユーザーがタップするまで再生しない。
理由: 自動再生は意図しない通信・音声の発生につながる。UX改善としてユーザー要望を受けて変更。
2026-06-11: AdMob制限中はZucksに直接切り替え(フェールオーバーではなく)
背景: AdMob が「制限付きで広告配信中」で全枠 "No ad to show" 状態。当初は「AdMob失敗時にZucksフェールオーバー」を実装したが、そもそも AdMob から試す意味がない状態だった。
決定: バナー/ネイティブともにZucksを直接呼ぶ構成に変更。レート DB も AdMob=0 / Zucks=1000 に更新。AdMob制限解除後に DB を更新するだけで切り戻せる設計。
理由: 制限中の AdMob に毎回空振りリクエストを送るのは無駄。DB変更だけで比率を変えられるサーバー側制御の仕組みを活用。
2026-06-11: インタースティシャル広告を恒久廃止
背景: 動画詳細から戻るときにインタースティシャルを表示していたが、AdMob コンソールで「ナビゲーション操作時のインタースティシャル」としてポリシー違反と判定され、広告配信が「制限付き」に落とされていた。
決定: MovieDetailViewController のインタースティシャル実装を完全撤去(復活なし)。
理由: 復活させれば再びポリシー違反で制限される。ユーザー体験としても戻り操作を邪魔する広告は離脱要因になる。AdMob のポリシーページでも明記されている禁止事項。
2026-06-07: App Store 年齢制限は「疑似ギャンブル=頻繁/実ギャンブル=いいえ」で申告(→18+)
背景: Apple新方式の年齢制限アンケートで、パチスロ動画アプリをどう申告するか。「疑似ギャンブル」と「ギャンブル(実金)」の区別が肝。
決定: 賭博“コンテンツの閲覧”として 疑似ギャンブル=頻繁、賭博“を実際に行える機能”は無いので ギャンブル(実金)=いいえ。結果 18+。UGC=はい・広告=はい。次回以降の提出もこの方針を踏襲する。
理由: 実態に即した申告。ギャンブル(実金)を「はい」にすると実在賭博アプリ扱いとなりライセンス提出・地域制限・リジェクトを招くため絶対に避ける。18+は旧17+相当で妥当。
2026-06-07: iOSの広告は AdMob に集約する(nend/i-mobile を撤去)
背景: 自前で5広告網(AdMob/Nend/AdGeneration/Zucks/i-mobile)を束ねていたが、nend は2023年サービス終了、i-mobile の埋め込みframeworkは Xcode 26 でリンク不能。レビューでも収益≒ゼロの主因が死んだメディエーションと判明。
決定: nend と i-mobile を撤去し AdMob にフォールバック集約。AdGeneration / Zucks は現行Podで生存するため当面維持。将来は AdMob Mediation + AppLovin MAX 等への載せ替えを想定。
理由: 死網の維持はビルド不能要因かつ無収益。AdMob はメディエーション対応で現行サポートあり。AdGeneration/Zucks まで一度に剥がすとコード変更が膨らむため段階的に。
2026-06-07: iOS deployment target を 15 に引き上げる
背景: 旧構成は 9.0/11.0/11.2 混在。AdMob 13・Firebase 12・Realm 20 等は iOS 12〜15 以上が前提で、Xcode 26 の下限も 12。
決定: アプリ・拡張・Pod すべて iOS 15 に統一。
理由: 現行SDK群(特に AdMob 13 は iOS15必須)を通すための最小ライン。iOS15未満端末の切り捨ては避けられないが、広告SDK要件上不可避。
2026-06-07: OneSignal 通知拡張は一旦パススルー化(リッチ通知は後日再統合)
背景: OneSignal v5 で拡張は OneSignalExtension を要するが、use_frameworks!+拡張ターゲットで二重コピー競合・モジュール解決に難航。出荷を止めたくない。
決定: 通知拡張を OneSignal 非依存のパススルー実装にスタブ化(通常プッシュは動く、リッチ通知=画像付きは無効)。再統合は follow-up。
理由: リッチ通知は必須ではなく、出荷(=塩漬け脱却)を優先。基本プッシュは本体側の OneSignal v5 で機能する。
2026-06-07: 通知拡張の bundle ID を ...NotificationServiceExtension3 に変更
背景: App Store配信時、拡張ID ...NotificationServiceExtension2 が現Team(JFBRCYHN24)で登録不能("not available"=アカウント移管前の旧Team側が押さえたまま)。本体IDは移管済みで登録可。
決定: 拡張IDを未使用の ...NotificationServiceExtension3 に変更。
理由: 拡張はパススルー化済みでIDを変えても実害なし。新IDなら JFBRCYHN24 で登録でき配信が通る。旧IDの解放を待つより確実。
2026-05-04: v2 curate.py を v3 に統合する際は FastAPI ルーター化する
背景: v2 の curate.py は独立スクリプトとして cron で python curate.py 起動。v3 に移すにあたり、独立スクリプトのまま持ち込むか FastAPI ルーターにするか選択が必要。
決定: v3 の他の cron と統一して /cron/curate の FastAPI ルーターとして実装(cron/curator.py)。crontab からは curl http://127.0.0.1:8000/cron/curate で呼ぶ。
理由: v3 の他の cron(movieUploadsClassic / autoEntryMovie / updateHotMovies 等)がすべて HTTP エンドポイント方式で統一されているため、curate だけ独立スクリプトにすると運用とログ収集 (journalctl -u 7channel-web) が分散する。loopback URL なら Nginx/SSL のオーバーヘッドもなく、独立スクリプトと比べてコールドスタートも不要(既存 uvicorn プロセス内で実行)。
2026-05-04: 7channel は Lightsail Micro 1 台のみで完結させる
背景: v2 から v3 への吸収を進めるにあたり、多目的Lightsail にも一部役割を残すか、完全に Lightsail Micro 1 台に集約するかを判断する必要があった。
決定: 多目的Lightsail には 7channel 関連を一切残さず、Lightsail Micro 1 台に完結させる。多目的Lightsail は別プロジェクト(aix.ikapps.com / dev-timer / dev-tracker / mother / mydb.ikapps.com 等)専用。
理由: 7channel と他プロジェクトを混在させると、片方の作業時に依存関係を見落としやすい(実例: 今回の v2 DB_HOST が削除済み kizuna を指したまま 1 週間沈黙していた)。1 サービス = 1 サーバー の境界で運用する方が、移管・削除・ロールバック時の事故が減る。mydb.ikapps.com (phpmyadmin) だけは 7channel DB を覗くツールとして多目的Lightsail に残すが、それは「補助ツール」扱いで本体ロジックは無し。
2026-05-04: MySQL strict mode 互換は sql_mode 緩和ではなくコード修正で対応
背景: kizuna RDS → Lightsail MySQL 移行後、新DBはデフォルト strict mode(STRICT_TRANS_TABLES,NO_ZERO_DATE,NO_ZERO_IN_DATE)で、旧コードが暗黙の '0000-00-00' 投入や created カラム省略 INSERT で 500 連発。
決定: 短期止血の sql_mode 緩和ではなく、v3 の INSERT 文を strict 互換にコード修正する。'0000-00-00' フォールバックは '1970-01-01 00:00:00' に置換、必須カラム(created/updated/poplar_degree_updated)は明示。
理由: sql_mode 緩和は将来 MySQL アップグレードや別環境で再発する温床になる。コード修正は1セッションで終わる規模+永続的な改善になる。
2026-05-04: YouTube quota 拡張は別 Google Cloud プロジェクトでキー発行する
背景: movieUploadsDetail 用 YOUTUBE_API_KEY_2 が quota 超過。同じプロジェクト内でキーを増発しても 1日 10,000 ユニットの上限はプロジェクト単位で共有されるため意味なし。
決定: 新規 Google Cloud プロジェクトを作成 → そこで YouTube Data API v3 を Enable → キー発行 → YOUTUBE_API_KEY_3 として .env 登録。コード側は KEY_3 を参照。
理由: quota はプロジェクト単位(10,000/日)。プロジェクト分離が唯一の正攻法。同時に「API レスポンスが 200 以外 / error キーありなら del を変更せずスキップ」という防御もコードに入れて、quota 超過時に「全動画削除済み」誤判定で連鎖障害を起こさないようにする。
2026-05-04: ローカルレガシー PHP テスト用 DB は MariaDB を採用
背景: macOS Docker 上のレガシー CakePHP(PHP 5.x 時代の mysqlnd 同梱)から Lightsail MySQL 8 へ接続すると [2054] Server sent charset unknown to the client(collation ID 255 を古い PHP が認識できない)。
決定: ローカル MySQL は MariaDB(brew install mariadb)にする。Lightsail からはスキーマのみ(mysqldump --no-data)取得し、データは空でローカル流し込み。
理由: MariaDB は collation がレガシー互換でハマらない。MySQL 8 + collation 設定上書きでも動かせるが、設定漏れリスクがある。スキーマのみ持って来る方針はローカルが本番に影響しない+手間最小。
2026-05-04: Lightsail OS と MySQL を JST 化、過去データには触れない
背景: Lightsail はデフォルト UTC で動作し、NOW() も publishedAt 由来の upload_date も UTC で記録されていた。旧 PHP 本番は JST 運用だった。
決定: timedatectl set-timezone Asia/Tokyo + MySQL 再起動で system_time_zone=JST に。コード側で YouTube publishedAt を astimezone(ZoneInfo("Asia/Tokyo")) で JST 変換してから格納。過去データ(既に UTC で入った行)は書き換えない。
理由: JST 運用に揃える方が UI 表示・運用判断と一致して自然。過去データ書き換えは差分が大きく、JST 化のメリットに見合わない。今後の INSERT/UPDATE のみ JST にすれば実用上問題ない。
2026-05-04: Lightsail Nano では MySQL 同居不可、Micro ($7/月) にアップサイズ採用
背景: 段階3最終で kizuna RDS を Lightsail に移行する際、Nano (512MB RAM) にFastAPI+MySQL同居は OOM 多発で実質不可能。1回目 import 中にサーバー応答停止 → 強制 reboot 経験。
決定: Lightsail を Nano $5 → Micro $7 (1GB RAM) にアップサイズ + swap 1GB追加。MySQL の innodb_buffer_pool_size = 128M などチューニング。
理由: ① +$2/月で安定性が圧倒的に向上、② 550MB DB に対して 1GB RAM は妥当な比率、③ swap は import 時のスパイクに備える保険、④ アップサイズはスナップショット → 新インスタンス → 静的IP切替で実質ダウンタイムなし。
2026-05-04: CloudFront を廃止し Let's Encrypt + Nginx 直接 HTTPS に切替
背景: CloudFrontはCachingDisabled運用で実質「ACM証明書を貼るためだけ」の存在になっていた。最終ゴールの「Lightsail 1台集約」を達成するため、HTTPS終端も Lightsail で完結させたい。
決定: Lightsail に certbot + DNS-01 challenge (Route53) で Let's Encrypt 取得、Nginx で 443 SSL 終端。Route53 7channel.ikapps.com を CloudFront ALIAS → Lightsail 静的IP に切替。CloudFront は Disable して月末以降に削除予定。
理由: ① 構成が1段シンプルに(iPhone → Lightsail 直接)、② レイテンシ改善(CloudFront経由 90ms → 直接 60ms)、③ ACM証明書管理から解放、④ Let's Encrypt は完全自動更新(90日毎)、⑤ DAU 500・日本のみのトラフィックで CloudFront のエッジ分散は過剰。
2026-05-04: B-6 (beginBroadcast/S3Uploader) は保留不可、iOSアプリのデータ供給に必須
背景: 段階3で B-6 (/api2/beginBroadcast) を「cron 10分毎で影響低」と判断して保留していたが、cutover後に iOSアプリ "Avatar3" + cron の双方が叩いて 404 を出していた。元 PHP 実装を読み直したところ、/S3Uploader/current201609 で全テーブルを集計して S3 (channel7/current/201609) に public-read で put し、iOSアプリは S3 から直接基本データ(top_movie / cast / channel / device 等)を取得する設計だった。
決定: B-6 を緊急で v3 に実装(cron/s3_uploader.py、22テーブル集計→ boto3 で S3 put)。Lightsail に IAM ユーザー認証情報を ~/.aws/credentials で配置。
理由: ① iOSアプリの基本データ取得経路を放置できない(古いデータ表示・新着動画が出ない)、② cron 10分毎の更新が必要、③ S3 への public-read アクセスは既存運用の継続。
教訓: 「保留してもいい」と判断する前に、そのエンドポイントが何のために存在するかを実装ベースで深掘りすべきだった。
2026-05-03: Lightsail → kizuna RDS 接続は「Public化 + SG許可」方式(VPC peering 不可のため)
背景: Phase D で Lightsail から kizuna RDS にアクセスする経路が必要。schulte-operation の前例(VPC peering)を検証したが、Lightsail VPC peering は「default VPC とのみ可能」という公式制約があり、kizuna はカスタムVPC vpc-4b77802e にいるため使えない。選択肢は ① EC2-2 を踏み台にした SSH トンネル、② RDS を Public化して SG で Lightsail static IP を許可、③ 一気に Lightsail MySQL に移行、の3つ。
決定: ②(RDS PubliclyAccessible=true + SG 35.73.116.227/32 許可)を採用。kizuna は前は Public だったので元に戻すだけ。
理由: ① はSSHトンネル維持の追加運用負担(autossh等)が発生・EC2-2を踏み台専用で残す必要、③ は段階3の本流だが今すぐは規模が大きくスケジュール圧迫。② は最小工数で確実に動き、最終段で RDS も Lightsail に持っていく時に簡単にロールバックできる。Public化のセキュリティ後退は IP制限とパスワード認証で許容範囲。
2026-05-03: 段階3 はPHP→Python(FastAPI)移植を v3 新規実装で進める(v2 を吸収)
背景: 段階3で「Lightsail 1台に集約」を目指すが、PHP 5.3 + 旧CakePHP は新環境に持っていけない。多目的サーバーは既に v2 + AI スクリプトが居て、レガシーPHPを混ぜたくない。schulte-operation の前例(PHP→FastAPI で Lightsail 移行・Weighted Routing カットオーバー)を参考に方針確定。
決定: 7channel-server-v3 を新規作成(v2をベースにコピーし v2 admin機能をそのまま吸収)。iOSアプリ用 API(access log 分析で特定した 9 メソッド)と cron 系(12メソッド)を Python/FastAPI で再実装。最終的に Lightsail Small ($10/月) にデプロイして CloudFront のオリジンを切替する。
理由: ① PHP 5.3 互換性問題を完全回避できる、② iOS app が使ってるエンドポイントは access log で実証済み(9個のみ)、PHP 60+ Controller のうち本当に必要なのはごく一部、③ Python なら既存 v2 と運用ノウハウを共有できる、④ schulte の前例で同じパターンの成功実績あり、⑤ Lightsail 集約で月-$50(年-$600)の削減見込み。
2026-05-03: ローカル開発時の RDS 接続は SSH トンネル経由(kizuna は PubliclyAccessible=false のため)
背景: kizuna RDS は PubliclyAccessible=false のためローカル直接接続不可。v3 ローカル開発で本番DBにアクセスして PHP本番 vs FastAPI 比較テストをしたい。
決定: EC2-2 (43.206.39.55) を踏み台にして SSH ローカルポート転送(-L 13306:kizuna....:3306)でアクセス。v3 の database.py に DB_PORT パラメータを追加して、環境変数で 13306 に切替可能にした。
理由: ① PubliclyAccessible を一時 ON するよりセキュアで副作用なし、② RDS の SG変更も不要、③ 終了時に SSH を閉じるだけでクリーンアップ完了、④ EC2-2 は同じ VPC 内なので RDS にアクセス可能。
2026-04-28: STRAIGHT_JOIN は本番投入せず、ロールバック後はインデックス調整のみで対応
背景: CastMovieRelation::getRows を STRAIGHT_JOIN 化したところ、EXPLAINテストでは250msだったが本番では最大14秒・平均7.7秒まで悪化。データ分布が偏ると movies 駆動でLIMIT 30を満たすために大量スキャンが発生する典型パターン。
決定: STRAIGHT_JOIN を削除して元の自然JOIN形式に戻す(ロールバック)。今後 JOIN順序の固定(STRAIGHT_JOIN)は本番投入しない。スロークエリ対策はインデックス追加とアプリレベルの単純な書き換え(COUNT化、ORDER BY 列の置換等)のみに限定。
理由: ① EXPLAINや1ケースのテストではデータ分布の偏りを検出できない、② オプティマイザを縛る変更は劣化リスクが大きい、③ インデックス追加だけでも同等以上の効果が出ることが多い(過去に証明済み)。
2026-04-28: 7channel本番のHTTPS終端は CloudFront 経由(Let's EncryptでなくACM流用)
背景: 段階2でCLB廃止する際、CLBがACM証明書でHTTPS終端していたため代替が必要。当初はEC2-2にLet's Encrypt導入(A案)の方針だったが、Amazon Linux AMI 2018.03 がEOLでcertbot導入が困難と判明。
決定: CloudFront を間に挟む(B案)に変更。新たに us-east-1 でACM証明書を発行し、CloudFront に紐付け。CloudFront → EC2-2 はHTTP(80)で内部通信。多目的サーバー集約(最終ゴール)は段階3以降に持ち越し。
理由: ① EC2-2 のEOL OS で証明書管理を増やすのは技術的負債、② CloudFront は月$0.5程度の追加コスト(DAU 500)で運用が安定、③ ACM証明書の自動更新がAWS任せで運用ゼロ、④ EOL Apache を表に出さなくて済むセキュリティ面のメリット。
2026-04-28: EC2-2 は専用機として残し、多目的サーバーには7channelを置かない
背景: 段階2の当初プランは「多目的サーバーNginxに集約」だったが、ユーザーから「多目的サーバーには既に v2 + AI スクリプトがあり、レガシーPHPを混ぜたくない」との方針確認。
決定: EC2-2はそのまま専用機として残す(CLBだけ廃止して、CloudFront → EC2-2 直接)。最終ゴールの「Lightsail 1台集約」は、PHP 5.3 + 旧CakePHPのリプレースが終わってから実現する(段階3以降)。
理由: ① レガシーアプリと新アプリの相互影響を避ける、② 多目的サーバー(Nano $5)にPHP本番アプリの負荷を載せるのは性能的にもリスク、③ PHP 5.3 は新OSに移植が困難で、Lightsail集約はリプレースとセット運用が現実的。
2026-04-27: CakePHP find/getAll 経由のクエリはコード書き換えせずインデックスで対症療法
背景: Api3Controller::nextMovieDataForCast のクエリ(A: 950ms)が MovieCastRelation->getAll(...) 経由で recursive=1 の自動JOINになっており、ORDER BY が JOIN先の列のため filesort が発生。インデックスで270msまで改善するが、根本解決は2段階クエリ化や recursive=-1 + 手動 joins への書き換えが必要。
決定: A はインデックス追加(movies.idx_del_upload_unix)のみで対応し、コードは書き換えない。一方、直接 SQL を組んでいる Model メソッド(CastMovieRelation::getRows 等)は STRAIGHT_JOIN への書き換えを行う。
理由: ① CakePHP find/getAll の戻り値は $row['Movie'][...] 形式で、JOIN方法を変えると配列構造が変わりやすく、API契約に影響するリスク。② 270ms はスロー閾値1秒未満で実用上OK。③ 直接 SQL の Model メソッドはレスポンス形式が固定なので STRAIGHT_JOIN 書き換えが安全かつ効果的。
2026-04-27: cast_imps は書き捨てテーブルとしてINSERTを停止、データもTRUNCATE候補
背景: 540万行・250MB のテーブルだが、ソースコード grep で SELECT 箇所がゼロと判明。INSERT のみされ続けて読まれていない死テーブル。
決定: Api3Controller::impressionCast() の INSERT 行をコメントアウトしてデプロイ。テーブル本体のTRUNCATEは保留(ユーザーが「とりあえずいいや」と判断)。
理由: 完全に未使用と確認できたため、書き込み停止に副作用なし。実テーブルは後でいつでも消せるので、安全マージンとして残す。
2026-04-27: movie_imps は直近3ヶ月のみ保持、旧データは old_movie_imps に退避
背景: 1100万行・1.5GBの閲覧履歴ログ。HotMovie/HotWeekMovie/HotMonthMovie/HotTotalMovie のランキング集計用。/3//4 cron は休止中だったが、/3(1ヶ月)は復活希望。
決定: テーブルswap方式で直近3ヶ月(約2万行)に圧縮。旧データは old_movie_imps に退避してLightsail移行時までキープ。/4(10年集計)は完全データがないため休止継続。さらに cleanMovieImps cron を追加して3ヶ月超を毎日自動削除。
理由: ① INSERT/SELECT継続、② 復活させた /3 は3ヶ月分で十分カバー、③ swap方式はDELETEより高速・安全、④ 自動クリーンアップで再肥大を防止、⑤ 旧データは消さずに残すことで万一の検証に対応。
2026-04-27: スロークエリ対策はインデックス追加とアプリ側クエリ改善の両輪
背景: スロークエリログ有効化後、ワースト3クエリを特定。articles.done_image_scraping(インデックス無し・31万行スキャン)、updateCastMovieCount(1628行fetchしてPHP count())、ORDER BY DATEDIFF(毎行計算)。
決定: ① インデックス追加(articles.done_image_scraping)、② SQL COUNT(*) への置換(find('count', recursive=-1))、③ DATEDIFF(定数, col) ASC を等価変換で col DESC に書き換え(3箇所)。
理由: インデックスはノーリスクで効果大。クエリ改善は CakePHPの recursive パラメータと数学的等価性を活用すれば挙動変更ゼロ。両輪でアプローチすれば本番影響を抑えつつ最大効果。
2026-04-26: AWSコスト削減は超段階的にやる方針を採用
背景: 7channel本番環境(EC2×2 + CLB + RDS、月$106)は技術的負債(PHP 5.3 / Amazon Linux 2018.03 / nodejs8.10 Lambda 等の全EOL)も抱えており、一気に作り直すと事故リスクが高い。
決定: リプレースではなく段階的なコスト削減で進める。最初の段階1として、ALB配下のEC2を2台→1台に減らす(片方の AppName タグを外して CMN_attachToELB の自動再登録を回避し、CLBからデタッチ+停止)。観察期間を経て段階2以降へ。
理由: ① ロールバックが1コマンド(タグ戻すだけ)で可能、② サービス影響ほぼゼロ、③ 1台で本当に捌けるかの実測データが取れる。「いきなりLightsail移行」よりリスクが極小。
2026-04-25: Lightsail 上のアプリから AWS リソースへのアクセスは IAM ユーザーのアクセスキー方式を採用
背景: SNS アラートメールが新サーバーから飛ばなくなった。Lightsail のデフォルトロール AmazonLightsailInstanceRole には SNS:Publish 権限がなく、ロール自体が AWS 管理リソースのため IAM ポリシーをアタッチできない。SNSトピック側にクロスアカウント許可を入れても、IAM側ロールに権限がないため動かない。
決定: IAM ユーザー ichirokisanuki のアクセスキーを ~/.aws/credentials(mode 600)に配置して、boto3 のデフォルト認証チェーン経由で SNS を叩く方式にした。
理由: Lightsail のデフォルトインスタンスロールは AWS 管理で変更不可なので、ロールベース認証は実質不可能。アクセスキー方式は旧サーバーでも採用していた実績ある方法で、運用がシンプル。
2026-04-24: 7channel-server-v2 を独立リポジトリではなく7channel-operationに統合
背景: 7channel-server-v2 は元々独立したGitリポジトリ(.git 内蔵)として運用していたが、7channel-operation モノレポからもgit管理したかった。サブモジュール化と統合の二択だった。
決定: 7channel-server-v2/.git を削除して 7channel-operation リポジトリに通常のディレクトリとして統合。GitHub上の独立リポジトリは作られていなかったので破棄も不要。
理由: 個人開発でサブモジュールを使うほどの複雑さは不要。1リポジトリで完結する方が運用が楽。
2026-04-24: 7channel-server-v2 を多目的Lightsailサーバーに移行
背景: 旧Lightsailインスタンス(ec2-user@13.230.63.19)で動いていた7channel-server-v2を、新しい多目的サーバーに集約したかった。
決定: multipurpose-server1(ubuntu@13.230.63.19)に移行し、Nginxバーチャルホストで 7ch.ikapps.com をルーティングする構成にした。
理由: 複数サービスを1台でNginxで束ねる運用に統一するため。旧インスタンスを廃止してコストを抑える意図もある。
2026-04-24: 静的IP 7channel-v2-ip を多目的サーバーにアタッチして固定運用
背景: 多目的サーバーは動的IPのままだと再起動のたびにRoute53を更新する必要があり運用が煩雑。
決定: 旧サーバーで使っていた静的IP 7channel-v2-ip(13.230.63.19)を多目的サーバーに移植してアタッチ。
理由: Lightsailの静的IPはアタッチ中無料。Route53のAレコードを固定できるため運用が楽になる。
DEVLOG(作業ログ)
開発日誌
このプロジェクトでの作業を時系列で記録する。 最新のエントリが上に来る。
2026-08-11
16:57 - 採用・却下の通知メールを停止(本番反映済み)
やったこと
- 動画の採用・不採用処理の結果メール「7ch: 採用N件 却下N件」を You 要望で停止。送信元は2箇所: 現役の
7channel-server-v3/cron/curator.py(v3 cron 経由)と、旧 v2 由来のcurate.py(cron 停止済みだが deploy.sh の配布対象に残存)。両方のsend_notificationブロックをコメントアウト(削除せず、復活可能な形)。 - 採用・不採用の処理本体(movies 登録・キャスト関連付け・却下)は不変。出演者付与🟢/🔴・機種関連付け・エラーアラート・トークン日報の各メールは対象外で継続。
- コミット
fab1739→ deploy.sh で Lightsail Micro へ本番反映(7channel-web 再起動、active・/admin 200 を確認)。
2026-08-10
19:42 - 日報のJSTずれ(cronデーモンがUTCのまま3ヶ月)を修正+トークン使用量を解剖して縮小マスタ復活(device −43%/コール)
① 日報が0:00に来ない問題 → cron デーモンの UTC 先祖返りを発見・修正
- You指摘「日報は日本時間0:00で・集計もJST基準で」を調査 → OS・コードはJSTだが、初回自動日報が 9:00 JST 発火(=0:00 UTC)で当日朝までの部分集計になっていた。
- 真因: cron デーモンがサーバ起動時(5/4 06:57・JST化前)の UTC 設定のまま3ヶ月動いていた(
timedatectl変更はデーモン再起動まで反映されない)。systemctl restart cronでJST化し、検証エントリが指定分ちょうど(16:26:02 JST)に発火することを実測確認。 - 副次修復: 時刻指定の既存cron4本(updateHotMovies日次・cleanMovieImps・refreshChannelImages・日報)も3ヶ月間ずっと9時間遅れのJST午後に発火していた→本来の早朝JSTに復帰。切替はUTC朝の発火が全部済んだ後に実施=二重実行・取りこぼしなし。
- コード側にも
ZoneInfo("Asia/Tokyo")を明示(記録ts・日次集計。環境に依存しない)。送られ損ねた8/9分日報を手動送信(93.4万トークン)。
② 「100万/日は多い」→ トークン使用量の解剖(実測ベース)
- 実効 約14kトークン/動画で、8割超が毎回同梱の固定部(codexベース2.1k+ルール+マスタ+hints)。動画テキスト自体は1〜2k/本。
- 無駄ランキング: (1) device が全マスタ810台を毎回同梱=Gemini時代の縮小マスタ最適化が退役した /cron/run 経路にだけあり、現行の /matcher/device/pending は全量を返す先祖返り(1コール約18kが古機種リストに消える) (2) 5分毎の小口発火(平均3.2本/回=固定部の支払い回数が約2.5倍) (3) castマスタ245人中、直近1年活動は163人。
- 時間帯分布: 18〜21時台(投稿ラッシュ)だけで日量の8割。昼はほぼゼロ。
③ 対策①「縮小マスタ復活」のみ実装(You判断・②まとめ待ちは見送り)
/matcher/device/pendingがload_devices_recent(3年以内追加 or 直近180日実使用)を返すように変更。ジャグラー/ハナハナ/沖ドキ等の定番現役機は「実使用」条件で自動残留(手動例外リスト不要)を実測確認。- 縮小で外れた機種(例: 押忍!サラリーマン番長)は Luna が新機種名として出力→
resolve_machinesの全マスタ名前照合(NFKC正規化・完全一致)で既存IDへ振替=マッチ不能にならない二段構え(Gemini時代に実証済みの番人)。 - 実測: device 1コール入力 30.2k → 17.4k(−43%)・マスタ810→144台。日量では3割前後の削減見込み(deviceが日量の2/3のため)。下げ止まり要因は device hints が5.7KBとやや大きいこと(学習資産なので現状維持)。
- 収穫: 再判定テストで生放送告知動画が
machines:[]と正しく判定=昨日「物申す」で自動追記した知見が効いている実地確認になった。
気づき: timedatectl set-timezone 後は cron デーモン再起動が必須(やらないと旧TZが残り続ける)。時刻指定cronがあるサーバのTZ変更時は要チェック。
次回: 明日0:00の日報で縮小マスタの削減効果を数字で確認(見込み: 100万→70万前後/日)。さらに削るなら②まとめ待ち(−60〜70%まで到達可・ラグ15分化)が控え。
2026-08-09
18:05 - トークン日報+失敗アラートを整備、codex auth失効インシデントをサーバ専用ログインで恒久解決
新体制の運用監視レイヤーを固めた回。作業中に予告どおりの auth 失効が実際に起き、その場で恒久策まで打った。
やったこと:
- メール微調整: 物申すリンク(新機種メールは登録フォームリンク)と YouTube 動画URLの間に空行を追加(3種すべて)。
- 📊 トークン日報(新設):
codex exec --jsonのturn.completedイベントから判定コールごとの使用量(入力/キャッシュ/出力)をlogs/token_usage.jsonlに記録し、新規daily_token_report.pyが日単位で集計(出演者/機種の回数・入出力・計+合計)。cron0 0 * * *で毎日0:00に前日分をメール。手動送信テスト済み(初日: cast 17.3k + device 32.2k = 49,496トークン=サブスク枠にはごく軽い実測)。集計は判定コールのみ(物申すの知見化コールは対象外)。 - 🚨 codex auth 失効インシデント(検知→復旧→恒久策): 日報テスト中に今日12:15からサーバ codex が401で全滅していたのを発見。エラーは「refresh token was already used」=移行時に注意点としていた Mac との auth.json 共有によるローテーション競合が約1日で顕在化。応急で auth.json 再コピー→滞留 cast7本/device6本を消化(フラグ設計で取りこぼしなし)。恒久策としてサーバ専用の ChatGPT ログインセッションを確立: サーバで
codex login(localhost:1455 コールバック)を起動し、ssh -L 1455:127.0.0.1:1455トンネル経由で You が Mac ブラウザから1回ログイン→Mac と独立した refresh token に。以後 Mac 側のログイン/利用と衝突しない(codex login --device-authという代替手段があることもログで判明)。 - 🚨/✅ 失敗アラート(新設): 判定コール失敗で throttle 付きアラートメール(初回即時+継続中は3時間ごと+判定成功で✅復旧通知・状態は
logs/alert_state.json)。「新着なし」の空振り回は codex を呼ばないので状態に影響させず、実際の判定成功のみを復旧と判定。メール送信は v3 venv の notify.py へ subprocess 委譲(runner は stdlib のまま・.envのNOTIFY_V3_DIRで有効化=Mac では無効)。疑似障害(偽 CODEX_BIN)で 🚨→抑止→✅ のフルサイクルを実地検証。 - 監視は三重に: 🚨即時アラート → 3hごと継続通知 → 📊日報の0トークン。
詰まったこと / 気づき:
- ssh リモートコマンド内で
pkill -f "codex login"すると自分のリモートシェル自体がパターンにマッチして切断される(exit 255)。パターンはpgrep -a -fで確認してから。 - auth.json 共有の失効は「まれに」ではなくほぼ1日で確実に起きる(Mac で Codex を日常使用しているため)。コピー運用は非常用の応急手段と割り切る。
次回: 新体制の初期観察の継続(🔴の的中感・新機種メール頻度・日報の数字感・hints の育ち)。
2026-08-08
20:36 - メール新フォーマット化+機種関連付けを「自動書き込み+物申す」へ全面移行(cast と同型化)
18:48 の基盤(サーバcodex判定+cast物申す)の上に、同日中にメール刷新と機種側の同型化まで進めた。
やったこと:
- メール新フォーマット(cast→device 順に適用): まとめ1通→動画ごとに1通へ。件名=自信度絵文字(
🟢/🔴 出演者の自動関連付け/機種の自動関連付け/機種の確認(新機種あり))、本文=「名前(機種名)/動画タイトル/リンク/YouTube動画URL」。自信度は Luna の判定出力に movie 単位のconfidence(high/low) を追加して実現("high" 以外は 🔴)。SNS の件名絵文字は publish 実測で通ることを確認。汎用notify_mailヘルパーを cast_matcher.py に新設。 - 機種の自動書き込み化(承認フロー廃止・You 3択判断): 既存機種マッチ(kind=0)は
movies.device_idへ直接書き込み(WHERE device_id=0ガード=手動・既存設定は上書きしない)。判断根拠を実測: ①過去の承認実績 799承認/3却下=99.6%で「はい」が形骸化+pending102件が滞留 ②cast型テキスト存在ガードは71%しか通らない(動画側が「マイジャグ5」「SAO2」「北斗転生」等の略称で書くため)→ ハード遮断は不可、事後の物申す+🔴目印で足りると判断。新機種提案(kind=1)のみ登録フォームメールの人間ゲートを温存(マスタ自動作成はパチンコ混入・命名・重複リスク)。滞留 pending102件は You 指示で「いいえ」状態に一括更新(新機種40件の登録フォーム待ちは残置・旧リンク有効)。 - 機種の物申すフォーム(新設):
/device-feedback?m=&t=(HMAC "devfb" ステートレストークン)。「変更しない(指摘だけ)/外す/正しい機種に付け替える」の3択で、付け替えは全機種マスタの<datalist>入力補完+完全一致解決(マスタに無い名前はエラー)。指摘→codex→device_matching_hints.md自動追記+監査ログは cast と共通基盤(cast_feedback.py→matcher_feedback.pyに改名して cast/device 共通化)。旧「いいえ」ページの理由フォームも互換残置(同POSTのデュアルモード)。 - パチンコ混入の実データ修正: You 指摘の「ガンダムSEEDクライマックスはパチンコ」を調査 → 8/7 に Luna が新機種提案→You が登録フォーム承認→一律スロット扱い(is_pachinko=0)で混入した経緯を特定。
devices.1167を is_pachinko=1 に修正(判定候補マスタから除外)、再発防止に「SEED系現行機はパチンコ」を hints へ追記(フィードバックパイプライン経由)、登録フォーム時代の新機種71台を目視して混入は1台のみと確認。movie#145909 の紐付けは動画内容と合致するので温存。 - E2Eテスト全パス: 自動書き込み→🟢/🔴メール→フォームで「ガルフィ」付け替え→「外す」→原状復帰。「いいえ」理由フォーム→「【生放送】告知動画は機種を付けない」ルールの自動追記も一周確認。テスト直後の 19:55 cron から実データの新形式メールが自然に流れ始めた。
決めたこと: 機種も自動書き込み化(kind=1のみ人間ゲート)・物申すは付け替えまで対応・pending102は「いいえ」一括 → DECISIONS.md 参照。
留意:
- devices テーブルに id=0「なし」番兵行があり、
names.get(other_device_id)で拾うと「+なし」表示になる(matcher_feedback は修正済み。main.py の device_approve に同型の既知quirkが残るが従来挙動なので放置)。 - サーバの device_matching_hints.md も自動で育つ→cast 同様に定期 scp 回収(今回2ルール追記分は回収済み)。
- 承認メール(はい/いいえ)は今後 kind=1 の登録フォームメールのみ。旧メールのリンクは全て生きている。
次回: 新体制の初期観察(🔴メールの的中感・新機種確認メールの頻度・自動書き込みの精度・hints の育ち方)。
18:48 - 関連付け判定をサーバ同居 codex(gpt-5.6-luna) へ恒久移行+「物申す」フィードバック自動学習フローを新設
1ヶ月保留していた判定エンジン恒久策を決着させ、Mac stopgap を退役。さらに付与メールから誤判定を指摘→hints 自動追記の学習ループまで同日で本番投入した。
やったこと:
- 事前評価(dry-run): 直近の判定済み48本+mcr正解51ペアをサーバDBからフィクスチャ化し、
gpt-5.6-luna(codex exec・reasoning=low)+テキスト存在ガードで判定→現行 sonnet 判定と突き合わせ。F1 0.96・完全一致44/48本・8本11秒(sonnet比約10倍速)・1チャンク約19kトークン。不一致4件は保守的な取りこぼし2・sonnet側の見落としの可能性1・評価側の都合(ガードが概要500字打ち切り、本番は全文)1で、移行GOと判断。モデルスラッグは~/.codex/models_cache.jsonからgpt-5.6-lunaを特定(luna単体はChatGPTアカウント不可エラー)。 - matcher の codex 対応:
local-matcher/local_matcher.pyにJUDGE_BACKEND=claude|codex切替とcodex_judge()を追加(--sandbox read-only --ephemeral --ignore-user-config・最終応答は-oファイル経由)。run_matcher.shは Mac/Linux 両対応化(caffeinate はあるときだけ・DIR自動検出)。 - サーバ切替: codex 0.144.1 musl静的バイナリを
/usr/local/bin/codexへ設置(Nodeなしで動く)、Mac の~/.codex/auth.jsonをコピー(chmod 600)して疎通確認(2,102トークンと軽量=--ignore-user-configでプラグイン群を読まないため)。~/7channel-matcher/に配置し.envはJUDGE_BACKEND=codex・BASE=http://127.0.0.1:8000(localhost直叩き)・CODEX_BIN=/usr/local/bin/codex(cron の PATH に /usr/local/bin が無いため明示必須)。初回実走で cast 5本→5件付与・device 5本→2件提案を18秒で完走。cron*/5 * * * *(flock -n 付き)登録、Mac launchd unload+plist撤去(repoにコピー残存・JUDGE_BACKEND未指定なら claude のままなので Mac 復帰も可能)。反映ラグ最大1時間→5分・Macスリープ問題消滅(pending 0 の回は LLM を呼ばないので5分毎でもコストは新着分だけ)。 - 「物申す」フィードバック機能(新設・本番デプロイ済み): cast 付与メールの動画ごとに
物申す:リンクを追加(cron/cast_match.py)。リンク先/cast-feedback?m=&t=は HMAC(MATCHER_API_TOKEN, "castfb:") のステートレストークン(テーブル追加なし・冪等)。フォーム(新規cast_feedback.py・device承認ページと同トーン)で①誤タグのチェック解除=mcr DELETE ②自由記述→サーバ codex が汎用ルール化しknowledge/cast_matching_hints.mdの新設節「## 運用フィードバック(自動追記)」へ日付付き自動追記(次回判定から反映)。一般化に適さない指摘は追記しない判断も codex がする。原文はknowledge/cast_feedback_log.mdに監査ログ(codex失敗時もタグ解除は反映・原文保全)。config.pyにCODEX_BIN/CODEX_MODEL追加。 - 実地テスト全パス: 正トークン200/偽トークン404、実フィードバック投稿→「『シーサ。の指令!』シリーズ → シーサ。本人も出演者として付ける」が自動追記(dry-run評価で見つけた実際の取りこぼしなのでそのまま採用)、タグ解除→DB反映→SQLで完全復元。サーバの hints はローカル repo に scp 回収済み。
決めたこと: ①恒久策=サーバ同居 codex(app-server ではなく exec。サブスクcronのToSグレーはYou許容) ②フィードバックは完全自動追記(プレビュー確認なし・監査ログで巻き戻し担保)→ DECISIONS.md 参照。
留意:
- auth.json は Mac と同一 refresh token 共有=まれに失効したら再コピー。
- サーバの hints は自動追記で git と乖離していく→時々 scp 回収。deploy 時に knowledge/ を上書き scp しないこと。
- 週次監査(Gemini weeklyAudit)は 07-02 から crontab コメントアウトのまま(判定エンジンとは別レーン。復活させるなら別途)。
次回: 実際の付与メールで「物申す」リンクの見た目・導線を実地確認(まだメール未確認)。数日 ~/7channel-matcher/logs/matcher.log を観察して安定判定。
2026-07-05
夜 - iOS 8.5.0(2026-06アプデ)を ASOメタデータ刷新とともに App Store 審査へ提出
2026-06-29に実装(T1〜T4)+サーバ配信キー投入まで済んでいたアプデを、ASOメタデータの作り込みと合わせて提出まで到達させた。ASC状態は WAITING_FOR_REVIEW。
やったこと:
- 状況の実査: ASC API(app-store-connect MCP)で 8.5.0 が
PREPARE_FOR_SUBMISSIONで枠だけ存在・ビルド未紐付け・メタデータが旧内容のままだと確認。残ゲートを特定。 - ASOメタデータの下ごしらえ:
pr-manager/products/7channel/の aso_update / release_checklist から確定値を集約し、ASC入力欄ごとにコピペ即入力できるasc_paste_2026-06-29.mdを作成(文字数検証込み)。 - promo-expert に2往復レビュー依頼:
- サブタイトル: 旧案「機種・出演者・番組でサクサク探せる」は App名「…サクサク検索」と「サクサク/検索≒探せる」が重複=2行目が言い直しと判明。前半の索引価値語は温存し後半を便益へ→
機種・出演者・番組で、まとめて楽しむに確定(「次の実践まですぐ」案はYou不採用、集約便益角度を選択)。 - 概要: 冒頭フックが約束を言えていない+YouTube比の探しやすさ回収ゼロを指摘され全面リライト。You判断で YouTube語は出さず婉曲(②) + 「実践→実戦」表記 に。1行目「〜が、もう要らない」が日本語的に硬い指摘を受け 「〜で、もう迷わない」 に微修正。
- サブタイトル: 旧案「機種・出演者・番組でサクサク探せる」は App名「…サクサク検索」と「サクサク/検索≒探せる」が重複=2行目が言い直しと判明。前半の索引価値語は温存し後半を便益へ→
- キーワード: 「実践」はキーワードに残し概要は「実戦」=表記違いで両検索を拾う使い分けに。
- スクリーンショットの寸法エラー対応: Youが6.5インチ枠にアップロード→寸法エラー。制作済みは 6.7(1290×2796)/6.9(1320×2868) のみで6.5枠(1284×2778/1242×2688許容)に合わないのが原因。
app_store_screenshots/6.5/に 1290×2796→1284×2778 を sips 変換した4枚を生成して解決。 - 提出: Youがビルド紐付け・メタデータ貼付・スクショ差し替え・審査提出を実施。ASC
WAITING_FOR_REVIEWを確認。リリース方式はAFTER_APPROVAL(自動公開)のまま。
次回: 審査結果待ち→承認公開後に本番実データ観測(T1再生安定・T2レビュー導線・GA4/Crashlytics)。実機QAは未実施のまま提出。差別化5枚目スクショは未制作。
17:36 - ローカルmatcherを caffeinate 化:起床時の判定がスリープに割られてタイムアウト→滞留する目詰まりを解消
「最近関連付けがおかしい・毎時動いてるか微妙」という体感の原因を調査し、run_matcher.sh を caffeinate -i で包む修正を入れた。本番サーバは無関係、Mac ローカルの stopgap 判定ランナーのみ変更。
- 調査で判明した実態: launchd(
com.7channel.local-matcher)は登録・稼働自体は正常(前回exit 0)。ただし問題は2つ。- ① 実質「毎時」になっていない:
StartInterval 3600はMacスリープ中は非発火。ログのギャップが明白(7/4 19:27→7/5 11:37 の約16h空白、7/4 01:50→16:21 の約14.5h空白=夜間スリープ)。これは stopgap の宿命として既知。 - ② 走り出した判定がスリープに割られてタイムアウト:
7/4 01:50 cast開始→06:55 timed out after 240s(壁時計5時間なのに「240秒でタイムアウト」)、7/5 11:37 cast開始→15:22 反映(本来2〜3分が壁時計4時間)。判定の途中でMacが再スリープに入り、claude呼び出しのawake累積が240sに達してタイムアウト→cast/deviceその回丸ごとスキップ→pendingが22→27→28本と滞留。これが「反映が遅い・おかしい」の正体。
- ① 実質「毎時」になっていない:
- Youの要望: スリープ中に無理に起こす必要はない。「起きたら確実に走る」だけでよい。→ 実は
StartIntervalは起床直後に1回発火するので**「スリープ明けに実行」は既に実現済み**。足りないのは「走り出した回を完走させる」こと。 - 対処:
run_matcher.shの python 実行を/usr/bin/caffeinate -iで包む(1行)。ジョブ実行中の数分だけ idle スリープを抑止=起床時に発火した判定が途中スリープで止まらず完走する。Macを起こし続けはしない。plist は未変更なのでunload/load不要(launchd は毎回スクリプトを叩くだけ)。 - 確認: 手動で1回消化 → 実行時点で pending 0(
cast:新着なし / device:新着なし)。滞留は解消済み。次のスリープ→起床でタイムアウトが出なくなるか数日観察して安定判定する。
17:09 - 判定エンジンの恒久策を調査:ローカルLLM(qwen2.5:7b)を実測しcast判定F1 12%で不可判明(保留)
現行の判定は「Mac ローカル claude -p(sonnet) 毎時 launchd」の stopgap(2026-07-02〜)。Youのサブスク消費・Macスリープ中は非発火という宿命があり、恒久策を検討するための調査スパイク。本番は今回一切変更していない(調査のみ)。
- 動機の整理: 「Claude/特定ベンダー依存を減らしたい」「毎時縛りを外したい」「恒久的に安く」。検討で判明した前提:
- Max サブスクでは生の Anthropic API は認証できない(Max は Claude Code/claude.ai 用。生APIは APIキー=トークン従量課金が別途必要)。
- Codex CLI は ChatGPT サブスクで動くが、無人cronでの自動利用はToS的にグレー+レート上限+"素のLLMでなくエージェント"で重い。
- 「毎時しか」はClaudeの制約ではなく実装(スケジュール)都合。自前 launchd なら任意間隔で回せる。
local_matcher.pyは既に判定をclaude_judge(prompt)の1関数に隔離済み=バックエンド差し替え=この1関数を替えるだけ(脱依存の下地は完成している)。
- 実測(この2026機で実施): Ollama +
qwen2.5:7bを導入。DB由来の正解付きフィクスチャ(判定済み40本+確定cast_ids+出演者マスタ245人。13308トンネル経由でmovie_cast_relationsから生成)を作り、qwen の cast判定を正解と突き合わせ。 - 結果(qwen2.5:7b / cast): precision 11% / recall 14% / F1 12%。IDを捏造(正解[220]→qwen[23,50,89] 等・245人名簿の名前→ID照合ができていない)。temperature=0でbit単位に再現=インストール不良ではなく7Bの実力。速度は22秒/8本(2026機 Metal GPU)。
- 気づき: プロンプトはチャンクあたり10,855tok(マスタ同梱でも num_ctx 16384 内に収まり切り詰めなし)。2019 Intel(CPUのみ)ではこのprefillだけで桁違いに遅くなる見込み=精度と速度の両方が向かい風。
- 詰まった点(実験設定の勘所): ①ollama
num_predict未設定で生成が暴走(1回10分超でタイムアウト)→上限1500で解決。②ollamanum_ctx既定2048だとマスタが黙って切り詰められ精度が出ない→16384に拡大。
保留中の分岐(次回決める): ①qwen2.5:14b を試す(9GB DL・精度改善見込みだが2019速度は悪化)/②作り替え(IDでなく名前を出させPythonでID照合+候補castをREGEXP事前絞り込みでプロンプト激減→精度も速度も改善狙い・工数あり)/③見切って Haiku 生API(月$2規模・フロンティア品質・2019速度問題なし)。
再開メモ: 実験スクリプト(build_fixture.py/eval_fixture.py/compare_matcher.py)とフィクスチャ(fixture_cast.json)は**セッションの scratchpad(揮発性)**にある。再開時は再生成 or リポジトリへ移設が必要。13308トンネルは上げたまま(本番cronも同じものを使い回すため無害)。
2026-07-02
16:32 - 関連付け判定をローカルMacのClaude(毎時)へ緊急移行、サーバGemini cronを停止
flash-lite 移行後も Gemini API の関連付けが失敗し続けるため、判定主体を Mac ローカルの claude -p(毎時 launchd) に一時退避(stopgap)。サーバは無改修で、既存の /matcher/cast/*・/matcher/device/*(Bearer認証・2026-06-28にクラウドroutine用に構築済み)を流用。
- 新規
local-matcher/(deploy対象外):local_matcher.py(Python制御=pending取得/CHUNK8分割/results POST、判定だけclaude -pmodel=sonnet に委譲。プロンプトはroutines/*.mdから移植)+run_matcher.sh+com.7channel.local-matcher.plist(StartInterval 3600)。.envにトークン(サーバ .env から取得・git非追跡)。 - launchd:
~/Library/LaunchAgents/com.7channel.local-matcher.plistを load。死んでいた旧cast-matcher/device-matcherplist(Codex時代・スクリプト削除済)は掃除。 - サーバ crontab: Gemini 3本(castMatch/run・deviceMatch/run・weeklyAudit)をコメントアウト(
/tmp/ct.bakバックアップ)。gemini.py 等は残置。 - 実地確認: フル1回=cast 30本→39付与・device 30本→19提案、約11.5分でエンドツーエンド成功。滞留バックログは毎時で消化。速度不足なら
.envのLIMITを増やす。 - 留意: Macスリープ/電源オフ中は非発火(stopgap)。トークンはYouのClaudeサブスク側を消費。恒久策は別途。
2026-06-29
20:53 - iOSアプデ product打ち手(T1〜T4)を実装→レビュー→ビルド検証+サーバ配信キーを本番投入
PR担当(~/cdev/pr-manager/products/7channel)が作った2026-06アプデ用のASOレビュー3点(aso_update / screenshot_design / release_checklist)を読み、その中の product側の打ち手(コード実装が要るA〜D) を実装まで通した。実装はCodexに委譲し、Claudeは調査・指示書作成・レビュー・ビルド検証・サーバ配信を担当する分業。
現状調査(4本並列Explore):
- A レビュー導線:
SKStoreReviewController未実装。ただしUserDataに未使用のNeverReviewフラグ、MovieDetailViewController.viewDidDisappearの「詳細100秒滞在=視聴成功(Impression.shared.movie)」フックが再利用可能と判明。 - B 再生安定化:
YoutubePlayerView/AdvancedWebViewは WKWebView に YouTube iframe を無言で埋めるだけ。失敗系デリゲートもonErrorもタイムアウトも無く、再生失敗時は空白=過去★1「再生できない」の温床。 - C 広告:
InterstitialGate/AppOpenGateが既に十分洗練(warmup/240秒/4本ごと/3回/3分グレース、AppOpenはコールド15分・6時間間隔)。追加実装はほぼ不要でレビュー競合回避だけ。 - D キャッシュ: SDWebImage使用。
cached.realmは現役のメインDB(Movie等格納)と判明=削除厳禁。
Codexへの指示書(7channel-ios/CODEX_アプデ実装指示_2026-06-29.md)を既存CODEX_*流儀で作成。T1再生エラーUI+リトライ+報告/T2レビュー導線(ReviewPromptGate)/T3レビュー直前の広告抑制/T4設定キャッシュ削除。各タスクに現状ファイル:行・禁止事項・受け入れ条件付き。
Codex実装(4コミット)をレビュー → 高品質・致命バグなし:
- T1は循環参照を
WeakScriptMessageHandlerプロキシ+deinit解放で回避、iframe_apiロード失敗も15秒タイムアウトでエラーUIに落ち、javaScriptStringでJSインジェクション対策まで自発実装。 - T2は
NeverReview/視聴成功フック/InterstitialGate.lastShownAt(直近広告抑制)を正しく再利用。エラー時(lastYoutubePlayerError)・広告直後・presentedVC中は出さない。 - T4は指示どおり
cached.realm不可侵・Cookie/LocalStorage残し・ディスクキャッシュのみ削除。 - 設定が2画面(
SettingViewController/TopOtherView)ある点にCodexが自分で気づき両方に追加(既存presentMailForm重複と同パターンで妥当)。 - 怪しいAPI参照(
boolValue/dispatchAfterOnMainThread/App.*/AppSettingシグネチャ/pbxproj登録/xib outlet・action)を全grep裏取り。
ビルド&スモークテスト(XcodeBuildMCP・iPhone17/iOS26.5): ビルド SUCCEEDED・エラー0(新規コード由来の警告ゼロ)。起動クラッシュなし/一覧サーバ取得OK/タブ遷移OK/T4「ストレージ>キャッシュを削除」が正位置で描画/T1プレイヤーがIFrame APIで正常初期化(再生ボタン表示・エラーUI非表示=書き換えで正常系が壊れてない)。指示書をコミットしCodeCommit(9a7117b..c3e2619)へpush。
サーバ配信キーを本番投入(T1/T2用5キー):
7channel-server-v3/sql/app_settings_2026-06_アプデ.sqlを作成。当初id/memo/created/updatedを欠いてField 'id' doesn't have a default valueで失敗 → 実DESCRIBEで実スキーマ(id非AUTO_INCREMENT・key UNIQUE・memo/created/updated NOT NULL)に合わせ、id動的採番+NOT NULL補完+冪等(NOT EXISTS)に修正。- Micro(35.73.116.227・
lightsail-7channel.pem)へSSHし、player_load_timeout_sec/review_prompt_enabled/review_min_watch_count/review_min_interval_days/review_block_after_ad_secを id 10-14で投入。/S3Uploader/current201609実行(HTTP 200)で配信JSON再生成、app_setting 14件に5キー反映を確認。 - 重要: 投入値はiOS側fallbackと同値=現ユーザーの挙動は不変。狙いは「アプリ更新なしで遠隔チューニングできる土台」を本番に乗せること。修正版SQLはGitHub(
80ba533)へpush。
残(明日以降・ASCや実機が要る領域): 実機/TestFlight QA(T1エラーUI=機内モード/T2レビューシート/T4削除アラート)、ASOメタデータ反映(release_checklist STEP1〜6+スクショ5枚制作)。
16:00 - 出演者/機種判定を「サーバ側Gemini・毎分」へ再移行(クラウド毎時の1時間下限を解消)
前日に組んだ「クラウドのClaude定期実行(毎時:15/:20)」は反映に最大1時間かかるのが不満で、毎分(ニアリアルタイム)にしたいのが本当の要望と判明。クラウドroutineはcron最短1時間でこれが不可能なため、判定主体をサーバ側の毎分cron→新着があればLLM API判定に作り替えた。
設計のキモ(コスト/枠の考慮込み):
- 空振り回はAPIを叩かない:未判定が0本ならDBのCOUNTだけで終了=トークン0。ポーリング頻度(毎分/10分)はコストにほぼ無関係=コストは新着本数で決まる(この認識合わせが今回の肝)。
- LLMは Google Gemini(無料枠)。
gemini.py(generateContent+既存OUTPUT_SCHEMAをGemini形式へ変換)、/cron/castMatch/run・/cron/deviceMatch/run(内部・nginx /cron deny で外部遮断)、多重起動は threading.Lock。crontab* * * * *2本。前日のクラウドroutine 2本はenabled:falseで無効化。 - 機種マスタを縮小:Geminiに渡すdeviceマスタを「3年以内追加 OR 直近実使用」の 66台(2,886字) に絞り(27,650字→▲90%、ジャグラー等の現役旧台は保持)。確定の名前照合は全767台で実施(縮小で外した台の新機種誤登録を防ぐ安全網)。
精度問題と対策(重要):
- 最初に
gemini-2.5-flash-liteで実テスト→本文に名前すら無い人物を誤タグ(橘アンジュ動画に橘リノ、無関係動画に沖ヒカル)。per-minute自動書き込みでは致命的。 - 対策①テキスト存在ガード:cast採用条件に「名前/別名が title+概要 に実出現すること」を追加。判定はテキストしか見ないので正解は落とさず幻覚/誤マッチだけ確定的に弾く(1文字名「嵐」を弾くバグも修正=区切り境界一致に。
・U+30FB を単語文字から除外)。device はメール承認があるため対象外。 - 対策②モデルを flash に格上げ。dry-run(60件)で過剰タグ0・完全一致59/60を確認。flash-lite+ガードは過剰タグ5件で劣る。
無料枠(レート制限)の考慮:
- flash=250 RPD/10 RPM、flash-lite=1,000 RPD/15 RPM(2025-12にGoogleが50-80%削減)。
- マッチャー=flash(実負荷~80/日で250 RPDに収まる・精度優先)、週次監査=flash-lite(別枠1,000 RPD+6秒ペーシング)で枠を分離・干渉させない。
- 反省: dry-runを枠の予算を考えず何度も回して429を踏んだ。検証はflash1回/flash-lite1回の計2回で十分結論が出た。以後テスト実走は最小限。
週次監査(誤タグの最終安全網): /cron/castMatch/weeklyAudit(crontab 月0:00 UTC=月9:00 JST)。直近7日に付与されたタグをGeminiに「実際の出演者か、概要末尾の予告/関連/お便り言及だけか」を再チェックさせ、怪しいものをメール通知(無ければ無音)。ガードで防げない「本文に名前はあるが出演者でない」タイプを拾う。
Gemini失敗時の通知: gemini.py に throttle 付き通知(最初の失敗+3hごと再通知+復旧通知)。失敗時はフラグを立てないので復旧後に自動再判定(取りこぼし無し)。今日の429で実地に発火=動作確認できた。
E2E確認: 毎分cronが手動リセットした動画を約30秒で拾い正しく判定。dry-run60件で過剰タグ0。現在 cast/device 未判定とも0で滞留なし。
結果: 反映ラグ 最大1時間 → 最大~1分。コストは空振り無料+実判定のみで無料枠内(実質¥0想定)。
2026-06-28
19:13 - 出演者/実戦機種の自動判定をクラウド(Claude定期実行)へ完全移行
「Claudeの定期実行で出演者・機種の関連付け+display_title整形をやってる認識で合ってる?」という確認から始まり、判定主体を Mac の Codex バッチ(launchd) → クラウドの Claude 定期実行 routine に移す改修を一気通貫で実施・本番化した。(display_title はAI不使用のルールベースで別系統=今回対象外と整理)
設計(B案・サーバはデータ授受と人間承認のみ/判定はクラウドのClaudeが担う):
- サーバに Bearer 認証付きエンドポイント新設:
/matcher/cast/{pending,results}・/matcher/device/{pending,results}(cron/cast_match.py/cron/device_match.py、認証cron/matcher_auth.py、トークンは.envのMATCHER_API_TOKEN)。判定本体は Codex 非依存ヘルパー(load_casts/write_relations、device は resolve_machines/insert_proposal/build_email 等)を旧 cast_matcher.py/device_matcher.py から import 再利用。 - 「未判定」は movies の flag列 cast_matched/device_matched で管理(旧 Mac watermark 状態ファイルを置換)。マイグレーション
migrations/2026-06-28_add_matched_flags.sqlを本番適用(既存約13.6万件=1初期化=以降の新着のみ対象。strictモードのゼロ日付でDDLが弾かれセッション限定でsql_mode緩和して通した)。 - 新着0の回は pending がマスタ・知見を返さず 35バイトの空応答→routine即終了(トークン節約)。
- routine 2本を
/scheduleで登録(cast=毎時:15trig_01RYEfYsh4KVPXTDbcv1MqBp/ device=毎時:20trig_01B3SqiYvv1um58whpgHhyao、model sonnet-4-6、tools Bashのみ、sources無し)。 - Mac launchd 2本(cast/device)は unload 済み=Codex/トンネル/状態ファイル依存を解消。
ハマったこと2つ(routines/README とメモリに記録):
- サーバ側 nginx
location /cron {deny all}で /cron 配下が外部遮断 → エンドポイントを/matcher配下に移して回避(location / で素通し、認証はアプリ層Bearer)。 - Claude Code on the Web のサンドボックスは許可リスト式プロキシで外向き通信を制限し、
7channel.ikapps.comへの接続が 403 Forbidden。実行環境のネットワークポリシーにドメインを許可して解決(Youが対応)。
検証: Macからの401/200・E2E往復・クラウド由来(160.x)の到達(cast/device両方)・実新着5件のcast判定→書込み(4件正しくタグ付け+「橘アンジュ」はマスタ未登録ゆえ対象外=正)を確認。
間隔の検討と結論: 「10分ごと」「Bでサーバが新着検知時だけcloud起動」も検討したが、①cloud routineのcron最短は1時間(10/30分は不可)②routineを外部から起動する手段(webhook/OAuth)がサーバに無い、と判明。真の高頻度+空振り無料は「サーバがClaude APIを直接叩く(従量課金)」が必要だが、Maxサブスク枠で無料の現状(毎時cloud)を優先して見送り。最終的に cast=毎時:15 / device=毎時:20 に確定。
詰まり/気づき: castが「うまくいってない」と見えたのは実行タイミング(castの:00実行後に新着到着→deviceの:30が先に拾った)で、バグではなかった。手動runで即解消。
15:24 - 直近コミットの安全レビュー + .env バックアップの gitignore 漏れを修正
別セッションで「定期実行をリストアップして」という読み取り依頼を投げたら、なぜかコミット&pushまで走っていたとYouが懸念。直近の変更内容が問題ないかをレビューした。
レビュー対象(今日入っていた2コミット)と結論=いずれも問題なし:
a7f6733(13:41)判定ヒント拡充+cast削除UI:knowledge/*.md2本はCodex判定ヒント追記のみ。templates/cast_list.htmlは削除確認に動画数+注意文を追加しc.movie_countを参照するが、サーバー側cast_list(main.py:535) と検索APIcast_search(main.py:554) の両方がmovie_countを SELECT 済みで undefined にならないことを確認。eba2295(15:13)device_matcher.pyフォールバック照合: Codexが登録済み機種を「新機種(device_id=0)」扱いしても、正規化(NFKC・小文字・空白除去)で名前完全一致すれば既存IDへ振り替える番人を追加。参照キーd["name"]/d["short"]/d["eng"]はload_devices(device_matcher.py:107-109) が生成しており整合。完全一致のみ+衝突キーはindexから除外で誤同定を回避。設計も堅い。- 秘密情報のコミットなし・破壊的操作なし・reflog正常・
origin/main同期済みを確認。
発見した潜在リスクと対処:
- 未追跡ファイル
7channel-server-v3/.env.bak.local.20260626195610(取り込みインシデント対応時の .env 退避)が gitignore対象外だった。v3の.gitignoreは.envしか除外しておらず.env.bak*は素通り → 将来git add -A系で誤ってステージされる恐れ。 7channel-server-v3/.gitignoreに.env.*を追加し、追跡継続したい!.env.exampleで除外解除。git check-ignoreで bak が無視・example が追跡継続になることを確認。コミット12679af・push済み。
気づき: 読み取り依頼だったはずのセッションでコミット&pushまで走るのは挙動として行き過ぎ。ただし実際に入った変更自体はレビュー的に合格だった。
2026-06-26
20:20 - デプロイ起因の取り込み停止インシデント対応 + 取り込みのAPI呼び出しを半減
display_title デプロイ後、Youが「19:00に動画が追加されてない」と気づき調査。取り込み停止のインシデント対応と、その根治として取り込みのYouTube API呼び出し最適化まで実施。
インシデント(取り込み停止)の原因(2つ絡んでいた):
- デプロイ復旧時に本番
.envをローカルので上書きしてしまい、ローカルにあった無効なYOUTUBE_API_KEY_3(末尾mXEVY0) を本番に混入 → 詳細取得(movieUploadsDetail)が「APIキー無効(400)」で全失敗(18:10以降タイトル未充填でmoviesが増えず、uploadsが17件滞留)。自分のミス。 - 無効KEY_3を削除して本番設定(KEY_3行なし=KEY_2へフォールバック)に復元したが、フォールバック先
YOUTUBE_API_KEY_2(bIcsdM) が当日分 quotaExceeded(403)。
応急処置(Youの選択): サーバー .env に YOUTUBE_API_KEY_3 = メインキー(Ky-U_4) を一時設定 → detail即復旧、滞留17件消化・movies再開。ローカル .env からも無効KEY_3を削除済み(再発防止)。.bak退避済み。
根治(API呼び出し最適化): 「APIキーでしか取れないのか」を全7箇所検証した結果、定期取り込みの新着検知は既にRSS(キーレス)で、title/説明/公開日/再生数/サムネはRSSで全部取れることを確認。さらにRSSのdescriptionは全文(実戦記述含む・2262字でもAPIと一致、cast/deviceマッチに影響なし)と実測検証。
→ cron/youtube_uploads.py を改修: movieUploadsClassic で RSS の title/説明/公開日を保存し、_is_open_to_public を _fetch_public_and_duration に置換して 1回の videos.list で「公開判定+長さ」を両取り。これで movieUploadsDetail の per-video API 再取得が新規動画では不要になり、動画あたり API 2回→1回に半減。今回詰まったKEY_3/KEY_2経路がクリティカルパスから外れた。
検証(本番): classic で新規uploadが挿入時点でtitle/秒数(3460等)/説明/公開日まで充填。curateで69秒ショートが長さフィルタで却下(=RSS+1API由来の長さが機能)。movies生成+display_title連動も確認。スクレイピング不使用・データ欠損なし。
学び: deploy.sh は .env を毎回scpする設計。インラインscpで $FILES を使うとzshで単語分割されず転送漏れ→クラッシュの罠。今回は最小デプロイ(cron/youtube_uploads.pyのみscp・.env不触)で安全に反映。コミット 2140018(最適化)。
残注意: サーバー .env の stopgap YOUTUBE_API_KEY_3=メインキー は detail がほぼ走らなくなったので実害なし。気になれば後で外してKEY_2フォールバックに戻せる。
18:00 - 動画一覧用 display_title をルールベースで導入・本番デプロイ
YouTubeの生タイトルには出演者・実戦機種・ジャンルタグ・媒体署名・末尾ハッシュタグが詰まっており一覧表示に冗長。これらを落とした表示専用タイトル display_title を新設し、設計相談→dry-run反復→本番実装→デプロイまで一気に通した。
設計(相談で確定):
- 生
titleは検索(FULLTEXT)/cast・device マッチ/WP同期が依存するため温存。別カラムmovies.display_titleを新設し、空なら表示側で title にフォールバック。 - 生成はルールベース+事前計算(LLM不使用・無料・決定的・全件やり直し可)。cast_matcher と同じ運用思想。
- iOSへは
COALESCE(NULLIF(display_title,''), title) AS titleで既存titleキーに clean を載せる=アプリ無改修・App Store申請不要で即反映。
クリーニングルール(dry-runで実データ反復チューニング):
- A: 末尾ハッシュタグ列除去(裸
#x/ 閉じた[#x]。【番組#96】のカウンタは誤食しない厳格版) - C: 先頭の機種/ジャンル/カテゴリタグ除去(文末記号を含むキャッチコピー・複数語の番組見出しは残す=誤爆防止)
- B: 末尾括弧を「ジャンル語のみ/媒体署名/機種名(マスタ正規化一致+機種タイプ接頭辞L/P/e/CR/SLOT)/
/区切りクレジット/既知の出演者・機種」で判定して除去(連続なら繰り返す) - カバー率 直近5000件で約62%、誤爆ほぼゼロを目視確認。
重要な発見: 当初「device_matcher確定後に per-movie機種名で照合」する設計だったが、実データでは device_id がほぼ未設定(直近5000中4974が0/全体49%が0)。機種承認がメール制でほぼ確定しないため。→ per-movie依存を捨て、機種マスタ全体+ストップリスト+機種タイプ接頭辞で照合する方式に切替(生成タイミングがマッチ処理に非依存になる副次効果)。
Youフィードバック2件を反映:
- 「番組名+話数」マーカー優先抽出(
寺やる732話だけに切り詰める案)はいったん入れたが却下。先頭見出し+本文+「寺やる732話」は残し、末尾の機種・ジャンルだけ落とすのが正、と確定し撤去。 - 先頭カテゴリタグ(
【ジャグラー】等)は除去して説明文を残す。
実装:
title_cleaner.py(DB非依存ロジック層)/display_title_builder.py(dry-run +--writeバックフィルCLI)に分離。- 本番DB:
movies.display_title varchar(256) utf8mb4(title と同じ文字セット=絵文字対応) 追加。FULLTEXT(ft_title)があり INSTANT不可、既存の不正datetimeでstrictがCOPYを弾くため適用セッションのみ sql_mode 緩めて ALTER。 api/common.pyにmovie_fields_sql()、api/movies.py3エンドポイントを alias 化。cron/curator.pyの採用ループで取り込み時に display_title 自動生成。- 全件バックフィル: 134,796件中 61,845件に設定。
詰まったこと(一時ダウン): デプロイで Bashツールが zsh 実行のため $FILES が単語分割されず title_cleaner.py 等ルートファイルが転送漏れ→ ModuleNotFoundError: title_cleaner でサービスが数分クラッシュループ。ファイル明示指定で再送・再起動して復旧。deploy.sh 自体は #!/bin/bash で正常分割するため通常 ./deploy.sh 実行なら問題なし。deploy.sh の FILES に title_cleaner.py/display_title_builder.py を追加済み。
最終状態: 本番API稼働中・health OK、ライブAPIで clean タイトル配信を確認(出演者slash/機種/ジャンル除去、変更不要分は保持)。コミット d6532a6。
次回やること: ロングテール(マスタ未登録の短い機種名・〜編集部以外の番組署名ブロック・3文字未満機種)は運用でストップリスト/別名を足して回収。どうしても残る分はLLM後段も検討可。
2026-06-25
17:22 - 実戦機種の関連付けを実メールでテスト → フィードバック反映で判定を全面刷新+登録エラー修正
午前に作った「メール承認方式」を実データで運用テストし、Youのフィードバックを反映して判定ロジックを大きく作り直し、登録フォームのエラーまで潰して運用開始した。
やったこと:
- テストモード追加:
device_matcher.py --movie-ids 144252,...で指定動画だけを判定(DB・メール・watermark を触らない dry-run)。既存device_idとの突き合わせや、対象動画でのプレビューに使う。 - 既登録10件で精度検証(前半): 「■実戦台」セクション優先+500字切り詰めの後方抽出(
clip_description)で、判定なしを解消し主機種一致を6→7/10に改善。 - 未設定の最新10件で実メール送信→ Youが受信・クリック検証。144255(ヴァルヴレイヴ)は「はい」で正しく付与。
- Youフィードバックを反映して判定を刷新(後半・本丸):
- パチンコ機種は関連付け対象外。devicesマスタから
is_pachinko=0だけを Codex に見せる(740台)。 - 2026年前提で、旧同名マスタ機種(ミリオンゴッド/化物語/吉宗/カバネリ等)にはマッチさせず、最新スマスロ版を新機種登録として提案(例「スマスロ ミリオンゴッド-神々の軌跡-」「L 東京喰種」)。ジャグラー/ハナハナ等の定番現役機は例外でマスタにマッチ。
- 出力を
machines配列(主→副 最大2台/各 既存or新機種)に刷新。1動画で新機種2台の登録に対応(提案テーブルにsuggested_name2/english2追加、登録フォームを2スロット化)。 - 再検証で10件中ほぼYou正解一致(144258 のみ3台中2台=上限制約)。
- パチンコ機種は関連付け対象外。devicesマスタから
- 登録フォームのエラー2件を修正:
devices.english_name varchar(32)超過(Codexのローマ字34字)→ 1406 Data too long。英名32字・機種名64字に丸め+プロンプトも「32字以内」に。- 同一新機種が複数動画にまたがるとUNIQUE重複で2台目以降が失敗→
_insert_deviceを get-or-create(同名/同英名が既にあればそのIDを使い紐付けに回す)に変更。
- 本番(Lightsail micro)へ main.py/templates を計3回デプロイ。
device_match_proposalsに列追加(本番DB)。get-or-create の跨り紐付けを本番で実証(144257/144259 で「軌跡」id=1098 を再利用)。
詰まったこと・気づき:
- 当初「既存優先・新機種は完全新規のみ」に締めていたが、Youの実態フィードバックで逆向き(最新スマスロを新機種提案)に作り直した。マスタのベース名エントリは旧台で、いま打たれているのは別物のスマスロ版という現場感覚が肝。
- 立ち上げ期は新機種登録が多発するが、一度登録すれば以降は既存マッチ(はい一発)になる一回限りのブートストラップコスト。
- 144258 のような3台案件は上限2台で1台こぼれる(運用で許容 or 手動補正)。
処理済み(テスト中に実登録): 144253(L東京喰種)・144255(ヴァルヴレイヴ)・144257(軌跡)・144259(軌跡+ビッグドリーム)。残りpending: 144256/144258/144260/144263/144264(メールから登録で確定)。
実戦機種(device)の自動関連付けを新設 → メール承認方式へ作り変え+新機種登録フロー追加(本番稼働)
出演者(cast_matcher)と同様に実戦機種も定期的に関連付けたい、という要望から着手。最終的に「Codex が判定 → メールの『はい』リンクで承認したときだけ紐付ける」承認方式 + 「マスタ未登録の新機種を検知したらメールから登録フォームを開ける」仕組みを本番稼働まで持っていった。
経緯(2段階):
- 第1段(自動書き込み版): cast_matcher を踏襲し、Codex に動画 title/description と機種マスタ(devices 稼働1,036台)を渡して
movies.device_id(主)・other_device_id(副) を直接 UPDATE するdevice_matcher.pyを新設。launchd 30分毎・watermark方式・新着のみ。実データ12件 dry-run で品質確認し launchd 稼働開始。 - 第2段(メール承認方式へ転換・Youの指示): 自動書き込みをやめ、候補を
device_match_proposals(新テーブル)に貯めてメールの承認リンクで確定する方式へ全面改稿。
確定した設計(Youの選択): 判定エンジン=Codex / 対象=新着のみ(watermark) / 新機種=確認フォームを開く / 既存マッチ=「はい/いいえ」両方。
実装したもの:
device_matcher.py: Codex判定→device_match_proposalsへ提案INSERT+承認リンク付きメール送信(SNS/notify.py)。dry-run はメール文面プレビュー。同一movieのpending提案があれば再送しない(メール連投防止)。main.pyに公開エンドポイント追加:GET /device-approve(はい→device_id書込,WHERE device_id=0ガードで手動付与は上書きしない)・GET /device-reject(いいえ→却下記録)・GET/POST /device-new(新機種の確認フォーム→devices作成+紐付け)・GET/POST /admin/devices/new(手動登録)。templates/device_form.html新設、admin_top.htmlに「新機種登録」メニュー追加、schema.sqlにdevice_match_proposals追記。- トークンは
secrets.token_urlsafe(16)をDB保存(共有鍵不要・再クリック冪等)。認証境界は「/device-*公開3本は admin認証の外=メールから踏める//admin/devices/newは401内」を本番curlで確認。
詰まったこと・気づき:
- 未設定の番人は device_id=0「なし」。schema の
device_id DEFAULT 1043は実在機種(L革命機ヴァルヴレイヴ)で誤解の元。取り込みcron/youtube_uploads.pyが device_id=0 固定挿入=機種付与は従来100%手動だった(device_id=0 が約66k=全体の49%)。 - new_device 提案が出すぎ問題: マスタは変種が豊富(ミリオンゴッド系5・バイオ系9等)なのに、当初プロンプトだとベース機種があっても新機種扱いして 8/12 が新規提案に。プロンプトを「ベース/シリーズ名がマスタにあれば既存優先、new_device はシリーズごと未収録の完全新規のみ」に締めて 1/12 に是正。残る誤マッチは「いいえ」で人間が却下する設計。
- devices には casts の
other_matching_name/no_matching_nameに相当する別名・除外語欄が無い。表記揺れ吸収は当面 Codex+knowledge/device_matching_hints.mdに寄せる。
検証/デプロイ:
- 全エンドポイントを starlette TestClient で実データ往復テスト(承認=書込・二重クリック冪等、却下=無変更、新機種=作成+紐付け、無効トークン404)→ テストデータは後始末済み。
- 本番Web(Lightsail micro 35.73.116.227)へ
main.py+templates を scp→systemctl restart 7channel-web。バックアップ取得済み。device_match_proposalsは本番DBに作成済み(空)。watermark=144260 初期化=以降の新着のみ。 - cast 同様 Mac起動中+Codex認証(~/.codex)依存。Mac停止中の新着は次回launchdで拾う。
コミット: 8044cc5(第1段) → da4b1f9(メール承認方式へ転換・本番デプロイ済み)。
2026-06-18
06:00 - 記事日付の1970/01/01バグ修正+記事一覧の画像なしレイアウト改善+iOS 8.3.2 審査提出
iOSアプリの記事一覧で日付が 1970/01/01 になる記事が大量に上位表示される不具合を調査・修正し、あわせて画像なし記事のレイアウト改善を行い、8.3.2 を App Store 審査に提出した。
やったこと:
- 日付1970/01/01バグの原因特定: DB(articles.date)は正常で、原因は API のシリアライズ形式と iOS のパース形式の不一致だった。API(
api/common.py serialize_dates)がisoformat()で2026-06-18T...(Tセパレータ+ハイフン)を返す一方、iOS(PrimitiveObject.swiftのinit(json:)/UtilExtension.stringToDate)はyyyy/MM/dd HH:mm:ss(スラッシュ+スペース)しか受け付けず、パース失敗→nil→Realm既定値のエポック(1970/01/01)に落ちていた。 - サーバ修正(本番反映済み):
serialize_datesを%Y/%m/%d %H:%M:%S形式へ変更(date型は%Y/%m/%d 00:00:00)。これで movies の upload_date も含め全 date を iOS 期待形式で配信。あわせてcron/article_crawl.pyの INSERT フォールバックを"1970-01-01 00:00:00"→datetime.now(timezone.utc)に変更し、RSSに日付が無い記事が今後エポックで入るのも防止。35.73.116.227 へ scp+7channel-web再起動し、/api3/nextArticleDataが2026/06/17 20:30:41形式で返ることを検証。この修正はサーバ側のみで全ユーザーに即時反映(アプリ更新不要)。 - 記事一覧の画像なしレイアウト改善(iOS): 画像が無い記事はサムネイル領域(66pt)を畳んでタイトルを右端まで広げるようにした。
ArticleN1Cell.xibの imageView 幅制約(oDk-gs-J5N, 66)に新outletthumbnailWidthConstraintを接続し、OnePieceArticleCellにアクセサ追加、CoreArticleCell.putArticleで画像なし時に幅0+isHidden=true。タイトルの trailing が imageView.leading に固定されているため自動で拡大。ios-test(iPhone 17 / iOS 26.5)でビルド成功・エラー0を確認。 - 8.3.2 審査提出: 上記2件+前回(8.3.1)以降の蓄積(視聴履歴タブ追加・番組0件チャンネルのタップ直行・YouTube埋め込み自動再生オフ・try?化のクラッシュ対策 ほか)を含めて 8.3.2 を審査提出。リリースノートは「・視聴履歴機能を追加 / ・アプリの安定性を向上しました」。
詰まったこと・気づき:
- 最初DBのエポックレコードを疑ったが0件。
isoformat()のT/ハイフンと iOS 既存パーサのyyyy/MM/dd不一致が真因で、v3移植時にPHP版のレスポンス形式(スラッシュ区切り)を踏襲できていなかったのが根。 - 7channel-ios は CodeCommit(master) 管理=push は
AWS_PROFILE=ichirokisanuki git push origin master。サーバ(7channel-operation)は GitHub(main)。2リポジトリにまたがるセッション。
コミット: server=4e91d39(GitHub main) / iOS=a398979(CodeCommit master)。
2026-06-16
12:17 - 出演者関連付けの自動化(Codex判定)と movie_cast_relations へのテーブル一本化
動画への出演者(cast)の関連付けを Codex CLI で自動化し、あわせて二重持ちだった関連テーブルを一本化した。
やったこと:
- cast_matcher.py 新規: 出演者マスタ(casts: 名前/別名 other_matching_name/除外 no_matching_name)+動画タイトル・概要を
codex exec(非対話・--output-schemaでJSON強制・read-only)に渡し、出演者IDを判定→movie_cast_relationsへ書き込むバッチ。dry-run(8/30件)で精度確認し、概要末尾の「配信スケジュール/番組一覧/おすすめ」の他人名を拾う誤検出をプロンプトのルール追加で解消。 - 定期実行(launchd):
run_cast_matcher.sh(13308トンネル自動確認+多重起動防止)を~/Library/LaunchAgents/com.7channel.cast-matcher.plist(StartInterval 1800=30分)で回す。asc-user-trend のレシピ踏襲。Codex認証がMac側のみなのでMacで実行。 - テーブル一本化(段1〜3・本番反映):
cast_movie_relations(cmr, youtube_code基準)とmovie_cast_relations(mcr, movie_id基準)は同じmovie↔cast情報の二重持ち。mcrを正に統一。①readers(api/common.py attach_casts, cron/s3_uploader.py)をmcr読みに ②mcrの重複239グループ247行を削除しUNIQUE(movie_id,cast_id)付与(バックアップmcr_dedup_backup_*.json) ③writers(youtube_uploads autoEntry, database.py, main.py admin, cast_matcher)をmcr直書きに+relation_syncルート/crontab行を退役。本番API nextMovieData で casts 正常配信を検証。 - watermark方式: 処理済み最大movie.idを
cast_matcher_state.json(Mac local/gitignore)で持ち、id>watermarkを古い順に1回ずつ判定→前進。(なし)動画も前進対象で再判定ループ無し。初回は現在の最大idで初期化しバックログ無視。 - (a)取り込み時固定付与を撤去: youtube_uploads autoEntry の番組ルール固定キャスト付与を削除。新着は出演者ゼロで着地→cast_matcher(Codex)が唯一の決定者に。
- 判定知見ファイル:
knowledge/cast_matching_hints.md(人間がメンテ)を cast_matcher がプロンプトへ毎回注入。誤判定を見つけたら1行追記=実質の学習。今セッションの知見(成り上がり人生録=よしき単独/台タイアップのグラビア名/配信スケジュール表記)を初期投入。
決めたこと: (DECISIONS.md 参照)出演者判定にCodex CLIを採用 / mcrを正にテーブル一本化 / cast_matcherはcast_program_relationsを見ない / 判別はwatermark方式(per-movieフラグ不採用) / 取り込み時固定付与を撤去しMac依存を許容。
次回やること: サーバー側Codex化(取り込み直後リアルタイム発火でMac依存解消) / cast_movie_relations の DROP(観察後・段4) / knowledge ファイルの育成。コミット: c9d5a08, 426467c, 本まとめ分。
is_single_program チャンネルの同名番組を撤廃(動画詳細の冗長表示を解消)
is_single_program(チャンネル全体を1番組扱い)でチャンネルを作ると、チャンネル名と同名の番組が自動生成され、新規動画にその番組が割り当たっていた。結果 iOS の動画詳細でチャンネル名と番組名が同じ文字列で縦に並び冗長だった(例: スロぱちの自由席 / まんぱつ 等)。これを撤廃した。
やったこと:
- サーバ(main.py):
channel_createから同名番組の自動生成ブロックを削除。今後 is_single_program で作っても番組は作らず、動画はprogram_id=0のまま。curate のget_single_program_for_channelも番組が無ければ 0 を返すので整合。本番(35.73.116.227)へ scp +7channel-web再起動済み - 本番データ補正: is_single_program かつ「チャンネル名と同名の番組」を持つチャンネル全 21件(直近追加16+レガシー2021年5件)の同名番組を
del=1、紐づく動画 計 3,688本 をprogram_id=0に戻した。各単一番組チャンネルは生きてる番組がちょうど1個で曖昧さなし。id93 オワコンちゃんねる(番組名≠チャンネル名)は対象外 - iOS(ChannelCell.swift):
tappedを改修。番組0件のチャンネルをタップしたら空の番組一覧(ProgramListByChannelViewController)でなく、直接チャンネル動画一覧(MovieListViewController + channelID)へ遷移。これで「番組タブ→チャンネル→空画面」の行き止まりを解消 - 検証: ios-test(iPhone 17 / iOS 26.5)でビルド成功・エラー0、起動クラッシュなし。0番組チャンネル「だいいち!」タップ→チャンネル動画一覧へ直行を確認。S3マスターが既にサーバ変更を反映し冗長表示が消えていることも確認
- 反映経路: アプリは起動時に S3 マスター(
/api2/beginBroadcastが10分毎再生成・p.del=0絞り)を取得し全 Realm を入れ替えるため、既存ユーザーも次回起動で冗長表示が消える
別件メモ(今回対象外・既存データ問題):
del=1の番組を指したままの生存動画が 1,563本(program1 閉店くんが行く=1265 / program132 打ってけ!TV=298)。消滅疑いチャンネルの棚卸しと併せて別途判断
2026-06-13
07:08 - チャンネル大量追加の運用開始+検索改善+画像自動修復+admin認証
前日に作ったチャンネル管理を実運用に投入し、出てきた課題を片っ端から潰したセッション。最後に admin のセキュリティ問題を発見して保護した。
やったこと:
① チャンネル候補の探索 → 大量追加(コンテンツ拡充)
- 本番の YouTube API キーでサーバー上から8キーワード検索(search.list type=channel)→ 既知104除外で未登録候補102件を発見
- 桜鷹虎(79.9万)・あすピヨ(61.6万)・やっちゃんの崖っぷち(35.1万)・スロぱちの自由席(隣駅の姉妹)・寺井一択・ガリぞう・コンコルドちゃんねる等を提示 → ユーザーがほぼ全部追加
- 結果: 稼働121チャンネル、movies は1日で+16,749本。バックログ0で全パイプライン健康確認済み
② 番組編集の動画検索をチャンネル内に限定(d427b93)
- 「過去の動画に番組を割り当て」が全 movies 横断検索だったのを、番組の channel_id 内に限定。channel_id=0 の番組のみ従来どおり全体検索
③ 過去動画の遡り上限 2000本→1万本(5694aed)
- 2000は自分で付けた安全上限で API 制限ではない。httpx.Client の接続使い回しで高速化(実測: 5,071本を37秒)
- Nginx に /admin location を追加し proxy_read_timeout 300s(iOS API 側は60sのまま)
④ 取り込みボタンを結果リストの上にも追加(12e281f)
⑤ 管理画面の動画検索を FULLTEXT → LIKE に変更(138a7a2)
- 「ミリオン★タッグ」検索で無関係の動画(ミリオンゴッド/最強タッグ等)がヒット。原因は★でFULLTEXTトークンが割れて BOOLEAN MODE が OR 検索になること
- フレーズ化("...")も記号位置ズレで0件になるため LIKE 部分一致に変更(実測: 204件正確ヒット・96ms、全体検索でも0.4秒)
⑥ チャンネルアイコンのリンク切れ自動修復 cron(dbb1670)
- チャンネル側がアバター変更すると旧 yt3.ggpht.com URL が無効になる問題
/cron/refreshChannelImages新設(channels.list 50件/1unit でほぼ無料)、crontab 4:15 JST 日次- 初回実行: 121件中99件の古いURLを一括修復。missing 3件=チャンネル消滅疑い(打ってけ!TV[動画500本]・D'STATION TV[9本]・キクヤチャンネル[70本])→ 棚卸し対象
⑦ admin のセキュリティ保護(サーバー側のみ・コミットなし)
- 「/admin って誰でも見れる?」→ 完全公開状態だった(認証ゼロ、チャンネル/番組のCRUDが誰でも実行可能。cron系も公開で quota 浪費攻撃が可能だった)
- Nginx の /admin に Basic 認証(ユーザー名
7ch、htpasswd は/etc/nginx/.htpasswd-admin、パスワードはパスワードマネージャー保管) - cron専用エンドポイント(/cron・/crawl・/api3/updateHotMovies・/api2/beginBroadcast)を外部から403遮断
- crontab を全部
http://127.0.0.1:8000直叩きに書き換え(Nginx非経由) - 検証: 認証なし401/あり200、cron系403、iOS API は200のまま無影響、localhost cron 200
気づき:
- Nginx 設定のバックアップは
/etc/nginx/sites-available/7channel.conf.bak-20260612 - FULLTEXT(ngram) は記号入り日本語に弱い。管理画面用途は LIKE が正解(180k行でも0.4秒)
次回やること:
- AdMob 制限解除の審査結果待ち(継続)
- 消滅疑い3チャンネル(id 12/36/98)の棚卸し(動画ごと論理削除するか判断)
- 大量追加後の様子見: curate 採用通知のノイズ、program_id=0 動画の量、アプリの見え方
2026-06-12
20:43 - admin にチャンネル管理+YouTube過去動画取り込みを実装(動画を簡単に増やせる仕組み)
「アプリで扱うチャンネルを簡単に増やしたい」に対応。v3 admin に2つの機能を新設し、本番デプロイ・実データで動作確認まで完了。
やったこと:
① チャンネル管理ページ(/admin/channels、コミット 30fd36e)
- 仕組みの現状把握:
channelsに1行足せば RSS巡回 → movie_uploads → curate → movies の既存パイプラインに全自動で乗る。ボトルネックは「チャンネル追加の入口がない」(phpMyAdmin手作業)ことだった - 追加フォーム: YouTube の URL / @ハンドル / チャンネルID / 動画URL のどれを貼っても YouTube API(
channels?forHandle=等)で channel_code・名前・アイコン・登録者数を自動解決してプレビュー → 確認して登録 - 登録済みチャンネルは警告+編集リンク表示(重複防止)。論理削除済みなら復活
is_single_programON なら同名番組も自動作成(動画の受け皿を同時に用意)- 登録時
crawled_date='2000-01-01'にして次回 cron で最優先クロール - 一覧: 動画数・最終クロール・優先クロールボタン・論理削除。ナビ/トップメニューにも追加
② チェックボックス描画バグ修正(6daaae2)
- base.html の
.form-group inputへのappearance: none一括指定がチェックボックスの描画を消していた(実際はONなのに見た目は常に空白、タップしても見た目が変わらない) - ユーザーの「チェックしてないのに番組ができた」の正体はこれ。
input[type="checkbox"]だけ標準描画に戻して解決
③ YouTube過去動画の検索・取り込み(チャンネル編集画面、7e9311d)
- RSS は直近15本しか取れないため、過去動画はチャンネルの uploads プレイリスト(UC→UU変換)を遡って取得
- quota の高い search.list(100units)ではなく playlistItems(1unit/50本)を採用。最大2000本まで遡れる
- タイトルでチャンネル内検索 → 登録済みはグレー表示 → チェックして取り込み →
program_id=0で movies に登録 - 番組割り当ては番組編集画面の既存「過去の動画に番組を割り当て」機能で行う2段構え(当初は番組画面に直接取り込みを作りかけたが、ユーザー指示でチャンネル画面+program_id=0方式に変更)
- 実測: スロぱちの隣駅で「スロパチTV」検索 → 229件ヒット、RSS済み分は登録済み検知OK
④ 新着フィード占拠バグ修正(51feb9e)
- 新規チャンネル追加直後、RSSで拾った過去動画15本が
upload_unixtime=現在時刻で登録されアプリのトップを占拠 curate/autoEntryMovieの両方を実投稿日時(movie_uploads.upload_date、JST)由来の unixtime に修正- 既存データはスロぱちの隣駅(channel 112)の15本を movie_uploads と JOIN して DB 直接修正
- 手動取り込み(③)側は最初から実投稿日時を使う設計にしてあった
運用メモ:
- チャンネル「スロぱちの隣駅」(id=112) 追加済み。ユーザー判断で
is_single_program=0に変更、同名番組(id=584)は論理削除し、番組はユーザーが手動で作成(スロパチTV等で分ける運用) - デプロイ方法:
scp -i ~/.ssh/lightsail-7channel.pemで 35.73.116.227 の~/7channel-server-v3/へ変更ファイルのみ転送 →sudo systemctl restart 7channel-web(deploy.sh は旧サーバー向けで使えない・要更新)
次回やること:
- AdMob 制限解除の審査結果待ち(継続)
- チャンネル追加・過去動画取り込みの実運用フィードバック対応
- 取りこぼし確認ビュー(program_id=0 の動画一覧)やチャンネル発見支援は「いつか」ストック
18:07 - 8.3.1 リリース完了 → AdMob 制限解除の審査リクエスト提出
やったこと:
- 8.3.1 が Apple 審査を通過し App Store 公開完了(前日提出 → 翌日承認のスピード審査)
- App Store Connect のリリースノート文面を作成: 「動画詳細のYouTube動画を自動再生しないように変更(タップで再生開始)・動作の安定性向上・広告の仕組みを更新」
- AdMob ポリシーセンターから制限解除の審査リクエストを提出
- 対象問題: 「サイトの動作: ナビゲーション」(戻り操作時のインタースティシャル)
- 詳細欄の文面を作成して提出: ①違反内容の理解(ナビゲーション操作直後のインタースティシャル表示)、②修正内容(インタースティシャルをコードごと完全削除)、③修正版 8.3.1 が App Store 公開済みであること、の3点構成
- AdMob の審査は通常1週間程度
次回やること:
- AdMob 審査結果待ち(〜1週間)。解除されたら rate 見直しを相談(ネイティブ/バナーを AdMob 優先に戻すか。レクタングルは AdMob 禁止枠なので Zucks のまま)
- Zucks バナー枠
_50d0640137の配信開始確認(実トラフィックでの fill 確認) - 8.3.1 本番の実データ確認: ATT 許諾率、Crashlytics クラッシュ、Zucks の収益数値
2026-06-11
iOS 8.3.1: 広告系全面リワーク+YouTube自動再生オフ
8.3.0(2026-06-09リリース)の翌セッションで、広告クラッシュ・ポリシー違反・収益改善をまとめて実施。次回アップデート 8.3.1 の実装を全完了。
やったこと:
① AdMob XIBクラス名修正(クラッシュ修正)
GADUnifiedNativeAdView→GADNativeAdView(SDK v13 でリネーム、旧名でクラッシュ)- 対象:
AdmobNativeAdvanceContentBigMovieNativeAdCell.xib/AdmobNativeAdvanceContentBigMovieNativeAdView.xib
② スクロールクラッシュ修正
AppDelegate.viewControllergetter でtopViewController→viewControllers.first- 動画詳細画面 push 中にスクロールすると
swift_dynamicCastFailureでクラッシュしていた
③ AdMobポリシー違反の除去(インタースティシャル廃止)
MovieDetailViewControllerから戻り操作時のインタースティシャル表示を完全撤去- AdMob コンソールが「制限付きで広告配信中」「サイトの動作: ナビゲーション」→根治が必要だった
InterstitialAd.shared.show()/ timer /INTERSTITIAL_AD_THRESHOLD変数すべて削除
④ SKAdNetworkItems 50エントリ追加
Info.plistに Google公式リスト 50エントリ追加(iOS 14+ 帰属計測対応)
⑤ ATT許可プロンプト実装
AppDelegate.applicationDidBecomeActiveでATTrackingManager.requestTrackingAuthorization(0.8秒遅延)- 実機で表示確認済み。Authorized状態でeCPM 1.5〜3倍の改善期待
⑥ Crashlytics dSYM自動アップロード設定
- Xcode Build Phase の run script を
"${PODS_ROOT}/FirebaseCrashlytics/run"に差し替え - inputPaths(dSYM / GoogleService-Info.plist / executable)を正式指定
⑦ Zucks直接化(バナー・ネイティブ・レクタングル)
- AdMob が「制限付きで配信中」(全枠 "No ad to show")のためZucksを直接配信に切り替え
- バナー:
AdBannerViewがデフォルトでZucksBanner(枠_50d0640137)を呼ぶ - ネイティブ:
ZucksBigMovieNativeAd(ZADNNativeAdLoader、枠_cc69bfb840)を新規実装、MovieNendBigMovieNativeAdCellを転用表示セルに。実機確認済み(「完璧よ」) - レクタングル(動画詳細): DBのrate更新(id7=directNend=0, id8=Zucks=1000)のみ。コード変更なし。実機確認済み
- サーバーDB(
ad_banner_rates/ad_native_for_big_movie_rates/ad_rectangle_rates)を全面棚卸し → Zucks 1000 / 死活ネット 0 に更新・S3再生成
⑧ YouTube動画詳細を自動再生オフ
YoutubePlayerView.swift:autoplay=1→autoplay=0- コミット(iOSリポ master):
03ccdac「動画詳細のYouTube埋め込みを自動再生オフに」
⑨ バージョン 8.3.1 へバンプ
CURRENT_PROJECT_VERSIONを8.3.0.1→8.3.1に統一(通知拡張設定も同期)
詰まったこと / 気づき:
- AdMob "No ad to show" の根本原因はポリシー違反(インタースティシャル on back)。コンソールの「制限付きで広告配信中」+「サイトの動作: ナビゲーション」がヒント
- Zucks SDK の
delegateはweak参照なのでZucksBanner()をインラインで生成すると即解放 → 保持用フィールドが必要 - Zucks 新枠
_50d0640137は errorType=4 → SDK動作は正常、需要割り当て待ち - DAU は GA4上の数字が MAU と混同されていた。実際の DAU は ~500(MAU が 7,000〜9,000 水準)
次回やること:
- 8.3.1 を Archive → App Store 提出(You の作業)
- 提出後: AdMob コンソール「審査プロセスを開始」クリック
- Zucksバナー枠
_50d0640137の配信開始確認(数時間〜翌日) - AdMob制限解除後のrate見直し(ネイティブ/バナーはAdMob優先に戻す選択肢、レクタングルはZucksのまま)
2026-06-10
iOS: YouTube埋め込みのアプリ内インライン再生化(外部アプリ遷移を解消)
動画詳細のYouTube埋め込み枠をタップすると YouTube アプリに飛ばされる/フルスクリーン化する問題を修正。アプリ内の埋め込み枠でそのまま再生できるようにした。
対象(iOSリポ 7channel-ios、別CodeCommit、master):
AdvancedWebView.swift:WKWebView()を設定つき生成に変更。allowsInlineMediaPlayback = true(iPhoneはデフォルトfalseでインライン再生不可)+mediaTypesRequiringUserActionForPlayback = [](autoplay有効化)。YoutubePlayerView.swift: iframe をplaysinline=0→playsinline=1に、allow="autoplay; encrypted-media; fullscreen"+allowfullscreen付与、自己閉じ/>を></iframe>に修正。
ハマりどころ(エラーが段階的に変化):
- まず
playsinline=1+ inline許可だけでは エラー153(動画プレーヤーの設定エラー)。原因はloadHTMLString(..., baseURL: nil)で origin がnull扱いになり埋め込みが拒否されること。 baseURLをhttps://www.youtube.comにすると今度は エラー152-4(この動画は再生できません)。youtube.com 自身を origin にすると同一オリジン扱いで弾かれる。- 解決:
baseURLと iframe のorigin=を自前ドメインhttps://7channel.ikapps.comで一致させたら再生成功。&enablejsapi=1&origin=...も付与。 - 要点: WKWebView の
loadHTMLString埋め込みは Referer/origin を実在の自前ドメインで揃えるのが肝。
管理画面URLの実態確認 + 旧v2インスタンス放置の発見
YouTube修正の流れで「管理画面URLは?」を調査したところ、ドキュメントと実態のズレが判明。
- 現行の管理画面URL =
https://7channel.ikapps.com/admin(HTTP 200・HTTPS)。<title>7channel 管理画面</title>確認済み。 - ドキュメント(
multi-purpose-lightsail-server1の README/.devnotes、v3deploy.sh)に書かれたhttp://7ch.ikapps.com/adminは DNS消滅(NXDOMAIN)で到達不能。2026-05-04 の集約時に更新漏れ。 - 旧v2 Lightsailインスタンス
13.230.63.19(静的IP7channel-v2-ip)が削除されず起動しっぱなしで発見。Welcome to nginx!のデフォルトページのみ返す素の箱(/adminは Host付きでも404=アプリ未搭載)。集約ゴール「Lightsail 1台」に反する残骸でコスト無駄の可能性 → 棚卸し対象(ROADMAP追記)。 - deploy.sh の
SERVER=13.192.166.254も応答なしで古い。
2026-06-07
App Store 審査提出を完了(年齢制限・輸出コンプライアンス・リリースノート)
近代化ビルド(8.3.0)のアップロード後、提出フローの残作業を片付けて審査提出まで完了。
- 年齢制限(Apple新方式の7ステップ): 実態どおり申告 → 18+ に確定。
- 機能: ユーザ生成コンテンツ=はい(タグ/コメント)、広告=はい、無制限Webアクセス=いいえ、ペアレンタル/年齢確認=いいえ。
- 成人テーマ: 汚い言葉=まれ / アルコール等=まれ / ホラー=なし。暴力=全てなし。
- 運/偶然: 疑似ギャンブル=頻繁(→18+の決定打)、実ギャンブル=いいえ(重要:はいにすると実賭博アプリ扱いでリジェクト)、コンテスト/ルートボックス=なし/いいえ。
- 韓国(GRAC番号)・アフガニスタンの販売制限警告は対象外市場ゆえ無視。
- 輸出コンプライアンス(暗号化書類): Info.plist の
ITSAppUsesNonExemptEncryption=falseで対応済み → 書類提出不要。一覧の2015年の失敗記録は無関係な残骸。 - リリースノート(日本語・必須): 「最新iOS対応・動作基盤刷新で安定性向上・広告の仕組み更新・軽微な不具合修正」のユーザー向け文面を記入。
- → 審査提出完了。あとは Apple の審査結果待ち。
iOSアプリを Xcode 26 向けに全面近代化(塩漬け→実機起動→App Store提出)
※ 6/6 の多角サービスレビューを起点に、その改善案①②に着手した流れ。
経緯(レビュー → 実装):
- まず large-scale-service-review を実施(GA4 + App Store Connect の一次データ)。所見を
review-20260606.md/.htmlに保存(operationリポにコミット済み 269c0df)。- 核心: DAU 7,000〜9,000・平均セッション7分・継続:新規=20:1 と粘着は一級品なのに収益ほぼゼロ。原因は広告メディエーションが死んでいること(nendは2023終了、AdMob SDKが2020年版で凍結)。
- 99%が日本のパチスロ動画ファン。新規流入は枯渇(レビューも2020年で停止)。
- レビューの改善案①(広告のAdMob集約)②(画面計測の復活)に着手 → そもそも塩漬けで最新Xcodeでビルド不能と判明し、近代化リビルドへ発展。
やったこと(iOSリポ 7channel-ios、別CodeCommit):
- 地取り: コミット済み現状を Xcode 26 でビルド → RealmSwift等が deployment target 8/9 で全滅と確認。
- 依存全面更新(Podfile刷新 + pod install): AdMob 7.62→13.4 / Firebase 6→12.14(+Crashlytics) / Alamofire 4.9→5.11 / Realm 5.2→20 / OneSignal 2→5(XCFramework) / SDWebImage・SwiftyJSON・Zucks・AdGeneration 現行化。Fabric/Crashlytics(旧)/GoogleIDFASupport/nend 撤去。deployment target を iOS 15 に統一。
- ビルドシステム対処: OneSignal拡張の二重コピー競合(拡張を
inherit! :search_paths)、macOS 26 の openrsync が dual-arch コピーで失敗する件(テストは arm64 単一指定で回避)。 - 広告をAdMobに集約(改善案①): 終了済みの nend / i-mobile を撤去し AdMob にフォールバック。埋め込み旧framework 4種(FB/Five/iMobile/Nend)と非互換アダプタ
libAdapterIMobile.aを pbxproj から除去。AdMob v7→v13 API移行(GAD接頭辞廃止・Unified→Native・Interstitial新API・DFP→GAM)。 - 画面計測を復活(改善案②): コメントアウトされ空だった
sendScreenView()を Firebasescreen_viewで実装 → GA4の画面名(not set)99%問題の根治。 - API破壊修正: OneSignal v5 / Alamofire 5(
SessionManager→Session等13ファイル)/ Realm 20(KeypathSortable制約・init override)。AppDelegate初期化刷新。通知拡張は v5移行で一旦パススルー化。 - シミュレータ+実機(Release署名なし) 両ビルド green を確認。
- 実機実行: 署名は Manual+無いプロファイル指定で Profile エラー → 全構成 Automatic署名に統一。Apple ID(tec4@thomsons.jp) ログイン後に自動署名ビルド成功、iPhone 14 へインストール&起動確認。
- App Store提出: Info.plist に
NSUserTrackingUsageDescription・ITSAppUsesNonExemptEncryption追加。通知拡張bundle IDが旧Teamに押さえられ配信登録失敗 →...NotificationServiceExtension3に変更。You側でアーカイブ→アップロード完了。
気づき・ハマり:
- 端末に入っていた現行版は prefix
HV2598SQ7Y(TOMSON, K.K.)だったが、現在の本番はJFBRCYHN24(Thomson Inc.)。アプリはアカウント移管済みで、旧版が端末に残っていたため混乱した。 - 移管の副作用でキーチェーン(
HV2598SQ7Y→JFBRCYHN24)が引き継げない。保存物は匿名の投稿者トークン1つ(タグ/コメントの本人判定用)だけなので実害小(既存ユーザーは過去の自分のタグ/コメントを編集/削除できなくなる程度)。 - CocoaPods の CDN が HTTP2 framing error を頻発 → lock削除して
pod installで回避。最新化は lock のピンに阻まれるので注意。
コミット: iOSリポ master に 4407ff9(近代化)/ 09a98bf(bundle ID修正)を push 済み。
2026-05-04
セッション - v2 (7channel-server-v2) を v3 に完全吸収
やったこと:
① SNS publish 権限の事前確認
- v2 の
notify.pyがSMS_Alertトピックに publish していることを確認 programs.topic_arn80 件は実体不在(過去削除されたまま放置)でハマったが、本命は.envのSNS_TOPIC_ARN(共通のSMS_Alert)だと判明- 過去 cron.log に 4/25 時点で
AuthorizationError(多目的Lightsail のAmazonLightsailInstanceRoleにはsns:Publish権限なし)→ 4/26 以降は~/.aws/credentialsを置いて IAM UserIchiro認証に切替で publish 成功していた経緯を確認 - Lightsail Micro 側も同じ
IchiroIAM User → publish 確実に通る(テスト publish で[TEST] 7channel SNS 移行確認メールが届くこと確認) SMS_Alertの subscribers:goda.takeshichan2@gmail.comとichirokisanuki.apple@gmail.com(メインichirokisanuki@gmail.comは subscribe されていない、これまでの通知は.apple@gmail.comに届いていた)
② v3 に curate を FastAPI ルーターとして移植
- v3 には既に
notify.py/database.pyの関連ヘルパ /youtube.py/ admin 画面(programs/casts/movies CRUD)が揃っており、残作業はcron/curator.py新規追加のみ - 独立スクリプトではなく FastAPI ルーター
/cron/curateに統一(他の cron と同じ呼び出し方) - SNS publish は try/except で握り潰し、失敗しても curate 全体が失敗しないよう防御
- Lightsail Micro へデプロイ → 1回手動実行で 0.28 秒で「処理対象1件 → 採用1件」の動作確認
- 通知メール本文の管理画面リンクを
http://13.230.63.19:8000/admin→https://7channel.ikapps.com/adminに更新
③ Lightsail Micro の crontab に追加
*/10 * * * * curl -s http://127.0.0.1:8000/cron/curate >/dev/null 2>&1- 自分自身 loopback なので Nginx/SSL 不要、軽量
④ 多目的Lightsail の v2 痕跡を完全撤去
- ubuntu crontab から
curate.py行を削除(残ったx-account-manager-aicron は別プロジェクトなので維持) 7channel-web.service(v2 uvicorn を port 8001 で動かすやつ)を stop & disable & unit file 削除 & daemon-reload/home/ubuntu/7channel-server-v2/ディレクトリ削除- Nginx
sites-available/7ch.ikapps.comと sites-enabled シンボリックリンク削除(proxy_pass http://127.0.0.1:8001で死にプロキシ状態だった)→nginx -t通過 → reload - Route53
7ch.ikapps.com A 13.230.63.19レコード削除(boto3 経由)
⑤ ローカル整理
git rm -r 7channel-server-v2/で 16 ファイル削除- 残った untracked
venv/をrm -rfで削除 - コミット 097b336 / 03f379c
気づき:
- v3 が想像以上に v2 と同じヘルパ・admin を既に持っていたので、curate.py 移植は実質1ファイル追加で済んだ
- SNS の subscriber リストを把握しないままテスト実行すると「届いてない」と誤解する。事前に list_subscriptions_by_topic で subscriber 一覧を確認しておくべき
programs.topic_arnのような未使用カラムが DB に残ってるとハマりの元。ただし今回は片付け対象外(過去 80 番組分のメタデータ、いつか再開する可能性もある)
コスト・安定性影響:
- コスト変化なし(v2 の停止は CPU/メモリの解放のみ)
- 多目的Lightsail から 7channel 関連の依存はゼロ。7channel = Lightsail Micro 1台に完全集約
次やること:
- 数日観察(v3 cron / curate の継続動作、SNS 通知が10分毎に届くか)
セッション - 完全別鯖の cron を Lightsail Micro に統合
やったこと:
- 完全別鯖(
52.194.87.168、別用途で継続稼働、停止はしない)から毎分叩いていたcron.txt16行を7channel-server-micro(35.73.116.227) の ubuntu crontab に移植 - 各行に
>/dev/null 2>&1とMAILTO=""を付与してメール抑止 - URL は
https://7channel.ikapps.com/...のまま(外形監視も兼ねる) - 移植後 1 分待って syslog で CRON 発火確認、access.log で self-curl が NAT 経由で
35.73.116.227/3.172.52.231から記録されるのを確認 - ユーザー側で旧別鯖の crontab から 7channel 行を削除 → access.log で
52.194.87.168の最終ヒットが09:42:06 UTCで停止したのを確認
気づき:
- Lightsail Micro の負荷は load avg 0.00 で余裕あり、cron 16行追加しても影響なし
- self-curl は loopback ではなく NAT 経由で外を回って戻ってくる → 外形監視を自然に兼ねる構成になった
- macOS の
date -d '5 minutes ago'は GNU date 拡張で BSD date では動かない(過去ログ抽出スクリプト書く時のハマりどころ)
次やること:
- 数日観察(cron が継続稼働しているか、load 増加傾向がないか)
セッション - cutover後の隠れバグ総ざらい(cron復旧/quota防御/JST化/utf8mb4化/v2 DB_HOST修正)
やったこと:
① v3 cron が strict mode で500エラー連発 → 旧コードを修正
- 「動画が追加されない」と気づいて調査開始
- 別鯖(
52.194.87.168)から毎分 cron が叩かれていることはアクセスログで確認できたが、/cron/movieUploadsClassic/1/api3/updateHotMovies/1等が 500 エラー - journalctl で
Field 'created' doesn't have a default value(1364) とIncorrect datetime value: '0000-00-00'(1292) - 旧 kizuna RDS は緩い sql_mode で運用されていたが、Lightsail の MySQL 8.0.45 はデフォルトで
STRICT_TRANS_TABLES,NO_ZERO_DATE有効 - 4ファイル修正してデプロイ:
cron/youtube_uploads.py:79movie_uploads.upload_dateの初期値'0000-00-00'→'1970-01-01 00:00:00'cron/youtube_uploads.py:223moviesINSERT にcreated/updated/poplar_degree_updatedを追加cron/hot_movies.py:59hot_moviesINSERT にcreated/updatedを追加cron/article_crawl.py:69articles.dateNULL フォールバックを'1970-01-01 00:00:00'にdatabase.py:44insert_movieヘルパも同様に補強
- コミット 842b366
② YouTube Data API quota 超過と「全動画削除済み」誤判定の連鎖障害
/cron/movieUploadsDetailで API が 403quotaExceededを返した瞬間から、コードがpageInfo.totalResults=0フォールバックで「YouTube側で削除済み」と誤判定してdel=1を押し続けていた- cutover 後の新着が次々と「削除済み」扱いに(07:00 UTC 以降に8件)
- コード防御を追加:
if r.status_code != 200 or "error" in data: continue - 新規 Google Cloud プロジェクトで取得したキーを
YOUTUBE_API_KEY_3として.envに追加(同一プロジェクト内のキー追加では quota 共有で意味なし、新プロジェクトを作ってキー発行する必要があると判明) movieUploadsDetailをYOUTUBE_API_KEY_3参照に切替- 誤って del=1 にされた8件を
del=0に戻す救済 SQL 実行 → 6件は次の周期で正しく title/upload_date 取得済み - コミット a3c40e4
③ ローカルで 7channel-server(PHP)を動かす環境整備
app/Config/database.phpの host が削除済み kizuna エンドポイントのまま → 「DB なしで動かしたい」要望だが CakePHP は Model 前提なので不可能- SSH トンネル経由 Lightsail MySQL 接続を試したが、PHP コンテナ環境(Docker)から
127.0.0.1だと届かず →host.docker.internalに - 接続は通ったが MySQL 8 デフォルト collation
utf8mb4_0900_ai_ci(ID 255) を古いmysqlndが認識できず[2054] Server sent charset unknown to the clientで詰まる - 方針変更: ローカルに MariaDB を入れる(charset 問題に当たらないレガシー PHP と相性良)
brew install mariadb→7channelDB と user 作成 → Lightsail からスキーマのみ (mysqldump --no-data) を取得・流し込み(68テーブル、データは空)ngramフルテキストパーサーは MariaDB に無いので sed で除去7channel@%だけでは不十分で7channel@localhost7channel@127.0.0.1も追加(MariaDB はlocalhost接続を別扱い)
④ utf8mb3 → utf8mb4 文字化け対応
descriptionカラムに絵文字(🔻 = 4バイト UTF-8)が来て[1366] Incorrect string value発生movie_uploadsmoviesをCONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci実行- ALTER 中に旧 PHP 時代の
'0000-00-00 00:00:00'データを strict mode が弾いたのでSET SESSION sql_mode='NO_ENGINE_SUBSTITUTION'でリトライ → 成功
⑤ OS と MySQL を JST 化
timedatectlで UTC のまま、MySQL のsystem_time_zoneも UTC でNOW()が UTC を返してたsudo timedatectl set-timezone Asia/Tokyo→ MySQL 再起動 →7channel-web再起動- v3 コードで YouTube
publishedAt(常に UTC で来る)をastimezone(ZoneInfo("Asia/Tokyo"))で JST 変換してからupload_dateに格納するよう修正 - 過去データ(UTC で入った行)には触れず、今後の INSERT/UPDATE のみ JST に
⑥ v2 curate.py の DB_HOST 取り残し
- 「69844 がなぜ movies に追加されない?」という問いから判明
- 多目的Lightsail で 10分毎に動いている
7channel-server-v2/curate.py(programs.matching_wordベースで自動振り分け)の.envのDB_HOSTが削除済み kizuna エンドポイントのまま cron.logを見たらUnknown MySQL server host 'kizuna...'で 10分毎にエラー終了していた.envをDB_HOST=172.26.8.107(Lightsail Micro 内部IP)に変更 → 手動実行で「採用 7 件 / 却下 3 件」復活- 69844 も
program_id=522(俺たちタッグだろ) で正しくマッチして movies 入り
気づき・教訓:
- 「サーバー移管時の周辺要素チェック」memory に sql_mode / DB_HOST 取り残し / OS+MySQL TZ の3パターンを追記
- DB引っ越し時は旧エンドポイント文字列で
~/cdev/と各サーバーの.env全部を grep してから旧 DB を削除する手順が必要 - YouTube Data API の quota はプロジェクト単位(10,000/日)。キーを複数発行しても同一プロジェクトなら共有
- ローカルレガシー PHP テストには MariaDB が無難(MySQL 8 collation 問題回避)
host.docker.internalで Docker コンテナからホスト Mac の MySQL に到達可能
コスト・安定性影響: ゼロ(コード修正+設定変更のみ)
次やること:
- 数日観察(v3 cron / curate.py / 新着が movies に流れているか)
- dev-timer #13 (5/11 11:00) で RDS 最終スナップ削除
- dev-timer #12 (6/1 11:00) で CloudFront 削除再挑戦
セッション - 古いEBSスナップショット全削除+RDSスナップ削除リマインド
やったこと:
- AWS アカウントに残っていた古いEBSスナップショットを総点検
- 11個の AMI(2013年〜2019年の旧サービス: 7channel初代 / 7ch-old-final / FootballNEXT / bizpice / news / tokyosoftware.info / haiena / current / current3 / laravel-tochu / COMMON_IMAGE_20170110)と紐付くもの
- AMI 未参照の単独スナップショット 1個(
before delete、2026-04-04 作成)
- AMI 未参照の単独スナップショット 1個(
- 合計 12個 / 96GB → 月約 $4.8 課金されていた
- AMI を全11個 deregister → スナップショット 12個全削除
- RDS最終スナップショット
kizuna-final-before-delete-20260504(9GB / 月$0.86)は1週間後に削除予定- dev-timer #13 を 2026-05-11 11:00 にセット
気づき:
- AWS で過去の AMI/EBSスナップショットが13年前から残っていた。AMI 自体は無料だが、紐付くEBSスナップショットは月課金され続ける($0.05/GB/月)
- 古いプロジェクト(FootballNEXT、bizpice 等)のサービス終了時にスナップショットを消し忘れる事故が多発。今回まとめて削除
コスト効果:
- EBSスナップショット削除で 月 -$4.8(年 -$58)
- RDSスナップ削除予定(5/11)で更に 月 -$0.86(年 -$10)
残TODO:
- dev-timer #12: 2026-06-01 11:00 — CloudFront ECQ9QLPK0563T 削除再挑戦
- dev-timer #13: 2026-05-11 11:00 — RDS最終スナップショット削除
セッション - 最終ゴール達成(RDS Lightsail移行+CloudFront廃止+Let's Encrypt直接HTTPS)
やったこと:
RDS テーブル整理(Lightsail移行前のスリム化)
cast_imps(5,372,939行 / 250MB): SELECTゼロの死テーブル、INSERTも停止済み → DROParticle_imps(1,048,833行 / 50MB): 同じパターン、v3 のimpressionArticleを no-op化してINSERT停止 → DROPkabutan_*系3テーブル: 株系の機能、未使用 → DROPtwitter_follow_candidates / twitter_followers_charts / twitter_follows: Twitter自動フォロー機能、停止中 → DROP- 計 約 313MB 削減、69テーブル / 500MB に
Lightsail Nano → Micro アップサイズ
- MySQL 同居で 512MB は OOM リスク → Micro (1GB RAM、$7/月) にアップサイズ
- スナップショット → 新Micro 起動 → 静的IP切替 → 旧Nano削除 の流れ
- Lightsail はインプレース変更不可だが、static IP 引き継ぎ + 同じ blueprint で実質ダウンタイムなし
MySQL 8 セットアップ + RDS データ移行
- Lightsail Micro に MySQL 8.0.45 インストール、
innodb_buffer_pool_size = 128M等チューニング - kizuna RDS から
mysqldumpで 362MB ダンプ - 1回目 import 中にOOM → swap 1GB 追加して再試行
- 2回目(高速モード
SET autocommit=0; SET unique_checks=0;)で成功(7分) - 68 テーブル / 550MB / movies 117,799行 / articles 313,650行 移行成功
- v3 の
.envでDB_HOST=127.0.0.1に切替、systemd restart で本番反映
phpmyadmin の接続も Lightsail MySQL へ
- Lightsail 同士は内部VPC (
172.26.0.0/16) で接続可能 → MySQL をbind-address = 0.0.0.0、7channel@172.26.%ユーザー追加、Lightsail FW で 3306 を172.26.0.0/16のみ許可 - phpmyadmin config の host を
172.26.8.107(Micro Private IP)に ichirokisanuki管理ユーザーが import に含まれてなかったので、Lightsail MySQL に同パスワードで作成(GRANT ALL ON *.* WITH GRANT OPTION)
kizuna RDS 削除
- 最終スナップショット
kizuna-final-before-delete-20260504作成(保険) - Deletion Protection が ON → modify で OFF にしてから delete
- delete-db-instance 完了確認、AWSアカウントから kizuna が完全消滅
- 月 -$20 確定(RDS本体)+ Public IPv4課金も停止
CloudFront 廃止 → Let's Encrypt 直接HTTPS
- Lightsail に
python3-certbot-dns-route53インストール - DNS-01 challenge で
7channel.ikapps.comの Let's Encrypt 証明書取得(自動更新設定済み) - Nginx を 443 listen + SSL 設定、80→443 リダイレクト
- Lightsail FW で 443 を開放
- Route53
7channel.ikapps.comの A レコードを CloudFront ALIAS → Lightsail static IP35.73.116.227に変更 - 本番動作確認:
https://7channel.ikapps.comが 200 OK / 60ms(CloudFront経由より速い) - Route53
lightsail.7channel.ikapps.com(CloudFront orig 用)も削除
CloudFront 削除トラブル
- ECQ9QLPK0563T を Disable した後 Delete を試みたら "subscribed to a pricing plan" エラー
- Security Bundle (WAF) 課金が残ってて、解除しても月末まで Delete 不可と判明
- Disabled のまま放置(実害ゼロ・トラフィックゼロ)
- dev-timer #12 で 2026-06-01 11:00 にリマインド設定
気づき:
- Lightsail Nano (512MB) で MySQL同居は OOM 確実 → Micro (1GB) でも swap 必須
- Lightsail インスタンスのスナップショット経由アップサイズは数分でできる、静的IP切替で DNS変更不要
- Lightsail 同士は内部VPC (
172.26.0.0/16) で繋がる、外部公開せずに DBアクセス可能 - CloudFront Security Bundle は 1ヶ月課金固定、解除→月末待ちが必要
- DNS-01 challenge は Route53 plugin で完全自動化(IAMユーザーのアクセスキーで認証)
コスト削減(最終):
最終月コスト: 約 $7(Lightsail Micro のみ)+ Route53/snapshot 微小
削減: $106 → $7 → 月-$99(年-$1,188)
CloudFront を月末に削除すれば更にゼロに
最終構成:
iPhone → 7channel.ikapps.com (Route53 A → Lightsail static IP)
↓
Lightsail Micro $7 (Nginx 443 SSL/Let's Encrypt → uvicorn 8000)
↓ 同居
FastAPI v3 + MySQL 8.0
↓
S3 channel7/current/201609 (iOSアプリ用基本データ)
セッション - Phase D 完遂(CloudFront切替・B-6緊急実装・クリーンアップ)
やったこと:
CloudFront origin 切替(cutover完了)
- ユーザーがCloudFront コンソールで origin を
lightsail.7channel.ikapps.comに切替・Deployed確認 https://7channel.ikapps.com経由で v3 (Lightsail) が応答することを確認- iOSアプリ「7ちゃんねる/8.2.0.3」(CFNetwork UA) の本物リクエストが Nginx ログに着弾、200 OK
- CloudFront経由で
Miss from cloudfront(CachingDisabled が正常動作)
B-6 緊急実装(保留してたが実は必須だった)
- iOSアプリ "Avatar3"(jp.thomsons.aaa.aaa)と cron (curl/7.53.1) の両方が
/api2/beginBroadcastを叩いていて 404 連発 - 元 PHP は
/S3Uploader/current201609を呼んで全テーブルから集計したJSONを S3 (channel7/current/201609) に public-read で put する処理 - iOSアプリは S3 から直接基本データ(top_movie / hot_movie / cast / channel / device 等)を取得する設計と判明 → 保留できる代物ではなかった
cron/s3_uploader.pyを新規実装(22テーブル分の集計、boto3 で S3 put、355KB JSON)- Lightsail に
~/.aws/credentialsで IAM ユーザー認証情報配置 /api2/beginBroadcastと/S3Uploader/current201609を v3 に追加・デプロイ- 実際に S3 アップロード成功(
s3://channel7/current/201609、Content-Length 355,814、public-read で iOSアプリから取得可能)
castsフィールド の調査と修正
- Cutover直後 castsが全部空だった件を調査 → 原因2つ:
- v3 の
attach_castsが PHP版にないcast_movie_relations.del = 0フィルタを入れていた → 削除して PHP互換化 - 新着動画(id ≥ 125384)はそもそも
cast_movie_relationsに未登録のため空が正解
- v3 の
- id=125383 で
casts="20,21,140"の取得を確認 → v3 動作正常
iOSアプリの古いデータ表示問題
- アプリで先頭が id=125382 表示 → 12:00〜20:22 の cron 404期間中に S3 が古いまま、アプリのキャッシュも古い
- アプリ完全終了→再起動で 125586(最新)が先頭表示に → 解決
軽微な改善(B案)
/v1/healthcheck200 OK 追加(旧 V1Controller 互換、senrigan-observer 用)- HEAD method を GET にマッピングするミドルウェアを追加 → CloudFront origin healthcheck の
Error from cloudfront解消、HEAD で 200 OK 返却
クリーンアップ(A案)
- EC2-2 (
i-01b0e45af32e40061) の Elastic IP43.206.39.55を disassociate + release - EC2-2 terminate 完了
- RDS
old_movie_imps(10,424,551 行 / 967.61 MB)を DROP - 本番疎通すべて 200 OK 確認(/health / /api3/* / /v1/healthcheck / /api2/beginBroadcast)
phpmyadmin(mydb.ikapps.com)副次対応
- EC2-2 を terminate したことで kizuna RDS への SSH トンネル踏み台が消失 → phpmyadmin から kizuna 接続不可
- kizuna は今 PubliclyAccessible=true なので、SSH トンネル不要で直接接続できることを利用
- 多目的サーバー static IP
13.230.63.19/32を kizuna RDS SG に許可 mydb.ikapps.comの/var/www/phpmyadmin/config.inc.phpを127.0.0.1:13308→kizuna.cxboespy3b8c.ap-northeast-1.rds.amazonaws.com:3306に変更phpmyadmin-tunnel-ch7.serviceをsystemctl disable --nowで停止・自動起動解除nc -vzで TCP 疎通確認 OK- 詳細は
~/cdev/common-php-my-admin/.devnotes/側で別途記録
気づき:
- B-6 (beginBroadcast) は「cron頻度低いし保留可」と判断してたが、iOSアプリが S3 経由で基本データを取得する設計なので保留不可だった。事前に「このエンドポイントは何のために存在するか」をもっと深掘りすべきだった
EC2-2 terminateの前に「このサーバーは他の用途で使われていないか」を確認するチェックリストが必要。今回 phpmyadmin の SSH トンネル踏み台になっていた事実を踏まえ漏らした- CloudFront キャッシュ動作と CachingDisabled の関係: GETリクエストは
Miss from cloudfrontと表示され、毎回 origin に到達。HEAD は別の挙動だが middleware で対応。
コスト削減効果(累計)
| Before | 段階1+2後 | 段階3後(今) | |
|---|---|---|---|
| 月コスト | $106 | $69 | 約$45 |
| 削減累計 | - | -$37 | -$61/月(年-$732) |
最終構成:
iPhone → CloudFront → Lightsail Nano (Python/v3) $5
↓
kizuna RDS $20
次回やること:
- 数日観察(5xx・cron・iOSアプリ)
- 将来: kizuna RDS を Lightsail MySQL に移行(更に -$20、最終形に到達)
2026-05-03
セッション - Phase D 着手(Lightsail立ち上げ・v3デプロイ・cutover直前で revert)
やったこと:
- Phase D 開始。最終ゴール(Lightsail 1台集約)に向けて新インスタンス立ち上げ
~/cdev/schulte-operationの前例を検証 → schulte の RDS(footballnext)は default VPC にいたから VPC peering で接続できたが、kizuna は カスタムVPCvpc-4b77802eにいるため Lightsail VPC peering 不可と再確認- ユーザー判断で「RDS Public化 + Lightsail static IP を SG許可」案(B案)を採用
- Lightsail Nano
7channel-server($5/月、Ubuntu 22.04、ap-northeast-1a)作成- 静的IP
7channel-server-ip(35.73.116.227) を allocate + attach
- 静的IP
- RDS
kizunaの PubliclyAccessible を false → true に戻し、SGsg-a35895c6に35.73.116.227/32で 3306 を許可 - Lightsail に v3 をデプロイ:
- rsync で
7channel-server-v3/を~/7channel-server-v3/に転送 - venv 作成、
pip install -r requirements.txt - Nginx インストール、
80 → 127.0.0.1:8000リバプロ設定 - systemd unit
7channel-web.serviceを作成・有効化
- rsync で
- 外部疎通確認OK(http://35.73.116.227/health → 200、
/api3/nextMovieData?limit=2の id=125383 で PHP本番と一致) - Route53 Aレコード作成:
lightsail.7channel.ikapps.com→35.73.116.227(CloudFront のオリジンに IP は使えないため) - CloudFront origin 切替: ユーザーがコンソールで CloudFront ECQ9QLPK0563T のオリジンを
lightsail.7channel.ikapps.comに変更 → 即時、「明日に進めたい」とユーザー判断で原状に revert- 再度オリジンを
ec2-43-206-39-55.ap-northeast-1.compute.amazonaws.comに戻す手順を案内
- 再度オリジンを
気づき:
- Lightsail VPC peering は default VPC とのみしかできない仕様(公式制約)。schulte の RDS は default VPC、7channel の kizuna はカスタムVPC、という違いで前例が直接使えなかった
- CloudFront のオリジンに IPアドレスは指定不可(DNSドメイン名のみ)。Lightsail の static IP を origin にするには Route53 の A レコード経由が必要
- Lightsail インスタンスは EC2 のような publicly resolvable hostname (
ec2-X-X-X-X.compute.amazonaws.com) を持たない。internal hostname(ip-172-26-4-27.ap-northeast-1.compute.internal)のみ - IAMユーザー
Ichiroには CloudFrontGetDistributionConfig権限すらないため、CLI からの update不可。コンソール操作必須
Phase D 残作業(明日):
- 朝にLightsail/v3 が安定稼働してることを確認
- CloudFront origin を
lightsail.7channel.ikapps.comに再切替(ユーザーがコンソール操作) https://7channel.ikapps.com経由で動作確認、iOSアプリ実機テストも- 数時間〜1日観察(5xx・レイテンシ・cron動作)
- 問題なければ:
- cron.txt は URL同じなので変更不要(CloudFront経由でLightsailに自動的に流れる)
- EC2-2 terminate
- RDS の不要IPv4解放
old_movie_impsDROP
セッション - 段階3着手(v3 新規実装でPHP→Python移植、Phase A〜C 完了)
やったこと:
方針確認
- 「最終的にLightsail 1台でプログラム+DB集約」が最終ゴール
- まず PHP→Python 移植してからデータ移行する方針に決定
- PHP互換性問題を完全回避(PHP 5.3 + 旧CakePHP は実質移行不可能)
~/cdev/各プロジェクトの DEVLOG / memory を grep しschulte-operationの前例(PHP→FastAPI でLightsail移行、Route53 Weighted Routing で 10%→50%→100% カットオーバー)を発見・参考に
利用エンドポイント特定(access log 分析)
EC2-2 のアクセスログから iOSアプリが実際に使っているエンドポイントを抽出 → /api3/* の9個だけと判明:
| エンドポイント | 週次回数 |
|---|---|
/api3/nextMovieData |
2,026 |
/api3/impressionMovie |
793 |
/api3/nextMovieDataForCast |
361 |
/api3/impressionCast |
323 |
/api3/nextArticleData |
253 |
/api3/nextMovieDataForProgram |
184 |
/api3/nextMovieDataForChannel |
95 |
/api3/nextMovieDataForDevice |
51 |
/api3/nextMovieDataForHot |
38 |
Web/Portal/管理画面系 は iOSアプリ無関係と判明 → 移植不要
v3 ディレクトリを新規作成(v2 を吸収)
7channel-server-v2を7channel-server-v3にコピーして拡張- DBスキーマ抽出(19テーブル / 14KB)→
v3/schema.sqlに保存
Phase A: iOS API 9メソッド の FastAPI 実装
api/movies.py:/api3/nextMovieData系 6エンドポイントapi/articles.py:/api3/nextArticleDataapi/impressions.py:/api3/impression{Movie,Article,Cast}api/common.py: cast 集約(GROUP_CONCAT で N+1問題解消、PHP版より高速)- ローカル uvicorn 起動 + SSH トンネル経由でkizunaRDS接続して動作確認 → PHP本番と全エンドポイントの結果完全一致(id・件数・casts文字列まで)
Phase B-1〜B-5: cron 系 12メソッド実装
| サブPhase | ファイル | 内容 |
|---|---|---|
| B-1 | cron/maintenance.py + cron/rotate_offset.py |
cleanMovieImps / updateProgramMovieCount / updateCastMovieCount / updateCastLastMovieUploadDate |
| B-2 | cron/hot_movies.py |
updateHotMovies (4 type: 1日/1週/1月/全期間) |
| B-3 | cron/cleanup.py |
removeNotNeedMovieUploads |
| B-4 | cron/article_crawl.py + cron/relation_sync.py |
crawl/article + cron/articleImageScraping + crawl/movieCastRelationSync(feedparser + BeautifulSoup) |
| B-5 | cron/youtube_uploads.py |
movieUploadsClassic / movieUploadsDetail / autoEntryMovie(YouTube Data API v3 + RSS) |
全部ローカル動作確認済み。/cron/movieUploadsClassic/1 で実際に新規動画1件が DB に追加されることまで確認。
Phase C: v2 admin 機能の動作確認
/admin/admin/programs/admin/casts系 21エンドポイントすべて 200 OK- HTML レンダリング正常、341KBのページが生成される
- v3 で v2 の admin が完全動作
B-6 保留
/api2/beginBroadcastは中身ほぼコメントアウトで、唯一活きてるのは/S3Uploader/current201609(S3アップロード処理、複雑)- cron頻度が10分毎なので影響なく後回し可
クリーンアップ
- uvicorn・SSHトンネル停止
- 一時SSH SG エントリ削除
気づき:
- access log 分析でiOSアプリ用エンドポイントが9個しかないと特定できたのが大きい。60+ Controller 数百メソッドのPHPコードのうち、本当に必要なのはごく一部
- N+1 問題(PHP版で各動画ごとに cast を SELECT)を Python 移植時に GROUP_CONCAT で解消、自然にパフォーマンス向上
- PHP は数値カラムを文字列で返す傾向(
"125383")、FastAPI は数値(125383)。iOSアプリ側の JSON デコード型次第で互換性問題の可能性 → カットオーバー前に要確認
次回やること:
- B-6 (beginBroadcast の S3アップロード処理) は別途
- Phase D: Lightsail Small インスタンス作成 → MySQL/PHP/Python セットアップ
- データ移行(mysqldump → Lightsail MySQL)
- v3 デプロイ
- CloudFront のオリジンを Lightsail に切替(最終のカットオーバー)
- 動作観察後 EC2-2 + RDS を完全削除
2026-04-28
セッション - 段階2完了(CLB廃止 + CloudFront経由HTTPS + 関連リソース大掃除)
やったこと:
スロークエリ第3弾の検証とロールバック
- 第3弾デプロイ後のスロークエリログを確認 → STRAIGHT_JOIN化した
CastMovieRelation::getRowsが最大14秒・平均7.7秒という致命的問題を発見 - 原因推測: EXPLAINテストでは
cast_id=132(関連数多)で250msだったが、本番では関連数の少ないcast_idで叩かれる際に movies 駆動でLIMIT 30を満たそうとして大量スキャン発生 - ローカルで元の自然JOIN形式にロールバック修正→デプロイ済み
- 残スローパターン3つは平均1.3〜1.7秒で許容範囲と判断(コード書き換えはしない)
段階2: CLB廃止 + CloudFront経由(最終ゴールに向けた大整理)
- 多目的サーバーには 7channel を置きたくない(既に v2 + AI スクリプトが居て分離したい)方針に決定 → 改訂案: EC2-2はそのまま専用機として残し、CLBだけ廃止
- HTTPS問題発覚: CLBが ACM 証明書(ap-northeast-1)でSSL終端していた → 直EC2-2では別途SSL必要
- 当初 Let's Encrypt(A案)の方針 → Amazon Linux AMI 2018.03 がEOLで certbot 導入が現実的でない → CloudFront経由(B案)に方針変更(既存のACM証明書を us-east-1 に新規発行して流用)
- 実行:
- EC2-2 にElastic IP
43.206.39.55を確保してアタッチ(IP固定化) - us-east-1 で ACM 証明書発行(DNS検証・手動・コンソール)
- CloudFront ディストリビューション
ECQ9QLPK0563T作成(手動・コンソール)- 代替ドメイン:
7channel.ikapps.com - キャッシュポリシー: CachingDisabled、オリジンリクエスト: AllViewer、HTTPS リダイレクト
- Origin Protocol を https-only → http-only に修正(EC2-2 が80番のみだったため504発生 → 修正で解決)
- 代替ドメイン:
- EC2-2 SG に CloudFront prefix list (
pl-58a04531) を追加して80番開放 - Route53
7channel.ikapps.comのALIASを CLB → CloudFront に切替(hosted zoneZAJWFBI6JY6TR) - 6時間観察期間 → CloudWatch メトリクスでクリーン確認
- EC2-2 にElastic IP
- クリーンアップ:
- CLB
Channel7削除 - Lambda 4個削除(CMN_detachEC2FromELB / CMN_attachToELB / CMN_startEC2 / CMN_totalHealthCheck)
- EventBridge 9ルール削除(per1min / Per5Minute / Per10Minute / CH7_cronSendPickupPush / SLN_cron / testcron / CMN_checkLoadBalancer / CMN_recoveryForEC2 / CMN_recoveryToLoadBalancer)
- EC2-1(停止中・i-0e9fe6c9d4bca23ae)terminate
- CLB
- SSH SGクリーンアップ(再追加IPと前回IPを両方削除)
気づき:
- STRAIGHT_JOIN は危険: EXPLAIN・1ケースのテストで速くても、データ分布の違いで本番が劇的に遅くなり得る。JOIN順序の固定は最終手段
- IAMユーザー
Ichiroには ACM/CloudFront/Route53 の 書き込み権限がない部分がある(CloudFront作成は手動コンソール、Route53 はzone単位で許可されてるzoneだけCLIで通る) - CloudFront作成時の罠:
- Origin に IPアドレス直接は入れられない → AWS DNS名(
ec2-XX-XX-XX-XX....compute.amazonaws.com)を使う - Origin Protocol Policy は デフォルトで https-only → EC2 が80番だけなら http-only に変更必須
- Origin SG が CLB の SG しか許可してない場合、
com.amazonaws.global.cloudfront.origin-facingprefix list を追加する
- Origin に IPアドレス直接は入れられない → AWS DNS名(
コスト削減効果:
- CLB $20 + Public IPv4 $11-15 + EC2-1 EBS $1 + Lambda $1 = 約 $33-37/月(年間 $400-450)
- 残: EC2-2 ($20) + IPv4 ($3.65) + RDS ($20) ≒ 月$45 → 段階3で更に削減予定
次回やること:
- 段階3: RDS
kizunaを多目的サーバーのMySQLに移行(2.3GB、月-$20見込み) - PHP 5.3 + 旧CakePHPアプリのリプレース方針決定
old_movie_imps(約1GB)のDROP(Lightsail移行と一緒に)
2026-04-27
セッション - スロークエリ第3弾(device_id等の絞り込みパターンに対処)
やったこと:
- 第2弾デプロイ後数時間のスロークエリログを集計 → 第2弾ワースト1〜3 すべて消滅確認
- 新たに浮上した3つのパターンを EXPLAIN で原因特定:
- A:
MovieCastRelation cast_id+delJOIN movies ORDER BY upload_unixtime(CakePHPgetAll、Api3Controller::nextMovieDataForCast)→ 950ms - B:
CastMovieRelation::getRows(cast_movie_relations 経由、ORDER BY upload_date DESC, id DESC LIMIT 30 OFFSET 0、Web側で使用)→ 1.7秒 - C:
movies WHERE device_id=? AND del=0 ORDER BY upload_date DESC LIMIT 30(CakePHP recursive=1 + 全カラム LEFT JOIN)→ 7.6秒
- A:
- インデックス追加で対処:
movies.idx_device_del_upload (device_id, del, upload_date)→ C: 7.3秒 → 62msmovies.idx_channel_del_upload (channel_id, del, upload_date)→ 同パターン予防movies.idx_del_upload_unix (del, upload_unixtime)→ A: 950ms → 270ms
- アプリ側書き換え:
- B:
Model/CastMovieRelation::getRowsを STRAIGHT_JOIN 形式に書き換え(movies 駆動 +idx_del_upload活用)→ 1.7秒 → 数百ms、デプロイ済み - A: コード書き換えは見送り(CakePHP
getAllのレスポンス形式変更リスク。270ms=閾値1秒未満で実用上OK)
- B:
- dev-timer 2件登録:
- 1時間後にテスト用通知
- 明日朝11:00 にスロークエリ第3弾の効果確認通知
気づき:
- 同じ「ID列での絞り込み + ORDER BY upload_date」パターンが program_id, device_id, channel_id とテーブル横断で出てくる
→
(<id_col>, del, upload_date)複合インデックスが対症療法として効く - アプリ書き換えはレスポンス形式が変わる可能性があり、API契約への影響リスクがある
→ CakePHP
find/getAll経由で結果オブジェクト形式に依存する箇所はインデックス対応のみが安全 - 直接 SQL を組み立ててる Model メソッド(
getRowsなど)は STRAIGHT_JOIN への書き換えが安全かつ効果的
次回やること:
- 明日朝11:00(dev-timer 通知時)にスロークエリログ再確認 → A/B/C パターン消えてるか
cleanMovieImpscron 初回実行確認- SSH SG クリーンアップ(自分のIP 2件)
セッション - スロークエリ第2弾(新ワースト1〜3 をインデックス追加で全解消)
やったこと:
- デプロイ後数時間経過のスロークエリログを集計確認 → ワースト1〜3(旧)全部消滅を確認
- 新たに浮上したワースト1〜3 を EXPLAIN で原因特定し、すべてインデックス追加だけで対処:
- 新ワースト1:
Movie.program_id = ? AND del = 0 ORDER BY upload_date DESC LIMIT 30の filesort →moviesにidx_program_del_upload (program_id, del, upload_date)追加 → 5.3秒 → 8ms - 新ワースト2:
cast_movie_relations経由の Movie 取得(前回ORDER BY upload_date DESCに書き換え後も filesort 残ってた) →moviesにidx_del_upload (del, upload_date)追加 → 1.7秒 → 21ms (EXISTS 書き換え・STRAIGHT_JOIN も試したが、元のクエリ + 新インデックスが最速だった) - 新ワースト3:
articles.url = ? LIMIT 1の URL 重複チェック(24万行フルスキャン) →articlesにidx_url (url)追加 → 約2秒 → 2.7ms
- 新ワースト1:
- SSHホワイトリストに今回のグローバルIP
220.215.166.104/32を追加(前回133.32.128.92/32のIPは既に変わっていた) - iOSアプリのアイコンファイル位置を確認:
7channel-ios/Channel7/Assets.xcassets/AppIcon.appiconset/(本命はicon-60@3x.png)
気づき:
- スロークエリ対策はアプリのコード書き換えより、適切な複合インデックス追加で済むケースが多かった
- EXPLAINで
Using filesort/Using temporaryが出ていたら、ORDER BY を含む複合インデックスで解消できることが多い - EXPLAIN の表示が同じでも、内部的な処理(インデックス活用)で実測時間が桁違いに変わることがある(EXISTS 書き換え不要だった件)
次回やること:
- SSH SGの仮追加IP(
133.32.128.92/32と220.215.166.104/32)をまとめて削除(クリーンアップ) - 段階1の観察継続(数日)→ 段階2(CLB完全廃止 + Nginx集約)へ
- 明日朝5:00 の
cleanMovieImps初回実行を確認
2026-04-27
セッション - 7channel本番のRDS/PHP最適化(cron頻度調整・インデックス整備・テーブル整理・クエリ改善)
やったこと:
- 段階1(EC2-2単体運用)の24時間観察 → 問題なし(CLB 5xx=0、EC2-2 CPU 1.0-1.5%、RDS変化なし)
- 7channel-server のcron
cron.txtを分析 → 毎分11個の処理が走っていることを確認 - EC2-2 / RDS の重い処理を実測(PHP 5.3 / CakePHP / db.t3.micro MySQL 8.4)
/crawl/article3.1秒、movieUploadsClassic2-2.4秒など- RDS統計: Select_scan 75万、Created_tmp_disk_tables 3612 → 改善余地大
- スロークエリログ有効化(parameter group
mysql84でslow_query_log=ON / long_query_time=1、再起動なし) - cron頻度調整(ユーザー実施):
movieUploadsClassic/0,1を5分毎、removeNotNeedMovieUploadsを30分毎に - インデックス追加・整理:
cast_impsにcast_id追加(ユーザー実施)moviesの重複/無価値インデックス4つ削除(id/youtube_code_2/del/tweet_done、ユーザー実施)articles.done_image_scraping追加(ワースト1対策、ユーザー実施)
- imps系テーブル整理:
cast_impsはSELECT皆無の書き捨てテーブルと判明 →Api3Controller::impressionCastのINSERTをコメントアウト(ローカル修正→デプロイ)movie_impsをテーブルswap方式で削減: 1,100万行 → 21,329行(直近3ヶ月)、old_movie_impsに旧データ保管- 自動クリーンアップ用
CronController::cleanMovieImps()を新規実装+0 5 * * *cron追加
- HotMonthMovie 復活(ユーザー cron 編集):
33 4 * * * /api3/updateHotMovies/3を有効化 - スロークエリ ワースト2/3 のソース改善:
- ワースト2
updateCastMovieCount:getAllで1,628行fetchしてPHPcount()していたのをfind('count', recursive=-1)に変更 - ワースト3 (3箇所):
ORDER BY DATEDIFF(?, upload_date) ASCを等価変換でORDER BY upload_date DESCに置換 → upload_date インデックス活用可
- ワースト2
気づき:
7channel-serverは CodeCommit + CodeDeploy 管理。git pushにはAWS_PROFILE=ichirokisanukiが必須(デフォルトプロファイル未設定のため)- Lightsail インスタンスはデフォルト IAM ロールで AWS リソースを叩けないため、IAMユーザーのアクセスキー方式が現実解(前回学習を本番でも踏襲)
- RDS は 割り当て容量で課金。データを消してもストレージ料は変わらない(縮小には新インスタンスへの再構築が必要)
次回やること:
- 数時間後にスロークエリログ再確認(ワースト1〜3 が消えてるか)
- 明日朝5:00 の
cleanMovieImps初回実行を確認 old_movie_impsは数週間〜Lightsail移行時にDROP予定(焦らない)
2026-04-26
セッション - 最終ゴールの共有(CLB廃止・Lightsail集約・RDS廃止)
やったこと:
- ユーザーから長期目標(LB削除、Lightsail 1台で動かす、RDSのデータをLightsailに持っていく)が共有された
- これらは既にROADMAPの段階2/3に含まれているため、ROADMAP変更は不要と確認
- プロジェクトメモリに「7channel本番のAWS構成最終ゴール」として保存(次回以降のセッションで自動的に参照される)
セッション - AWSコスト分析と段階1実行(ALBから1台外す)
やったこと:
- 月$106のAWS請求内訳を分析:
- VPC $22.31(Public IPv4 6個分の課金)
- RDS $20.53、ELB $20.19、EC2 $20.14、Registrar $15、Route53 $5
- EC2 2台に SSH 接続(自分のIP
133.32.128.92/32をSSH SGに一時追加)して構成を把握- PHP 5.3.29 / Apache 2.2.34 / Amazon Linux AMI 2018.03(全部EOL、2014〜2023年でサポート終了)
- CakePHPアプリ。CodeDeploy管理、
/var/www/html配下 - 直近1週間のリクエスト: 30,000〜50,000/日、iOSアプリUA識別: 約700/日、QPS平均1未満
- メモリ: 461MB中185MB使用(buffer抜き)、CPU平均 0.6%
- RDS
kizuna(db.t3.micro MySQL) を分析- 77テーブル、合計2.3GB。最大は
movie_imps(1100万行/1.5GB) - CPU平均 5.7-7.3%、接続数ほぼ常時0(スパイク時58)
- 77テーブル、合計2.3GB。最大は
- Lambda 4個 + EventBridge ルール多数を確認
- 実在Lambda:
CMN_totalHealthCheck(外形監視)、CMN_detachEC2FromELB/CMN_attachToELB/CMN_startEC2(EC2オートヒーリング) - 全部 nodejs8.10(EOL)
- 多数の孤児EventBridgeルール(参照Lambdaが既に削除済み)を発見
- 実在Lambda:
- 段階1実行(ALBから1台外す・低リスク・即リバート可能):
- EC2-1 (
i-0e9fe6c9d4bca23ae) のAppNameタグを削除(CMN_attachToELBの自動再登録対象から外す) - CLB
Channel7から EC2-1 をデタッチ - EC2-1 を停止(Public IPも解放)
- 効果: 約 $7.4/月削減、サービスは EC2-2 で継続稼働中
- EC2-1 (
気づき:
- iOSアプリのDAU 500だが、実トラフィックは Lightsail Nano 1台で余裕で捌ける規模
- 7channel本番サーバーは技術的負債が深刻(PHP/OS/Lambda 全部EOL)。コスト削減は同時にリプレースを促すきっかけにできる
- Lambda+EventBridge での自前オートヒーリングは2019年頃のベストプラクティス、今ならALB+ASGかLightsailで不要
次回やること:
- 1台体制で数日様子見
- 問題なければ段階2(ALB完全廃止 + Nginx集約)へ
- SSHホワイトリストに追加した
133.32.128.92/32を削除(クリーンアップ)
2026-04-25
セッション - 動画追加時のアラートメール復旧
やったこと:
- 動画追加時のSNS経由アラートメールが届かない問題を調査
- cron.log で
AuthorizationError: ...is not authorized to perform: SNS:Publishを確認 - 原因特定: Lightsailインスタンスは AWS 管理アカウント(
385232584017)で動いており、デフォルト付与のAmazonLightsailInstanceRoleには SNS:Publish 権限がない。しかもこのロールは AWS 管理で変更不可 - SNSトピック側にはクロスアカウント許可(
AllowLightsailPublish)が既にあったが、IAM側ロールを変更できないので無効 - 解決策として旧サーバーで使っていた IAM ユーザーのアクセスキーを新サーバーの
~/.aws/credentialsに配置([default]プロファイル、パーミッション 600) notify.send_notificationを手動実行してメール到達を確認
気づき:
- Lightsail インスタンスは「ユーザーアカウント」と「AWS管理アカウント」の二層構造になっている
AmazonLightsailInstanceRoleは AWS 管理リソースなので IAM ポリシーをアタッチできない- Lightsail から AWS リソースを使う場合、IAMユーザーのアクセスキーを置く方式が現実解
2026-04-24
セッション - サブディレクトリ整理とcron復旧
やったこと:
lightsail-claude-avatar/をゴミ箱へ移動(不要のため)7channel-server-v2/を7channel-operationリポジトリに統合- 元々独立リポジトリ(
.git内蔵)だったが、.gitを削除して通常ファイルとして取り込み - GitHub上の独立リポジトリ
7channel-server-v2はそもそも作られていなかったのでアーカイブ不要
- 元々独立リポジトリ(
- 旧サーバーで動いていた
curate.pyの10分おきcronが新サーバーで未稼働だったので復旧*/10 * * * * cd /home/ubuntu/7channel-server-v2 && venv/bin/python curate.py >> cron.log 2>&1
- 「サーバー移管時はcron等の周辺要素も漏れなくチェック」を memory に保存
気づき:
- サーバー移行ではコードのデプロイだけでなく、cron / systemd timer / 常駐デーモンのチェックも必須
セッション - リポジトリ初期化と7channel-server-v2の多目的サーバー移行
やったこと:
git init実行、.gitignore設定(7channel-server/,7channel-ios/, Claude Code ローカル状態).devnotes/運用を導入(DEVLOG.md, WIP.md)- GitHubにprivateリポジトリ(
7channel-operation)を作成、初回commit & push 7channel-server-v2を旧Lightsail(13.230.63.19/ ec2-user)から多目的サーバー(multipurpose-server1/ ubuntu)に移行- 新サーバーに
python3.10-venvインストール、venv作成 - systemdサービス(
7channel-web)を8001ポートで設定・起動 - Nginx に
7ch.ikapps.comのバーチャルホスト設定を追加 - ファイルをSCPで転送、依存パッケージインストール、サービス起動確認(HTTP 200)
- Route53のAレコードを
13.230.63.19に手動更新 - 静的IP
7channel-v2-ip(13.230.63.19)を多目的サーバーにアタッチ deploy.shを新サーバー向けに更新
- 新サーバーに
multi-purpose-lightsail-server1のREADME/CLAUDE.mdを更新してcommit & push
次回やること:
7channel-server-v2/とlightsail-claude-avatar/をこのリポジトリでコミット管理するか方針決め
最近のコミット
- 986deda 採用・却下の通知メール停止を .devnotes に記録(DEVLOG/WIP/DECISIONS 更新) 2026/8/11
- fab1739 採用・却下の通知メールを停止(curator.py / curate.py の send_notification をコメントアウト) 2026/8/11
- e22589b 日報をJST化(cronデーモンUTC先祖返り修正)+deviceマスタ縮小でトークン−43%/コール(.devnotes 更新) 2026/8/10
- 87039bf トークン日報+失敗アラートを整備・codex authをサーバ専用ログインへ恒久化(.devnotes 更新) 2026/8/9
- e11294b 機種関連付けを自動書き込み+物申す(付け替え対応)へ同型化・メールにYouTube URL追加(.devnotes 更新) 2026/8/8
- 9b8ff9f 出演者付与メールを動画ごと1通・自信度絵文字(🟢/🔴)付きの新フォーマットに変更 2026/8/8
- c7f68fb 関連付け判定をサーバ同居codex(gpt-5.6-luna)へ恒久移行+「物申す」フィードバック自動学習を新設(.devnotes 更新) 2026/8/8
- 2e5cb60 iOS 8.5.0 をASOメタデータ刷新とともにApp Store審査提出(.devnotes 更新) 2026/7/5
- c54ff5c ローカルmatcherを caffeinate 化しスリープ割り込みのタイムアウト滞留を解消(.devnotes 更新) 2026/7/5
- 49771d2 .devnotes 更新: 判定エンジン恒久策の調査(ローカルLLM qwen2.5:7b実測→cast判定F1 12%で不可・保留) 2026/7/5