GEMINI LABJP
NANOLITE — Nano Banana 2 Lite is here: Google's fastest and most cost-efficient Gemini Image model, made for running lightweight image generation cheaplyOMNIFLASH — Gemini Omni Flash is in public preview, a natively multimodal model that lets enterprises and developers build custom, dynamic video workflowsAGENTS — Managed Agents expand with background: true for async server-side runs and polling, remote MCP server integration, and refreshing credentials across interactionsMEMORY — The Memory Bank IngestEvents API is generally available, decoupling event ingestion from memory generation so you can stream content continuouslyTHROUGHPUT — Provisioned Throughput now lets you submit up to seven pending orders for the same model and regionDEPRECATE — Image generation models shut down on August 17, and the Grok 4.1 family on the Gemini Enterprise Agent Platform on August 20NANOLITE — Nano Banana 2 Lite is here: Google's fastest and most cost-efficient Gemini Image model, made for running lightweight image generation cheaplyOMNIFLASH — Gemini Omni Flash is in public preview, a natively multimodal model that lets enterprises and developers build custom, dynamic video workflowsAGENTS — Managed Agents expand with background: true for async server-side runs and polling, remote MCP server integration, and refreshing credentials across interactionsMEMORY — The Memory Bank IngestEvents API is generally available, decoupling event ingestion from memory generation so you can stream content continuouslyTHROUGHPUT — Provisioned Throughput now lets you submit up to seven pending orders for the same model and regionDEPRECATE — Image generation models shut down on August 17, and the Grok 4.1 family on the Gemini Enterprise Agent Platform on August 20
Articles/API / SDK
API / SDK/2026-05-15Intermediate

Making Gemini API 12x Faster with asyncio: Lessons from Multilingual App Store Generation

A real-world account of parallelizing Gemini API calls with asyncio during the iOS update of Beautiful HD Wallpapers. Learn how asyncio.gather() with rate limit handling cut multilingual generation from 13 minutes to 65 seconds.

gemini-api277asyncio3python104multilingual8indie-dev43app-store7python-sdk

In May 2026, I was simultaneously pushing major updates across four iOS apps — Beautiful HD Wallpapers, Ukiyo-e Wallpapers, Relaxing Healing, and Law of Attraction Everyday. The update involved new iPhone resolution support, AdMob mediation expansion, and StoreKit 2 migration. Amid all that, one task turned out to be an unexpected time sink: regenerating App Store descriptions across 12 languages.

When you're running apps with over 50 million cumulative downloads across the wallpaper and wellness categories, keeping metadata updated in 12 languages is just part of the job. But the sequential script I had been using was painfully slow. Switching to asyncio with the Gemini API cut processing time to roughly one-twelfth of what it was. Here's how I did it.

The Problem: 12 Languages × 4 Apps = 13 Minutes of Waiting

My original script used a simple for loop to generate descriptions one language at a time.

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
 
# Sequential — slow
results = {}
start = time.time()
for lang in LANGUAGES:
    results[lang] = generate_description("Beautiful HD Wallpapers", lang)
    print(f"✓ {lang} done")
 
elapsed = time.time() - start
print(f"\nTotal: {elapsed:.1f}s")  # Measured: ~780 seconds (13 minutes)

About 780 seconds for 12 languages. Multiply that by 4 apps and you're looking at nearly an hour per update cycle — not a workflow I wanted to repeat.

Parallelizing with asyncio.gather()

Python's asyncio combined with the Gemini SDK's async method (generate_content_async) lets you fire off multiple requests simultaneously. The key is using a Semaphore to cap concurrency and avoid rate limit errors.

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]:
    """
    Generate a description with concurrency limited by semaphore.
    Returns (language, description) tuple.
    """
    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.
        """
        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")
    # Cap at 5 concurrent requests to stay within RPM limits
    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"❌ Error: {result}")
        else:
            lang, text = result
            output[lang] = text
            print(f"✓ {lang} done")
    return output
 
start = time.time()
results = asyncio.run(generate_all_languages("Beautiful HD Wallpapers"))
elapsed = time.time() - start
print(f"\nTotal: {elapsed:.1f}s")  # Measured: ~65 seconds

The measured result was around 65 seconds — down from 780. That's roughly a 12x speedup.

Pitfall 1: 429 Rate Limit Errors Under Concurrency

The more you parallelize, the more you'll see ResourceExhausted (429) errors. The Gemini API free tier has a relatively low RPM cap, and even paid tiers have limits that matter when you're firing 48 requests in parallel.

During my own runs, I hit rate limits when I set the Semaphore too high. Here's the exponential backoff retry wrapper I settled on:

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:
    """
    Retry with jittered exponential backoff on rate limit errors.
    """
    async with semaphore:
        for attempt in range(max_retries):
            try:
                response = await model.generate_content_async(prompt)
                return response.text
            except google_exceptions.ResourceExhausted:
                if attempt == max_retries - 1:
                    raise
                wait = (2 ** attempt) + random.uniform(0, 1)
                print(f"⚠️ Rate limit hit — retrying in {wait:.1f}s")
                await asyncio.sleep(wait)

For the free tier (15 RPM), keep your Semaphore at 3–4. On paid plans, 5 has been stable for me. Tuning this number is more important than it looks.

Pitfall 2: Error Handling with asyncio.gather()

By default, asyncio.gather() without return_exceptions=True will propagate the first exception and cancel remaining tasks. That means one failed language kills the whole batch.

# ❌ Dangerous: one error stops everything
results = await asyncio.gather(*tasks)
 
# ✅ Safe: errors are returned as values, not raised
results = await asyncio.gather(*tasks, return_exceptions=True)
 
for result in results:
    if isinstance(result, Exception):
        print(f"Skipping due to error: {result}")
    else:
        lang, text = result
        output[lang] = text

With 12 languages in flight, I'd rather get 11 successful results and log one failure than lose everything to a single timeout.

Running All 4 Apps in One Pass

The final version handles all 4 apps at once — 48 requests total.

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)
 
    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)
    # Organize results into {app: {lang: text}} structure

In practice, 48 requests complete in 90–120 seconds. The sequential equivalent would have been over 50 minutes.

What This Means for Independent Developers

I started building apps independently in 2013 and have been maintaining them for over 12 years. One thing I've learned is that script performance directly affects motivation.

A script that takes 13 minutes becomes something you put off. A script that takes 65 seconds becomes something you run frequently — and when you run it frequently, you actually review the output and make it better. After parallelizing, I started checking each language's description individually and made small quality improvements I would have skipped before.

The asyncio approach isn't magic, but it removes friction. That reduction in friction compounds over time.

If you're already using the Gemini API synchronously, the migration path is straightforward: replace generate_content() with generate_content_async(), wrap your calls in an async def, add a Semaphore, and use asyncio.gather(). Most of the logic stays the same.

Start with a small batch — 3 or 4 tasks — confirm it works, then scale up.


Reference: The Gemini API Python SDK documentation includes async examples.

Share

Thank You for Reading

Gemini Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

API / SDK2026-05-18
Gemini API asyncio Patterns for Production: How I Cut Processing Time by 80% in My Indie App Backend
A hands-on report on integrating Gemini API asyncio into a production backend. Covers Semaphore-based rate limiting, exponential backoff, and partial failure handling from real experience building a 50M+ download wallpaper app.
API / SDK2026-05-16
Automating App Store and Google Play Review Replies with Gemini API — The 8-Second Rule I Discovered the Hard Way
A practical implementation record of automating multilingual app review replies using Gemini API, based on managing apps with 50M+ cumulative downloads. Covers the undocumented 8-second wait rule that Apple's API silently enforces.
API / SDK2026-07-04
When Gemini API Leaks Japanese Into Your English Output Once in a While — Field Notes on Measuring the Contamination Rate and Tightening It in Stages
You told Gemini to answer in English, and 3 out of 100 runs slip a Japanese sentence into the tail. Here is why you cannot stop that 'once in a while', and a production pattern that measures the contamination rate as an SLO and tightens it with graded recovery, with working code.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →