Skip to content

スケジュール関数4本(rankingUpdate/rankingCache/statusCacheBackfill/scheduledOgpDelete)をGoへ移植#128

Merged
cursor[bot] merged 1 commit into
alpha-0.2from
cursor/go-scheduled-functions-5520
Jul 1, 2026
Merged

スケジュール関数4本(rankingUpdate/rankingCache/statusCacheBackfill/scheduledOgpDelete)をGoへ移植#128
cursor[bot] merged 1 commit into
alpha-0.2from
cursor/go-scheduled-functions-5520

Conversation

@kojira

@kojira kojira commented Jul 1, 2026

Copy link
Copy Markdown
Member

概要

HTTPエンドポイント(status/sanpai/ranking/register/ogpRewrite)に続き、Pub/Sub(Cloud Scheduler)トリガーのスケジュール関数4本をGoへ移植します。

  • rankingUpdateGo(60分毎): 全ユーザーをstatus.total降順で取得し順位を再計算してcache_data/ranking_cacheに書き込む
  • rankingCacheGo(120分毎): point_ranking/{id}が未作成のユーザーに初期値を作成
  • statusCacheBackfillGo(30分毎): 直近6ヶ月アクティブでstatus未計算のユーザーを1回の実行で最大10件バックフィル
  • scheduledOgpDeleteGo(毎時): Cloud Storageのogps/プレフィックス配下を削除

いずれもユーザーが直接待つHTTPエンドポイントではないためコールドスタート短縮による体感速度改善効果はありませんが、実行時間短縮による課金削減とコード基盤の統一を目的に移植しました。Node版とは別関数名・別Pub/Subトピック・別Cloud Schedulerジョブで完全に独立してデプロイし、安定稼働を確認してからNode版を停止する方針です(他のGo移植と同じパターン)。

Node版との挙動差異(意図的なもの)

  • rankingUpdateGo: Node版はorderBy済みの配列に対してsort((a,b)=>b.battlePoint-a.battlePoint)を再度呼んでいますが、battlePoint(camelCase)は存在しないフィールド名のタイプミスで常にNaNを返す比較関数になっており、V8の配列ソートの挙動上実質的に無意味な処理(既存のFirestoreクエリ順を維持するno-op)になっています。Go版はこの無意味な再ソートを行わず、Firestoreクエリの結果順をそのまま使います(観測できる出力はNode版と同一)。
  • rankingCacheGo: Node版はsnapshot.forEach(async (item) => {...})という非同期コールバックをawaitしないfire-and-forgetな実装になっており、理論上は全ユーザー分の書き込みが完了する前に関数の実行が終了したとみなされ得ます(書き込み欠落のリスク)。実装上の不備と判断し、Go版では各ユーザーの処理を順番に確実に完了させます(書き込まれるデータ自体はNode版と同一)。
  • scheduledOgpDeleteGo: Node版はbucket.deleteFiles({prefix:"ogps/"})の戻り値をawait/returnしておらず、削除失敗時の挙動が不可視なfire-and-forgetになっています。Go版は1ファイルの削除失敗をログに残しつつ他のファイルの削除は継続します(冪等なクリーンアップジョブのため実害はありません)。
  • 詳細は docs/backend.md「スケジュール関数のGo移植」節を参照してください。

CI/デプロイ

  • dev-deploy.ymlにPub/Subトリガーのデプロイ(--trigger-topicは存在しなければ自動作成)と、Cloud Schedulerジョブの作成/更新ステップ(update || createで冪等)を追加しました。
  • Cloud SchedulerのlocationはプロジェクトのApp Engineアプリのロケーションと一致させる必要があるためgcloud app describeから動的に取得し、レガシー表記(us-central等)をCloud Scheduler形式(us-central1等)に変換する処理を追加しています。
  • go test ./...をFirestoreエミュレータ起動状態(firebase emulators:exec)で実行するよう変更し、既存のFirestore統合テスト(sanpai_test.go等、これまでCIでは自動スキップされていた)も含めて実際にCIで検証されるようにしました。

テスト

  • Firestoreエミュレータを使った統合テストを新規4本のスケジュール関数それぞれに追加(ranking_update_test.go/ranking_cache_test.go/status_cache_backfill_test.go/scheduled_ogp_delete_test.go)。
  • ranking_update_test.go/status_cache_backfill_test.goは共有Firestoreエミュレータを使う他テストのデータ汚染に対して頑健になるよう設計(絶対値ではなく相対的な検証、複数回実行での最終的な整合性確認)。
  • go build/go vet/gofmt/go test ./...(Firestoreエミュレータあり)全て通過。actionlintでCI設定も検証済み。
  • 第三者視点のレビューを実施し、指摘事項(Cloud Schedulerジョブの更新ロジック不備、App Engineロケーション表記差異、日付演算の差異の未文書化、Storageバケット名未設定時の早期エラー化等)に対応済み。

見送った機能

userOGP(OGP画像生成、Canvas/Chart.jsによるサーバーサイド画像合成)は引き続き対象外です。理由はdocs/backend.md「Go移植を見送った機能」節を参照してください。

Open in Web Open in Cursor 

…sCacheBackfill/scheduledOgpDelete)

- rankingUpdateGo/rankingCacheGo/statusCacheBackfillGo/scheduledOgpDeleteGoを
  Pub/Sub(Cloud Scheduler)トリガーのCloud Run functions(Go)として追加。
  Node版とは別関数名・別トピック・別スケジューラジョブで完全に独立してデプロイし、
  安定稼働を確認してからNode版を停止する方針。
- rankingUpdateGo: Node版の`sort`再呼び出し(battlePointのタイプミスで実質no-op)を
  再現せず、Firestoreクエリの結果順をそのまま使用(観測可能な出力は同一)。
- rankingCacheGo: Node版のforEach内fire-and-forgetな非同期処理を、
  各ユーザーの処理を確実に完了させる実装に変更(書き込み内容自体は同一)。
- statusCacheBackfillGo: status/statusGoと同じ集計ロジックを再利用。
- scheduledOgpDeleteGo: Cloud Storageの`ogps/`プレフィックス配下を削除。
  個別ファイルの削除失敗をログに残しつつ他のファイルの削除は継続する。
- CI(dev-deploy.yml)にPub/Subトリガーのデプロイ・Cloud Schedulerジョブの
  作成/更新ステップを追加。Cloud SchedulerのlocationはApp Engineアプリの
  ロケーションと一致させる必要があるため動的に取得し、レガシー表記
  (us-central等)をCloud Scheduler形式に変換する処理を追加。
- CIのgo testをFirestoreエミュレータ起動状態(firebase emulators:exec)で
  実行するよう変更し、既存のFirestore統合テスト(sanpai_test.go等)も
  含めて実際にCIで検証されるようにした。
- 第三者視点のレビューを実施し、指摘事項(Cloud Schedulerジョブの更新ロジック、
  日付演算の差異の文書化、Storageバケット名未設定時の早期エラー化等)に対応済み。

Co-authored-by: kojira <kojira@users.noreply.github.com>
@cursor
cursor Bot merged commit ec0cfc9 into alpha-0.2 Jul 1, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants