Your Discord bot works in development. In production with thousands of users across hundreds of servers, performance problems emerge. Here's how to handle them.
Memory Management
Gateway Intents
Only request the intents your bot needs:
const client = new Client({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMessages
// Don't include GuildMembers if you don't need member events
// Don't include MessageContent if you only use slash commands
]
});
Each intent increases memory usage. GuildPresences alone can add 100MB+ on large bots.
Cache Control
discord.js caches everything by default. In production:
const client = new Client({
intents: [...],
makeCache: Options.cacheWithLimits({
MessageManager: 50, // Cache 50 messages per channel
GuildMemberManager: 100, // Cache 100 members per guild
PresenceManager: 0, // Don't cache presences
ReactionManager: 0, // Don't cache reactions
GuildScheduledEventManager: 0
})
});
A bot on 1,000 servers can reduce memory from 2GB to 300MB with proper cache limits.
Sweep Old Data
Periodically clean caches:
const client = new Client({
sweepers: {
messages: {
interval: 3600, // Check every hour
lifetime: 1800 // Remove messages older than 30 minutes
},
users: {
interval: 3600,
filter: () => user => user.bot && user.id !== client.user.id
}
}
});
Sharding
When your bot exceeds 2,500 servers, Discord requires sharding. Each shard handles a subset of guilds:
// sharder.js
const { ShardingManager } = require('discord.js');
const manager = new ShardingManager('./bot.js', {
token: 'YOUR_TOKEN',
totalShards: 'auto' // Discord calculates needed shards
});
manager.on('shardCreate', shard => {
console.log(`Launched shard ${shard.id}`);
});
manager.spawn();
Cross-Shard Communication
Getting data across shards:
// Get total user count across all shards
const results = await client.shard.broadcastEval(c => c.guilds.cache.reduce((acc, guild) => acc + guild.memberCount, 0));
const totalUsers = results.reduce((acc, count) => acc + count, 0);
Database Optimization
Connection Pooling
const mysql = require('mysql2/promise');
const pool = mysql.createPool({
host: 'localhost',
user: 'bot',
password: 'password',
database: 'bot_db',
connectionLimit: 10,
queueLimit: 0
});
Caching Frequent Queries
Don't query the database on every command:
const guildSettings = new Map();
async function getGuildSettings(guildId) {
if (guildSettings.has(guildId)) {
return guildSettings.get(guildId);
}
const [rows] = await pool.query('SELECT * FROM guild_settings WHERE guild_id = ?', [guildId]);
const settings = rows[0] || defaultSettings;
guildSettings.set(guildId, settings);
// Expire cache after 5 minutes
setTimeout(() => guildSettings.delete(guildId), 300000);
return settings;
}
Error Handling
Uncaught errors crash Node.js. In production:
process.on('unhandledRejection', (error) => {
console.error('Unhandled rejection:', error);
// Don't exit - log and continue
});
client.on('error', (error) => {
console.error('Client error:', error);
});
Monitoring
Health Checks
setInterval(() => {
const used = process.memoryUsage();
console.log({
rss: Math.round(used.rss / 1024 / 1024) + 'MB',
heapUsed: Math.round(used.heapUsed / 1024 / 1024) + 'MB',
guilds: client.guilds.cache.size,
ping: client.ws.ping + 'ms'
});
}, 60000);
PM2 Monitoring
pm2 monit # Real-time monitoring
pm2 logs # View logs
Hosting
For small to medium bots, Space-Node's Discord Bot plans provide dedicated resources:
- Small (Free): Perfect for bots under 50 servers
- Middle (€3/6mo): 5GB storage, 128MB RAM for growing bots
- Large (€6/6mo): 10GB storage, 256MB RAM for established bots
For large bots (1,000+ servers), a VPS gives full control over the runtime environment and resources.
