Awesome-omni-skill nosql-databases
Apply NoSQL best practices for MongoDB, Convex, and document databases. Use when designing schemas, writing queries, optimizing performance, or building applications with non-relational databases. Use with database-expert for query optimization and DBA-level tuning (20+ years experience).
git clone https://github.com/diegosouzapw/awesome-omni-skill
T=$(mktemp -d) && git clone --depth=1 https://github.com/diegosouzapw/awesome-omni-skill "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/development/nosql-databases" ~/.claude/skills/diegosouzapw-awesome-omni-skill-nosql-databases && rm -rf "$T"
skills/development/nosql-databases/SKILL.mdNoSQL Databases (MongoDB, Convex, Document Stores)
Expertise: Senior database administrator with 20+ years of experience in document stores, key-value systems, and non-relational data modeling. Focus on query optimization, indexing strategy, and data access best practices.
General NoSQL Principles
Document Design
- Embed vs Reference: Embed when data is always read together and rarely grows unbounded; reference when data is shared, large, or updated independently
- Avoid unbounded arrays: Documents with arrays that grow without limit cause performance degradation; use separate collections with references
- Denormalize for read patterns: Optimize for how data is read; duplicate when it improves query performance and consistency is acceptable
Query Patterns
- Index every query path: Queries without indexes cause full collection scans; at scale, indexed queries are orders of magnitude faster
- Project only needed fields: Reduce network and memory by projecting only required fields (
in MongoDB, selective fields in Convex)projection - Paginate large result sets: Never
or.collect()
without limits when result sets can be large (e.g. >1000 documents).find()
Consistency
- Understand read-your-writes: Document stores often offer eventual consistency; use appropriate read concern when strong consistency is required
- Design for idempotency: Retries and eventual consistency make duplicate operations possible; design mutations to be idempotent
MongoDB
Index Types and When to Use
| Type | Use Case | Example |
|---|---|---|
| Single-field | Equality, sort on one field | |
| Compound | Multi-field queries; order matters | |
| Multikey | Arrays (one index entry per array element) | |
| Text | Full-text search | |
| Geospatial | Location queries | , |
Index Rules
- Index fields in WHERE, sort, and projection—avoid full collection scans
- Compound index order: equality → sort → range; put most selective fields first
// Good for db.collection.find({ channel: "x" }).sort({ createdAt: -1 }) db.collection.createIndex({ channel: 1, createdAt: -1 }); - Covered queries: When query + projection use only indexed fields, MongoDB reads only the index (no document fetch)
- Avoid low-selectivity operators:
,$nin
,$ne
often match large portions of the index$exists: false - Limit indexes per collection: Max 64 indexes; each index adds write cost—measure before adding
Aggregation Pipeline Optimization
- Use
and$match
early to reduce documents and fields early in the pipeline$project - Use
and$indexStats
to analyze query patterns and index usage$queryStats - Prefer
with$lookup
andpipeline
for complex joins; avoid unboundedlet
on large collections$lookup
Explain and Profiling
db.collection.find({ userId: "x" }).explain("executionStats"); // Check: stage "IXSCAN" (index scan) vs "COLLSCAN" (full scan) // Review: docsExamined, nReturned, executionTimeMillis
Security
- Use parameterized queries; never concatenate user input into queries
- Apply principle of least privilege for database users
- Validate and sanitize
and aggregation$where
inputs$function
Convex
Schema and Indexes
Indexes are defined in the schema; every query should use an index via
.withIndex():
// schema.ts defineSchema({ messages: defineTable({ channel: v.string(), userId: v.id("users"), text: v.string(), createdAt: v.number(), }) .index("by_channel", ["channel"]) .index("by_channel_created", ["channel", "createdAt"]) .index("by_user", ["userId"]), });
Query Best Practices
-
Use
instead of.withIndex()
: Index-based queries are efficient;.filter()
scans the table.filter()// Good: uses index const messages = await ctx.db.query("messages").withIndex("by_channel", q => q.eq("channel", channelId)).collect(); // Avoid: full table scan const messages = await ctx.db.query("messages").filter(q => q.eq(q.field("channel"), channelId)).collect(); -
Use
for full-text: When you need search, define and use search indexes.withSearchIndex() -
Paginate with
: For large result sets, use pagination; avoid.paginate()
on unbounded queries.collect() -
Staged indexes for large tables: When adding indexes to tables with substantial data, use staged indexes to avoid slow backfill during deployment
Index Removal
- Ensure an index is completely unused before removing it; deployments will delete unused indexes
Other Document Stores (Firestore, DynamoDB, etc.)
Firestore
- Composite indexes for multi-field queries; define in Firebase Console or
firestore.indexes.json - Batch reads with
to reduce round tripsgetAll() - Use
andlimit()
for paginationstartAfter()
DynamoDB
- Design for single-table access patterns; partition key + sort key define access
- Use GSIs (Global Secondary Indexes) for alternate query patterns
- Avoid scans; use
with key conditionsQuery
Performance Checklist
- Every query path has a supporting index
- No full collection/table scans in hot paths (verify with explain/profiler)
- Projections limit returned fields
- Large result sets use pagination
- Unbounded arrays avoided in document design
- Read/write patterns inform embedding vs referencing
- Mutations are idempotent where retries are possible