Skip to content

Multi-Profile Management

Running multiple browser profiles efficiently is essential for scaling automation. This guide covers strategies for managing profile pools, concurrent execution, and resource optimization.

Quick Start

Run multiple profiles concurrently:

import { GoLogin } from '@gologin/sdk';
import puppeteer from 'puppeteer-core';
async function runProfile(profileName: string, task: (page: any) => Promise<void>) {
const gologin = new GoLogin({ profileName });
const { browserWSEndpoint } = await gologin.start();
const browser = await puppeteer.connect({ browserWSEndpoint });
try {
const page = await browser.newPage();
await task(page);
} finally {
await browser.close();
await gologin.stop();
}
}
// Run 3 profiles in parallel
await Promise.all([
runProfile('profile-1', async (page) => await page.goto('https://example.com')),
runProfile('profile-2', async (page) => await page.goto('https://example.com')),
runProfile('profile-3', async (page) => await page.goto('https://example.com')),
]);

Profile Pool Architecture

Creating a Profile Pool

class ProfilePool {
private available: string[] = [];
private inUse: Set<string> = new Set();
private waitQueue: ((profile: string) => void)[] = [];
constructor(profileNames: string[]) {
this.available = [...profileNames];
}
async acquire(): Promise<string> {
// Return available profile immediately
const profile = this.available.shift();
if (profile) {
this.inUse.add(profile);
return profile;
}
// Wait for a profile to become available
return new Promise((resolve) => {
this.waitQueue.push((profile) => {
this.inUse.add(profile);
resolve(profile);
});
});
}
release(profileName: string): void {
this.inUse.delete(profileName);
// If someone is waiting, give them the profile
const waiting = this.waitQueue.shift();
if (waiting) {
waiting(profileName);
} else {
this.available.push(profileName);
}
}
get stats() {
return {
available: this.available.length,
inUse: this.inUse.size,
waiting: this.waitQueue.length,
};
}
}

Using the Pool

const pool = new ProfilePool([
'worker-001', 'worker-002', 'worker-003',
'worker-004', 'worker-005'
]);
async function processTask(url: string): Promise<string> {
const profileName = await pool.acquire();
try {
const gologin = new GoLogin({ profileName });
const { browserWSEndpoint } = await gologin.start();
const browser = await puppeteer.connect({ browserWSEndpoint });
const page = await browser.newPage();
await page.goto(url);
const content = await page.content();
await browser.close();
await gologin.stop();
return content;
} finally {
pool.release(profileName);
}
}
// Process many URLs with limited concurrency
const urls = ['url1', 'url2', 'url3', /* ... hundreds more */];
const results = await Promise.all(urls.map(processTask));

Concurrency Control

Semaphore Pattern

Limit concurrent browser instances:

class Semaphore {
private permits: number;
private waiting: (() => void)[] = [];
constructor(permits: number) {
this.permits = permits;
}
async acquire(): Promise<void> {
if (this.permits > 0) {
this.permits--;
return;
}
await new Promise<void>((resolve) => {
this.waiting.push(resolve);
});
}
release(): void {
const next = this.waiting.shift();
if (next) {
next();
} else {
this.permits++;
}
}
}
// Limit to 5 concurrent browsers
const semaphore = new Semaphore(5);
async function processWithLimit(task: () => Promise<void>) {
await semaphore.acquire();
try {
await task();
} finally {
semaphore.release();
}
}

Worker Pool Pattern

class WorkerPool {
private workers: Worker[] = [];
private taskQueue: (() => Promise<void>)[] = [];
private running = 0;
constructor(
private profileNames: string[],
private maxConcurrency: number
) {}
async submit(task: (gologin: GoLogin, browser: any) => Promise<void>) {
return new Promise<void>((resolve, reject) => {
this.taskQueue.push(async () => {
try {
await this.executeTask(task);
resolve();
} catch (error) {
reject(error);
}
});
this.processQueue();
});
}
private async processQueue() {
while (this.running < this.maxConcurrency && this.taskQueue.length > 0) {
const task = this.taskQueue.shift()!;
this.running++;
task().finally(() => {
this.running--;
this.processQueue();
});
}
}
private async executeTask(
task: (gologin: GoLogin, browser: any) => Promise<void>
) {
const profileName = this.profileNames[this.running % this.profileNames.length];
const gologin = new GoLogin({ profileName });
const { browserWSEndpoint } = await gologin.start();
const browser = await puppeteer.connect({ browserWSEndpoint });
try {
await task(gologin, browser);
} finally {
await browser.close();
await gologin.stop();
}
}
}

Resource Management

Memory Optimization

class ResourceMonitor {
private maxMemoryMB: number;
constructor(maxMemoryMB: number = 8000) {
this.maxMemoryMB = maxMemoryMB;
}
async canLaunchMore(): Promise<boolean> {
const used = process.memoryUsage();
const usedMB = used.heapUsed / 1024 / 1024;
return usedMB < this.maxMemoryMB * 0.8; // 80% threshold
}
getOptimalConcurrency(): number {
const totalMemory = require('os').totalmem() / 1024 / 1024;
const perBrowser = 400; // MB per browser instance
return Math.floor((totalMemory * 0.7) / perBrowser);
}
}

Graceful Shutdown

class ProfileManager {
private activeProfiles: Map<string, GoLogin> = new Map();
private shuttingDown = false;
async start(profileName: string): Promise<GoLogin> {
if (this.shuttingDown) {
throw new Error('Manager is shutting down');
}
const gologin = new GoLogin({ profileName });
await gologin.start();
this.activeProfiles.set(profileName, gologin);
return gologin;
}
async stop(profileName: string): Promise<void> {
const gologin = this.activeProfiles.get(profileName);
if (gologin) {
await gologin.stop();
this.activeProfiles.delete(profileName);
}
}
async shutdownAll(): Promise<void> {
this.shuttingDown = true;
console.log(`Shutting down ${this.activeProfiles.size} profiles...`);
await Promise.all(
Array.from(this.activeProfiles.keys()).map((name) => this.stop(name))
);
console.log('All profiles stopped');
}
}
// Handle process termination
const manager = new ProfileManager();
process.on('SIGINT', async () => {
await manager.shutdownAll();
process.exit(0);
});

Profile Rotation Strategies

Round-Robin

class RoundRobinRotator {
private index = 0;
constructor(private profiles: string[]) {}
next(): string {
const profile = this.profiles[this.index];
this.index = (this.index + 1) % this.profiles.length;
return profile;
}
}

Weighted Rotation

class WeightedRotator {
private weights: Map<string, number>;
private totalWeight: number;
constructor(profileWeights: Record<string, number>) {
this.weights = new Map(Object.entries(profileWeights));
this.totalWeight = Array.from(this.weights.values()).reduce((a, b) => a + b, 0);
}
next(): string {
let random = Math.random() * this.totalWeight;
for (const [profile, weight] of this.weights) {
random -= weight;
if (random <= 0) return profile;
}
return Array.from(this.weights.keys())[0];
}
adjustWeight(profile: string, delta: number) {
const current = this.weights.get(profile) || 1;
const newWeight = Math.max(0.1, current + delta);
this.weights.set(profile, newWeight);
this.totalWeight = Array.from(this.weights.values()).reduce((a, b) => a + b, 0);
}
}

Health-Based Rotation

class HealthBasedRotator {
private health: Map<string, { successes: number; failures: number }> = new Map();
constructor(private profiles: string[]) {
for (const p of profiles) {
this.health.set(p, { successes: 0, failures: 0 });
}
}
recordResult(profile: string, success: boolean) {
const h = this.health.get(profile)!;
if (success) h.successes++;
else h.failures++;
}
next(): string {
// Pick the healthiest profile
return this.profiles.sort((a, b) => {
const hA = this.health.get(a)!;
const hB = this.health.get(b)!;
const rateA = hA.successes / (hA.successes + hA.failures + 1);
const rateB = hB.successes / (hB.successes + hB.failures + 1);
return rateB - rateA;
})[0];
}
getBlockedProfiles(): string[] {
return this.profiles.filter((p) => {
const h = this.health.get(p)!;
const rate = h.successes / (h.successes + h.failures + 1);
return rate < 0.3;
});
}
}

Batch Operations

Batch Profile Creation

async function createProfiles(count: number, prefix: string): Promise<string[]> {
const profiles: string[] = [];
for (let i = 0; i < count; i++) {
const name = `${prefix}-${i.toString().padStart(3, '0')}`;
const gologin = new GoLogin({
profileName: name,
createProfile: true,
platform: 'windows',
locale: 'en-US',
timezone: 'America/New_York',
});
await gologin.start();
await gologin.stop();
profiles.push(name);
console.log(`Created profile ${i + 1}/${count}: ${name}`);
}
return profiles;
}
// Create 20 profiles
const profiles = await createProfiles(20, 'worker');

Batch Processing

async function batchProcess<T, R>(
items: T[],
processor: (item: T, profile: string) => Promise<R>,
options: {
profiles: string[];
concurrency: number;
retries?: number;
}
): Promise<R[]> {
const results: R[] = [];
const pool = new ProfilePool(options.profiles);
const semaphore = new Semaphore(options.concurrency);
await Promise.all(items.map(async (item, index) => {
await semaphore.acquire();
const profile = await pool.acquire();
try {
let lastError: Error | null = null;
for (let attempt = 0; attempt <= (options.retries || 0); attempt++) {
try {
results[index] = await processor(item, profile);
break;
} catch (error) {
lastError = error as Error;
if (attempt < (options.retries || 0)) {
await new Promise(r => setTimeout(r, 1000 * (attempt + 1)));
}
}
}
if (results[index] === undefined && lastError) {
throw lastError;
}
} finally {
pool.release(profile);
semaphore.release();
}
}));
return results;
}

Monitoring

Dashboard Metrics

class ProfileMetrics {
private metrics = {
totalLaunches: 0,
totalErrors: 0,
activeSessions: 0,
avgSessionDuration: 0,
profileStats: new Map<string, {
launches: number;
errors: number;
totalDuration: number;
}>(),
};
recordLaunch(profile: string) {
this.metrics.totalLaunches++;
this.metrics.activeSessions++;
const stats = this.getProfileStats(profile);
stats.launches++;
}
recordStop(profile: string, durationMs: number) {
this.metrics.activeSessions--;
const stats = this.getProfileStats(profile);
stats.totalDuration += durationMs;
}
recordError(profile: string) {
this.metrics.totalErrors++;
const stats = this.getProfileStats(profile);
stats.errors++;
}
private getProfileStats(profile: string) {
if (!this.metrics.profileStats.has(profile)) {
this.metrics.profileStats.set(profile, {
launches: 0, errors: 0, totalDuration: 0
});
}
return this.metrics.profileStats.get(profile)!;
}
getSummary() {
return {
...this.metrics,
errorRate: this.metrics.totalErrors / this.metrics.totalLaunches,
profileStats: Object.fromEntries(this.metrics.profileStats),
};
}
}

Best Practices

  1. Right-size your pool — 5-10 profiles for most use cases
  2. Monitor resources — Watch memory and CPU usage
  3. Handle failures gracefully — Retry with different profiles
  4. Rotate intelligently — Use health-based rotation
  5. Clean up properly — Always stop profiles on shutdown

Next Steps