Dimitri Glazkov

A long sample, for measuring

· 7 min read

This is a sample post, written for the engine's tests and for its mechanical intuition, which weighs it on every deploy: how many bytes it takes on the wire, how many requests a reader's browser makes to read it, and how long the server takes to answer. It is long on purpose, about as long as the longer essays on the blog, so that what is measured is the weight of reading, not the weight of the page around it. Nothing in it is real writing; it is the shape of writing, with headings, lists, a quote, some code and links, in roughly the proportions an essay has.

Why a long post

A short page hides a lot. Almost any engine serves a hundred words quickly, and almost any stylesheet looks fine around a single paragraph. The differences start to show at length: when the text runs past the first screen, past the first round trip of the connection, past the point where a reader decides whether to stay. A page that arrives in one round trip is read as soon as it is asked for; one that needs three is read after a pause that the reader feels without naming it. So the long post is the one to watch.

There is another reason. A long post has more of everything that can go wrong: more links to resolve, more headings to set, more chances for a wide line of code to push the page sideways on a phone. When the engine changes, this is where a regression is most likely to show first, and where it is cheapest to see.

What the engine does with it

At start, the engine reads every post in the content directory into memory. For each one it keeps the title, the date, the categories and tags, and the Markdown as written, and it works out the post's links: which go to other posts, which to pages, and which go out to the rest of the web. It then renders every page once, inlines the stylesheet, and compresses the result ahead of time, so that answering a request is a lookup and a write.

That last part is the one this post measures most directly. The time to answer should be the time it takes to find a few kilobytes in a table and hand them to the network; anything more is the engine doing work it could have done before the request came. The weight on the wire should be what the reader reads, plus the little it takes to set it well; anything more is decoration the reader pays for.

A list, to see how lists sit

And a numbered one

  1. Load the content directory.
  2. Resolve every link.
  3. Render every page.
  4. Compress every page.
  5. Answer.

A quote

The fastest request is the one that is never made, and the second fastest is the one whose answer was ready before it was asked for.

That is not anyone's line in particular; it is the idea behind the engine, said once so that a quote has something to hold. A quote on the blog sets off the words of someone else, and it should read as quieter than the text around it, not louder.

Some code

A post now and then shows code, and code is where a reading column most often breaks on a phone: a line that will not wrap pushes the whole page wider than the screen. The engine lets a code block scroll sideways on its own, so that the page around it does not.

/// Answers a request from what was rendered at start: a lookup, and a write.
fn answer(table: &Table, path: &str, accepts: Encodings) -> Response {
    match table.get(path) {
        Some(entry) => entry.respond(accepts),
        None => match table.other_slash(path) {
            Some(to) => Response::redirect(to),
            None => table.not_found(accepts),
        },
    }
}

And some inline code, as Site::load, to see how it sits in a sentence beside ordinary words.

Links in and out

A long post usually links to a few others on the blog and a few places elsewhere. This one links back to a ball on a slope, forward in time, and to casino samples, back in time; and out, to RFC 4287, which says what an Atom feed is, and to the CommonMark spec, which says what Markdown is. Each of these is kept as data about the post, so that a page can later show the posts around this one as a map rather than a list.

The margin

The Editorial look puts a margin beside the reading column. Today it holds what is around the post: the posts just before and after it in time, and the posts it links to and that link to it. Later it will hold more: a timeline of the years, a map of related ideas, a small chart where a post is about numbers. The margin is where what helps the reading goes, so that the column itself stays only the words.

On a phone, the margin drops below the post, after the comments, where it is still there for whoever reaches the end, and out of the way of whoever is reading.

The length itself

From here on, the post is mostly length, so that it weighs what a long essay weighs. Each paragraph says something small about how the engine is meant to work, in different words, so that the compression sees text, not repetition.

An engine that serves from memory has one obvious cost: the memory. A blog of a few hundred posts, each a few tens of kilobytes rendered, comes to a few tens of megabytes with both compressions kept, which is small beside what a service is given. The images are the larger part, and they too fit, for now; when they no longer do, they can move to a bucket and be served from there, without changing how the pages are made.

Start-up is the other cost. Rendering and compressing every page takes a moment at start, and a server that has been idle starts again when a reader comes. That moment is paid by the first reader after a quiet spell, and it is why the intuition counts a slow first answer as noise, apart from the others, rather than averaging it into the time to answer.

The pages themselves are plain HTML with a stylesheet inside them. There is no framework on the page, no script to fetch the text after the page arrives, and no font fetched from elsewhere: the type is the reader's own serif, Charter where there is Charter and Georgia where there is not, at a size meant for reading for a while.

Light and dark follow the reader's own setting. The colours are a handful of tokens, a paper-like background and an ink-like text, with one accent for what is a link or a kicker, and each has its dark counterpart, chosen so that the page reads as the same page at night.

Addresses stay as they were on the old blog: a post at its date and slug, a page at its slug, a category under /category/. A path that differs only by its trailing slash is sent, permanently, to the one that is there, so that old links and hand-typed ones both arrive.

The feed is Atom, at /feed/, with the newest posts whole in it, their images and links made absolute so that a reader's feed reader can follow them from wherever it shows them.

Comments from the old blog are kept with the posts they answered, and shown folded under each post, to be opened by whoever wants them. New comments are not taken; the old ones are part of the record.

That is the end of the sample. It is long enough to weigh something, and plain enough that what it weighs is the engine's doing.