The icons and the images of the site always came with 200, also when the browser had them already. The answers had Cache-Control: public, max-age=86400, but no ETag. When the browser asked again, for example when a person loaded the page again, it had no tag to send, and mkfifo.com sent all the bytes again.
A tag for each answer
Now an answer with the status 200 has an ETag: the first 16 bytes of a SHA-256 of its Content-Type and its body. A GET or a HEAD with the same tag in If-None-Match gets 304, with no body. The values of If-None-Match can be a list, a tag with W/, or *.
An answer with Set-Cookie gets no tag. A 304 has no cookie, and a browser that keeps its old copy would also keep no new cookie.
mkfifo.com makes the answer, then compares the tags. Thus a 304 does not save the work of the VM, only the bytes. The cache of Cloudflare in front of the Worker saves the work: it keeps a public answer for its max-age, and the VM does not get its Requests.
Turns for the clients
The rate limits count the Requests of each client address in a window of time: 120 in one minute for one FIFO. They do not count the work of a Request. One Durable Object does the work of all the FIFOs, and the VM gives each process a part of the time. Thus many Requests of one address at the same time took the time of many processes.
Now at most 4 Requests of one client address run at the same time. A process, MkFIFO.Slots, gives the turns. The other Requests of that address wait in the order of arrival, at most 20 seconds, then get 503 with Retry-After: 2. A line of the terminal, and a letter of a page or of "Try", get a message. The Requests of the other addresses do not wait for them.
The process monitors each Request that has a turn and each Request that waits. A Request that stops with an error gives its turn to the next one. The process also ends the wait itself, after 20 seconds: a turn cannot go to a Request that stopped to wait.
An email from the Worker has no client address. The rate limits do not count the emails, because Cloudflare Email Routing limits them, and the turns do the same.
A regex in each keyword
/fpo/300x250 took 26 to 32 ms in the Worker on my computer, and robots.txt 16 ms. The answer of /fpo had 7 calls of jq, each with the same filter of 575 characters, and the parse of that filter took 413 µs of the 590 µs of a call.
The parser of jq used a regex to make sure that a keyword is a whole word: or and not the start of order. It looks for and, or and as after each term. Since OTP 28, Elixir compiles a regex of ~r again each time it runs. A check with that regex took 2.2 µs on my computer, and the same check with a match of the bytes took 0.22 µs. Now the parser has no regex for keywords, names and numbers. The parse of the same filter takes about 200 µs, and a call about 270 µs. The test against jq 1.7 gives the same output for its 41,897 cases.
Fewer calls in /fpo
Each text of /fpo had two values from jq: its y and its font-size. Now each text has font-size="10", and one value: transform="translate(150 125) scale(5)". A short call gives the height of a square. The answer has 2 long calls, not 7.
I drew the old SVG and the new one in Chromium, and compared the pixels. Only the edges of some letters differ, by the anti-aliasing of a font of 10 that is 5 times larger.
A /fpo takes 2.4 ms on my computer now, not 5.5 ms. In the Worker on my computer, it takes 17 to 19 ms, and robots.txt 15 ms.
/image and /img
/image was a free name, and any person could make a FIFO there that looks like a part of /images. Now image and img are names of the site, and each route of images has the line Cc: image, img: /img/width=192,format=webp/photo gives the same image as /images/width=192,format=webp/photo.
A mirror gets the Requests to its own name, but a route had the name of its FIFO in its pattern: /images/width=:width/:src did not agree with /img/width=192/photo. Now a route examines the path after its first segment, the name of the FIFO that got the Request. The same rule works for a FIFO of a person: an envelope with To: shop/:item and Cc: store takes /store/apple.
Fewer minutes of CI
A run of the CI took 15 to 23 minutes, also for a change of one post. The test of the builtins against the tools of the shell took 9.5 minutes, the test of accessibility 4, the TLA+ models 3, and mix precommit 3.
Now mix precommit, the fonts and the contracts run each time, and scripts/changes.sh decides for the other checks. It finds the last commit with a good run: the branch of a merge, else the commit before the push, else main. A check runs only when a file that it examines changed after that commit. A change of the CI, of mix.exs, of mix.lock or of config/ runs all the checks. Thus the commit that sets the date of a post, and the merge of a tested branch, need only a few minutes.