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-15中級

Gemini API を asyncio で並列化したら、壁紙アプリの多言語説明文生成が12倍速になった

Beautiful HD Wallpapers の iOS アップデート対応で Gemini API を asyncio で並列化した実体験を紹介します。12言語への順次処理を asyncio.gather() で並列化し、レート制限対策を含めた実装パターンをまとめました。

gemini-api279asyncio3python103multilingual4indie-dev21app-store7python-sdk

2026年5月、iOS 向けの4アプリ(Beautiful HD Wallpapers、浮世絵壁紙、Relaxing Healing、Law of Attraction Everyday)の大型アップデートを同時進行させていました。新しい iPhone モデルへの解像度対応、AdMob メディエーション拡張、StoreKit 2 への移行と、やることが山積みだったのですが、その中でひとつ想定外に時間を取られたのが「多言語 App Store 説明文の再生成」です。

2014年から個人アプリ開発を続けて12年、累計 5,000 万 DL 超の壁紙・癒し系アプリを運営していると、各ストアのメタデータを12言語で管理する必要があります。モデル交代やコンセプト変更のたびに全言語版を更新するのですが、Gemini API を使った順次処理のスクリプトが遅くて困っていました。asyncio で並列化したところ、処理時間が約12分の1になりました。今回はその実装と、実際にハマった落とし穴をまとめます。

順次処理の限界:12言語 × 4アプリで13分かかっていた

当初のスクリプトは単純な for ループで各言語の説明文を生成していました。12言語の App Store 説明文を順番にリクエストするだけのシンプルな実装です。

import google.generativeai as genai
import time
 
genai.configure(api_key="YOUR_GEMINI_API_KEY")
model = genai.GenerativeModel("gemini-2.5-flash")
 
LANGUAGES = [
    "Japanese", "English", "Chinese (Simplified)", "Chinese (Traditional)",
    "Korean", "French", "German", "Spanish", "Portuguese", "Italian",
    "Arabic", "Russian"
]
 
def generate_description(app_name: str, lang: str) -> str:
    prompt = f"""
    Write an App Store description for "{app_name}" in {lang}.
    Keep it under 4000 characters. Focus on beauty, relaxation, and daily use.
    """
    response = model.generate_content(prompt)
    return response.text
 
# 順次処理(遅い)
results = {}
start = time.time()
for lang in LANGUAGES:
    results[lang] = generate_description("Beautiful HD Wallpapers", lang)
    print(f"✓ {lang} 完了")
 
elapsed = time.time() - start
print(f"\n合計: {elapsed:.1f}秒")  # 実測: 約780秒(13分)

12言語で約780秒(13分)かかっていました。4アプリ分だと1時間近くになります。アップデートのたびにこれを待つのは現実的ではなく、「面倒だから後で」という先送りの温床になっていました。

asyncio と threading の違い:なぜ asyncio を選んだか

並列化の方法として threading も選択肢に入ります。ただ、Gemini API の呼び出しは CPU ではなくネットワーク I/O の待ち時間が支配的です。こういったケースでは threading よりも asyncio の方が軽量で扱いやすいと感じています。

threading はスレッドごとにメモリを消費し、GIL(グローバルインタープリタロック)の影響もあります。一方 asyncio はシングルスレッドで動作し、I/O 待ちの間に別のタスクを進める設計です。Gemini API のようなネットワーク往復が多い処理には、asyncio の方がシンプルに書けて管理もしやすいと感じています。

具体的に言うと、threading で12スレッドを立てた場合、それぞれのスレッドが独立したメモリ空間を持ち、スレッド間の同期(Lock や Queue)を手動で管理する必要があります。asyncio では Semaphore ひとつで同時実行数を制御でき、エラー処理も try/except の通常のパターンで書けます。個人開発でメンテナンスコストを低く抑えたい場合には、asyncio の方が合っていると感じています。

もう一点、Gemini API の Python SDK (google-generativeai) は generate_content_async() という非同期メソッドを公式にサポートしているため、既存の同期コードからの移行が比較的スムーズです。generate_content()generate_content_async() に置き換え、呼び出し元を async def 関数にするだけで基本的な構造は変わりません。

multiprocessing については、このケースでは不要です。CPU バウンドの処理(画像処理や数値計算など)には multiprocessing が有効ですが、API 呼び出しは I/O バウンドなので asyncio で十分です。

asyncio.gather() で並列化する

asyncio と Semaphore を使って、複数のリクエストを同時に送る実装です。Semaphore で同時リクエスト数を制限することで、レート制限への対策も同時に行います。

import asyncio
import google.generativeai as genai
import time
 
genai.configure(api_key="YOUR_GEMINI_API_KEY")
 
LANGUAGES = [
    "Japanese", "English", "Chinese (Simplified)", "Chinese (Traditional)",
    "Korean", "French", "German", "Spanish", "Portuguese", "Italian",
    "Arabic", "Russian"
]
 
async def generate_description_async(
    model: genai.GenerativeModel,
    app_name: str,
    lang: str,
    semaphore: asyncio.Semaphore
) -> tuple[str, str]:
    """
    semaphore で同時リクエスト数を制限しながら非同期生成する。
    返り値は (言語, 説明文) のタプル。
    """
    async with semaphore:
        prompt = f"""
        Write an App Store description for "{app_name}" in {lang}.
        Keep it under 4000 characters. Focus on beauty, relaxation, and daily use.
        """
        # generate_content_async は非同期版
        response = await model.generate_content_async(prompt)
        return lang, response.text
 
async def generate_all_languages(app_name: str) -> dict[str, str]:
    model = genai.GenerativeModel("gemini-2.5-flash")
    # 同時リクエスト数を5に制限(RPM超過を防ぐ)
    semaphore = asyncio.Semaphore(5)
 
    tasks = [
        generate_description_async(model, app_name, lang, semaphore)
        for lang in LANGUAGES
    ]
 
    # 全タスクを並列実行
    results = await asyncio.gather(*tasks, return_exceptions=True)
 
    output = {}
    for result in results:
        if isinstance(result, Exception):
            print(f"❌ エラー: {result}")
        else:
            lang, text = result
            output[lang] = text
            print(f"✓ {lang} 完了")
    return output
 
# 実行
start = time.time()
results = asyncio.run(generate_all_languages("Beautiful HD Wallpapers"))
elapsed = time.time() - start
print(f"\n合計: {elapsed:.1f}秒")  # 実測: 約65秒(1分5秒)

実測で約65秒になりました。780秒 → 65秒、約12倍の高速化です。

落とし穴1:429 エラー(レート制限)への対処

並列化するとレート制限(429 Too Many Requests)に当たる頻度が上がります。Gemini API の無料枠は RPM(リクエスト/分)の上限が比較的低く、特に注意が必要です。実際に運用していて、Semaphore の値を10に設定したところ、複数言語分の結果が ResourceExhausted エラーになる場面がありました。

以下は指数バックオフ付きのリトライ実装です。リトライ間隔にランダム値(ジッター)を加えることで、複数タスクが同時にリトライして再び詰まる「サンダリングハード問題」を避けられます。

import asyncio
import random
import google.generativeai as genai
from google.api_core import exceptions as google_exceptions
 
async def generate_with_retry(
    model: genai.GenerativeModel,
    prompt: str,
    semaphore: asyncio.Semaphore,
    max_retries: int = 3
) -> str:
    """
    レート制限時に指数バックオフでリトライする。
    """
    async with semaphore:
        for attempt in range(max_retries):
            try:
                response = await model.generate_content_async(prompt)
                return response.text
            except google_exceptions.ResourceExhausted as e:
                if attempt == max_retries - 1:
                    raise
                # ジッターを加えた指数バックオフ
                wait = (2 ** attempt) + random.uniform(0, 1)
                print(f"⚠️ レート制限: {wait:.1f}秒待機後リトライ")
                await asyncio.sleep(wait)
            except Exception as e:
                raise

Semaphore の最適値は利用プランによって変わります。無料枠(15 RPM)では Semaphore(3) 程度が安定していました。有料プランでは5で問題ありませんでした。

落とし穴2:asyncio.gather() のエラー処理

asyncio.gather()return_exceptions=True を指定しないと、最初の例外が発生した時点で全タスクが中断されます。12言語のうち1言語が失敗しただけで、残り11言語の結果が全て失われてしまいます。

# ❌ 危険:一つのエラーで全部止まる
results = await asyncio.gather(*tasks)
 
# ✅ 安全:エラーも返り値として受け取る
results = await asyncio.gather(*tasks, return_exceptions=True)
 
# 結果を安全に処理
for result in results:
    if isinstance(result, Exception):
        print(f"スキップ: {result}")
    else:
        lang, text = result
        output[lang] = text

失敗した言語は後から個別に再実行する設計にしておくと、運用が楽になります。

4アプリ分を一括処理する

最終的に4アプリ × 12言語 = 48リクエストを一括処理するスクリプトに仕上げました。

APPS = [
    "Beautiful HD Wallpapers",
    "Ukiyo-e Wallpapers",
    "Relaxing Healing",
    "Law of Attraction Everyday"
]
 
async def generate_all_apps():
    model = genai.GenerativeModel("gemini-2.5-flash")
    semaphore = asyncio.Semaphore(5)  # 全アプリ合計で5並列
 
    all_tasks = []
    for app in APPS:
        for lang in LANGUAGES:
            all_tasks.append(
                generate_description_async(model, app, lang, semaphore)
            )
 
    results = await asyncio.gather(*all_tasks, return_exceptions=True)
    # ... 結果を {app: {lang: text}} 形式の辞書に整理して返す

48リクエストを一括処理しても実測で約90〜120秒で完了しました。順次処理なら52分かかる計算だったので、体感としては全く別のツールになりました。

実運用での注意:生成結果の品質確認を省略しない

高速化の副作用として「結果を確認せずに使ってしまう」罠があります。順次処理で1言語ずつ確認しながら進めていた頃は、おかしな表現に気づきやすかったのですが、一括生成では画面をスクロールするだけになりがちです。

私の場合、特に以下の言語で細かい確認が必要でした。

アラビア語(Arabic)は右から左に書く言語のため、App Store で表示したときの折り返しが他言語と異なります。生成された説明文が文法的に正しくても、実際のストアページでの見た目の確認は必要です。中国語繁体字(Traditional Chinese)は、簡体字で生成された後に変換されたような表現になることがあり、台湾のストアで使うには少しぎこちない場合があります。

これらの確認作業自体は並列化で高速化できるものではありませんが、「13分待ってやっと出た結果を確認する」のと「65秒で出た結果を確認する」のでは、心理的な余裕が全然違います。速くなったことで、確認に時間をかけられるようになりました。

また、生成結果をファイルに保存しておくことも重要です。Gemini API の応答は毎回微妙に異なるため、一度良い結果が出たらそれをバージョン管理しておくことをお勧めします。私は app_descriptions/{app_name}/{lang}/v{date}.txt という形式で保存し、更新時に前回との diff を確認するフローにしています。

個人開発者が Gemini API の asyncio を使う上で感じていること

宮大工だった祖父が「手を動かすことが一つの信心」と言っていました。開発でも同じだと感じています。13分待つスクリプトは「後でやろう」になり、1分のスクリプトは「今すぐやろう」になります。この違いが、アップデートの品質に影響します。

並列化してから、各言語の説明文を丁寧に確認・調整できるようになりました。特に Arabic と中国語繁体字は微妙なニュアンスの調整が必要で、以前は「時間がないから今回はこれで」と妥協していました。処理が速くなると、その妥協がなくなります。

Gemini API は generate_content_async() を公式サポートしているため、既存の同期コードからの移行は思ったより少ない変更で済みます。まず1〜3言語の小さなバッチで試してから、徐々に並列数を増やしていくのがお勧めです。


asyncio の基礎については Gemini API Python SDK 公式ドキュメント のサンプルが参考になります。Python の非同期処理をさらに深く

シェア

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

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

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

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

API / SDK2026-05-16
App Store / Google Play のレビュー返信を Gemini API で自動化したときにはまった「8秒ルール」
5,000万DLのアプリ群を運営する中でGemini APIによるレビュー返信自動化を試みた実装記録。多言語対応の設計と、App Storeで8秒以上待機しないとペナルティを受けるという落とし穴を共有します。
API / SDK2026-07-04
Gemini API の英語出力に日本語が『たまに』混ざるとき — 混入率を計測して段階的に締める運用メモ
英語出力を指示したGemini APIが、100回に数回だけ日本語を混ぜてくる——この『たまに』を止められない本当の理由と、混入率をSLOとして計測し、段階的リカバリで本番品質まで締める運用パターンを実装コード付きで整理します。
API / SDK2026-05-29
Embedding APIでアプリレビュー1万件を意味クラスタリングして改善優先度を割り出す設計
5,000万DLを支える壁紙アプリで貯まり続けるレビューを、Gemini Embeddingで意味クラスタに束ねて改善優先度を割り出す。実運用で固まった3層パイプラインとコスト設計の勘所を残します。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →