From 90d6895de4003b3bb7e2ddac2633252af84122fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A4=EC=A0=95=EB=AF=BC?= Date: Mon, 27 Jul 2026 13:39:10 +0900 Subject: [PATCH] Pin script RTDB region and gate empty-source backfill marker MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 스크립트 부트스트랩의 databaseURL 기본값에 리전 포함 — 기존 `...firebaseio.com`은 SDK가 "Database lives in a different region" 경고만 내고 다른 리전의 없는 DB를 보게 해서, 조회가 조용히 빈 결과를 돌려주고 실행이 멈췄다. 지금까지 스크립트가 RTDB를 안 써서 드러나지 않던 문제다. FIREBASE_DATABASE_REGION으로 덮어쓸 수 있다 - 백필 스크립트가 접속한 database URL을 먼저 출력하도록 변경 - 소스가 비었을 때 완료 마커를 자동으로 남기지 않고 --mark-empty를 요구하도록 변경 — 빈 조회는 "정말 투표가 없다"와 "엉뚱한 DB에 붙었다"를 구분하지 못하는데, 후자에서 마커가 켜지면 이후 dailyArchive가 인덱스만 믿고 전 유저를 건너뛴다 --- scripts/_bootstrap.ts | 11 ++++++++--- scripts/backfill-user-votes-by-date.ts | 22 +++++++++++++++++++--- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/scripts/_bootstrap.ts b/scripts/_bootstrap.ts index 5da8fed..0dcb7d7 100644 --- a/scripts/_bootstrap.ts +++ b/scripts/_bootstrap.ts @@ -7,14 +7,19 @@ * gcloud auth application-default login * 또는 GOOGLE_APPLICATION_CREDENTIALS=<서비스계정.json> * - * 도구는 RTDB를 쓰지 않지만 src/firebase가 getDatabase()를 호출하므로 - * databaseURL이 없으면 import 시점에 throw한다 — 그래서 형식상 URL을 채워준다. + * databaseURL은 반드시 **리전을 포함**해야 한다. 리전 없는 기본 호스트 + * (`...firebaseio.com`)로 붙으면 SDK가 "Database lives in a different region"만 + * 경고하고 다른 리전의 (존재하지 않는) DB를 보게 되어, 조회가 조용히 빈 결과를 + * 돌려준다 — 그 값을 사실로 믿는 스크립트는 위험한 판단을 내릴 수 있다. + * 리전은 FIREBASE_DATABASE_REGION, 전체 URL은 FIREBASE_DATABASE_URL로 덮어쓴다. */ import { initializeApp, getApps } from "firebase-admin/app"; const projectId = process.env.FIREBASE_PROJECT ?? process.env.GCLOUD_PROJECT ?? "mmday-panit"; +const databaseRegion = process.env.FIREBASE_DATABASE_REGION ?? "asia-southeast1"; const databaseURL = - process.env.FIREBASE_DATABASE_URL ?? `https://${projectId}-default-rtdb.firebaseio.com`; + process.env.FIREBASE_DATABASE_URL ?? + `https://${projectId}-default-rtdb.${databaseRegion}.firebasedatabase.app`; if (getApps().length === 0) { initializeApp({ projectId, databaseURL }); diff --git a/scripts/backfill-user-votes-by-date.ts b/scripts/backfill-user-votes-by-date.ts index 4637845..974fe79 100644 --- a/scripts/backfill-user-votes-by-date.ts +++ b/scripts/backfill-user-votes-by-date.ts @@ -8,8 +8,14 @@ * 멱등하다 — 여러 번 돌려도 같은 값으로 덮어쓸 뿐이다. * * 실행: - * npx tsx scripts/backfill-user-votes-by-date.ts # 미리보기 - * npx tsx scripts/backfill-user-votes-by-date.ts --apply # 실제 쓰기 + * npx tsx scripts/backfill-user-votes-by-date.ts # 미리보기 + * npx tsx scripts/backfill-user-votes-by-date.ts --apply # 실제 쓰기 + * npx tsx scripts/backfill-user-votes-by-date.ts --apply --mark-empty # 소스가 빈 상태에서 마커만 + * + * ⚠️ 소스(/userVotes)가 비었을 때는 마커를 자동으로 남기지 않는다. 빈 조회는 + * "정말 투표가 없다"와 "엉뚱한 DB에 붙었다"를 구분하지 못하는데, 후자에서 마커가 + * 켜지면 이후 아카이브가 인덱스만 믿고 전 유저를 건너뛴다. 비었다는 것을 사람이 + * 확인했을 때만 --mark-empty로 명시한다. */ import "./_bootstrap"; import { rtdb } from "../src/firebase"; @@ -22,11 +28,21 @@ const CHUNK = 500; async function main(): Promise { const apply = process.argv.includes("--apply"); + const markEmpty = process.argv.includes("--mark-empty"); + + console.log(`database: ${rtdb.app.options.databaseURL}`); const snap = await rtdb.ref("/userVotes").get(); if (!snap.exists()) { console.log("no /userVotes data — nothing to backfill"); - if (apply) await markBackfilled(); + if (!apply) return; + if (!markEmpty) { + console.log( + "마커를 남기지 않았다. 위 database URL이 맞고 정말 투표가 없다면 --mark-empty를 붙여 다시 실행할 것." + ); + return; + } + await markBackfilled(); return; }