Documentation Index
Fetch the complete documentation index at: https://aqualink.rive.wtf/llms.txt
Use this file to discover all available pages before exploring further.
Multi-Node Setup
Scale your music bot across multiple Lavalink nodes for improved performance, load distribution, and high availability.
Basic Multi-Node Configuration
Simple Multi-Node Setup
const { Aqua } = require('aqualink');
const aqua = new Aqua(client, [
{
host: 'node1.example.com',
port: 2333,
auth: 'node1_password',
ssl: false,
name: 'node-1',
regions: ['us-east'],
},
{
host: 'node2.example.com',
port: 2333,
auth: 'node2_password',
ssl: false,
name: 'node-2',
regions: ['us-west'],
},
{
host: 'node3.example.com',
port: 2333,
auth: 'node3_password',
ssl: false
name: 'node-3',
regions: ['eu-central'],
}
],
{
nodeResolver: 'leastLoad' // Options: 'leastLoad', 'leastRest', 'random' (default: 'leastLoad')
}
);
Load Balancing Strategies
AquaLink provides several built-in load balancing strategies that can be set using the loadBalancer option in the Aqua constructor.
leastLoad (default): Selects the node with the lowest overall load, calculated based on CPU usage, memory, playing players, and REST API calls. This is generally the best option for most use cases.
leastRest: Selects the node with the fewest REST API calls.
random: Selects a random connected node.
const { Aqua } = require('aqualink');
const aqua = new Aqua(client,
[ /* add your nodes here. */],
{
loadBalancer: 'leastLoad'
}
);
Advanced Node Configuration
You can add custom properties to your node configurations for use in advanced, custom logic (like a custom node selector). The library itself will ignore these extra properties.
Example with Custom Properties
const nodeConfigs = [
{
host: 'premium-node.example.com',
port: 2333,
ssl: 'secure_password',
name: 'premium-1',
regions: ['us-east-1'],
priority: 1,
}
];
Node Selection Logic
Custom Node Selector Example
While AquaLink has a built-in node selector based on the loadBalancer option, you can implement your own logic to choose nodes for specific players.
class CustomNodeSelector {
constructor(aqua) {
this.aqua = aqua;
}
selectNode(guildId, options = {}) {
const availableNodes = this.getAvailableNodes();
if (availableNodes.length === 0) {
throw new Error('No available nodes');
}
if (options.preferredRegion) {
const regionalNodes = availableNodes.filter(node =>
node.regions.includes(options.preferredRegion)
);
if (regionalNodes.length > 0) {
return this.selectBestNode(regionalNodes);
}
}
return this.selectBestNode(availableNodes);
}
getAvailableNodes() {
return Array.from(this.aqua.nodeMap.values())
.filter(node => node.connected && !this.isNodeOverloaded(node))
.sort((a, b) => this.getNodeScore(a) - this.getNodeScore(b));
}
isNodeOverloaded(node) {
const stats = node.stats;
if (!stats) return true;
const cpuUsage = stats.cpu?.systemLoad || 0;
const memoryUsage = (stats.memory?.used / stats.memory?.allocated) * 100 || 0;
const playerCount = stats.playingPlayers || 0;
return cpuUsage > 85 || memoryUsage > 80 || playerCount > 100;
}
getNodeScore(node) {
const stats = node.stats;
if (!stats) return Infinity;
const cpuScore = (stats.cpu?.systemLoad || 0) * 10;
const memoryScore = ((stats.memory?.used / stats.memory?.allocated) * 100 || 0);
const playerScore = (stats.playingPlayers || 0);
return cpuScore + memoryScore + playerScore;
}
selectBestNode(nodes) {
return nodes[0];
}
}
const nodeSelector = new CustomNodeSelector(aqua);
Player Migration
Seamless Migration Example
This example shows how you could manually migrate a player from one node to another, preserving its state.
async function migratePlayer(player, targetNode = null) {
if (!targetNode) {
targetNode = nodeSelector.selectNode(player.guildId);
}
if (player.nodes.name === targetNode.name) {
console.log('Player already on target node');
return player;
}
const state = {
guildId: player.guildId,
voiceChannel: player.voiceChannel,
textChannel: player.textChannel,
queue: player.queue.slice(),
current: player.current,
position: player.position,
volume: player.volume,
filters: player.filters.filters,
paused: player.paused,
loop: player.loop
};
console.log(`Migrating player from ${player.nodes.name} to ${targetNode.name}`);
await player.destroy();
const newPlayer = aqua.createPlayer(targetNode, {
guildId: state.guildId,
voiceChannel: state.voiceChannel,
textChannel: state.textChannel,
});
await newPlayer.connect();
if (state.current) {
newPlayer.queue.push(state.current);
}
if (state.queue.length > 0) {
newPlayer.queue.push(...state.queue);
}
if (newPlayer.queue.length > 0) {
await newPlayer.play();
if (state.position > 0) {
newPlayer.seek(state.position);
}
if (state.paused) {
newPlayer.pause(true);
}
}
if (state.volume !== 100) {
await newPlayer.setVolume(state.volume);
}
if (Object.keys(state.filters).length > 0) {
newPlayer.filters.filters = state.filters;
await newPlayer.filters.updateFilters();
}
newPlayer.setLoop(state.loop);
console.log('Player migration completed');
return newPlayer;
}
Node Monitoring
Health Monitoring Example
Here is an example of a class that periodically checks the health of all connected nodes.
class NodeHealthMonitor {
constructor(aqua) {
this.aqua = aqua;
this.healthData = new Map();
}
startMonitoring(interval = 30000) {
this.monitorInterval = setInterval(() => {
this.checkAllNodes();
}, interval);
console.log('Node health monitoring started');
}
stopMonitoring() {
if (this.monitorInterval) {
clearInterval(this.monitorInterval);
console.log('Node health monitoring stopped');
}
}
async checkAllNodes() {
for (const [id, node] of this.aqua.nodeMap) {
await this.checkNodeHealth(node);
}
}
async checkNodeHealth(node) {
try {
const stats = node.stats;
const health = {
nodeId: node.name,
connected: node.connected,
cpu: stats?.cpu?.systemLoad || 0,
memory: stats?.memory ? (stats.memory.used / stats.memory.allocated) * 100 : 0,
players: stats?.playingPlayers || 0,
uptime: stats?.uptime || 0,
timestamp: Date.now()
};
this.healthData.set(node.name, health);
} catch (error) {
console.error(`Health check failed for node ${node.name}:`, error);
}
}
getHealthReport() {
const report = {};
for (const [nodeId, health] of this.healthData) {
report[nodeId] = {
status: health.connected ? 'online' : 'offline',
cpu: `${health.cpu.toFixed(1)}%`,
memory: `${health.memory.toFixed(1)}%`,
players: health.players,
uptime: this.formatUptime(health.uptime)
};
}
return report;
}
formatUptime(uptime) {
const days = Math.floor(uptime / (1000 * 60 * 60 * 24));
const hours = Math.floor((uptime % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
const minutes = Math.floor((uptime % (1000 * 60 * 60)) / (1000 * 60));
return `${days}d ${hours}h ${minutes}m`;
}
}
const healthMonitor = new NodeHealthMonitor(aqua);
healthMonitor.startMonitoring();