At 100+ profiles, hardcoding profile_ids in workers breaks — you need a pool manager that tracks tier, health, and lease state. This recipe sits between CMDB and the queue worker: workers ask the pool for an available profile, not a static list.

Architecture

CMDB (Postgres/JSON)     → source of truth: profile_id, tier, geo, ban_status
Redis pool sets          → mlx:pool:prod, mlx:pool:warm, mlx:pool:burn
Health cron (5 min)      → probe start/stop → move between sets
Queue worker             → acquire(profile_id) → lease → job → release

CMDB profile row

profile_id: uuid-...
tier: prod | warm | burn
geo: US
platform: ebay.com
ban_status: ok | suspect | burned
last_health_at: 2026-06-17T10:00:00Z
last_job_at: 2026-06-17T09:55:00Z
health_fail_count: 0

Redis pool sets

import redis

r = redis.Redis(decode_responses=True)

def sync_pool(profile_id: str, tier: str, ban_status: str):
    """Call after CMDB update or health probe."""
    for t in ("prod", "warm", "burn"):
        r.srem(f"mlx:pool:{t}", profile_id)
    if ban_status == "burned":
        r.sadd("mlx:pool:burn", profile_id)
    else:
        r.sadd(f"mlx:pool:{tier}", profile_id)

Acquire profile (worker API)

import random

def acquire_profile(tier: str = "prod", geo: str | None = None) -> str | None:
    """Return profile_id or None if pool exhausted."""
    candidates = list(r.smembers(f"mlx:pool:{tier}"))
    random.shuffle(candidates)
    for profile_id in candidates:
        lease_key = f"mlx:lease:{profile_id}"
        if r.exists(lease_key):
            continue  # already leased
        if geo and not profile_geo_matches(profile_id, geo):
            continue
        if r.set(lease_key, "pool-manager", nx=True, ex=900):
            return profile_id
    return None

Health probe cron

async def health_probe(profile_id: str) -> bool:
    """Lightweight: API start → CDP ping → stop. No platform login."""
    try:
        async with httpx.AsyncClient(timeout=90) as client:
            start = await client.post(
                f"{MLX}/profile/start",
                json={"profile_id": profile_id, "headless": True},
                headers={"Authorization": TOKEN},
            )
            start.raise_for_status()
            cdp = start.json().get("cdp_url")
            ok = await cdp_ping(cdp)  # websocket connect + close
            await client.post(
                f"{MLX}/profile/stop",
                json={"profile_id": profile_id},
                headers={"Authorization": TOKEN},
            )
            return ok
    except Exception:
        return False

async def run_health_cycle():
    for tier in ("prod", "warm"):
        for profile_id in r.smembers(f"mlx:pool:{tier}"):
            if r.exists(f"mlx:lease:{profile_id}"):
                continue
            ok = await health_probe(profile_id)
            if not ok:
                fails = r.incr(f"mlx:health_fail:{profile_id}")
                r.expire(f"mlx:health_fail:{profile_id}", 86400)
                if fails >= 3:
                    sync_pool(profile_id, tier, "burned")
                    notify_ops(f"Profile {profile_id} moved to burn pool")
            else:
                r.delete(f"mlx:health_fail:{profile_id}")

Tier routing rules

TierUsePool behavior
prodLive seller/ad accountsHealth probe every 5 min; strict ban tagging
warmNew profiles warming upManual browse jobs only; no bulk automation
burnBanned or suspect profilesExcluded from acquire; forensics only

Ban signals from DLQ handler or observability auto-move profiles to burn.

Warm pool bootstrap

# New profiles enter warm tier for 7–14 days
def onboard_profile(profile_id: str, geo: str):
    cmdb_insert(profile_id, tier="warm", geo=geo, ban_status="ok")
    sync_pool(profile_id, "warm", "ok")
    schedule_warm_jobs(profile_id)  # manual browse, no platform login automation

Operational rules

Related

Disclosure: MLX-MMO affiliated with Multilogin. Verify API endpoints against official documentation.