Performance Optimization¶
Async Concurrency¶
halfORM supports async I/O through psycopg 3's AsyncConnection. Use async
execution when you need to run several independent queries at the same time — for
example fetching data from multiple tables without waiting for each query to finish
before starting the next one.
Setup¶
import asyncio
from half_orm.model import Model
db = Model('my_database')
Person = db.get_relation_class('public.person')
Order = db.get_relation_class('public.order')
Open one async connection per process (or per request in a web framework) and close it when done:
async def main():
await db.aconnect()
try:
await run_queries()
finally:
await db.adisconnect()
asyncio.run(main())
Fan-out pattern¶
Run independent queries concurrently with asyncio.gather:
async def dashboard_data(user_id: int):
person = Person(id=user_id)
orders = Order(user_id=user_id)
profile, recent_orders, order_count = await asyncio.gather(
person.ho_aselect(),
orders.ho_order_by('created_at desc').ho_limit(5).ho_aselect(),
orders.ho_acount(),
)
return profile, recent_orders, order_count
This sends all three queries to PostgreSQL at the same time and collects the results as they arrive, instead of executing them sequentially.
Async executor reference¶
| Method | Returns | Notes |
|---|---|---|
await rel.ho_aselect(*fields) |
list[dict] |
All matching rows |
await rel.ho_acount() |
int |
Row count |
await rel.ho_ais_empty() |
bool |
True if no rows match |
await rel.ho_ainsert() |
dict |
Inserted row |
await rel.ho_aupdate(**kwargs) |
— | Updates matching rows |
await rel.ho_adelete() |
— | Deletes matching rows |
ho_aselect returns a list
Unlike ho_select() which returns a generator, ho_aselect() returns a
plain list so the underlying cursor can be closed before yielding control
back to the event loop.
When async helps¶
- Web APIs: fetch data for a response from multiple tables concurrently.
- Batch processing: run independent per-record queries in parallel.
- Dashboards: aggregate several counts/sums in one
gathercall.
When async does NOT help¶
- A single sequential operation (one query, one insert) — sync is simpler.
- Queries that depend on each other's results —
awaitthem sequentially. - CPU-bound work — use
multiprocessinginstead.
Query Limiting¶
Always constrain large result sets to avoid fetching more rows than needed:
# Limit result size
recent = Person().ho_order_by('created_at desc').ho_limit(100).ho_select()
# Check before fetching
if not Person(email='user@example.com').ho_is_empty():
person = Person(email='user@example.com').ho_get()
Count vs. Select¶
Prefer ho_count() / ho_acount() over fetching rows just to count them: