Fronta úloh přesouvá pomalou práci mimo požadavek: webový proces zapíše malý záznam „pošli tento e-mail“ nebo „zmenši tento obrázek“, hned se vrátí k uživateli a samostatný proces workeru si záznam vyzvedne a práci odvede. Valkey je nejběžnější místo, kam se takový záznam ukládá, protože každá populární knihovna front - BullMQ v Node, Celery a RQ v Pythonu, fronta Laravelu v PHP, Sidekiq v Ruby - je postavená na protokolu Redis a Valkey ho mluví beze změny.
Knihovny zvládají těžké části dobře: opakování, zpoždění, backoff, dead letters, souběžnost. Co nedokážou, je ochránit tě před Valkey nastaveným jako cache. Fronta na serveru, který při nedostatku paměti vyhazuje klíče, tiše ztrácí úlohy, a fronta na serveru, který nic neukládá na disk, ztratí při restartu všechny čekající úlohy. Tento článek nastaví jednotlivé knihovny a pak projde nastavení serveru, způsoby selhání a dimenzování, které z fronty dělají něco, čemu lze věřit.
Kdy je fronta správný nástroj#
Fronta se vyplatí, když je práce pomalá, může selhat a být zopakována a nemusí skončit dřív, než uživatel uvidí odpověď. Obvyklí kandidáti:
- Odesílání e-mailů, SMS a push notifikací.
- Generování náhledů, PDF, exportů a reportů.
- Volání API třetích stran, která jsou pomalá nebo omezená rate limitem.
- Webhooky, které přijmeš, chceš je okamžitě potvrdit a teprve potom zpracovat.
- Plánovaná a opakující se práce: noční úklidy, souhrny, opakování plateb.
K čemu fronta není, je práce, na kterou uživatel čeká. Pokud další stránka potřebuje výsledek, fronta jen přidá latenci a smyčku s dotazováním.
Než kvůli tomu přidáš Valkey, zjisti, jestli to nedělá už tvoje databáze. Tabulka s SELECT ... FOR UPDATE SKIP LOCKED je v PostgreSQL a MySQL 8 korektní fronta a má vlastnost, kterou nemá žádná fronta na Valkey: zařazení úlohy a potvrzení dat, která ji vyvolala, proběhnou v jedné transakci. Tuto možnost prochází úlohy na pozadí na malém serveru. Valkey vyhrává, když je objem úloh vysoký, když chceš funkce vyzrálé knihovny místo psaní vlastní, nebo když už Valkey provozuješ pro relace a cache.
Nastavení serveru, na kterém fronta závisí#
O tom, zda úlohy přežijí, rozhodují tři nastavení na serveru Valkey. Zkontroluj je dřív, než napíšeš jakýkoli kód.
| Nastavení | Fronta potřebuje | Proč |
|---|---|---|
maxmemory-policy | noeviction | Jakákoli jiná politika může při plné paměti smazat klíče úloh |
appendonly | yes | Čekající úlohy přežijí restart |
appendfsync | everysec | Při tvrdém zastavení se ztratí nejvýš zhruba sekunda zařazených úloh |
maxmemory | Nastaveno, s rezervou | Nahromaděné úlohy se musí vejít do paměti |
Úlohy ztrácí vyhazování klíčů. Při allkeys-lru plný Valkey maže nejdéle nepoužité klíče a úloha, která nejdéle čeká ve zpožděné sadě, je přesně taková. BullMQ to při startu kontroluje a loguje varování, když politika není noeviction; ostatní nekontrolují vůbec. S noeviction plný server odmítá nové úlohy s chybou, kterou tvůj kód vidí a může na ni alarmovat. To je chování, které chceš: hlasité selhání místo tichého. Politiky podrobně popisuje paměť Valkey a politiky vyhazování.
$ valkey-cli -h 203.0.113.20 -p 6380 --askpass INFO memory | grep -E 'maxmemory|used_memory_human'$ valkey-cli -h 203.0.113.20 -p 6380 --askpass INFO persistence | grep -E 'aof_enabled|rdb_last'Pokud stejný Valkey drží cache, která má vyhazovat, a frontu, která nesmí, upřímná odpověď jsou dvě instance. Malá instance pro frontu stojí méně než ladění ztracených úloh.
Na RE:NODE se plán Valkey ukládá na disk pomocí AOF i snapshotů a přichází chráněný heslem s přihlašovacími údaji generovanými pro každý server. Běží jako samostatný server dostupný na vlastním hostu a portu, takže workery na plánu aplikace i webový proces jinde se připojují ke stejné frontě.
BullMQ v Node.js#
BullMQ je současná knihovna front pro Node, nástupce Bullu. Pod sebou používá ioredis a každou frontu ukládá jako sadu klíčů pod prefixem bull:<queue>:.
$ npm install bullmqimport { Queue } from "bullmq";export const connection = { host: process.env.VALKEY_HOST, port: Number(process.env.VALKEY_PORT), password: process.env.VALKEY_PASSWORD,};export const emails = new Queue("emails", { connection, defaultJobOptions: { attempts: 5, backoff: { type: "exponential", delay: 10_000 }, removeOnComplete: { age: 3600, count: 1000 }, removeOnFail: { age: 7 * 24 * 3600 }, },});// in the web processawait emails.add("welcome", { userId: 42 }, { jobId: `welcome:42` });import { Worker } from "bullmq";import { connection } from "./queue.js";const worker = new Worker("emails", async (job) => { await sendWelcomeEmail(job.data.userId);}, { connection: { ...connection, maxRetriesPerRequest: null }, concurrency: 5 });worker.on("failed", (job, err) => console.error(job?.id, err.message));process.on("SIGTERM", async () => { await worker.close(); process.exit(0); });Řádky, na kterých záleží:
- `maxRetriesPerRequest: null` na spojení workeru. Workery BullMQ používají blokující příkazy, které čekají neomezeně;
iorediss limitem opakování je vzdá a worker se rozbije. BullMQ to nastaví samo, když vytváří spojení, a varuje, pokud mu předáš klienta nastaveného jinak. - `removeOnComplete` a `removeOnFail`. BullMQ ve výchozím stavu ponechává každou dokončenou úlohu. Na vytížené frontě je to nejčastější způsob, jak se Valkey zaplní: ne čekající prací, ale historií, kterou nikdo nečte. Drž omezené okno.
- `jobId`. Vlastní id dělá zařazování idempotentním: přidání úlohy s už existujícím id nic neudělá. Tak se vyhneš dvojímu odeslání uvítacího e-mailu, když se požadavek zopakuje.
- `worker.close()` při `SIGTERM`. Přestane přijímat nové úlohy a počká na běžící, takže deploy nepřeruší úlohu v půlce. Řádné ukončení a health checks vysvětluje, proč na tomto handleru signálu záleží na každé platformě.
Opakující se úlohy - „každou noc ve 3:00“ - jsou v současných verzích BullMQ zabudované jako job schedulers, starší kód používá volbu repeat u add. Použij jedno, ne obojí, a zkontroluj dokumentaci pro nainstalovanou verzi.
Celery a RQ v Pythonu#
Celery používá Valkey jako broker (kde zprávy čekají) a volitelně jako backend výsledků (kde se uchovávají návratové hodnoty).
import osfrom celery import Celeryurl = os.environ["VALKEY_URL"] # redis://:password@host:port/0app = Celery("shop", broker=url, backend=url.replace("/0", "/1"))app.conf.update( task_acks_late=True, worker_prefetch_multiplier=1, task_reject_on_worker_lost=True, result_expires=3600, broker_transport_options={"visibility_timeout": 3600},)@app.task(bind=True, autoretry_for=(ConnectionError,), retry_backoff=True, max_retries=5)def send_welcome(self, user_id): ...$ celery -A tasks worker --loglevel=INFO --concurrency=4$ celery -A tasks beat --loglevel=INFONastavení, které je třeba pochopit, je visibility_timeout. U transportu s protokolem Redis se zpráva převzatá workerem nemaže, ale skryje se, dokud není potvrzena. Pokud ji worker nepotvrdí během visibility timeoutu - ve výchozím stavu hodina - zpráva se doručí znovu jinému workeru. Úloha, která legitimně běží déle než tento timeout, proto běží dvakrát nebo víckrát. Nastav timeout nad nejdelší úlohu a s task_acks_late=True stejně dělej úlohy bezpečné pro dvojí spuštění. result_expires záleží ze stejného důvodu jako removeOnComplete v BullMQ: výsledky, které nikdy nepřečteš, se hromadí jako klíče.
RQ je jednodušší volba, když nepotřebuješ směrování, chordy a canvas z Celery.
from redis import Redisfrom rq import Queue, Retryq = Queue("default", connection=Redis.from_url(os.environ["VALKEY_URL"]))job = q.enqueue(send_welcome, 42, retry=Retry(max=3, interval=[10, 60, 300]), job_timeout=600)$ rq worker --url "$VALKEY_URL" high default lowNázvy front se uvádějí v pořadí priority: worker vyprázdní high, než se podívá na default. Neúspěšné úlohy skončí v registru neúspěšných úloh, který si můžeš prohlédnout a znovu zařadit přes rq info a dashboard RQ. Plánované úlohy (enqueue_in, enqueue_at) potřebují worker spuštěný s --with-scheduler.
Fronty Laravelu#
Systém front Laravelu potřebuje k běhu na Valkey jen konfiguraci.
QUEUE_CONNECTION=redisREDIS_HOST=203.0.113.20REDIS_PORT=6380REDIS_PASSWORD=a-long-generated-password$ php artisan queue:work redis --queue=high,default --tries=3 --backoff=10 --max-time=3600V config/queue.php má spojení redis hodnotu retry_after, ve výchozím stavu 90 sekund. Hraje stejnou roli jako visibility timeout v Celery: úloha rezervovaná déle než retry_after se vrátí zpět do fronty. $timeout tvé nejdelší úlohy musí být o pár sekund kratší než retry_after, jinak dlouhé úlohy běží dvakrát. Dokumentace Laravelu to říká přímo a pořád je to nejčastější chyba front v Laravelu.
--max-time způsobí, že worker po hodině čistě skončí, takže ho ten, kdo ho dohlíží, spustí znovu načisto, což omezuje úniky paměti v dlouho žijících procesech PHP. Po každém deployi spusť php artisan queue:restart, protože běžící worker drží ve paměti starý kód. Laravel Horizon přidává nad stejné fronty s protokolem Redis dashboard a automatické vyvažování. Dohled nad workerem pokrývá nasazení Laravelu do produkce.
Opakování, idempotence a dead letters#
Každá z výše uvedených front doručuje alespoň jednou. Worker může úlohu dokončit a zemřít dřív, než ji potvrdí, a úloha poběží znovu. Navrhuj s tím počítat, ne proti tomu.
- Dělej úlohy idempotentní. „Označ fakturu 512 jako zaplacenou“ lze bezpečně zopakovat. „Přičti 10 k zůstatku“ ne. Ukládej značku zpracování - nejsilnější je unikátní omezení v databázi na id události - a nejdřív ji zkontroluj.
- Do payloadu dávej id, ne objekty. Úloha nesoucí celý záznam uživatele běží se zastaralými daty, pokud čeká hodinu. Úloha nesoucí
userId: 42načte aktuální stav. - Opakuj s backoffem a pak přestaň. Exponenciální backoff s limitem zhruba pěti pokusů zvládne přechodná selhání. Úloha, která selže pětkrát, nezvládne ani šestý pokus; potřebuje člověka.
- Neúspěšné úlohy drž tam, kde se na ně někdo podívá. Sada neúspěšných v BullMQ, registr neúspěšných v RQ a tabulka
failed_jobsv Laravelu jsou dead-letter fronty. Alarmuj na jejich velikost, ne na jednotlivá selhání. - Odděl pomalé a rychlé fronty. Pokud se noční export a e-maily s resetem hesla dělí o frontu a dva workery, export e-maily zablokuje. Naléhavé práci dej vlastní frontu a vlastní workery.
Dimenzování Valkey pro frontu#
Spotřeba paměti fronty je nahromaděný backlog plus případná historie, kterou držíš. Typický záznam úlohy - payload, volby, časová razítka a trocha účetnictví knihovny - má několik set bajtů až několik kilobajtů. Backlog 100 000 úloh po 2 KB je zhruba 200 MB, a proto je ustálená velikost méně důležitá než nejhorší případ: co se stane, když jsou workery hodinu mimo kvůli incidentu a webový proces dál zařazuje.
| Situace | Co spotřebovává paměť | Hrubé vodítko |
|---|---|---|
| Malá aplikace, úlohy zpracované během sekund | Málo; backlog blízko nule | 256 MB bohatě stačí |
| Stálý provoz, dokončené úlohy držené hodinu | Historie dokončených úloh | Počet úloh za hodinu krát velikost |
| Velké payloady (HTML, soubory v base64) | Payloady | Ulož soubor jinam, zařaď odkaz |
| Workery zastavené kvůli incidentu | Celý backlog | Dimenzuj na hodinu zařazování |
Nikdy nedávej soubory do úlohy. Nahraj obrázek na disk nebo do objektového úložiště a zařaď jeho cestu. Měř, neodhaduj: INFO memory pro celek, MEMORY USAGE na vzorku klíče úlohy a vlastní počty knihovny (getJobCounts() v BullMQ, rq info, celery inspect).
CPU je u fronty na Valkey málokdy limit; jedna malá instance zvládne tisíce zařazení za sekundu. Co workery potřebují, je vlastní CPU a paměť na tom, co je spouští.
Co sledovat
Fronta selhává pomalu a pak najednou, takže sleduj čísla, která se pohnou první:
- Velikost backlogu - čekající úlohy na frontu. Backlog, který přes den roste a v noci se vyprázdní, je v pořádku; ten, který jen roste, znamená, že workery nestíhají.
- Stáří nejstarší čekající úlohy. Užitečnější než počet: 10 000 úloh po milisekundě není nic, deset úloh čekajících hodinu je incident.
- Neúspěšné úlohy za hodinu, s alarmem na nárůst, ne na jednotlivá selhání.
- Paměť Valkey vůči limitu, z
INFO memory, s alarmem hodně před zdínoeviction. - Připojení klienti, z
INFO clients. Flotila workerů, která prosakuje spojení, skončí na limitu klientů serveru.
Každá knihovna má dashboard, který ukáže většinu z toho - Bull Board nebo Taskforce pro BullMQ, Flower pro Celery, rq-dashboard pro RQ, Horizon pro Laravel - ale dashboard, na který se nikdo nedívá, není monitoring. Exportuj dvě tři čísla, na kterých záleží, do toho, co už tě alarmuje; o jejich výběru je monitoring, který ti něco řekne.
Workery je také třeba dohlížet. Proces workeru, který skončí - neošetřená výjimka, zastavení pro nedostatek paměti, deploy - se musí automaticky spustit znovu, jinak se fronta tiše přestane vyprazdňovat, zatímco webový proces do ní dál přidává. Na VDS je to jednotka systemd s Restart=always; na hostingovém panelu je to vlastní chování restartu serveru. Tak či tak po každém deployi ověř, že se workery vrátily.
Řešení potíží#
Úlohy mizí, aniž by se spustily nebo selhaly. Vyhazování klíčů, nebo restart bez persistence. Zkontroluj evicted_keys v INFO stats a politiku vyhazování.
Stejná úloha běží dvakrát. Visibility timeout nebo retry_after kratší než úloha, nebo worker, který zemřel dřív, než potvrdil. Zvyš timeout a udělej úlohu idempotentní.
Workery po výpadku sítě přestanou brát úlohy. Spojení spadlo a klient to vzdal. V BullMQ zkontroluj maxRetriesPerRequest: null; jinde zajisti, aby byl worker dohlížen a po ukončení spuštěn znovu.
Paměť Valkey stabilně roste bez backlogu. Dokončené úlohy a výsledky se uchovávají donekonečna. Nastav removeOnComplete, result_expires nebo ekvivalent a nech staré vypršet.
`OOM command not allowed when used memory > 'maxmemory'`. Politika noeviction dělá svou práci. Fronta je plná: workery jsou mimo nebo příliš pomalé, nebo paměť zaplňuje historie.
FAQ#
Může Valkey nahradit RabbitMQ nebo Kafku?
Pro úlohy na pozadí jedné aplikace ano, a s menší starostí o provoz. RabbitMQ je lepší ve složitém směrování mezi mnoha službami a Kafka v uchovávání velkých logů událostí pro přehrání. Pokud vybíráš mezi nimi pro e-maily a náhledy v aplikaci, fronta na Valkey je přiměřená volba.
Fungují BullMQ, Celery a RQ s Valkey beze změny?
Mluví protokolem Redis a používají standardní příkazy a skriptování v Lua, které Valkey implementuje. Připojuješ se stejnou URL redis:// a stejnými klientskými knihovnami. Svou verzi před ostrým během otestuj proti svému serveru, jako u jakékoli změny infrastruktury.
Mají fronta a cache sdílet jeden Valkey?
Radši ne. Cache chce vyhazovací politiku a fronta nesmí nikdy vyhazovat. Na jedné instanci se musíš rozhodnout a obě volby jsou pro jednoho z nich špatné. Vyřeší to dvě malé instance.
Kolik workerů mám provozovat?
Tolik, aby byl backlog ve špičce blízko nule, a ne víc, než dokáže práce využít. U úloh vázaných na I/O, jako je odesílání e-mailů, jeden proces se souběžností 5 až 20 zajde daleko. U úloh vázaných na CPU, jako je zpracování obrázků, jeden worker na jádro.
Co se stane s plánovanými úlohami, když se Valkey restartuje?
S povolenou persistencí jsou na disku a načtou se se vším ostatním; zpožděná úloha, jejíž čas během restartu uplynul, se spustí, jakmile se workery znovu připojí. Bez persistence je každá čekající i plánovaná úloha pryč.




Komentáře
Zcela anonymně: žádný účet, žádný e-mail, žádné cookie. Ukládáme jen jméno, které napíšeš, text a čas - nic dalšího. Počet odkazů je omezený a značkování se nevykresluje.