Valkey provádí příkazy po jednom, takže každý jednotlivý příkaz je už sám o sobě atomický. Potíže začínají, když tvoje logika potřebuje příkazů víc - přečíst hodnotu, rozhodnout se, zapsat výsledek - protože mezi kroky se může připlést jiný klient. Na to existují čtyři nástroje, seřazené podle síly: jeden atomický příkaz jako INCR nebo SET ... NX, transakce MULTI/EXEC, optimistické zamykání přes WATCH a Lua skript spuštěný příkazem EVAL. Použij nejjednodušší z nich, který tu mezeru zacelí. Pro většinu logiky typu „zkontroluj, potom změň“ je to krátký Lua skript; pro prostou dávku zápisů, které se nesmí proložit cizími, je to MULTI/EXEC; a WATCH se hodí, když se rozhodnutí musí udělat v kódu aplikace.
Tento článek vysvětluje, co přesně každý z nich zaručuje, které záruky lidé předpokládají, ačkoli neexistují (žádný rollback tu není), a několik atomických vzorů - čítače, compare-and-delete, zámky, odečítání skladových zásob, klíče idempotence - které pokryjí většinu reálného použití. Předpokládá, že se umíš připojit; pokud ne, začni článkem Valkey: první kroky.
Proč jsou jednotlivé příkazy bezpečné a posloupnosti ne#
Valkey vykonává příkazy v jednom hlavním vlákně. Dokud běží jeden příkaz, neběží žádný příkaz jiného klienta, a proto INCR counter nikdy neztratí aktualizaci tak, jak to dokáže „přečíst, přičíst jedničku, zapsat“ v kódu aplikace. Chyba je v tvém kódu. Představ si dva workery, které odečítají zásobu:
worker A: GET stock:42 -> 1worker B: GET stock:42 -> 1worker A: SET stock:42 0 (sold the last item)worker B: SET stock:42 0 (sold the same item again)Každý příkaz byl atomický. Posloupnost nebyla. Valkey udělal přesně to, co mu bylo řečeno, v pořadí, které tvůj kód nekontroloval. Řešením není „být rychlejší“; je potřeba buď udělat čtení, rozhodnutí a zápis jako jeden celek, nebo poznat, že se data pod rukama změnila, a zkusit to znovu.
Než sáhneš po něčem těžším, zjisti, jestli to nezvládne jediný příkaz. Mnohé to zvládnou:
| Potřeba | Jeden atomický příkaz |
|---|---|
| Počítat bez ztracených aktualizací | INCR, INCRBY, HINCRBY, ZINCRBY |
| Vytvořit, jen pokud neexistuje | SET key value NX |
| Vytvořit s expirací v jednom kroku | SET key value NX EX 30 |
| Přečíst a smazat | GETDEL |
| Přečíst a obnovit TTL | GETEX key EX 60 |
| Přesunout mezi seznamy | LMOVE source dest LEFT RIGHT |
| Vyjmout člena s nejnižším skóre | ZPOPMIN |
SET key value NX EX 30 je důležitý zvlášť: starší dvoukroková podoba, SETNX a potom EXPIRE, může nechat klíč bez expirace, pokud klient zemře mezi oběma voláními. Jediný příkaz to nedokáže. Kde existuje jeden příkaz, dej mu přednost; zbytek článku je pro případy, kdy žádný takový není. Datové typy, na kterých tyto příkazy pracují, popisuje článek datové typy ve Valkey.
MULTI a EXEC: fronta, ne izolace logiky#
MULTI otevře transakci. Každý další příkaz se nespustí, ale zařadí do fronty a server odpoví QUEUED. EXEC pak spustí celou frontu v pořadí, jako jeden nepřerušitelný blok: mezi prvním a posledním zařazeným příkazem se nevykoná žádný příkaz jiného klienta. Vyzkoušet si to můžeš v valkey-cli, kde se při otevřené transakci v promptu objeví (TX):
127.0.0.1:6379> MULTIOK127.0.0.1:6379(TX)> DECR stock:42QUEUED127.0.0.1:6379(TX)> LPUSH orders:pending "order-9001"QUEUED127.0.0.1:6379(TX)> EXEC1) (integer) 02) (integer) 1EXEC vrací jednu odpověď na každý zařazený příkaz, ve stejném pořadí. DISCARD místo toho frontu zahodí a nic se nezmění.
Co to dává: izolaci (nikdo nevidí napůl hotový stav) a provedení všeho najednou. Co to nedává, a právě tady se lidé mýlí nejčastěji:
- Žádné čtení, které by krmilo pozdější zápisy. Uvnitř
MULTIdostanešQUEUED, ne hodnotu. Nemůžeš provéstGETčísla a použít ho v dalším zařazeném příkazu, protožeGETještě neproběhl. Všechna rozhodnutí je potřeba udělat před otevřením transakce. - Žádný rollback. Pokud jeden příkaz selže za běhu, ostatní se přesto provedou. Transakce s
INCRna klíči, který obsahuje text, ohlásí chybu u tohoto jednoho příkazu a ostatní v klidu aplikuje. - Dva druhy selhání se řeší různě. Pokud je příkaz chybný už při zařazování (neznámý příkaz, špatný počet argumentů), server si chybu zapamatuje a
EXECcelou transakci odmítne sEXECABORT. Pokud je příkaz správně utvořený, ale selže při provádění (hodnota špatného typu), selže sám.
127.0.0.1:6379> SET name "alice"OK127.0.0.1:6379> MULTIOK127.0.0.1:6379(TX)> INCR nameQUEUED127.0.0.1:6379(TX)> SET other 1QUEUED127.0.0.1:6379(TX)> EXEC1) (error) ERR value is not an integer or out of range2) OKKlíč other se nastavil, přestože INCR name selhal. Pokud přicházíš ze světa SQL, je to překvapení, na které je třeba být připravený: transakce ve Valkey jsou dávka, kterou nelze přerušit, ne jednotka práce, kterou lze vzít zpět. Důvodem je designové rozhodnutí - rollback vyžaduje undo log a projekt dává přednost rychlému a jednoduchému serveru s úvahou, že selhání příkazu za běhu je programátorská chyba, která se má zachytit při vývoji.
MULTI není jen pipelining
Pipelining odešle mnoho příkazů, aniž by čekal na odpověď na každý; šetří round tripy, ale příkazy jiných klientů se mohou provést mezi tvými. MULTI/EXEC přidává záruku, že se to nestane. Většina klientských knihoven oba pojmy mísí, takže si ověř, co používáš ty:
// ioredis: .multi() is a real MULTI/EXEC; .pipeline() is notconst results = await valkey .multi() .decr("stock:42") .lpush("orders:pending", "order-9001") .exec();// results: [[null, 0], [null, 1]] (error, value) per command# redis-py: transaction=True (the default) wraps the pipeline in MULTI/EXECpipe = r.pipeline(transaction=True)pipe.decr("stock:42")pipe.lpush("orders:pending", "order-9001")print(pipe.execute()) # [0, 1]V redis-py odešle pipeline(transaction=False) stejné příkazy vůbec bez MULTI. Je to rychlejší a naprosto správné pro dávku nezávislých zápisů, a špatné pro cokoli, co potřebuje záruku bez prokládání.
WATCH: optimistické zamykání#
Samotné MULTI nezvládne „přečti zůstatek a odečti, jen pokud stačí“. Chybějící kus dodá WATCH. Serveru řekneš, které klíče tě zajímají; pokud některý z nich změní jiný klient před tvým EXEC, transakce se nespustí a EXEC vrátí prázdnou (null) odpověď. Pak data přečteš znovu a zkusíš to znovu. Říká se tomu optimistické, protože se nic nezamyká: předpokládáš, že nikdo nezasáhne, a ošetříš případ, kdy zasáhl.
Postup je vždy stejný:
- Zavolej
WATCHna klíče, které se chystáš číst. - Přečti je normálně (mimo
MULTI) a rozhodni se ve vlastním kódu. MULTI, zařaď zápisy,EXEC.- Pokud
EXECvrátil null, někdo změnil sledovaný klíč. Vrať se ke kroku 1.
V Pythonu to vypadá takto, výběr, který odmítne jít pod nulu:
import redisdef withdraw(r, account, amount): key = f"balance:{account}" with r.pipeline() as pipe: while True: try: pipe.watch(key) balance = int(pipe.get(key) or 0) # immediate mode after WATCH if balance < amount: pipe.unwatch() return False pipe.multi() pipe.decrby(key, amount) pipe.execute() # raises WatchError if changed return True except redis.WatchError: continue # someone wrote to it - retryNěkolik pravidel, která ušetří čas při ladění:
WATCHse ruší příkazyEXEC,DISCARD,UNWATCHa zavřením spojení. Ať transakce proběhla, nebo se přerušila, sledování je pryč, takže při opakování musíšWATCHzavolat znovu.WATCHpatří ke spojení. S poolem spojení musíš proWATCH, čtení iEXECpoužít totéž spojení, což dělá kontextpipeline()výše. ZavolatWATCHpřes jedno spojení z poolu aMULTIpřes jiné je klasická chyba: žádné hlášení, žádná ochrana.- Počítá se jakýkoli zápis do sledovaného klíče, i když zapíše stejnou hodnotu. Horký klíč při velké konkurenci roztočí opakovací smyčku naprázdno; omez počet pokusů a skonči srozumitelnou chybou, místo abys cyklil donekonečna.
- Sledovat klíč, který ještě neexistuje, je dovolené a užitečné: jeho vytvoření se počítá jako změna, takhle poznáš souběh dvou klientů, kteří inicializují totéž.
WATCH je správná volba, když rozhodnutí potřebuje aplikační logiku, kterou se do Lua rozumně nevejde - volání jiné služby, parsování JSON vlastními knihovnami. Pro logiku, která se vejde do pár řádků Lua, je skript jednodušší a smyčku s opakováním nepotřebuje vůbec, protože ho nic nemůže přerušit.
EVAL: spusť Lua na serveru#
EVAL pošle serveru Lua skript, který proběhne jako jeden atomický krok. Dokud běží, nespustí se žádný jiný příkaz, takže čtení, rozhodnutí a zápis nelze proložit - bez WATCH, bez opakování, jedním round tripem. Příkaz vypadá takto:
EVAL script numkeys key [key ...] arg [arg ...]numkeys říká, kolik následujících slov jsou názvy klíčů; ve skriptu jsou dostupné jako pole KEYS a vše za nimi jako ARGV. Obě pole se indexují od 1, jak je v Lua zvykem. Skript, který odečte zásobu, jen pokud je jí dost:
-- KEYS[1] stock key; ARGV[1] quantity wantedlocal have = tonumber(redis.call("GET", KEYS[1]) or "0")local want = tonumber(ARGV[1])if have < want then return -1endreturn redis.call("DECRBY", KEYS[1], want)Z příkazové řádky ho spustíš přes --eval. Všimni si samotné čárky, která odděluje klíče od argumentů, s mezerou po obou stranách - to je syntaxe valkey-cli, ne Lua:
$ valkey-cli -h db.example.net -p 6380 --askpass --eval take_stock.lua stock:42 , 3(integer) 17Nebo přímo, kde numkeys zadáváš sám:
$ valkey-cli -h db.example.net -p 6380 --askpass \ EVAL "return redis.call('GET', KEYS[1])" 1 stock:42"20"Uvnitř skriptu spouštíš příkazy Valkey přes redis.call(command, arg, ...). Novější verze Valkey přijímají i alias server.call, ale redis.call funguje na Valkey i na starším Redisu, takže je to bezpečnější zápis, pokud se tvé skripty mohou stěhovat. Několik detailů, na kterých v praxi záleží:
- Každý klíč, kterého se dotkneš, deklaruj v `KEYS`. Skripty, které skládají názvy klíčů uvnitř Lua kódu, fungují na jednom serveru, ale v clusteru se rozbijí, protože server musí vědět, které klíče skript používá, aby ho mohl nasměrovat. I když cluster neplánuješ, deklarace klíčů nic nestojí.
- Hodnoty se vracejí jako typy Lua a zpět jako odpovědi. Číslo v Lua se převede na celočíselnou odpověď (desetinná část se zahodí), tabulka na pole,
falsena nil. Pokud potřebuješ desetinné číslo, vrať ho jako řetězec přestostring. - `tonumber` před aritmetikou. Vše v
ARGVjsou řetězce, stejně jako odpovědiGET. - Chyby.
redis.callskript zastaví a vrátí klientovi chybu, když příkaz selže.redis.pcallvrátí chybu jako tabulku, takže ji skript může zpracovat. Ani jeden nevrací zpět zápisy, které skript už provedl - stejně jako uMULTIzanechá skript, který selže v polovině, dřívější zápisy na místě. Kontroly nejdřív, zápisy nakonec. - Čas předávej jako argument, místo abys spoléhal na hodiny uvnitř skriptu, kdykoli to jde; skript je pak čistou funkcí svých vstupů, což se mnohem snáz testuje.
EVALSHA a cache skriptů
Posílat celý text skriptu při každém volání je plýtvání. SCRIPT LOAD uloží skript na serveru a vrátí jeho hash SHA1, EVALSHA pak skript spustí podle hashe:
$ valkey-cli -h db.example.net -p 6380 --askpass SCRIPT LOAD "$(cat take_stock.lua)""9a7f1c0e5b2d8a64c3e19f0b7d25a8c4e6f13b02"$ valkey-cli -h db.example.net -p 6380 --askpass \ EVALSHA 9a7f1c0e5b2d8a64c3e19f0b7d25a8c4e6f13b02 1 stock:42 3(integer) 14Hash výše je jen ilustrační, tvůj bude jiný. Tři fakta o cache skriptů, která způsobují skutečné incidenty:
- Žije v paměti a mechanismy persistence ji neukládají, takže po restartu odpoví
EVALSHAchybouNOSCRIPT. Co restart zachová, najdeš v článku o persistenci AOF a RDB. - Klientské knihovny to řeší tak, že nejdřív zkusí
EVALSHAa přiNOSCRIPTse vrátí kEVALs plným textem. Pokud volášEVALSHAručně, musíš udělat totéž. SCRIPT EXISTS sha [sha ...]ukáže, zda jsou hashe v cache,SCRIPT FLUSHcache vyprázdní a cache patří serveru, ne databázi.
Funkce v Redisu 7 a ve Valkey
Novější verze přidávají FUNCTION LOAD a FCALL, které na serveru ukládají pojmenované, verzované knihovny Lua funkcí; ty se ukládají spolu s daty a přežijí restart. Tím odpadá problém NOSCRIPT, za cenu dalšího kroku při nasazení: knihovnu načítáš při deployi, místo aby skripty líně načítal klient. Pro většinu aplikací zůstává jednodušší cestou EVAL přes klientskou knihovnu; o funkcích uvažuj, když stejnou serverovou logiku sdílí mnoho služeb. Než se na ně spolehneš, ověř verzi serveru příkazem INFO server.
Cena atomicity: pomalý skript blokuje všechny
Protože skript drží jediné hlavní vlákno, pomalý skript zastaví všechny ostatní klienty. Drž skripty na pár příkazech s omezeným objemem práce. Nikdy uvnitř neprocházej neomezenou množinu (KEYS *, obří seznam, SMEMBERS na milionu členů). Server má časový limit skriptu, ve výchozím stavu pět sekund (nastavení se jmenuje lua-time-limit, novější verze mu říkají také busy-reply-threshold); po jeho překročení začne ostatním klientům odpovídat BUSY a povolí SCRIPT KILL, který funguje, jen pokud skript zatím nic nezapsal. Skript, který už zapisoval, lze zastavit jen příkazem SHUTDOWN NOSAVE, který zahodí všechno od posledního uložení. Ber ho jako hasicí přístroj za sklem a drž skripty tak krátké, abys ho nikdy nepotřeboval.
Atomické vzory, které stojí za to znát#
Čítač se stropem
Běžná kontrola limitu - „zvyš, ale ne nad 100“ - je přesně ten druh čtení, rozhodnutí a zápisu, který samotné INCR nevyjádří:
-- KEYS[1] counter; ARGV[1] maximum; ARGV[2] ttl in secondslocal n = redis.call("INCR", KEYS[1])if n == 1 then redis.call("EXPIRE", KEYS[1], tonumber(ARGV[2]))endif n > tonumber(ARGV[1]) then redis.call("DECR", KEYS[1]) return 0endreturn nJe to stavební kámen limiteru s pevným oknem. Všechny varianty včetně posuvných oken a token bucketu najdeš v článku o rate limitingu ve Valkey.
Compare-and-delete: bezpečné uvolnění zámku
Zámek ve Valkey je klíč vytvořený příkazem SET lock:report <token> NX PX 30000: uspěje, jen pokud zámek nikdo nedrží, a po 30 sekundách vyprší, aby spadlý držitel nezablokoval všechny navždy. Token je náhodný řetězec jedinečný pro držitele. Chyby se dělají při uvolnění. Pokud klíč prostě smažeš přes DEL, můžeš smazat zámek, který mezitím vypršel a získal ho někdo jiný. Smazat ho smíš, jen pokud v něm pořád je tvůj token, a to je čtení následované zápisem:
-- KEYS[1] lock key; ARGV[1] the holder's tokenif redis.call("GET", KEYS[1]) == ARGV[1] then return redis.call("DEL", KEYS[1])endreturn 0Stejný tvar - porovnej, potom jednej - dává „prodluž můj zámek, pokud je pořád můj“ (nahraď DEL příkazem PEXPIRE) i „potvrď tento job, pokud ho pořád vlastním“. Takový zámek chrání před souběhem dvou workerů na jednom serveru. Není to záruka vzájemného vyloučení při rozdělení sítě nebo dlouhé pauze držitele, takže tam, kde by duplicitní práce způsobila skutečnou škodu, udělej idempotentní i samotnou práci.
Check-and-set s verzí
Optimistická kontrola souběhu na jednom záznamu bez WATCH: číslo verze ulož do hashe a zapisuj, jen když odpovídá tomu, co jsi přečetl.
-- KEYS[1] hash; ARGV[1] expected version; ARGV[2] new valuelocal v = redis.call("HGET", KEYS[1], "version")if v ~= ARGV[1] then return 0endredis.call("HSET", KEYS[1], "value", ARGV[2])redis.call("HINCRBY", KEYS[1], "version", 1)return 1Aplikace přečte HGETALL, provede změnu a zavolá skript s verzí, kterou viděla. Nula znamená, že byl někdo rychlejší, a aplikace rozhodne, zda data znovu načte a zopakuje.
Klíče idempotence
Když se požadavek může opakovat (webhook, callback platební brány, zpráva z fronty doručená dvakrát), zaznamenej, že jsi ho už zpracoval, a opakování přeskoč. Zvládne to jeden příkaz:
$ valkey-cli -h db.example.net -p 6380 --askpass SET idem:evt_1042 1 NX EX 86400OK$ valkey-cli -h db.example.net -p 6380 --askpass SET idem:evt_1042 1 NX EX 86400(nil)OK znamená, že jsi první a máš práci provést; (nil) znamená, že ji už někdo převzal. Výhrada se týká selhání mezi převzetím a dokončením: pokud worker po převzetí zemře, opakování bude odmítnuto. Pokud na tom záleží, ukládej stav („started“, „done“) místo holé značky a dej převzetí TTL dostatečně dlouhé pro práci a dostatečně krátké, aby šlo po pádu zopakovat. Jak stejný problém řeší knihovny pro fronty, popisuje článek fronty úloh s Valkey.
Přesun dat mezi klíči
Přesun položky ze seznamu čekajících do seznamu zpracovávaných je jediný LMOVE, atomický sám o sobě. Přesun člena mezi množinami s aktualizací čítače je případ pro MULTI/EXEC, protože všechny argumenty znáš předem:
MULTISMOVE tasks:todo tasks:doing task-77HINCRBY stats:tasks doing 1EXECPokud by cokoli z toho záviselo na hodnotě, kterou je nejdřív třeba přečíst, použij skript.
Co zvolit#
| Situace | Sáhni po |
|---|---|
Stačí jeden krok (INCR, SET NX) | Jediný příkaz |
| Víc zápisů, všechny vstupy známe předem | MULTI/EXEC |
| Rozhodnutí potřebuje kód aplikace nebo cizí volání | WATCH a smyčka s opakováním |
| Přečíst, rozhodnout, zapsat na pár řádcích | Lua přes EVAL |
| Stejná logika u mnoha služeb | Knihovny FUNCTION |
Lua není vždy nejlepší odpověď. Skript je kód, který žije na serveru, mimo tvé obvyklé testy a deploye, v jazyce, ve kterém tým třeba nepíše každý den. Hůř se ladí, za běhu blokuje a je neviditelný pro každého, kdo čte jen aplikaci. Používej ho, když to atomicita opravdu vyžaduje, každý skript drž malý, ukládej skripty jako soubory v repozitáři a pojmenuj je podle toho, co dělají. Pokud píšeš sto řádků Lua, logika pravděpodobně patří do aplikace s jiným datovým modelem, nebo do databáze se skutečnými transakcemi. Poctivé shrnutí článku Redis: kdy ho potřebuješ platí i tady: nejspolehlivější atomická operace je ta, kterou jsi nepotřeboval.
Transakce, replikace a persistence#
Transakce nebo skript se zapisují do append-only souboru a posílají replikám jako příkazy, které se skutečně provedly, takže po restartu log přehraje následky, a ne znovu spouští tvou logiku. Proto se skript, který použil hodiny nebo náhodné číslo, při obnově nerozejde. Plynou z toho dva důsledky:
- Transakce je atomická vůči ostatním klientům, ne vůči pádu. Pokud server zemře uprostřed zápisu, AOF může skončit uprostřed transakce; při startu Valkey buď useká konec, nebo odmítne načítání, dokud soubor neopravíš, jak popisuje článek o persistenci AOF a RDB. Nestav na
MULTInic, co potřebuje trvanlivost typu všechno nebo nic. - S výchozím nastavením
appendfsync everysecse při tvrdém pádu může ztratit poslední sekunda zápisů, včetně transakcí.
Platí i řízení přístupu. Uživatel omezený pravidly ACL potřebuje oprávnění k EVAL/EVALSHA (kategorie @scripting) a ke každému příkazu a klíči, kterých se skript dotýká. Pokud skript selže s chybou oprávnění, ačkoli stejné příkazy ručně fungují, hledej právě tady; viz bezpečnost a ACL ve Valkey.
Jak to běží na serveru Valkey v RE:NODE#
Valkey v řadě databází je open-source fork Redisu, mluví protokolem Redis a je chráněný heslem, přihlašovací údaje se generují pro každý server zvlášť. Ukládá na disk pomocí AOF a snapshotů, takže následky tvých transakcí a skriptů přežijí restart, jak je popsáno výše. Připojuješ se přes valkey-cli nebo libovolného klienta Redis s hostitelem, portem a heslem z panelu a vše v tomto článku se spouští stejně: příkazy výše používají zástupné db.example.net:6380, nahraď ho vlastním hostitelem a portem. Tarify začínají na $2 měsíčně, nejmenší má 256 MB, což na čítače, zámky a klíče idempotence bohatě stačí. Pokud skript blokuje tvůj server, rychle to ukážou grafy paměti a CPU proti limitům tarifu v konzoli; čára CPU přilepená na limitu, zatímco roste latence, je typický příznak drahého skriptu.
FAQ#
Podporuje Valkey rollback?
Ne. DISCARD zahodí frontu před EXEC a transakce s chybně utvořeným příkazem se odmítne celá, ale jakmile se příkazy provádějí, selhání v polovině dřívější příkazy nevrátí. Totéž platí pro Lua skript, který selže po zápisu. Vstupy ověřuj nejdřív a zápisy dej nakonec.
Je MULTI/EXEC totéž co pipeline?
Ne. Pipeline jen sdružuje odesílání příkazů, aby se ušetřily round tripy, a příkazy jiných klientů se mohou provést mezi tvými. MULTI/EXEC zaručuje, že zařazené příkazy proběhnou pohromadě bez prokládání. Některé knihovny říkají „pipeline“ obojímu, takže si ověř, zda tvůj kód opravdu posílá MULTI.
Kdy použít WATCH místo Lua skriptu?
Když se rozhodnutí nedá udělat uvnitř Lua, protože mezi čtením a zápisem potřebuje kód aplikace nebo volání jiného systému. Pro cokoli, co se vejde do krátkého skriptu, je Lua jednodušší a smyčku s opakováním nepotřebuje. Při velké konkurenci o jediný klíč skript také vítězí, protože opakování WATCH se hromadí.
Proč po restartu dostávám NOSCRIPT?
Cache skriptů je v paměti a neukládá se, takže EVALSHA nenajde skript načtený před restartem. Spusť SCRIPT LOAD znovu, nebo nech klientskou knihovnu vrátit se k EVAL s plným textem, což většina dělá automaticky. Funkce načtené přes FUNCTION LOAD se naopak ukládají spolu s daty.
Může Lua skript volat jiný skript nebo používat blokující příkazy?
Skript nemůže zevnitř volat EVAL a blokující příkazy jako BLPOP ve skriptu neblokují; chovají se, jako by časový limit už vypršel. Ve skriptu nech jen neblokující příkazy a čekání řeš na straně klienta.
Jak dlouho smí skript běžet?
Krátce. Výchozí limit je pět sekund, po nichž ostatní klienti dostávají chyby BUSY, ale každý skript, který trvá déle než pár milisekund, už všechny ostatní zpomaluje. Omez objem práce skriptu a velké dávky rozděl do několika volání místo jednoho.


