# Cloud Functions から月 4,300 円のサーバーへ移した：1 日 3,700 円の課金、EEA で全滅した Yahoo!、移行で踏んだ 5 つの罠

> 無料のお試しが終わると 1 日約 3,700 円（月約 10.6 万円）かかった。待ち時間にも課金される構造が原因で、コードをほぼ書き換えずにシンガポールの VPS（月約 4,300 円）へ移した。ヘルシンキでは Yahoo! が全滅、秘密の値は 5 本でなく 15 本だった。

- URL: https://koshinstudio.com/notes/vps-migration/
- 公開: 2026-10-17
- 書いた人: Koshin（https://koshinstudio.com/about/）
- サイト: Koshin Studio

  English version (https://koshinstudio.com/en/notes/vps-migration/)

トレカダッシュ 開発の記録 ／ 2026年8月

  iOS アプリ「トレカダッシュ (https://koshinstudio.com/works/torecadash/)」の裏側は、もともと、Google の Cloud Functions（関数ごとに、動いた時間だけ課金されるサービス）で動いていました。2026 年 8 月 16 日に、無料のお試しの期間が終わると、8 月 17 日から、1 日約 3,700 円（月に直すと約 106,000 円）の実費がかかり始めました。

  この記事は、そのコードをほとんど書き換えずに、月約 4,300 円のサーバー（VPS）に移した記録です。決めたこと、あとで直したこと、踏んだ罠を書きます。

  数字は、2026 年 8 月 20 日の作業の記録から書いています。サーバーの住所や接続の情報は、書いていません。

## なぜ移したか：待ち時間にも、料金がかかっていた

  Cloud Functions の料金のうち、約 97% が、CPU の時間（vCPU 秒）でした。ところが、このアプリの関数は、ほとんど全部が、スクレイピング先や AI の返事を待っているだけです。その待ち時間も、フルに課金されていました。

  定額のサーバーに移せば、この構造的な無駄は消えます。

  Cloudflare Workers も検討しました。ただ、Firestore の管理用の道具（Admin SDK）が動かず、数百か所を別の API に書き換える必要があったので、やめました。VPS なら、今のコードが、そのまま動きます。

## 選んだもの、と、最初に間違えた場所

    項目 | 内容

      提供元 | Hetzner Cloud

      スペック | 2 vCPU、4GB メモリ、80GB SSD

      場所 | シンガポール

      料金 | 月 €26.99（約 4,300 円）

  最初は、ヘルシンキ（月 €5.99）に作りました。そして、作り直しました。

  理由は、Yahoo! ショッピングの API が、欧州経済領域（EEA）とイギリスからのアクセスを遮断していることでした。ヘルシンキから叩くと、API の返事ではなく、「EEA およびイギリスからご利用のお客様へ」という案内のページが、HTTP 403 で返ります。15 ジャンルが全滅しました。これは、ボット対策ではなく、法的な理由の地域ブロックなので、何度試しても、通りません。シンガポールからは、HTTP 200 で、普通に取れます。

  ヘルシンキのサーバーは削除し、かかった実費は €0.02（約 3 円）でした。

  Hetzner の料金は、欧州の 3 拠点だけ、月 €7 の割引があります。さらに、いちばん安い種類（CX シリーズ）は、欧州でしか売っていません。つまり、「安い場所 ＝ EEA ＝ Yahoo! が死ぬ場所」という関係です。安さで、欧州に戻してはいけません。

  シンガポールにした副次効果もありました。日本のサイトが近くなり、ポケモンカードの新着チェックが、52.7 秒から 15.8 秒（3.3 倍速）になりました。

## コードを書き換えずに動かす：require を横取りする

  実行役のプログラム（runner.js）を作りました。もとのコード（index.js）は、一切書き換えていません。

  定期実行の定義 132 個を、手で書き写す代わりに、require（読み込み）を横取りして、firebase-functions を、自分のものに差し替えました。定期実行を登録する呼び出しを、「一覧に登録するだけの関数」にして、そのあとで、もとのコードを読み込みます。すると、全部の定義が、自動で集まります。回収した数は、次のとおりです。

  定期実行 132 件 / HTTP のエンドポイント 256 件 / Firestore のトリガー 9 件
  手書きしなくて、正解でした。ある定期実行の間隔は、`every ${X_CHECK_INTERVAL_MINUTES} minutes` というテンプレート文字列で書かれています。目で書き写していたら、間違えていたはずです。132 件を機械的に突き合わせて、全件一致を確かめました。

  Firestore のトリガー（データの変化で動く関数）9 件は、VPS では発火しません。これらは、Cloud Functions 側に残しました（イベントで動くので、費用はほぼゼロです）。HTTP のエンドポイント 256 件も、アプリと管理画面が叩いているので、Cloud Functions に残しました。

## 移行で踏んだ 5 つの罠

### 1. 秘密の値は、5 本ではなく 15 本だった

  移行前の記録には、「秘密の値は 5 本」と書いてありました。実際にコードの中の環境変数を全部数えると、15 本ありました。足りていなかった 10 本は、楽天、Google マップ、Yahoo!、X の認証、課金の連携、会員サイトのセッション、デバッグ用などです。

  移し忘れは、エラーにならず、静かに、取得が 0 件になるので、気づきにくいものです。環境変数の名前を、コードから機械的に抜き出して数えます。

### 2. 秘密の値の中に、改行が入っていた

  会員サイトのセッションの値は、「1 行に 1 つのサイト」の形式で、値そのものに改行が入っています。素直に設定ファイルへ書くと、2 行目以降が、別の変数として扱われ、4 つのサイトのうち 3 つが、静かに死にます。設定ファイルの読み込みを、改行の書き方（\n）に対応させました。

### 3. 書き込み禁止の試運転で、無限ループになった

  楽天や Yahoo! の API には、呼び出しの間隔を守るための待ち（レート制限）があります。「最後に呼んだ時刻」を Firestore に書き、それが進むまで、ずっと待つ作りです。試運転で、書き込みを全部握りつぶすと、時刻が永遠に進まず、ループから抜けられません。（しかも、Cloud Functions のほうが 1.5 秒ごとに時刻を更新し続けるので、「他が更新したから、まだ待つ」を、無限に繰り返しました。楽天市場のジョブが、15 分以上帰ってこなくなって、発覚しました。）試運転でも、この待ちの記録だけは、書き込みを通すようにしました。

### 4. 「書き込みあり・通知なし」は、通知を静かに消す

  通知をまとめて送る処理は、送れたと見なすと、送信待ちの一覧を削除します。通知だけを偽物にして、書き込みを許すと、送らずに消すことになり、通知が失われます。そこで、試運転の段階では、通知に関わる 7 つの定期実行を、VPS 側に、登録しないようにしました。外してよいのは、通知を ON にするときだけです。

### 5. 出口の IP が変わると、取得が 1 つ、静かに止まる

  あるスーパーのサイトは、素のリクエストで取得していて、予備の手段がありませんでした。日本からは 200、シンガポールからは 403 になりました。全 75 ホストを、日本とシンガポールの両方から叩いて突き合わせると、「日本で見えて、シンガポールで見えない」のは 6 ホスト。そのうち 5 つは、もともと取得サービス経由か、予備つきで無事でした。壊れたのは 1 件だけでした。「シンガポールから 403」だけでは、壊れている証拠になりません。取得サービス経由かどうかを、必ず確かめます。

## 移行の順番：通知を二重に送らないために

- 段階 1：読むだけ（書き込みも通知もしない）で、取得の件数を、Cloud Functions と比べる。

- 段階 2：書くが、通知は Cloud Functions に任せる。

- 段階 3：Cloud Functions の、スケジュール関数 132 個だけを削除する。

- 段階 4：VPS を、通知 ON に切り替える。

  どちらか一方だけが、通知を送る状態を、常に保ちます。同時に両方が送ると、利用者に、同じ通知が 2 回届きます。

## 分かったこと

> 待つだけの処理に、時間で課金されるサービスは、合わない。

- 課金の単位を見る。待ち時間にも課金される構造だと、スクレイピングや AI の呼び出しは、割高になります。

- 置き場所には、法的な制約がある。安い場所が、使えない場所のこともあります。

- コードを書き換えずに移す道を探す。横取りと機械的な突き合わせで、手書きの誤りを避けました。

- 設定の数は、機械で数える。5 本ではなく 15 本でした。

- 段階を踏んで、通知の二重送信を避ける。

  この話に出てくるもの

- トレカダッシュ (https://koshinstudio.com/works/torecadash/) — トレカの抽選・予約・先着の情報を集める iPhone アプリ

- 配備のたびに、日次の処理が丸ごと走り直していた (https://koshinstudio.com/notes/rerun-on-deploy/) — 移行後の VPS での失敗

- 同時実行の枠を増やす前に見るもの (https://koshinstudio.com/notes/concurrency-slots/) — VPS の 2 コアの話

- Firestore の費用を円で出したら、読み取りの 91% が 1 か所だった (https://koshinstudio.com/notes/firestore-91/) — 別の費用の話
