GEMINI LABEN
NANOLITE — Nano Banana 2 Liteが登場しました。Googleで最も速く、最もコスト効率の高いGemini Imageモデルで、軽量な画像生成を安く回したい用途に向いていますOMNIFLASH — Gemini Omni Flashがpublic previewになりました。ネイティブにマルチモーダルなモデルで、企業や開発者が独自の動的な動画ワークフローを構築できますAGENTS — Managed Agentsが拡張されました。background: trueでサーバー側の非同期実行とポーリング、リモートMCPサーバー連携、対話をまたぐ認証情報のリフレッシュに対応しますMEMORY — Memory BankのIngestEvents APIが一般提供になりました。イベントの取り込みとメモリ生成を分離し、コンテンツを継続的にストリームできますTHROUGHPUT — Provisioned Throughputで、同一モデル・同一リージョンに対して最大7件の保留オーダーを提出できるようになりましたDEPRECATE — 画像生成モデルは8月17日に、Gemini Enterprise Agent PlatformのGrok 4.1系は8月20日に停止される予定ですNANOLITE — Nano Banana 2 Liteが登場しました。Googleで最も速く、最もコスト効率の高いGemini Imageモデルで、軽量な画像生成を安く回したい用途に向いていますOMNIFLASH — Gemini Omni Flashがpublic previewになりました。ネイティブにマルチモーダルなモデルで、企業や開発者が独自の動的な動画ワークフローを構築できますAGENTS — Managed Agentsが拡張されました。background: trueでサーバー側の非同期実行とポーリング、リモートMCPサーバー連携、対話をまたぐ認証情報のリフレッシュに対応しますMEMORY — Memory BankのIngestEvents APIが一般提供になりました。イベントの取り込みとメモリ生成を分離し、コンテンツを継続的にストリームできますTHROUGHPUT — Provisioned Throughputで、同一モデル・同一リージョンに対して最大7件の保留オーダーを提出できるようになりましたDEPRECATE — 画像生成モデルは8月17日に、Gemini Enterprise Agent PlatformのGrok 4.1系は8月20日に停止される予定です
記事一覧/API / SDK
API / SDK/2026-05-19上級

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

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

Gemini API191Files API5Cloudflare R2個人開発91画像処理2壁紙アプリ7

プレミアム記事

アーティスト・クリエイターの廣川政樹です。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-06-28
Gemini API で動画を「時刻つき」で読む — 該当シーンだけを引き出す
画面録画やアプリのデモ動画から「あの操作はどこ?」を探すのは骨が折れます。Gemini API の動画理解を使い、タイムスタンプ付きで該当箇所だけを引き出す方法と、FPS・解像度でトークンを抑える設計をまとめます。
API / SDK2026-06-03
Gemini Files API の孤児ファイルを棚卸しする — 多アプリ運用の照合と自動クリーンアップ設計
Files API にアップロードしたファイルは48時間で静かに消えます。多アプリ運用で発生する孤児ファイルとクォータ消費を、自前DBとの照合と定期クリーンアップで統制する本番設計を、壁紙アプリ運営の実装メモとしてまとめました。
API / SDK2026-05-03
Gemini Vision APIで壁紙アプリの画像を自動カテゴリ分類する — 3000枚を30カテゴリに処理した実践記録
壁紙アプリの画像3000枚を手動で分類するのは現実的ではありません。Gemini Vision APIを使った自動カテゴリ分類パイプラインの設計と実装、コスト分析まで、実際に動かして見えてきたことをすべて書きます。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →