[{"data":1,"prerenderedAt":667},["ShallowReactive",2],{"docs:/docs/concepts/replay":3},{"id":4,"title":5,"body":6,"description":660,"extension":661,"meta":662,"navigation":204,"path":663,"seo":664,"stem":665,"__hash__":666},"docs/docs/concepts/replay.md","Replay and Durable Delivery",{"type":7,"value":8,"toc":653},"minimark",[9,13,17,22,35,38,42,45,65,68,90,94,118,122,151,166,618,622,649],[10,11,5],"h1",{"id":12},"replay-and-durable-delivery",[14,15,16],"p",{},"Replay is a broker-side catch-up for load-balanced worker groups. It is not a general message-history feature. This page states exactly what it does so you can decide whether your design can rely on it.",[18,19,21],"h2",{"id":20},"the-short-version","The short version",[23,24,25,29,32],"ul",{},[26,27,28],"li",{},"An ordinary client that reconnects gets its subscriptions restored. It does not receive the messages that were published while it was away.",[26,30,31],{},"A subscription that was created fresh receives no history.",[26,33,34],{},"A worker in a load-balanced group with a persistent session does get the tasks that were dispatched while the group was scaled to zero, and exactly one member of the group receives each one.",[14,36,37],{},"If your design needs \"what did I miss\", keep your own store and read it on connect. Blueprint SDKs cache what they have seen in memory and nothing more.",[18,39,41],{"id":40},"what-replay-does","What replay does",[14,43,44],{},"Replay runs on the hosted platform only, where the durable delivery store is enabled and message recording is on. Self-hosted kraken ships with a no-op delivery store, so nothing is replayed there unless you configure a backend.",[14,46,47,48,52,53,56,57,60,61,64],{},"It fires when a connection that subscribed with ",[49,50,51],"code",{},"loadBalance: true"," also identifies as a durable worker. A connection is a durable worker when its actor type is ",[49,54,55],{},"agent"," or ",[49,58,59],{},"orchestrator",", which hold a persistent broker session, or when it has published room presence with ",[49,62,63],{},"persistent: true",".",[14,66,67],{},"On that signal the broker reads the group's cursor, queries the recorded messages for that room that are newer than the cursor, in pages of 100 up to 10 pages, and atomically claims each one so that exactly one member of the group receives it. The first time a group is seen its cursor is set to \"now\" and nothing is replayed; later reconnects replay only what was dispatched after that point.",[14,69,70,71,74,75,78,79,74,82,85,86,89],{},"Replayed frames arrive between a ",[49,72,73],{},"replayStart"," frame carrying ",[49,76,77],{},"count"," and a ",[49,80,81],{},"replayEnd",[49,83,84],{},"replayed",", and each message has ",[49,87,88],{},"isReplay: true"," in its metadata. Live messages that arrive during a replay are buffered and flushed afterwards, deduplicated by message id, so a worker sees each message once and in order.",[18,91,93],{"id":92},"what-replay-does-not-do","What replay does not do",[23,95,96,102,105,115],{},[26,97,98,99,101],{},"It does not run for a plain subscription, whatever the actor type. Without ",[49,100,51],{}," there is no group, no cursor, and no replay.",[26,103,104],{},"It does not run on an ordinary reconnect. Reconnection restores your subscriptions and nothing else.",[26,106,107,108,56,111,114],{},"It does not honour per-topic ",[49,109,110],{},"replayEnabled",[49,112,113],{},"maxReplayMessages"," settings. The control plane stores those with the app's topic configuration, but the broker does not read them. Treat them as documentation of intent, not behaviour.",[26,116,117],{},"It does not read the app's retention setting. Retention governs message logging for the dashboard and the message-logs API, not delivery.",[18,119,121],{"id":120},"which-sdks-use-it","Which SDKs use it",[14,123,124,125,128,129,132,133,135,136,139,140,143,144,147,148,150],{},"Of the blueprint SDKs, only ",[49,126,127],{},"@nolag/queue"," workers and ",[49,130,131],{},"@nolag/voice"," orchestrator pools subscribe with ",[49,134,51],{},". ",[49,137,138],{},"@nolag/agents"," inherits the connection-level option for the ",[49,141,142],{},"tasks"," and ",[49,145,146],{},"tools"," topics when you construct the core client with ",[49,149,51],{},", and forces it off for the rest. The other blueprint SDKs never trigger replay.",[14,152,153,154,156,157,160,161,56,163,165],{},"For the core SDK, pass ",[49,155,51],{}," (and optionally ",[49,158,159],{},"loadBalanceGroup",") to the client, or per subscription, and connect with an ",[49,162,55],{},[49,164,59],{}," actor:",[167,168,169,463,547],"code-tabs",{},[170,171,177],"pre",{"className":172,"code":173,"filename":174,"language":175,"meta":176,"style":176},"language-typescript shiki shiki-themes github-light github-dark","import { NoLag } from '@nolag/js-sdk'\n\nconst client = NoLag(WORKER_TOKEN, {\n  loadBalance: true,\n  loadBalanceGroup: 'image-workers',\n})\n\nclient.on('replay:start', ({ count }) => console.log('catching up on', count))\nclient.on('replay:end', ({ replayed }) => console.log('replayed', replayed))\n\nawait client.connect()\nconst room = client.setApp(APP_SLUG).setRoom('image-processing')\nroom.subscribe('jobs')\nroom.on('jobs', (job, meta) => {\n  if (meta.isReplay) console.log('missed while offline:', job)\n})\n","TypeScript","typescript","",[49,178,179,199,206,232,244,255,261,266,306,338,343,358,392,408,439,458],{"__ignoreMap":176},[180,181,184,188,192,195],"span",{"class":182,"line":183},"line",1,[180,185,187],{"class":186},"szBVR","import",[180,189,191],{"class":190},"sVt8B"," { NoLag } ",[180,193,194],{"class":186},"from",[180,196,198],{"class":197},"sZZnC"," '@nolag/js-sdk'\n",[180,200,202],{"class":182,"line":201},2,[180,203,205],{"emptyLinePlaceholder":204},true,"\n",[180,207,209,212,216,219,223,226,229],{"class":182,"line":208},3,[180,210,211],{"class":186},"const",[180,213,215],{"class":214},"sj4cs"," client",[180,217,218],{"class":186}," =",[180,220,222],{"class":221},"sScJk"," NoLag",[180,224,225],{"class":190},"(",[180,227,228],{"class":214},"WORKER_TOKEN",[180,230,231],{"class":190},", {\n",[180,233,235,238,241],{"class":182,"line":234},4,[180,236,237],{"class":190},"  loadBalance: ",[180,239,240],{"class":214},"true",[180,242,243],{"class":190},",\n",[180,245,247,250,253],{"class":182,"line":246},5,[180,248,249],{"class":190},"  loadBalanceGroup: ",[180,251,252],{"class":197},"'image-workers'",[180,254,243],{"class":190},[180,256,258],{"class":182,"line":257},6,[180,259,260],{"class":190},"})\n",[180,262,264],{"class":182,"line":263},7,[180,265,205],{"emptyLinePlaceholder":204},[180,267,269,272,275,277,280,283,286,289,292,295,298,300,303],{"class":182,"line":268},8,[180,270,271],{"class":190},"client.",[180,273,274],{"class":221},"on",[180,276,225],{"class":190},[180,278,279],{"class":197},"'replay:start'",[180,281,282],{"class":190},", ({ ",[180,284,77],{"class":285},"s4XuR",[180,287,288],{"class":190}," }) ",[180,290,291],{"class":186},"=>",[180,293,294],{"class":190}," console.",[180,296,297],{"class":221},"log",[180,299,225],{"class":190},[180,301,302],{"class":197},"'catching up on'",[180,304,305],{"class":190},", count))\n",[180,307,309,311,313,315,318,320,322,324,326,328,330,332,335],{"class":182,"line":308},9,[180,310,271],{"class":190},[180,312,274],{"class":221},[180,314,225],{"class":190},[180,316,317],{"class":197},"'replay:end'",[180,319,282],{"class":190},[180,321,84],{"class":285},[180,323,288],{"class":190},[180,325,291],{"class":186},[180,327,294],{"class":190},[180,329,297],{"class":221},[180,331,225],{"class":190},[180,333,334],{"class":197},"'replayed'",[180,336,337],{"class":190},", replayed))\n",[180,339,341],{"class":182,"line":340},10,[180,342,205],{"emptyLinePlaceholder":204},[180,344,346,349,352,355],{"class":182,"line":345},11,[180,347,348],{"class":186},"await",[180,350,351],{"class":190}," client.",[180,353,354],{"class":221},"connect",[180,356,357],{"class":190},"()\n",[180,359,361,363,366,368,370,373,375,378,381,384,386,389],{"class":182,"line":360},12,[180,362,211],{"class":186},[180,364,365],{"class":214}," room",[180,367,218],{"class":186},[180,369,351],{"class":190},[180,371,372],{"class":221},"setApp",[180,374,225],{"class":190},[180,376,377],{"class":214},"APP_SLUG",[180,379,380],{"class":190},").",[180,382,383],{"class":221},"setRoom",[180,385,225],{"class":190},[180,387,388],{"class":197},"'image-processing'",[180,390,391],{"class":190},")\n",[180,393,395,398,401,403,406],{"class":182,"line":394},13,[180,396,397],{"class":190},"room.",[180,399,400],{"class":221},"subscribe",[180,402,225],{"class":190},[180,404,405],{"class":197},"'jobs'",[180,407,391],{"class":190},[180,409,411,413,415,417,419,422,425,428,431,434,436],{"class":182,"line":410},14,[180,412,397],{"class":190},[180,414,274],{"class":221},[180,416,225],{"class":190},[180,418,405],{"class":197},[180,420,421],{"class":190},", (",[180,423,424],{"class":285},"job",[180,426,427],{"class":190},", ",[180,429,430],{"class":285},"meta",[180,432,433],{"class":190},") ",[180,435,291],{"class":186},[180,437,438],{"class":190}," {\n",[180,440,442,445,448,450,452,455],{"class":182,"line":441},15,[180,443,444],{"class":186},"  if",[180,446,447],{"class":190}," (meta.isReplay) console.",[180,449,297],{"class":221},[180,451,225],{"class":190},[180,453,454],{"class":197},"'missed while offline:'",[180,456,457],{"class":190},", job)\n",[180,459,461],{"class":182,"line":460},16,[180,462,260],{"class":190},[170,464,469],{"className":465,"code":466,"filename":467,"language":468,"meta":176,"style":176},"language-python shiki shiki-themes github-light github-dark","from nolag import NoLag, NoLagOptions\n\nclient = NoLag(WORKER_TOKEN, NoLagOptions(\n    load_balance=True,\n    load_balance_group=\"image-workers\",\n))\n\nawait client.connect()\nroom = client.set_app(APP_SLUG).set_room(\"image-processing\")\nawait room.subscribe(\"jobs\")\n\ndef on_job(job, meta):\n    if meta.is_replay:\n        print(\"missed while offline:\", job)\n\nroom.on(\"jobs\", on_job)\n","Python","python",[49,470,471,476,480,485,490,495,500,504,509,514,519,523,528,533,538,542],{"__ignoreMap":176},[180,472,473],{"class":182,"line":183},[180,474,475],{},"from nolag import NoLag, NoLagOptions\n",[180,477,478],{"class":182,"line":201},[180,479,205],{"emptyLinePlaceholder":204},[180,481,482],{"class":182,"line":208},[180,483,484],{},"client = NoLag(WORKER_TOKEN, NoLagOptions(\n",[180,486,487],{"class":182,"line":234},[180,488,489],{},"    load_balance=True,\n",[180,491,492],{"class":182,"line":246},[180,493,494],{},"    load_balance_group=\"image-workers\",\n",[180,496,497],{"class":182,"line":257},[180,498,499],{},"))\n",[180,501,502],{"class":182,"line":263},[180,503,205],{"emptyLinePlaceholder":204},[180,505,506],{"class":182,"line":268},[180,507,508],{},"await client.connect()\n",[180,510,511],{"class":182,"line":308},[180,512,513],{},"room = client.set_app(APP_SLUG).set_room(\"image-processing\")\n",[180,515,516],{"class":182,"line":340},[180,517,518],{},"await room.subscribe(\"jobs\")\n",[180,520,521],{"class":182,"line":345},[180,522,205],{"emptyLinePlaceholder":204},[180,524,525],{"class":182,"line":360},[180,526,527],{},"def on_job(job, meta):\n",[180,529,530],{"class":182,"line":394},[180,531,532],{},"    if meta.is_replay:\n",[180,534,535],{"class":182,"line":410},[180,536,537],{},"        print(\"missed while offline:\", job)\n",[180,539,540],{"class":182,"line":441},[180,541,205],{"emptyLinePlaceholder":204},[180,543,544],{"class":182,"line":460},[180,545,546],{},"room.on(\"jobs\", on_job)\n",[170,548,553],{"className":549,"code":550,"filename":551,"language":552,"meta":176,"style":176},"language-go shiki shiki-themes github-light github-dark","client := nolag.New(workerToken, nolag.Options{\n    LoadBalance:      true,\n    LoadBalanceGroup: \"image-workers\",\n})\nif err := client.Connect(); err != nil {\n    log.Fatal(err)\n}\nroom := client.SetApp(appSlug).SetRoom(\"image-processing\")\nroom.Subscribe(\"jobs\", func(data any, meta nolag.MessageMeta) {\n    if meta.IsReplay {\n        log.Println(\"missed while offline:\", data)\n    }\n})\n","Go","go",[49,554,555,560,565,570,574,579,584,589,594,599,604,609,614],{"__ignoreMap":176},[180,556,557],{"class":182,"line":183},[180,558,559],{},"client := nolag.New(workerToken, nolag.Options{\n",[180,561,562],{"class":182,"line":201},[180,563,564],{},"    LoadBalance:      true,\n",[180,566,567],{"class":182,"line":208},[180,568,569],{},"    LoadBalanceGroup: \"image-workers\",\n",[180,571,572],{"class":182,"line":234},[180,573,260],{},[180,575,576],{"class":182,"line":246},[180,577,578],{},"if err := client.Connect(); err != nil {\n",[180,580,581],{"class":182,"line":257},[180,582,583],{},"    log.Fatal(err)\n",[180,585,586],{"class":182,"line":263},[180,587,588],{},"}\n",[180,590,591],{"class":182,"line":268},[180,592,593],{},"room := client.SetApp(appSlug).SetRoom(\"image-processing\")\n",[180,595,596],{"class":182,"line":308},[180,597,598],{},"room.Subscribe(\"jobs\", func(data any, meta nolag.MessageMeta) {\n",[180,600,601],{"class":182,"line":340},[180,602,603],{},"    if meta.IsReplay {\n",[180,605,606],{"class":182,"line":345},[180,607,608],{},"        log.Println(\"missed while offline:\", data)\n",[180,610,611],{"class":182,"line":360},[180,612,613],{},"    }\n",[180,615,616],{"class":182,"line":394},[180,617,260],{},[18,619,621],{"id":620},"related","Related",[23,623,624,632,642],{},[26,625,626,631],{},[627,628,630],"a",{"href":629},"/docs/concepts/qos","Quality of Service"," describes what delivery guarantees the broker does provide.",[26,633,634,638,639,641],{},[627,635,637],{"href":636},"/docs/concepts/presence","Presence"," covers persistent presence and the ",[49,640,63],{}," flag.",[26,643,644,648],{},[627,645,647],{"href":646},"/docs/high-level-sdks/queue","Queue SDK"," is the blueprint built on this behaviour.",[650,651,652],"style",{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":176,"searchDepth":201,"depth":201,"links":654},[655,656,657,658,659],{"id":20,"depth":201,"text":21},{"id":40,"depth":201,"text":41},{"id":92,"depth":201,"text":93},{"id":120,"depth":201,"text":121},{"id":620,"depth":201,"text":621},"What NoLag replays, for whom, and when: durable catch-up for load-balanced worker groups on the hosted platform, and what happens on an ordinary reconnect.","md",{},"/docs/concepts/replay",{"title":5,"description":660},"docs/concepts/replay","I4Nta9pBUvAlf6SRfnSsZ_ydEaBBy_cq-5HSZD1yrkk",1789870600008]