From 87cbb930625cddeebbebca7318268d7c9873c015 Mon Sep 17 00:00:00 2001 From: fang Date: Thu, 15 Feb 2024 14:23:59 +0100 Subject: [PATCH 1/2] docket: add glob contents into eyre cache Globs contain big, static blobs of data, that only get updated periodically. Eyre has an affordance for caching responses at specified urls. Here we update docket to, whenever a glob gets added, updated or removed, manage the eyre response cache to match. We call +new-cache in every place where either +new-docket or +new-chad gets called, making sure to pass in the old charge so that we can clear potentially-stale entries from the cache. Note that docket's dynamic request serving behavior would respond to both /path.ext and /path/ext with the glob entry for the (clay-style) path /path/ext. However, we only add a response for the '/path.ext' url into the cache. This is the common case, and doing so avoids adding twice the amount of cache pressure. For the %groups desk, whose initial pageload requests 32 files, this takes initial load time down from ~0.5s to ~0.1s (on localhost, so no "real networking" overhead). About half of that remaining time is spent on the initial page request, which usually dynamically falls back to serving /index.html. About a fifth is spent on loading desk.js and session.js, which are served by docket independent of glob contents. Adding those into the cache could bring additional performance gains. This should be considered soft-blocked on urbit/urbit#6909 and urbit/vere#603. Without those changes, docket may make /apps/* urls permanently unusable for dynamic binding. --- desk/app/docket.hoon | 70 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 59 insertions(+), 11 deletions(-) diff --git a/desk/app/docket.hoon b/desk/app/docket.hoon index 5f37d8c2..4dcde1dd 100644 --- a/desk/app/docket.hoon +++ b/desk/app/docket.hoon @@ -259,14 +259,16 @@ ~| [%took-for-nonexistent-charge desk] ?> |((~(has by charges) desk) ?=([%uninstall ~] wire)) =* cha ~(. ch desk) + =/ pre (~(get by charges) desk) ?+ wire ~|(%bad-charge-wire !!) :: [%install ~] ?> ?=(%poke-ack -.sign) ?~ p.sign `state - =. charges (new-chad:cha hung+'Failed install') - ((slog leaf+"Failed installing %{(trip desk)}" u.p.sign) `state) + =. charges (new-chad:cha hung+'Failed install') + %. [(new-cache:cha pre) state] + (slog leaf+"Failed installing %{(trip desk)}" u.p.sign) :: [%uninstall ~] ?> ?=(%poke-ack -.sign) @@ -285,7 +287,8 @@ ?: ?=(%http i.t.t.wire) hung+'failed to fetch glob via http' hung+'failed to fetch glob via ames' - ((slog leaf+"docket: couldn't {act} thread; will retry" u.p.sign) `state) + %. [(new-cache:cha pre) state] + (slog leaf+"docket: couldn't {act} thread; will retry" u.p.sign) :: %fact ?+ p.cage.sign `state @@ -293,12 +296,12 @@ =+ !<([=term =tang] q.cage.sign) ?. |(=(term %cancelled) =(term %http-request-cancelled)) =. charges (new-chad:cha hung+'glob-failed') - :- ~[add-fact:cha] + :- [add-fact:cha (new-cache:cha pre)] ((slog leaf+"docket: thread failed;" leaf+ tang) state) %- (slog leaf+"docket: thread cancelled; retrying" leaf+ tang) =. charges (new-chad:cha %install ~) :_ state - [add-fact:cha fetch-glob:cha] + [add-fact:cha (weld fetch-glob:cha (new-cache:cha pre))] :: %thread-done =+ !<(=glob q.cage.sign) @@ -314,7 +317,7 @@ =/ have=@uv (hash-glob glob) ?. =(want have) =. charges (new-chad:cha hung+'glob hash mismatch') - %. `state + %. [(new-cache:cha `charge) state] =/ url=@t (fall (slaw %t i.t.t.t.wire) '???') %- slog :~ leaf+"docket: glob hash mismatch on {} from {(trip url)}" @@ -323,7 +326,7 @@ == =. charges (new-chad:cha glob+glob) =. by-base (~(put by by-base) base.href.docket.charge desk) - :_(state ~[add-fact:cha]) + [[add-fact:cha (new-cache:cha `charge)] state] == == == @@ -393,14 +396,14 @@ %live ?. ?=(%glob -.href.docket.charge) =. charges (new-chad:cha %site ~) - :_(state ~[add-fact:cha]) + :_(state [add-fact:cha (new-cache:cha `charge)]) :_(state ~[add-fact:cha]) :: ?(%held %dead) =/ glob=(unit glob) ?:(?=(%glob -.chad.charge) `glob.chad.charge ~) =. charges (new-chad:cha %suspend glob) - :_(state ~[add-fact:cha]) + :_(state [add-fact:cha (new-cache:cha `charge)]) == [[card-1 cards-2] state] :: @@ -433,7 +436,7 @@ :: ?: ?=(%site -.href.docket) =. charges (new-chad:cha %site ~) - :- ~[add-fact:cha] + :- [add-fact:cha (new-cache:cha pre)] state :: =. by-base (~(put by by-base) base.href.docket desk) @@ -454,7 +457,7 @@ :: if the glob changed, forget the old and fetch the new :: =. charges (new-chad:cha %install ~) - [[add-fact:cha fetch-glob:cha] state] + [[add-fact:cha (weld fetch-glob:cha (new-cache:cha pre))] state] [[card-1 cards-2] state] -- :: @@ -616,6 +619,7 @@ base.href.docket.charge :: :_ state + %+ weld (new-cache:cha `charge) :: =/ ours=? =/ loc location.glob-reference.href.docket.charge @@ -762,6 +766,50 @@ %+ ~(put by charges) desk [d chad:(~(gut by charges) desk *charge)] ++ new-chad |=(c=chad (~(jab by charges) desk |=(charge +<(chad c)))) + ++ new-cache + |= old=(unit charge) + ^- (list card) + :: docket tries to serve glob contents from the runtime http server cache. + :: as such, we must keep that cache updated as the glob changes. + :: + =; notes=(list note-arvo) + (turn notes arvo:(pass /cache)) + %+ weld + :: clear old cache entries, if any + :: + ?~ old ~ + ?. ?=(%glob -.chad.u.old) ~ + ?. ?=(%glob -.href.docket.u.old) ~ + =/ base=@t + (cat 3 '/apps/' base.href.docket.u.old) + %+ turn ~(tap by glob.chad.u.old) + |= [=path *] + ^- note-arvo + =/ url=@t (rap 3 base (spat (snip path)) '.' (rear path) ~) + [%e %set-response url ~] + :: set new cache entries, if needed + :: + =/ new (~(got by charges) desk) + ?. ?=(%glob -.chad.new) ~ + ?. ?=(%glob -.href.docket.new) ~ + =/ base=@t + (cat 3 '/apps/' base.href.docket.new) + %+ turn ~(tap by glob.chad.new) + |= [=path =mime] + ^- note-arvo + ::NOTE +payload-from-glob responds to both /path.ext and /path/ext, + :: and you could argue we should match that behavior here. however, + :: we already don't (cannot) match its "fall back to /index/html" + :: behavior, and the /path.ext case is by far the more common case. + :: so, we simply assume /path.ext intent, ignore /path/ext cases, + :: and reap 95% of the gains that way. + =/ url=@t (rap 3 base (spat (snip path)) '.' (rear path) ~) + =; payload=simple-payload:http + [%e %set-response url ~ & %payload payload] + ::NOTE matches +payload-from-glob + :_ `q.mime + :- 200 + [content-type+(rsh 3 (crip ))]~ ++ fetch-glob ^- (list card) =/ =charge From 039c103c5972e2799c8f451c22e898601bc9371e Mon Sep 17 00:00:00 2001 From: fang Date: Mon, 30 Sep 2024 16:42:38 +0200 Subject: [PATCH 2/2] docket: more efficient cache refreshing Instead of always wiping and optionally replacing for edits, don't wipe in the first place, for paths whose contents were changed but not deleted. --- desk/app/docket.hoon | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/desk/app/docket.hoon b/desk/app/docket.hoon index 06ca427f..e76ca078 100644 --- a/desk/app/docket.hoon +++ b/desk/app/docket.hoon @@ -780,22 +780,23 @@ :: docket tries to serve glob contents from the runtime http server cache. :: as such, we must keep that cache updated as the glob changes. :: - =; notes=(list note-arvo) - (turn notes arvo:(pass /cache)) - %+ weld - :: clear old cache entries, if any - :: - ?~ old ~ - ?. ?=(%glob -.chad.u.old) ~ - ?. ?=(%glob -.href.docket.u.old) ~ - =/ base=@t - (cat 3 '/apps/' base.href.docket.u.old) - %+ turn ~(tap by glob.chad.u.old) - |= [=path *] - ^- note-arvo - =/ url=@t (rap 3 base (spat (snip path)) '.' (rear path) ~) - [%e %set-response url ~] - :: set new cache entries, if needed + =; caches=(map @t (unit cache-entry:eyre)) + %+ turn ~(tap by caches) + |= [url=@t entry=(unit cache-entry:eyre)] + (arvo:(pass /cache) %e %set-response url entry) + %- %~ gas by + ^- (map @t (unit cache-entry:eyre)) + :: clear old cache entries, if any + :: + ?~ old ~ + ?. ?=(%glob -.chad.u.old) ~ + ?. ?=(%glob -.href.docket.u.old) ~ + =/ base=@t + (cat 3 '/apps/' base.href.docket.u.old) + %- ~(rep by glob.chad.u.old) + |= [[=path *] out=(map @t (unit cache-entry:eyre))] + (~(put by out) (rap 3 base (spat (snip path)) '.' (rear path) ~) ~) + :: overwrite with new cache entries, if needed :: =/ new (~(got by charges) desk) ?. ?=(%glob -.chad.new) ~ @@ -804,16 +805,15 @@ (cat 3 '/apps/' base.href.docket.new) %+ turn ~(tap by glob.chad.new) |= [=path =mime] - ^- note-arvo ::NOTE +payload-from-glob responds to both /path.ext and /path/ext, :: and you could argue we should match that behavior here. however, :: we already don't (cannot) match its "fall back to /index/html" :: behavior, and the /path.ext case is by far the more common case. :: so, we simply assume /path.ext intent, ignore /path/ext cases, :: and reap 95% of the gains that way. - =/ url=@t (rap 3 base (spat (snip path)) '.' (rear path) ~) + :- (rap 3 base (spat (snip path)) '.' (rear path) ~) =; payload=simple-payload:http - [%e %set-response url ~ & %payload payload] + `[& %payload payload] ::NOTE matches +payload-from-glob :_ `q.mime :- 200