Performance Optimization Guide¶
This guide covers techniques to optimize the performance of your Zenoo RPC applications, from basic optimizations to advanced strategies for high-throughput scenarios.
Overview¶
Zenoo RPC is designed for performance from the ground up, but there are several techniques you can use to maximize efficiency:
- Intelligent Caching - Reduce redundant RPC calls
- Batch Operations - Minimize network round trips
- Connection Pooling - Reuse HTTP connections
- Query Optimization - Fetch only what you need
- Async Patterns - Maximize concurrency
- Transaction Management - Ensure data consistency efficiently
Caching Strategies¶
Memory Caching¶
import asyncio
from zenoo_rpc import ZenooClient
from zenoo_rpc.models.common import ResPartner
async def setup_memory_cache():
async with ZenooClient("localhost", port=8069) as client:
# Setup memory cache with optimal settings
await client.cache_manager.setup_memory_cache(
name="memory",
max_size=2000, # Cache up to 2000 items
default_ttl=300, # 5 minutes default TTL
strategy="lru" # LRU eviction strategy
)
await client.login("demo", "admin", "admin")
# First call - hits database
partners1 = await client.model(ResPartner).filter(
is_company=True
).limit(100).all()
# Second call - served from cache
partners2 = await client.model(ResPartner).filter(
is_company=True
).limit(100).all()
print(f"Cache hit rate: {client.cache_manager.get_stats()['hit_rate']:.2%}")
asyncio.run(setup_memory_cache())
Redis Caching for Production¶
async def setup_redis_cache():
async with ZenooClient("localhost", port=8069) as client:
# Setup Redis cache for production
await client.cache_manager.setup_redis_cache(
name="redis",
redis_url="redis://localhost:6379/0",
default_ttl=600, # 10 minutes
key_prefix="zenoo:", # Namespace your keys
serializer="pickle" # Fast serialization
)
await client.login("demo", "admin", "admin")
# Cache expensive queries
expensive_data = await client.model(ResPartner).filter(
# Complex query that takes time
).all()
asyncio.run(setup_redis_cache())
Cache Warming¶
async def warm_cache(client: ZenooClient):
"""Pre-populate cache with frequently accessed data"""
# Warm up common queries
cache_warming_queries = [
# Active companies
client.model(ResPartner).filter(is_company=True, active=True).all(),
# Countries and states
client.model(ResCountry).all(),
client.model(ResCountryState).all(),
# Product categories
client.model(ProductCategory).all(),
# Common users
client.model(ResUsers).filter(active=True).all(),
]
# Execute all warming queries concurrently
await asyncio.gather(*cache_warming_queries)
print("Cache warmed up successfully")
Batch Operations¶
Bulk Create Operations¶
async def efficient_bulk_create(client: ZenooClient):
"""Efficiently create large numbers of records"""
# Prepare data for bulk creation
partners_data = []
for i in range(1000):
partners_data.append({
"name": f"Company {i:04d}",
"is_company": True,
"email": f"company{i:04d}@example.com",
"phone": f"+1-555-{i:04d}"
})
# Batch size optimization
batch_size = 100 # Optimal batch size for most cases
all_partners = []
for i in range(0, len(partners_data), batch_size):
batch = partners_data[i:i + batch_size]
# Create batch
partners = await client.model(ResPartner).bulk_create(batch)
all_partners.extend(partners)
print(f"Created batch {i//batch_size + 1}: {len(partners)} records")
print(f"Total created: {len(all_partners)} partners")
return all_partners
Bulk Update Operations¶
async def efficient_bulk_update(client: ZenooClient):
"""Efficiently update large numbers of records"""
# Update all companies without website
updated_count = await client.model(ResPartner).filter(
is_company=True,
website__isnull=True
).update({
"website": "https://company.example.com",
"comment": "Website added via bulk update"
})
print(f"Updated {updated_count} companies")
# Batch update with different values
companies = await client.model(ResPartner).filter(
is_company=True
).limit(100).all()
update_data = []
for company in companies:
update_data.append({
"id": company.id,
"website": f"https://{company.name.lower().replace(' ', '')}.com",
"comment": f"Updated on {datetime.now().isoformat()}"
})
# Bulk update with individual values
updated_partners = await client.model(ResPartner).bulk_update(update_data)
print(f"Updated {len(updated_partners)} companies with individual values")
Connection Optimization¶
HTTP/2 and Connection Pooling¶
async def optimized_client_setup():
"""Setup client with optimal connection settings"""
client = ZenooClient(
"localhost",
port=8069,
# Enable HTTP/2 for multiplexing
http2=True,
# Optimize connection pooling
max_connections=200, # Total connections
max_keepalive_connections=50, # Persistent connections
# Timeout optimization
timeout=30.0, # Reasonable timeout
# Enable compression
headers={
"Accept-Encoding": "gzip, deflate, br"
}
)
return client
Connection Reuse¶
async def reuse_connections():
"""Demonstrate efficient connection reuse"""
# ✅ Good - Reuse single client for multiple operations
async with ZenooClient("localhost", port=8069) as client:
await client.login("demo", "admin", "admin")
# Multiple operations with same client
tasks = [
client.model(ResPartner).filter(is_company=True).all(),
client.model(ProductProduct).filter(active=True).all(),
client.model(SaleOrder).filter(state="sale").all(),
]
results = await asyncio.gather(*tasks)
# ❌ Bad - Multiple clients for each operation
# This creates unnecessary connection overhead
Query Optimization¶
Field Selection¶
async def optimize_field_selection(client: ZenooClient):
"""Only fetch fields you actually need"""
# ❌ Bad - Fetches all fields
partners = await client.model(ResPartner).filter(
is_company=True
).all()
# ✅ Good - Only fetch needed fields
partners = await client.model(ResPartner).filter(
is_company=True
).fields(["name", "email", "phone"]).all()
# ✅ Better - Use specific queries for specific needs
partner_names = await client.search_read(
"res.partner",
domain=[("is_company", "=", True)],
fields=["name"],
limit=1000
)
Relationship Optimization¶
async def optimize_relationships(client: ZenooClient):
"""Efficiently handle relationship fields"""
# ✅ Prefetch related data
partners = await client.model(ResPartner).filter(
is_company=True
).prefetch("country_id", "state_id").all()
# Now accessing relationships is fast
for partner in partners:
if partner.country_id:
country = await partner.country_id # Already prefetched
print(f"{partner.name} - {country.name}")
# ✅ Use joins for complex queries
us_companies = await client.model(ResPartner).filter(
is_company=True,
country_id__code="US",
state_id__name__ilike="california%"
).all()
Pagination Optimization¶
async def optimize_pagination(client: ZenooClient):
"""Efficiently handle large datasets"""
page_size = 100 # Optimal page size
offset = 0
all_records = []
while True:
# Use cursor-based pagination when possible
records = await client.model(ResPartner).filter(
is_company=True,
id__gt=offset # Cursor-based pagination
).order_by("id").limit(page_size).all()
if not records:
break
all_records.extend(records)
offset = records[-1].id # Update cursor
# Process batch immediately to save memory
await process_batch(records)
return len(all_records)
async def process_batch(records):
"""Process records in batches to save memory"""
# Process the batch
for record in records:
# Do something with the record
pass
Async Patterns¶
Concurrent Operations¶
async def concurrent_operations(client: ZenooClient):
"""Execute multiple operations concurrently"""
# ✅ Good - Concurrent execution
async def get_partners():
return await client.model(ResPartner).filter(is_company=True).all()
async def get_products():
return await client.model(ProductProduct).filter(active=True).all()
async def get_orders():
return await client.model(SaleOrder).filter(state="sale").all()
# Execute all queries concurrently
partners, products, orders = await asyncio.gather(
get_partners(),
get_products(),
get_orders()
)
print(f"Loaded {len(partners)} partners, {len(products)} products, {len(orders)} orders")
Semaphore for Rate Limiting¶
async def rate_limited_operations(client: ZenooClient):
"""Use semaphore to limit concurrent operations"""
# Limit to 10 concurrent operations
semaphore = asyncio.Semaphore(10)
async def process_partner(partner_id):
async with semaphore:
partner = await client.model(ResPartner).get(partner_id)
# Process partner
await asyncio.sleep(0.1) # Simulate processing
return partner
# Process many partners with rate limiting
partner_ids = list(range(1, 101))
tasks = [process_partner(pid) for pid in partner_ids]
results = await asyncio.gather(*tasks)
print(f"Processed {len(results)} partners with rate limiting")
Transaction Optimization¶
Efficient Transaction Usage¶
async def optimize_transactions(client: ZenooClient):
"""Use transactions efficiently"""
# ✅ Good - Group related operations in transactions
async with client.transaction() as tx:
# Create company
company = await client.model(ResPartner).create({
"name": "New Company",
"is_company": True
})
# Create contacts for the company
contacts_data = [
{
"name": "John Doe",
"parent_id": company.id,
"email": "john@company.com"
},
{
"name": "Jane Smith",
"parent_id": company.id,
"email": "jane@company.com"
}
]
contacts = await client.model(ResPartner).bulk_create(contacts_data)
# All operations committed atomically
# ❌ Bad - Too many small transactions
# Creates unnecessary overhead
Savepoints for Complex Operations¶
async def use_savepoints(client: ZenooClient):
"""Use savepoints for complex nested operations"""
async with client.transaction() as tx:
# Main operation
company = await client.model(ResPartner).create({
"name": "Complex Company",
"is_company": True
})
# Create savepoint for risky operation
savepoint = await tx.savepoint("contacts_creation")
try:
# Risky operation that might fail
contacts = await create_complex_contacts(client, company.id)
await savepoint.release()
except Exception as e:
# Rollback to savepoint, keep main operation
await savepoint.rollback()
print(f"Contact creation failed: {e}")
# Transaction continues with company created
Monitoring and Profiling¶
Performance Monitoring¶
import time
from contextlib import asynccontextmanager
@asynccontextmanager
async def performance_monitor(operation_name: str):
"""Monitor operation performance"""
start_time = time.time()
start_memory = get_memory_usage()
try:
yield
finally:
end_time = time.time()
end_memory = get_memory_usage()
duration = end_time - start_time
memory_delta = end_memory - start_memory
print(f"{operation_name}:")
print(f" Duration: {duration:.2f}s")
print(f" Memory: {memory_delta:.2f}MB")
async def monitored_operations(client: ZenooClient):
"""Example of monitoring operations"""
async with performance_monitor("Bulk Partner Creation"):
partners = await efficient_bulk_create(client)
async with performance_monitor("Cache Warming"):
await warm_cache(client)
def get_memory_usage():
"""Get current memory usage in MB"""
import psutil
process = psutil.Process()
return process.memory_info().rss / 1024 / 1024
Query Performance Analysis¶
async def analyze_query_performance(client: ZenooClient):
"""Analyze and optimize query performance"""
# Enable query logging
client.enable_query_logging()
# Execute queries
await client.model(ResPartner).filter(
is_company=True,
country_id__code="US"
).all()
# Get query statistics
stats = client.get_query_stats()
print("Query Performance:")
print(f" Total queries: {stats['total_queries']}")
print(f" Average duration: {stats['avg_duration']:.3f}s")
print(f" Cache hit rate: {stats['cache_hit_rate']:.2%}")
print(f" Slowest query: {stats['slowest_query']:.3f}s")
Best Practices Summary¶
Do's ✅¶
- Use caching for frequently accessed data
- Batch operations when creating/updating multiple records
- Reuse client connections across operations
- Fetch only needed fields to reduce data transfer
- Use concurrent operations with asyncio.gather()
- Group related operations in transactions
- Monitor performance in production
Don'ts ❌¶
- Don't create new clients for each operation
- Don't fetch all fields when you only need a few
- Don't use individual operations for bulk data
- Don't ignore caching opportunities
- Don't use blocking operations in async code
- Don't create unnecessary transactions
- Don't ignore memory usage in long-running processes
Production Configuration¶
async def production_setup():
"""Optimal configuration for production"""
client = ZenooClient(
host="production-odoo.com",
port=443,
protocol="https",
# Performance settings
http2=True,
max_connections=100,
max_keepalive_connections=25,
timeout=60.0,
# Security settings
verify_ssl=True,
# Headers
headers={
"User-Agent": "MyApp/1.0 (Zenoo-RPC)",
"Accept-Encoding": "gzip, deflate, br"
}
)
# Setup Redis cache
await client.cache_manager.setup_redis_cache(
redis_url="redis://cache-server:6379/0",
default_ttl=300,
max_size=10000
)
return client
This guide provides a comprehensive overview of performance optimization techniques. Apply these strategies based on your specific use case and always measure the impact of optimizations in your environment.
Next Steps¶
- Testing Strategies - Test your optimized code
- Production Deployment - Deploy with confidence
- Caching Guide - Deep dive into caching
- Batch Operations - Master bulk operations