- Home
- Skills
- Data & Databases
- Beanie + MongoDB: Production Pitfalls
Works with the AI Agents you already use
Beanie + MongoDB: Production Pitfalls
Catches the Beanie + MongoDB bugs that pass your tests and break production — with a pre-deploy index checker.
$9.99
Beanie + MongoDB: Production Pitfalls
Example session with this skill installed
Refactor this to use Beanie's upsert API instead of the manual find/insert. It's called from webhook handlers.
async def get_or_create_contact(org_id, email, name):
contact = await Contact.find_one(Contact.organization_id == org_id, Contact.email == email)
if contact is None:
contact = Contact(organization_id=org_id, email=email, name=name, created_at=utcnow())
await contact.insert()
return contact
- Read your context and instructions
- Compiled the beanie + mongodb
## Summary
Beanie's `.upsert(on_insert=...)` would keep the race, so the refactor uses one atomic server-side upsert backed by a unique index instead.
## Beanie's upsert API is not safe here
In Beanie 2.1, `find_one(...).upsert(..., on_insert=Contact(...))` runs an `update_one` without `upsert=True` and, if nothing matched, a separate `insert_one`. That is the same check-then-insert race as the original code. Webhook providers redeliver events and deliver them concurrently, so this path would still produce duplicates or `DuplicateKeyError`.
Instead the function uses the raw collection with `find_one_and_update(upsert=True)` and `$setOnInsert`: a single atomic operation that also returns the document in one round trip.
```python
from datetime import UTC, datetime
import pymongo
from beanie import Document, PydanticObjectId
from pymongo import ReturnDocument
from pymongo.errors import DuplicateKeyError
```python
class Contact(Document):
organization_id: PydanticObjectId
email: str
name: str | None = None
created_at: datetime
class Settings:
name = "contacts"
indexes = [
```
# The unique index is what makes the upsert atomic. Without it,
# two concurrent upserts can both insert.
pymongo.IndexModel(
[("organization_id", 1), ("email", 1)],
unique=True,
name="contacts_org_email_unique",
```
),
]
```
async def get_or_create_contact(
org_id: PydanticObjectId, email: str, name: str | None
```sql
) -> Contact:
filter_ = {"organization_id": org_id, "email": email}
update = {"$setOnInsert": {"name": name, "created_at": datetime.now(UTC)}}
coll = Contact.get_pymongo_collection()
for attempt in range(2):
try:
raw = await coll.find_one_and_update(
filter_,
update,
upsert=True,
return_document=ReturnDocument.AFTER,
)
break
except DuplicateKeyError:
```
# Lost a race on the unique index; the retry matches the winner's doc.
```
if attempt:
raise
return Contact.model_validate(raw)
```
Requirements and behaviour
┌──────────────┬──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ │ │
├──────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Unique index │ Required on (organization_id, email). Without it, two concurrent upserts can both insert. │
├──────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Retry │ The loser of a race can still get DuplicateKeyError; it retries once and matches the winner's document. It never catches and logs, which would hide lost writes. │
├──────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ $setOnInsert │ name and created_at are written only on insert, so a repeat webhook doesn't overwrite an existing contact. │
├──────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Timestamps │ datetime.now(UTC) replaces utcnow(); the client must use tz_aware=True. │
├──────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Tenant scope │ The filter includes organization_id. │
└──────────────┴──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
Next steps
- Before deploying: the old code has probably created duplicates already. Dedupe contacts and confirm zero violations against production, or building the new unique index crashes the boot. Run scripts/check_indexes.py against prod to verify.
- Normalise emails (.strip().lower()) before calling, or different casings count as different contacts.
- Add a test that fails against the old code: force the interleaving, or assert the index exists and that a second raw insert raises. mongomock doesn't model concurrency, so a plain asyncio.gather test proves nothing.
Connects securely to your tools. The creator never sees your data.
About this skill
Your Beanie code passes review, the test suite is green, and the deploy rolls back. Or worse: it ships, and data quietly goes missing. This skill teaches your agent the failure modes that only show up in a real deployment, each one taken from a production incident in a multi-tenant SaaS running Beanie on MongoDB Atlas, and verified against the Beanie 2.1 source and a real MongoDB 8.0.
What it prevents
- Deploys that crash at boot.
init_beaniebuilds indexes on startup. Change an index's keys under the same name, or add a unique index over existing duplicates, and every worker dies. - Fixes that never apply. Beanie never drops old indexes, so the stale constraint keeps enforcing after you "fixed" it.
- Unique indexes on optional fields. Beanie writes
null,sparsedoesn't skip it, and the second document fails. - Race conditions. Check-then-insert,
save()overwriting concurrent edits, and Beanie's own.upsert(on_insert=...), which is not atomic: it's an update followed by a separate insert. - Wrong totals.
sort/skip/limitmutate the query andcount()applies them, so pagination totals come back capped at the page size. - Green tests, red production. mongomock silently drops
partialFilterExpression, rawaggregate()is a coroutine in prod but a cursor in tests, andasyncio.gatherrace tests pass against the buggy code. - Scripts that write when they should only read. A "dry-run" that calls
init_beaniecreates indexes in production, and a new TTL index starts deleting data within a minute.beanie migratedoes the same.
What you get
SKILL.md: 22 rules, each as symptom → cause → fix, plus a pre-deploy checklist for any model change.- Four reference guides with complete code: indexes, concurrency (atomic get-or-create, locks, sync jobs), testing (a mongomock fixture, race tests that actually fail), and operations (a safe script template, Atlas Search).
check_indexes.py: a read-only CLI that compares your models' indexes with a live database and tells you what the next deploy will do: boot blockers, duplicates that will break a unique index, documents a new TTL will delete, stale indexes still enforcing. Exits 1 on blockers, so it can gate CI.
Measured, not claimed
Six tasks built around these failure modes, no hints in the prompts, run with and without the skill:
| Model | Without skill | With skill |
|---|---|---|
| Claude Sonnet | 62% | 100% |
| Claude Opus | 88% | 100% |
Without the skill, Sonnet used Beanie's non-atomic upsert for a webhook handler and disabled the real index in tests to keep CI green. Opus got most of them right, but only after reading Beanie's source or running experiments for up to two minutes per task.
Works alongside MongoDB's official agent skills: they cover schema design and query tuning, this one covers how your Python code and your deploys behave.
Demo: the requesttemplate, Atlas Search).
check_indexes.py: a read-only CLI that compares your models' indexes with a live database and tells you what the next deploy will do: boot blockers, duplicates that will break a unique index, documents a new TTL will delete, stale indexes still enforcing. Exits 1 on blockers, so it can gate CI.
Measured, not claimed
Six tasks built around these failure modes, no hints in the prompts, run with and without the skill:
| Model | Without skill | With skill |
|---|---|---|
| Claude Sonnet | 62% | 100% |
| Claude Opus | 88% | 100% |
Without the skill, Sonnet used Beanie's non-atomic upsert for a webhook handler and disabled the real index in tests to keep CI green. Opus got most of them right, but only after reading Beanie's source or running experiments for up to two minutes per task.
Works alongside MongoDB's official agent skills: they cover schema design and query tuning, this one covers how your Python code and your deploys behave.
How to use
Skills on Agensi run inside your AI agent. Copy the prompt, paste it into your agent, and it connects to Agensi and installs this skill for you. If a payment is needed, your agent shows it to you first.
Reviews
No reviews yet
Be one of the first to try it. Every listed skill passes our trust checks below.
Security scanned
Passed our 8-point scan before listing
Fresh listing
Recently published to Agensi
Money-back guarantee
Doesn't do what its page promises? You get your money back.
Trust & safety
Security scanned
Verified clean 14 days ago
- Passed all security checks, Safe to install