Daily Cost
$139
+73% vs 30d ago
Monthly Est.
~$4,050
約60万円/月
MERGE Share
95%
$132 / $139
Daily Scan
22 TB
28施設 x ~40テーブル
Data Pipeline
Step 3 にボトルネック
1
PMS(FrontCrew)- 各ホテル
全国 28施設 のホテル管理システムから、宿泊・売上・予約などのデータを毎日CSV出力
2
Google Cloud Storage
ホテルごとの専用バケットにCSVをアップロード。コスト問題なし。
3
Cloud Functions → BigQuery MERGE
$132/日 = 95%
毎晩 02:00 JST に自動実行。28施設 x 約40テーブル = 約1,150回のMERGE。
問題: テーブルにクラスタリングがないため、1施設のMERGEで全施設の全データをフルスキャン。
問題: テーブルにクラスタリングがないため、1施設のMERGEで全施設の全データをフルスキャン。
たとえると: 図書館でAホテルの本を1冊棚に戻すために、毎回すべての本を床に出して並べ直している状態。本が増えるほど作業量が増える。
4
dbt(集計・加工)$3.5/日
統合データをレポート用に集計・変換。コスト正常。
5
Looker Studio / スプレッドシート
レベニュー・稼働分母・売上明細・カスタマイズシートに出力。$2.7/日。
Cost by Source
Cost by Account
Top Tables by Cost
上位5テーブル = 76%
BigQueryは「読んだデータ量」で課金($6.25/TB)。御客伝票 188GB x 28施設 = 5.3TB/日 → $33/日。40テーブル合計で約22TB/日。
30-Day Cost Trend
+73%
放置リスク: 施設追加+データ蓄積の二重増加。半年後に$200+/日の見込み。レポート月額(2〜3万/施設)を考えると利益を圧迫。
現在
~$4,050/月
約60万円
改善後
~$450/月
約7万円(90%減)
B. 定額プラン
~75%削減
従量制→月額固定。月$960〜1,200。携帯のギガ放題と同じ考え方。処理速度がやや遅くなる可能性あり。
低 / 即日
C. 差分MERGE
5〜50%削減
変更データだけ更新する方式に変更。効果はデータ更新頻度次第。CF側のコード変更が必要。
高 / 2〜3日
現実的
D. 段階実施
最大90%+
即日: B で出血止め → 1〜2日: A を上位5テーブルに → その後: 定額解約 or 縮小
Step1即日 / Step2 1〜2日
Overview
CF側コード変更不要
削減効果
85〜90%
作業時間
1〜2日
コード変更
不要
リスク
低
What & Why
今の問題: テーブルに整理がない。1施設のMERGEで全28施設の全データをフルスキャン → 1日$132。
やること: テーブルに hotel_id のクラスタリング(索引)を追加。MERGEで該当施設だけ読むようになる。スキャン量 22TB → 1〜2TB。
コード変更不要の理由: MERGEのSQLに既に hotel_id が条件に含まれている。BigQueryが自動的にクラスタを利用してスキャン削減する。stable社のコードは一切触らなくてよい。
対象テーブル(優先5テーブル)
全体の76%
| # | テーブル | 本番 | 行数 | Cost/日 | staging | MERGEキー |
|---|---|---|---|---|---|---|
| 1 | be_D_御客伝票 | 188 GB | 3.5億 | $31.83 | 2.82 GB | hotel_id, 明細番号, 行, レコード更新日 |
| 2 | be_D_御客情報 | 132 GB | 9,400万 | $22.30 | 1.96 GB | hotel_id, 御客番号, レコード更新日 |
| 3 | be_B_御客伝票 | 113 GB | 2.0億 | $19.22 | 1.73 GB | hotel_id, 集計日, 明細番号, 行, ... |
| 4 | be_D_御客詳細 | 82 GB | 4.0億 | $13.97 | 1.17 GB | hotel_id, 御客番号, レコード更新日 |
| 5 | be_D_御客GR | 77 GB | 6,100万 | $12.98 | 1.17 GB | hotel_id, グループ番号, レコード更新日 |
| 合計 | 592 GB | 11.5億 | $100.30 | 8.85 GB |
全テーブルのMERGEキーの先頭が hotel_id。 クラスタリングキーに hotel_id を指定すれば、約90テーブルすべてで効果がある。
Step 1: staging で検証
staging は本番の1/60程度のサイズ。数分で完了。
-- 1. クラスタリングつきの新テーブルを作成
CREATE TABLE `bb-frontcreue.staging_frontcrew_data.be_D_御客伝票_clustered`
CLUSTER BY hotel_id
AS SELECT * FROM `...be_D_御客伝票`;
-- 2. 旧テーブルをリネーム
ALTER TABLE `...be_D_御客伝票` RENAME TO be_D_御客伝票_old;
-- 3. 新テーブルを正式名に
ALTER TABLE `...be_D_御客伝票_clustered` RENAME TO be_D_御客伝票;
-- 4. バッチ確認後に旧テーブル削除
DROP TABLE `...be_D_御客伝票_old`;
CREATE TABLE `bb-frontcreue.staging_frontcrew_data.be_D_御客伝票_clustered`
CLUSTER BY hotel_id
AS SELECT * FROM `...be_D_御客伝票`;
-- 2. 旧テーブルをリネーム
ALTER TABLE `...be_D_御客伝票` RENAME TO be_D_御客伝票_old;
-- 3. 新テーブルを正式名に
ALTER TABLE `...be_D_御客伝票_clustered` RENAME TO be_D_御客伝票;
-- 4. バッチ確認後に旧テーブル削除
DROP TABLE `...be_D_御客伝票_old`;
上記を5テーブル分実行 → 翌日バッチの正常動作を確認 → 本番へ
Step 2: 本番テーブル再作成
日中に実施
タイミング: 深夜バッチ完了後〜翌日バッチ開始前(日中)に実施。リネーム中にMERGEが走ると競合するため。
1テーブルあたりの手順
A
CLUSTER BY hotel_id で新テーブル作成
元テーブルの全データをコピー + クラスタリング適用。188GBで数分〜15分。
B
テーブル名を入れ替え(2つのRENAME)
旧→_old、新→正式名。即時完了(メタデータ変更のみ)。
C
翌日バッチ確認後、旧テーブル削除
確認できるまで _old は残す(ロールバック用バックアップ)。
-- ========== be_D_御客伝票 (188GB) ==========
CREATE TABLE `bb-frontcreue.production_frontcrew_data.be_D_御客伝票_clustered`
CLUSTER BY hotel_id AS SELECT * FROM `...be_D_御客伝票`;
ALTER TABLE `...be_D_御客伝票` RENAME TO be_D_御客伝票_old;
ALTER TABLE `...be_D_御客伝票_clustered` RENAME TO be_D_御客伝票;
-- ========== be_D_御客情報 (132GB) ==========
CREATE TABLE `...be_D_御客情報_clustered`
CLUSTER BY hotel_id AS SELECT * FROM `...be_D_御客情報`;
ALTER TABLE `...be_D_御客情報` RENAME TO be_D_御客情報_old;
ALTER TABLE `...be_D_御客情報_clustered` RENAME TO be_D_御客情報;
-- ========== be_B_御客伝票 (113GB) ==========
CREATE TABLE `...be_B_御客伝票_clustered`
CLUSTER BY hotel_id AS SELECT * FROM `...be_B_御客伝票`;
ALTER TABLE `...be_B_御客伝票` RENAME TO be_B_御客伝票_old;
ALTER TABLE `...be_B_御客伝票_clustered` RENAME TO be_B_御客伝票;
-- ========== be_D_御客詳細 (82GB) ==========
CREATE TABLE `...be_D_御客詳細_clustered`
CLUSTER BY hotel_id AS SELECT * FROM `...be_D_御客詳細`;
ALTER TABLE `...be_D_御客詳細` RENAME TO be_D_御客詳細_old;
ALTER TABLE `...be_D_御客詳細_clustered` RENAME TO be_D_御客詳細;
-- ========== be_D_御客GR (77GB) ==========
CREATE TABLE `...be_D_御客GR_clustered`
CLUSTER BY hotel_id AS SELECT * FROM `...be_D_御客GR`;
ALTER TABLE `...be_D_御客GR` RENAME TO be_D_御客GR_old;
ALTER TABLE `...be_D_御客GR_clustered` RENAME TO be_D_御客GR;
-- ========== バッチ確認後に旧テーブル削除 ==========
-- DROP TABLE `...be_D_御客伝票_old`;
-- DROP TABLE `...be_D_御客情報_old`;
-- DROP TABLE `...be_B_御客伝票_old`;
-- DROP TABLE `...be_D_御客詳細_old`;
-- DROP TABLE `...be_D_御客GR_old`;
CREATE TABLE `bb-frontcreue.production_frontcrew_data.be_D_御客伝票_clustered`
CLUSTER BY hotel_id AS SELECT * FROM `...be_D_御客伝票`;
ALTER TABLE `...be_D_御客伝票` RENAME TO be_D_御客伝票_old;
ALTER TABLE `...be_D_御客伝票_clustered` RENAME TO be_D_御客伝票;
-- ========== be_D_御客情報 (132GB) ==========
CREATE TABLE `...be_D_御客情報_clustered`
CLUSTER BY hotel_id AS SELECT * FROM `...be_D_御客情報`;
ALTER TABLE `...be_D_御客情報` RENAME TO be_D_御客情報_old;
ALTER TABLE `...be_D_御客情報_clustered` RENAME TO be_D_御客情報;
-- ========== be_B_御客伝票 (113GB) ==========
CREATE TABLE `...be_B_御客伝票_clustered`
CLUSTER BY hotel_id AS SELECT * FROM `...be_B_御客伝票`;
ALTER TABLE `...be_B_御客伝票` RENAME TO be_B_御客伝票_old;
ALTER TABLE `...be_B_御客伝票_clustered` RENAME TO be_B_御客伝票;
-- ========== be_D_御客詳細 (82GB) ==========
CREATE TABLE `...be_D_御客詳細_clustered`
CLUSTER BY hotel_id AS SELECT * FROM `...be_D_御客詳細`;
ALTER TABLE `...be_D_御客詳細` RENAME TO be_D_御客詳細_old;
ALTER TABLE `...be_D_御客詳細_clustered` RENAME TO be_D_御客詳細;
-- ========== be_D_御客GR (77GB) ==========
CREATE TABLE `...be_D_御客GR_clustered`
CLUSTER BY hotel_id AS SELECT * FROM `...be_D_御客GR`;
ALTER TABLE `...be_D_御客GR` RENAME TO be_D_御客GR_old;
ALTER TABLE `...be_D_御客GR_clustered` RENAME TO be_D_御客GR;
-- ========== バッチ確認後に旧テーブル削除 ==========
-- DROP TABLE `...be_D_御客伝票_old`;
-- DROP TABLE `...be_D_御客情報_old`;
-- DROP TABLE `...be_B_御客伝票_old`;
-- DROP TABLE `...be_D_御客詳細_old`;
-- DROP TABLE `...be_D_御客GR_old`;
CREATE TABLE の課金: 5テーブル合計 592GB = 約$3.7の一時コスト。削減効果($100+/日)に比べれば問題なし。
Step 3: 効果確認
-- 翌日バッチ後に実行
SELECT DATE(creation_time) AS date,
COUNT(*) AS jobs,
ROUND(SUM(total_bytes_billed)/POW(1024,4), 2) AS tb,
ROUND(SUM(total_bytes_billed)/POW(1024,4)*6.25, 2) AS cost_usd
FROM `region-asia-northeast1`.INFORMATION_SCHEMA.JOBS
WHERE creation_time >= TIMESTAMP('YYYY-MM-DD')
AND job_type = 'QUERY' AND state = 'DONE'
GROUP BY date;
SELECT DATE(creation_time) AS date,
COUNT(*) AS jobs,
ROUND(SUM(total_bytes_billed)/POW(1024,4), 2) AS tb,
ROUND(SUM(total_bytes_billed)/POW(1024,4)*6.25, 2) AS cost_usd
FROM `region-asia-northeast1`.INFORMATION_SCHEMA.JOBS
WHERE creation_time >= TIMESTAMP('YYYY-MM-DD')
AND job_type = 'QUERY' AND state = 'DONE'
GROUP BY date;
スキャン量が 22TB → 1〜2TB に減っている
コストが $139 → $15〜20 に減っている
バッチの処理時間が大幅に遅くなっていない
確認OKなら _old テーブルを削除
問題発生時のロールバック: 旧テーブル(_old)をRENAMEで元に戻すだけ。即時復旧(データコピーなし)。
Confirmed
Cloud Functions管理: stable社
バッチは深夜のみ → 日中の作業で競合なし
staging環境での事前検証 → OK
stable社への依頼
1
staging で検証
上位5テーブルにクラスタリング(hotel_id)を追加。翌日バッチの正常動作を確認。
2
本番の上位5テーブルを日中に再作成
be_D_御客伝票 / be_D_御客情報 / be_B_御客伝票 / be_D_御客詳細 / be_D_御客GR
5テーブルで全体コストの76%($100/日)。
5テーブルで全体コストの76%($100/日)。
3
翌日バッチ結果を確認
スキャン量 22TB → 2〜3TB に減っていればOK。コスト $139 → $15〜20/日。