Sails.js 1.x中基于sails-mongo创建与管理MongoDB索引的最佳方案
Hey there! Let's tackle this index management challenge with Sails.js 1.0 and MongoDB, since we can't rely on the old index model property or the unmaintained sails-hook-mongoat anymore. Below are practical, production-ready solutions for both your deployment scenarios:
1. New Deployment: Automatically Create Indexes on App Startup
The cleanest approach here is to build a custom Sails hook that runs when your app boots up, reads index configurations from your models, and creates them using MongoDB's native API.
Step 1: Define Index Configs in Your Models
Add a custom indexes property to each model where you need indexes (supports all MongoDB index types, including reverse/descending ones):
// api/models/User.js module.exports = { attributes: { email: { type: 'string', required: true }, username: { type: 'string' }, createdAt: { type: 'number', autoCreatedAt: true } }, // Custom index configuration indexes: [ { keys: { email: 1 }, options: { unique: true, background: true } }, { keys: { username: 1 }, options: { sparse: true, background: true } }, // Reverse index example { keys: { createdAt: -1 }, options: { background: true } } ] };
The background: true option ensures index creation doesn't block your database during startup (critical for production).
Step 2: Build the Index Manager Hook
Create a new hook in api/hooks/index-manager/index.js to handle index creation on app lift:
module.exports = function defineIndexManagerHook(sails) { return { async initialize() { sails.log.info('Starting index management hook...'); // Loop through all models to process indexes for (const modelName in sails.models) { const model = sails.models[modelName]; if (!model.indexes) continue; // Skip models with no index config try { // Get the raw MongoDB collection for the model const collection = await model.getDatastore().manager.collection(model.tableName); // Create each defined index for (const indexConfig of model.indexes) { await collection.createIndex(indexConfig.keys, indexConfig.options); sails.log.info(`✅ Created index ${JSON.stringify(indexConfig.keys)} for model ${modelName}`); } } catch (err) { sails.log.error(`❌ Failed to create indexes for model ${modelName}:`, err); } } sails.log.info('Index management hook finished.'); } }; };
This hook runs automatically when your Sails app starts, so all indexes will be created on first deployment.
2. Deployment Updates: Migrate & Ensure Indexes Are Up-to-Date
For updates, you need to handle three scenarios: adding new indexes, updating existing index configurations, and removing unused old indexes. You can either extend the startup hook or build a standalone migration script.
Option A: Extend the Startup Hook for Incremental Updates
Modify the hook from above to compare existing indexes against your model configs, then update/clean up as needed:
// Inside the model loop in your index-manager hook const collection = await model.getDatastore().manager.collection(model.tableName); const existingIndexes = await collection.indexes(); // Convert existing index keys to a string format for easy comparison const existingIndexKeys = existingIndexes.map(idx => Object.keys(idx.key).map(k => `${k}_${idx.key[k]}`).join('_') ); for (const indexConfig of model.indexes) { const indexKeyStr = Object.keys(indexConfig.keys).map(k => `${k}_${indexConfig.keys[k]}`).join('_'); // Create missing indexes if (!existingIndexKeys.includes(indexKeyStr)) { await collection.createIndex(indexConfig.keys, indexConfig.options); sails.log.info(`✅ Created missing index ${indexKeyStr} for ${modelName}`); } else { // Update indexes if their configuration changed (e.g., unique/sparse flags) const existingIndex = existingIndexes.find(idx => Object.keys(idx.key).map(k => `${k}_${idx.key[k]}`).join('_') === indexKeyStr ); const needsUpdate = existingIndex.unique !== (indexConfig.options?.unique || false) || existingIndex.sparse !== (indexConfig.options?.sparse || false); if (needsUpdate) { sails.log.info(`🔄 Updating index ${indexKeyStr} for ${modelName}`); await collection.dropIndex(indexConfig.keys); await collection.createIndex(indexConfig.keys, indexConfig.options); } else { sails.log.info(`ℹ️ Index ${indexKeyStr} for ${modelName} is up to date`); } } } // Optional: Delete unused indexes (proceed with caution!) for (const existingIndex of existingIndexes) { if (existingIndex.name === '_id_') continue; // Skip default _id index const idxKeyStr = Object.keys(existingIndex.key).map(k => `${k}_${existingIndex.key[k]}`).join('_'); const isDesired = model.indexes.some(idx => Object.keys(idx.keys).map(k => `${k}_${idx.keys[k]}`).join('_') === idxKeyStr ); if (!isDesired) { sails.log.info(`🗑️ Dropping unused index ${idxKeyStr} for ${modelName}`); await collection.dropIndex(existingIndex.name); } }
Option B: Standalone Migration Script
If you don't want index operations to slow down app startup, create a standalone script to run during deployment (e.g., via CI/CD):
// scripts/manage-indexes.js const sails = require('sails'); // Lift Sails without starting HTTP/socket servers sails.lift({ hooks: { http: false, sockets: false, views: false }, log: { level: 'info' } }, async function(err) { if (err) { console.error('Failed to initialize Sails:', err); process.exit(1); } try { // Copy the index comparison/update logic from the hook here // ... (same code as the extended hook's model loop) console.log('✅ Index migration completed successfully'); await sails.lower(); process.exit(0); } catch (err) { console.error('❌ Index migration failed:', err); await sails.lower(); process.exit(1); } });
Run it during deployment with:
node scripts/manage-indexes.js
Key Notes for Production
- Index Locking: Use
background: trueto avoid blocking database operations during index creation. - Unique Index Safety: Before creating unique indexes, ensure your existing data has no duplicates (otherwise creation will fail).
- Permissions: Make sure your app's database user has
createIndexanddropIndexpermissions. - Low Peak Execution: For large collections, run index updates during low-traffic periods to minimize impact.
内容的提问来源于stack exchange,提问作者Gustavo Garcia

