Skip to content

Database Indexes ​

Comprehensive guide to MongoDB indexes used in CodeNotify for optimal query performance.

Overview ​

Indexes are critical for query performance in MongoDB. This document details all indexes used across collections, their purpose, and optimization strategies.

Collections ​

CodeNotify uses three main collections:

  1. users - User accounts and preferences
  2. contests - Multi-platform contest data
  3. notifications - Notification history and tracking

User Collection Indexes ​

Primary Indexes ​

Unique Email Index ​

typescript
{ email: 1 } // Unique

Purpose: Ensure email uniqueness and fast authentication queries

Queries Optimized:

  • User login by email
  • Email uniqueness check during registration
  • Find user by email

Usage:

typescript
db.users.findOne({ email: 'user@example.com' })

Performance: O(log n) lookup

Active Users Index ​

typescript
{ isActive: 1 }

Purpose: Filter active/inactive users efficiently

Queries Optimized:

  • Find all active users
  • Count active users
  • Notification recipient filtering

Usage:

typescript
db.users.find({ isActive: true })

Platform Preference Index ​

typescript
{ 'preferences.platforms': 1 }

Purpose: Find users subscribed to specific platforms

Queries Optimized:

  • Find users interested in Codeforces
  • Platform-specific notification queries
  • User segmentation by platform

Usage:

typescript
db.users.find({ 'preferences.platforms': 'codeforces' })

Role Index ​

typescript
{ role: 1 }

Purpose: Filter users by role (admin/user)

Queries Optimized:

  • Find all admins
  • Role-based access control queries
  • User management operations

Usage:

typescript
db.users.find({ role: 'admin' })

Last Login Index ​

typescript
{ lastLogin: -1 }

Purpose: Sort users by last login time

Queries Optimized:

  • Find recently active users
  • Identify inactive accounts
  • User engagement analytics

Usage:

typescript
db.users.find().sort({ lastLogin: -1 })

Registration Date Index ​

typescript
{ createdAt: -1 }

Purpose: Sort users by registration date

Queries Optimized:

  • Find newest users
  • User growth analytics
  • Cohort analysis

Usage:

typescript
db.users.find().sort({ createdAt: -1 })

Compound Indexes for Users ​

Active Users by Platform ​

typescript
{ 'preferences.platforms': 1, isActive: 1 }

Purpose: Find active users for specific platform

Queries Optimized:

  • Notification recipient queries
  • Platform-specific user counts
  • Active user segmentation

Usage:

typescript
db.users.find({
  'preferences.platforms': 'codeforces',
  isActive: true
})

Active Users by Notification Preference ​

typescript
{ isActive: 1, 'preferences.notifyBefore': 1 }

Purpose: Find users with specific notification timing

Queries Optimized:

  • Time-based notification queries
  • User preference analytics

Contest Collection Indexes ​

Single Field Indexes ​

Platform ID Index ​

typescript
{ platformId: 1 }

Purpose: Fast lookup by platform-specific ID

Queries Optimized:

  • Find contest by platform ID
  • Upsert operations during sync

Usage:

typescript
db.contests.findOne({ platformId: '1900' })

Contest Name Index ​

typescript
{ name: 1 }

Purpose: Sort and filter by contest name

Queries Optimized:

  • Alphabetical sorting
  • Name-based filtering

Platform Index ​

typescript
{ platform: 1 }

Purpose: Filter contests by platform

Queries Optimized:

  • Get all Codeforces contests
  • Platform-specific queries
  • Platform statistics

Usage:

typescript
db.contests.find({ platform: 'codeforces' })

Phase Index ​

typescript
{ phase: 1 }

Purpose: Filter contests by phase

Queries Optimized:

  • Find upcoming contests
  • Find running contests
  • Find finished contests

Usage:

typescript
db.contests.find({ phase: 'BEFORE' })

Start Time Index ​

typescript
{ startTime: 1 }

Purpose: Sort contests by start time

Queries Optimized:

  • Chronological sorting
  • Time-range queries
  • Upcoming contest queries

Usage:

typescript
db.contests.find({ startTime: { $gt: new Date() } })

Active Status Index ​

typescript
{ isActive: 1 }

Purpose: Filter active/archived contests

Queries Optimized:

  • Get only active contests
  • Soft delete filtering

Notification Status Index ​

typescript
{ isNotified: 1 }

Purpose: Track notification status

Queries Optimized:

  • Find contests needing notification
  • Prevent duplicate notifications

Usage:

typescript
db.contests.find({ isNotified: false })

Last Synced Index ​

typescript
{ lastSyncedAt: 1 }

Purpose: Track sync freshness

Queries Optimized:

  • Find stale contests
  • Sync monitoring
  • Data freshness queries

Compound Indexes for Contests ​

Platform + Start Time ​

typescript
{ platform: 1, startTime: 1 }

Purpose: Get platform contests sorted by time

Queries Optimized:

  • Platform-specific upcoming contests
  • Platform contest timeline
  • Platform-specific filtering with sorting

Usage:

typescript
db.contests.find({ platform: 'codeforces' }).sort({ startTime: 1 })

Performance: Highly optimized for common queries

Platform + Phase ​

typescript
{ platform: 1, phase: 1 }

Purpose: Get platform contests by phase

Queries Optimized:

  • Codeforces upcoming contests
  • Platform-specific running contests
  • Phase distribution per platform

Usage:

typescript
db.contests.find({ platform: 'leetcode', phase: 'BEFORE' })

Start Time + Active Status ​

typescript
{ startTime: 1, isActive: 1 }

Purpose: Get active contests sorted by time

Queries Optimized:

  • Upcoming active contests
  • Contest timeline (active only)
  • Notification queries

Usage:

typescript
db.contests.find({ isActive: true }).sort({ startTime: 1 })

Phase + Active Status ​

typescript
{ phase: 1, isActive: 1 }

Purpose: Get active contests by phase

Queries Optimized:

  • Active upcoming contests
  • Active running contests
  • Phase-based filtering (active only)

Usage:

typescript
db.contests.find({ phase: 'CODING', isActive: true })

Notification Status + Start Time ​

typescript
{ isNotified: 1, startTime: 1 }

Purpose: Find contests needing notification

Queries Optimized:

  • Notification scheduler queries
  • Time-based notification filtering
  • Prevent duplicate notifications

Usage:

typescript
db.contests.find({
  isNotified: false,
  startTime: { $gte: now, $lte: tomorrow }
})

Unique Compound Index ​

Platform ID + Platform ​

typescript
{ platformId: 1, platform: 1 } // Unique

Purpose: Ensure contest uniqueness per platform

Queries Optimized:

  • Upsert operations during sync
  • Duplicate prevention
  • Contest lookup by platform and ID

Usage:

typescript
db.contests.updateOne(
  { platformId: '1900', platform: 'codeforces' },
  { $set: contestData },
  { upsert: true }
)

Critical: Prevents duplicate contests during sync

Text Index ​

typescript
{ name: 'text', description: 'text' }

Purpose: Enable full-text search on contest names and descriptions

Queries Optimized:

  • Search contests by keywords
  • Fuzzy name matching
  • Description search

Usage:

typescript
db.contests.find({ $text: { $search: 'beginner' } })

Note: Only one text index per collection

Notification Collection Indexes ​

Single Field Indexes ​

User ID Index ​

typescript
{ userId: 1 }

Purpose: Find notifications for a user

Queries Optimized:

  • User notification history
  • User-specific queries

Usage:

typescript
db.notifications.find({ userId: ObjectId('...') })

Contest ID Index ​

typescript
{ contestId: 1 }

Purpose: Find notifications for a contest

Queries Optimized:

  • Contest notification tracking
  • Contest-specific queries

Type Index ​

typescript
{ type: 1 }

Purpose: Filter by notification type

Queries Optimized:

  • Type-based analytics
  • Notification type filtering

Status Index ​

typescript
{ status: 1 }

Purpose: Filter by delivery status

Queries Optimized:

  • Find failed notifications
  • Success rate analytics
  • Retry queries

Scheduled Time Index ​

typescript
{ scheduledAt: 1 }

Purpose: Find scheduled notifications

Queries Optimized:

  • Scheduler queries
  • Time-based filtering

Active Status Index ​

typescript
{ isActive: 1 }

Purpose: Filter active notifications

Queries Optimized:

  • Active notification queries
  • Soft delete filtering

Compound Indexes for Notifications ​

User + Created Date ​

typescript
{ userId: 1, createdAt: -1 }

Purpose: User notification history sorted by date

Queries Optimized:

  • User notification timeline
  • Recent notifications for user
  • Pagination queries

Usage:

typescript
db.notifications.find({ userId: ObjectId('...') }).sort({ createdAt: -1 })

User + Status ​

typescript
{ userId: 1, status: 1 }

Purpose: User notifications by status

Queries Optimized:

  • User's failed notifications
  • User's pending notifications
  • Status-based filtering per user

User + Contest (Unique) ​

typescript
{ userId: 1, contestId: 1 } // Unique, Sparse

Purpose: Prevent duplicate notifications per user per contest

Queries Optimized:

  • Duplicate prevention
  • User-contest notification lookup

Note: Sparse index (allows null contestId)

Contest + Created Date ​

typescript
{ contestId: 1, createdAt: -1 }

Purpose: Contest notification history

Queries Optimized:

  • All notifications for a contest
  • Contest notification timeline

Status + Scheduled Time ​

typescript
{ status: 1, scheduledAt: 1 }

Purpose: Find pending scheduled notifications

Queries Optimized:

  • Scheduler queries
  • Pending notification processing

Status + Next Retry Time ​

typescript
{ status: 1, nextRetryAt: 1 }

Purpose: Find notifications ready for retry

Queries Optimized:

  • Retry scheduler queries
  • Failed notification processing

TTL Index ​

Expiration Index ​

typescript
{ expiresAt: 1 } // TTL index, expireAfterSeconds: 0

Purpose: Automatic cleanup of old notifications

Behavior: MongoDB automatically deletes documents when expiresAt date is reached

Default: 90 days from creation

Usage:

typescript
// Set expiration (done automatically in pre-save hook)
notification.expiresAt = new Date(Date.now() + 90 * 24 * 60 * 60 * 1000);

Index Statistics ​

Checking Index Usage ​

javascript
// Get index statistics
db.contests.aggregate([
  { $indexStats: {} }
])

// Explain query plan
db.contests.find({ platform: 'codeforces' }).explain('executionStats')

Index Size ​

javascript
// Get collection stats including index sizes
db.contests.stats()

Index Maintenance ​

Creating Indexes ​

javascript
// Create single field index
db.users.createIndex({ email: 1 }, { unique: true })

// Create compound index
db.contests.createIndex({ platform: 1, startTime: 1 })

// Create text index
db.contests.createIndex({ name: 'text', description: 'text' })

// Create TTL index
db.notifications.createIndex(
  { expiresAt: 1 },
  { expireAfterSeconds: 0 }
)

Dropping Indexes ​

javascript
// Drop specific index
db.contests.dropIndex('platform_1_startTime_1')

// Drop all indexes except _id
db.contests.dropIndexes()

Rebuilding Indexes ​

javascript
// Rebuild all indexes
db.contests.reIndex()

Performance Considerations ​

Index Selectivity ​

High Selectivity (Good):

  • Unique fields (email, platformId+platform)
  • Fields with many distinct values (startTime, name)

Low Selectivity (Consider carefully):

  • Boolean fields (isActive, isNotified)
  • Enum fields with few values (role, phase)

Recommendation: Use compound indexes for low-selectivity fields

Index Size vs. Performance ​

Trade-offs:

  • More indexes = Faster reads, slower writes
  • Larger indexes = More memory usage
  • Compound indexes = More specific optimization

Best Practice: Create indexes based on actual query patterns

Write Performance Impact ​

Each index adds overhead to write operations:

  • Insert: Update all indexes
  • Update: Update affected indexes
  • Delete: Update all indexes

Recommendation: Only create necessary indexes

Query Optimization Tips ​

1. Use Covered Queries ​

Query only indexed fields to avoid document lookup:

typescript
// Covered query (fast)
db.contests.find(
  { platform: 'codeforces' },
  { platform: 1, startTime: 1, _id: 0 }
)

2. Use Index Hints ​

Force MongoDB to use specific index:

typescript
db.contests.find({ platform: 'codeforces' }).hint({ platform: 1, startTime: 1 })

3. Analyze Query Plans ​

typescript
db.contests.find({ platform: 'codeforces' }).explain('executionStats')

Look for:

  • IXSCAN (index scan) - Good
  • COLLSCAN (collection scan) - Bad (add index)
  • nReturned vs totalDocsExamined - Should be close

4. Use Projection ​

Fetch only needed fields:

typescript
db.contests.find(
  { platform: 'codeforces' },
  { name: 1, startTime: 1 }
)

5. Limit Results ​

Always use limit for large result sets:

typescript
db.contests.find({ platform: 'codeforces' }).limit(20)

Monitoring ​

Index Usage Monitoring ​

javascript
// Check if index is being used
db.contests.aggregate([
  { $indexStats: {} },
  { $match: { name: 'platform_1_startTime_1' } }
])

Slow Query Logging ​

Enable slow query logging in MongoDB:

javascript
db.setProfilingLevel(1, { slowms: 100 })

Index Recommendations ​

Use MongoDB Compass or Atlas to get index recommendations based on actual query patterns.

Best Practices ​

✅ Do ​

  1. Create indexes for frequently queried fields
  2. Use compound indexes for multi-field queries
  3. Monitor index usage with $indexStats
  4. Analyze query plans with explain()
  5. Use covered queries when possible
  6. Create unique indexes for uniqueness constraints
  7. Use TTL indexes for automatic cleanup
  8. Test index performance before production

❌ Don't ​

  1. Don't create unnecessary indexes (write overhead)
  2. Don't create redundant indexes (covered by compound)
  3. Don't ignore index size (memory usage)
  4. Don't skip index maintenance (rebuild periodically)
  5. Don't create indexes on high-cardinality fields without need
  6. Don't use too many compound indexes (diminishing returns)
  7. Don't forget to drop unused indexes

Index Creation Scripts ​

Users Collection ​

javascript
// Unique email
db.users.createIndex({ email: 1 }, { unique: true });

// Active users
db.users.createIndex({ isActive: 1 });

// Platform preferences
db.users.createIndex({ 'preferences.platforms': 1 });

// Role
db.users.createIndex({ role: 1 });

// Compound: active users by platform
db.users.createIndex({ 'preferences.platforms': 1, isActive: 1 });

Contests Collection ​

javascript
// Single field indexes
db.contests.createIndex({ platformId: 1 });
db.contests.createIndex({ platform: 1 });
db.contests.createIndex({ phase: 1 });
db.contests.createIndex({ startTime: 1 });
db.contests.createIndex({ isActive: 1 });
db.contests.createIndex({ isNotified: 1 });
db.contests.createIndex({ lastSyncedAt: 1 });

// Compound indexes
db.contests.createIndex({ platform: 1, startTime: 1 });
db.contests.createIndex({ platform: 1, phase: 1 });
db.contests.createIndex({ startTime: 1, isActive: 1 });
db.contests.createIndex({ phase: 1, isActive: 1 });
db.contests.createIndex({ isNotified: 1, startTime: 1 });

// Unique compound index
db.contests.createIndex(
  { platformId: 1, platform: 1 },
  { unique: true }
);

// Text index
db.contests.createIndex({ name: 'text', description: 'text' });

Notifications Collection ​

javascript
// Single field indexes
db.notifications.createIndex({ userId: 1 });
db.notifications.createIndex({ contestId: 1 });
db.notifications.createIndex({ type: 1 });
db.notifications.createIndex({ status: 1 });
db.notifications.createIndex({ scheduledAt: 1 });
db.notifications.createIndex({ isActive: 1 });

// Compound indexes
db.notifications.createIndex({ userId: 1, createdAt: -1 });
db.notifications.createIndex({ userId: 1, status: 1 });
db.notifications.createIndex({ contestId: 1, createdAt: -1 });
db.notifications.createIndex({ status: 1, scheduledAt: 1 });
db.notifications.createIndex({ status: 1, nextRetryAt: 1 });

// Unique compound index (sparse)
db.notifications.createIndex(
  { userId: 1, contestId: 1 },
  { unique: true, sparse: true }
);

// TTL index
db.notifications.createIndex(
  { expiresAt: 1 },
  { expireAfterSeconds: 0 }
);

Built with ❤️ for competitive programmers worldwide.