RE:NODE

Valkey: transakce, Lua skripty a atomické vzory (MULTI, WATCH)

Text: Tým RE:NODE
17 min čtení


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:

code
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řebaJeden atomický příkaz
Počítat bez ztracených aktualizacíINCR, INCRBY, HINCRBY, ZINCRBY
Vytvořit, jen pokud neexistujeSET key value NX
Vytvořit s expirací v jednom krokuSET key value NX EX 30
Přečíst a smazatGETDEL
Přečíst a obnovit TTLGETEX key EX 60
Přesunout mezi seznamyLMOVE source dest LEFT RIGHT
Vyjmout člena s nejnižším skóreZPOPMIN

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):

code
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) 1

EXEC 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ř MULTI dostaneš QUEUED, ne hodnotu. Nemůžeš provést GET čísla a použít ho v dalším zařazeném příkazu, protože GET ješ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 INCR na 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 EXEC celou transakci odmítne s EXECABORT. Pokud je příkaz správně utvořený, ale selže při provádění (hodnota špatného typu), selže sám.
code
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) OK

Klíč 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:

transaction.js
// 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
transaction.py
# 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ý:

  1. Zavolej WATCH na klíče, které se chystáš číst.
  2. Přečti je normálně (mimo MULTI) a rozhodni se ve vlastním kódu.
  3. MULTI, zařaď zápisy, EXEC.
  4. Pokud EXEC vrátil null, někdo změnil sledovaný klíč. Vrať se ke kroku 1.
reply arraynull, retryYour codedecides in the appGET keyread and decideMULTI / EXECwrite if unchangedCommittedno one touched itWATCH keystart watching
Transakce s WATCH a její opakování

V Pythonu to vypadá takto, výběr, který odmítne jít pod nulu:

withdraw.py
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 - retry

Několik pravidel, která ušetří čas při ladění:

  • WATCH se ruší příkazy EXEC, DISCARD, UNWATCH a zavřením spojení. Ať transakce proběhla, nebo se přerušila, sledování je pryč, takže při opakování musíš WATCH zavolat znovu.
  • WATCH patří ke spojení. S poolem spojení musíš pro WATCH, čtení i EXEC použít totéž spojení, což dělá kontext pipeline() výše. Zavolat WATCH přes jedno spojení z poolu a MULTI př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:

code
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:

take_stock.lua
-- 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:

bash
$ valkey-cli -h db.example.net -p 6380 --askpass --eval take_stock.lua stock:42 , 3(integer) 17

Nebo přímo, kde numkeys zadáváš sám:

bash
$ 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, false na nil. Pokud potřebuješ desetinné číslo, vrať ho jako řetězec přes tostring.
  • `tonumber` před aritmetikou. Vše v ARGV jsou řetězce, stejně jako odpovědi GET.
  • Chyby. redis.call skript zastaví a vrátí klientovi chybu, když příkaz selže. redis.pcall vrátí chybu jako tabulku, takže ji skript může zpracovat. Ani jeden nevrací zpět zápisy, které skript už provedl - stejně jako u MULTI zanechá 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:

bash
$ 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) 14

Hash 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í EVALSHA chybou NOSCRIPT. Co restart zachová, najdeš v článku o persistenci AOF a RDB.
  • Klientské knihovny to řeší tak, že nejdřív zkusí EVALSHA a při NOSCRIPT se vrátí k EVAL s plným textem. Pokud voláš EVALSHA ručně, musíš udělat totéž.
  • SCRIPT EXISTS sha [sha ...] ukáže, zda jsou hashe v cache, SCRIPT FLUSH cache 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ří:

incr_max.lua
-- 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 n

Je 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:

release_lock.lua
-- 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 0

Stejný 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.

cas_update.lua
-- 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 1

Aplikace 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:

bash
$ 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:

code
MULTISMOVE tasks:todo tasks:doing task-77HINCRBY stats:tasks doing 1EXEC

Pokud by cokoli z toho záviselo na hodnotě, kterou je nejdřív třeba přečíst, použij skript.

Co zvolit#

SituaceSáhni po
Stačí jeden krok (INCR, SET NX)Jediný příkaz
Víc zápisů, všechny vstupy známe předemMULTI/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íchLua přes EVAL
Stejná logika u mnoha služebKnihovny 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 MULTI nic, co potřebuje trvanlivost typu všechno nebo nic.
  • S výchozím nastavením appendfsync everysec se 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.

Pomohlo vám to?