Background
When serpbase calls fail, three mainstream strategies: fail-fast, retry, fallback. Wrong choice makes users wait or get errors.
1. fail-fast (Fast Failure)
import requests
def search_serp_failfast(query):
r = requests.post(
"https://api.serpbase.dev/google/search",
headers={"X-API-Key": "sk_xxx"},
json={"q": query, "gl": "us", "num": 5},
timeout=5,
)
r.raise_for_status()
return r.json()
try:
data = search_serp_failfast("python async")
except requests.exceptions.Timeout:
return "SERP timeout, try different query"
except requests.exceptions.HTTPError as e:
if e.response.status_code == 429:
return "SERP rate limited, try later"
return f"SERP error: {e.response.status_code}"
Pros: Simple, immediate failure, user-aware
Cons: No retry, network jitter shows errors
2. retry (Retry)
import time
import random
import requests
def search_serp_retry(query, max_retries=3):
for attempt in range(max_retries):
try:
r = requests.post(
"https://api.serpbase.dev/google/search",
headers={"X-API-Key": "sk_xxx"},
json={"q": query, "gl": "us", "num": 5},
timeout=5,
)
r.raise_for_status()
return r.json()
except (requests.exceptions.Timeout, requests.exceptions.HTTPError) as e:
if isinstance(e, requests.exceptions.HTTPError):
if 400 <= e.response.status_code < 500:
raise
if attempt < max_retries - 1:
wait = (2 ** attempt) + random.uniform(0, 1)
time.sleep(wait)
data = search_serp_retry("python async")
Pros: Tolerates network jitter
Cons: Adds latency (2-3 retries)
3. fallback (Degradation)
import requests
CACHE = {}
def search_serp_fallback(query):
try:
r = requests.post(
"https://api.serpbase.dev/google/search",
headers={"X-API-Key": "sk_xxx"},
json={"q": query, "gl": "us", "num": 5},
timeout=5,
)
r.raise_for_status()
data = r.json()
CACHE[query] = data
return data
except Exception:
if query in CACHE:
return CACHE[query]
return {"organic": [], "_stale": True}
Pros: Service never interrupted
Cons: Data may be stale
4. 3 Strategies Compared
| Dimension | fail-fast | retry | fallback |
|---|---|---|---|
| User perception | Immediate failure | Slow 2-3s | Slow 5s, but get data |
| Network jitter | Doesn't tolerate | Tolerates | Tolerates |
| Persistent failure | Immediate fail | Wastes time | Degrades to old data |
| Implementation complexity | ★ | ★★ | ★★★ |
| Best for | Critical business | General business | User experience priority |
5. Selection
| Scenario | Recommended |
|---|---|
| Real-time API, user can retry | fail-fast |
| Data accuracy critical | fail-fast |
| General business | retry (2-3 times) |
| User experience priority | fallback (degradation) |
| Monitoring / alerting | retry |
| SEO rank monitoring | fallback |
6. Real Code (Combining 3 Strategies)
def search_serp_smart(query):
"""fail-fast + retry + fallback combined"""
for attempt in range(3):
try:
r = requests.post(
"https://api.serpbase.dev/google/search",
headers={"X-API-Key": "sk_xxx"},
json={"q": query, "gl": "us", "num": 5},
timeout=5,
)
r.raise_for_status()
data = r.json()
CACHE[query] = data
return data
except requests.exceptions.Timeout:
if attempt < 2:
time.sleep(2 ** attempt)
except requests.exceptions.HTTPError as e:
if 400 <= e.response.status_code < 500:
raise
if attempt < 2:
time.sleep(2 ** attempt)
if query in CACHE:
return CACHE[query]
return {"organic": [], "_stale": True}
7. 5 Engineering Details
Detail 1: Distinguish Error Types
def smart_retry(query):
for attempt in range(3):
try:
return call_serpbase(query)
except requests.exceptions.Timeout:
time.sleep(2 ** attempt)
except requests.exceptions.HTTPError as e:
if 500 <= e.response.status_code < 600:
time.sleep(2 ** attempt)
else:
raise
except Exception as e:
raise
Detail 2: Exponential Backoff + Jitter
import random
wait = (2 ** attempt) + random.uniform(0, 1)
# Avoids multiple requests retrying simultaneously hitting server
Detail 3: Total Timeout
import time
start = time.time()
for attempt in range(3):
if time.time() - start > 10:
raise TimeoutError("Total retry timeout")
try:
return call_serpbase(query)
except requests.exceptions.Timeout:
time.sleep(2 ** attempt)
Detail 4: Record Retry Metrics
import time
def smart_retry_with_metrics(query):
start = time.time()
for attempt in range(3):
try:
data = call_serpbase(query)
duration = time.time() - start
prometheus_client.HISTOGRAM.observe(duration)
if attempt > 0:
prometheus_client.COUNTER.labels("retry_success").inc()
return data
except requests.exceptions.Timeout:
if attempt > 0:
prometheus_client.COUNTER.labels("retry").inc()
time.sleep(2 ** attempt)
Detail 5: Fallback Degradation Marker
def fallback_with_marker():
try:
return call_serpbase()
except Exception:
cached = get_cache()
if cached:
cached["_stale"] = True
cached["_stale_at"] = time.time()
return cached
return {"organic": [], "_stale": True}
8. Real Data (My 1-Month Project)
| Strategy | Failure Rate | Avg Latency | Stale Rate |
|---|---|---|---|
| fail-fast only | 0.3% | 1.5s | 0% |
| retry 2-3 times | 0.01% | 2.5s | 0% |
| fallback | 0% | 0.5s | 5% (stale data) |
| Combined (my project) | 0.005% | 1.8s | 1% |
Combined strategy is most stable: 99% success first try, 1% retry success, 0.005% fallback.
9. 5 Best Practices
- Don't retry 4xx (parameter error, retry won't help)
- Retry 5xx 2-3 times (server failure may recover)
- Retry timeout, not network errors (Timeout is network issue, retryable)
- Exponential backoff + jitter (avoid thundering herd)
- Fallback mark stale (LLM knows data is old)
10. Specific to serpbase
serpbase's auto-refund 100% triggers, meaning retry failure costs 0:
- retry 2-3 times, 0.3% failure → auto-refund → no waste
- fallback cache → data may be stale
- Combined strategy: retry primary, fallback secondary, almost no waste
My project uses combined 3 strategies, monthly cost $0.5 (retries almost all auto-refund), user experience seamless.
Summary
3 error handling strategies selection:
- fail-fast: Real-time critical, user can retry
- retry: General business, tolerate network jitter
- fallback: User experience priority, tolerate stale data
serpbase + combined 3 strategies, 99.99% success rate + 0 waste. serpbase auto-refund makes retry failure costless, combined strategy fits all scenarios.
Top comments (0)