◉GEMINI LABEN
●ARGON — gemini-4-argon が 10/13 に GA。複雑なコーディング・エージェント・数学・科学向けです●DEEP RES — deep-research-pro-preview-12-2025 は 10/23 に停止、残り6日。移行先は deep-research-preview-04-2026●NANO — gemini-nano-banana-2.1 が GA(10/06)。gemini-3.1-flash-image は非推奨で停止日は未発表です●MCP — ツール結果が 8.5万トークンに膨らんだ事例を、ADK で 310 トークンに包んで抑えた報告が出ています●FLASH — gemini-3.7-flash と 3.5-flash は新しい Flash モデルに自動で回されます(10/08)●NEW — クラッシュ報告を毎回ゼロから読ませない — 既知バグ早見表を先に grep し…●ARGON — gemini-4-argon が 10/13 に GA。複雑なコーディング・エージェント・数学・科学向けです●DEEP RES — deep-research-pro-preview-12-2025 は 10/23 に停止、残り6日。移行先は deep-research-preview-04-2026●NANO — gemini-nano-banana-2.1 が GA(10/06)。gemini-3.1-flash-image は非推奨で停止日は未発表です●MCP — ツール結果が 8.5万トークンに膨らんだ事例を、ADK で 310 トークンに包んで抑えた報告が出ています●FLASH — gemini-3.7-flash と 3.5-flash は新しい Flash モデルに自動で回されます(10/08)●NEW — クラッシュ報告を毎回ゼロから読ませない — 既知バグ早見表を先に grep し…
記事一覧/API / SDK
◈ API / SDK/2026-05-19上級

Gemini Files API と Cloudflare R2 を組み合わせた画像処理パイプライン設計 — 壁紙アプリ運営の実装メモ

Gemini Files API と Cloudflare R2 を組み合わせ、壁紙アプリの画像処理パイプラインを再設計した30日の記録です。48時間制限・冪等性・コスト監視の落とし穴と対処を、実装コードと数値で整理しました。

Gemini API248Files API5Cloudflare R2個人開発121画像処理3壁紙アプリ8

✦ プレミアム記事

アーティスト・クリエイターの廣川政樹です。2013年から個人開発で続けている壁紙アプリは、累計5,000万ダウンロードを超えたあたりから、新規画像の受け入れ本数が想像以上に増えていきました。1日に数百枚単位で画像が追加され、その都度メタデータの抽出やキャッチコピーの生成を回す必要が出てきます。

最初は素朴に Gemini API に画像を都度アップロードして処理していたのですが、48時間でファイルが消える仕様と、リトライ時に同じファイルを何度も上げ直してしまうコストが、月末に小さくない金額になって戻ってきました。そこから1ヶ月かけて、Cloudflare R2 と Gemini Files API を組み合わせた画像処理パイプラインに作り直しました。本稿はその設計の記録です。

始まりは「同じ画像を3回アップロードしていた」と気づいた日

2026年3月、壁紙アプリの管理画面に小さな違和感がありました。新規画像が1日あたり400枚前後追加されているのに、Gemini Files API の課金明細を見ると、その3倍近いアップロードが計上されていたのです。

原因はすぐに分かりました。Gemini Files API はアップロードしたファイルが48時間で自動削除されます。私の旧パイプラインは「処理中にエラーが起きたら全部リトライ」というナイーブな実装だったため、わずかなネットワークエラーや一時的な5xx で、同じ画像が何度も再アップロードされていました。

このとき気づいたのは、Files API は「永続ストレージ」ではなく「処理セッション用の一時領域」として扱うべきだという当たり前の事実でした。永続化は別レイヤに寄せるべきで、その役割に Cloudflare R2 がよく合います。

Files API と R2 をどう役割分担したか

最終的に落ち着いた構成は、次のような二段ストレージです。

層役割寿命単価の感覚
Cloudflare R2画像本体の永続化・配信永続egress 無料、保存¥2台/GB月
Gemini Files APIGemini 処理セッション用の一時アップロード48時間処理コール時のみ課金

設計上の原則は3つに絞りました。

  1. 画像本体は必ず先に R2 へ保存し、R2 上の object key を「真実の在処」とする
  2. Gemini の処理が必要になったタイミングで初めて Files API に上げ、結果が返ったら使い捨てる
  3. リトライは Files API の file_uri をキャッシュしてから行い、48時間以内なら再アップロードしない

この方針で書き直したアップロードラッパーは次のような形になりました。

// src/lib/image-pipeline/upload.ts
import { GoogleGenerativeAI } from "@google/generative-ai";
import { createHash } from "node:crypto";
 
interface UploadResult {
  fileUri: string;       // Gemini Files API の参照
  fileHash: string;      // SHA-256(本体)
  expiresAt: number;     // UnixMS
  r2Key: string;         // 永続側の object key
}
 
export async function ensureGeminiFile(
  bytes: Uint8Array,
  mimeType: string,
  kv: KVNamespace,        // Cloudflare KV(メタデータ)
  bucket: R2Bucket,       // Cloudflare R2(本体)
  apiKey: string,
): Promise<UploadResult> {
  const fileHash = sha256(bytes);
  const cacheKey = `gemini-file:${fileHash}`;
  const r2Key = `originals/${fileHash}.bin`;
 
  // 1) R2 永続層に未保存なら保存(put は idempotent)
  if (!(await bucket.head(r2Key))) {
    await bucket.put(r2Key, bytes, { httpMetadata: { contentType: mimeType } });
  }
 
  // 2) Files API キャッシュを KV から復元
  const cached = await kv.get<UploadResult>(cacheKey, "json");
  if (cached && cached.expiresAt > Date.now() + 5 * 60_000) {
    return cached; // 48h 残り5分以上あれば再利用
  }
 
  // 3) Files API へアップロード(必要なときだけ)
  const genAI = new GoogleGenerativeAI(apiKey);
  const file = await genAI.files.upload({
    file: new Blob([bytes], { type: mimeType }),
    config: { displayName: fileHash },
  });
 
  const result: UploadResult = {
    fileUri: file.uri,
    fileHash,
    expiresAt: Date.now() + 47 * 60 * 60_000, // 47h で期限切れ扱い
    r2Key,
  };
  await kv.put(cacheKey, JSON.stringify(result), { expirationTtl: 47 * 3600 });
  return result;
}
 
function sha256(b: Uint8Array): string {
  return createHash("sha256").update(b).digest("hex");
}

R2 への保存はハッシュ名で完全に冪等になっており、Files API の方は KV に 47時間 TTL で参照を残しています。アップロード時間のばらつきを考慮して 48 ではなく 47 に丸めているのが地味なポイントです。残り時間が薄いキャッシュを掴むと、Gemini を呼んだ瞬間に「ファイルが見つからない」というエラーになります。

✦

ここまでお読みいただきありがとうございます。

この記事の続きを読む

この先には、実装コードやベンチマーク結果など、実務でお役に立てる内容をご用意しています。このサイトは広告を掲載しておらず、サーバーや開発にかかる費用はメンバーの皆様のご支援で成り立っています。もしお役に立てていましたら、ご支援いただけますと大変ありがたいです。

この記事で得られること
✦Files API の48時間ファイル寿命を前提にした R2 二段構成の役割分担
✦アップロード冪等性とリトライをハッシュキーで担保する実装パターン
✦失敗時の再アップロードコストを月¥3,000台に抑えるレート制御とコスト監視の組み立て方
Stripe による安全な決済 · いつでもキャンセル可能
✦

この記事を購入する

この先の内容をすべてお読みいただけます。一度のご購入で、いつでも何度でもアクセスできます。このサイトは広告を掲載しておらず、皆さまのご支援がサーバー費用などの運営を支えています。

または
メンバーシップなら全記事が読み放題 →
シェア

お読みいただきありがとうございます

Gemini Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • ✦コピー&ペーストで使える実装コード付き
  • ✦毎日新しい上級ガイドを追加
  • ✦¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

関連記事

◈ API / SDK2026-09-29
『F6号』と『A4くらい』が同じ列に並ぶ作品台帳を、Gemini の読み取りと号数表の換算で cm に揃えるまで
作家さんの作品台帳に『F6号』『41×31.8cm』『A4くらい』が混ざっていました。Gemini API には読み取りだけを頼み、換算は号数表、向きは画像で決める小さな道具を、動くコードとともに一本ずつ組み立てます。
◈ API / SDK2026-09-11
文字起こしの固有名詞が直らないとき、カスタム語彙に何を入れて何を諦めるか
gemini-3.5-transcribe の custom_vocabulary は最大1,000語ですが、枠を埋めることが目的ではありません。誤認ログの正解と出力の対から、枠を渡す語だけを選び直す60行のスクリプトと、その判断基準をまとめます。
◈ API / SDK2026-09-08
値上げの日をまたぐ費用の見積もりを、はじめから二段で書く
Gemini Flash の導入価格は2026年12月31日で終わり、2027年1月1日から標準価格に切り替わります。世代を据え置いても避けられないこの日を、はじめての費用見積もりでどう扱うか。動くコードとともに書き残します。