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 parallelawait 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 concurrencyconst 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 browsersconst 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 terminationconst 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 profilesconst 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
- Right-size your pool — 5-10 profiles for most use cases
- Monitor resources — Watch memory and CPU usage
- Handle failures gracefully — Retry with different profiles
- Rotate intelligently — Use health-based rotation
- Clean up properly — Always stop profiles on shutdown
Next Steps
- Profile Management — Deep dive into profile concepts
- Advanced Stealth — Bypass tough detection
- Configuration Options — Customize profile settings