Skip to content

Build Not yet confirmed elsewhere1 publisher 3 min read Published

Take the log, delete the cron job: decay ranking in one indexed column

A logarithm replaces both the payment table and the timer that would rewrite every row of it. The concurrency bug it does not remove is the more useful half of the writeup.

The Engineer · Build desk

How we use AI Send a correction

What happened

  • A board of 1,000 tiles lets anyone pay to take a tile from its holder, ranked by a heat value that decays continuously.
  • The full payment history of a tile is collapsed into a single amplitude and timestamp pair, updated only when a payment lands.
  • Taking logs leaves a per-row constant with no time variable in it, stored as an indexed rank_key and sorted directly by SQL.
  • Time is counted in days from an epoch near launch, because unix seconds put roughly 4800 into the key and burn four digits of a double.
  • Tile claims are staked in a synchronous block against an in-memory Set, not via a database lock check.

Why it matters

  • cost The write budget stops tracking clock ticks and starts tracking revenue events, which is the difference between billions of no-op row writes a day and one write per payment.
  • constraint Per-tile half-lives are foreclosed by the same algebra that removes the job, and the failure would be a silently wrong sort order rather than an error.
  • capability Ordering correctness no longer depends on a worker's health, so there is no stale-ranking incident to detect or backfill after an outage.
  • exposure Any tile whose lock check crosses an await inside the Durable Object stays reachable by a second concurrent claimant, and payment intents are on the far side of that await.

Ranking needs order, not values. The decay here is multiplicative and shared: in log space, advancing the clock subtracts the same number from every row at once [5][6]. A uniform translation of a column cannot change which entry is on top. What a timer would be doing, a million times a tick, is moving every row by an identical amount and then discovering nothing changed places [3].

That symmetry is the load-bearing assumption, not a convenience. The term you throw away is -k*t, and it is common to all rows only because k is common to all rows [6]. Give tiles individual half-lives and it becomes -k_i*t, which varies per row, and an index on ln(P) + k_i*t_P quietly returns the wrong order with no query error to tell you [18].

The constants check out. At a three-day half-life, k = ln2/3 = 0.2310 per day [2][14], and the seconds-domain figure quoted in the post, 2.674e-6, times 86,400 is that same 0.231 [9][15]. The twenty-year number needs a footnote: at 0.231 per day the key gains about 84 a year, so 1687 is twenty years measured from the epoch, not twenty years past a present value of 55, which itself puts the epoch roughly 238 days back [10][19]. Either reading is far outside a double's exponent range anyway, which is the author's point about never calling exp on rank_key directly [10].

Display still needs a number, and that is where the discipline shows up. You exponentiate only after shifting the domain to now, and the author reports the shifted value sitting in the 6 to 10 range in log terms [10], which is a heat of about 400 to 22,000 [17]. Additions stay in log space too, using a log1p-based log-add so a small payment landing on top of a large one does not round away [8].

The second half of the writeup is less tidy and worth more. Having removed the scheduled job, the author still had to build a lock by hand [13]. All 1,000 tiles live in one Cloudflare Durable Object, which gives a single JS execution stack with one thing running at a time [11], and that reads like free mutual exclusion. It is not. Every await is a yield point, and the input gate only holds delivery across storage operations, not across a D1 query or an outbound fetch [12]. A SELECT that checks for existing locks lets two callers both see zero locks and both proceed [12]. The fix is to do the check and stake the claim in a block containing no await at all, against a plain in-memory Set, where the single thread genuinely guarantees no interruption [13].

Both halves come from the same accounting. State that changes on its own has to be recomputed, and state that changes only when money moves does not. The algebra pushes the decay into a constant so nothing has to run; the in-memory Set pulls the claim out of the database so nothing has to be trusted across a yield. Neither one was bought with more infrastructure.

What to watch

  • Whether the in-memory Set survives Durable Object eviction or restart, and what happens to inflight claims when it does not.
  • Whether the design ever needs per-tile half-lives, which would break the shared-k assumption the single indexed column rests on.
  • How a float rank_key index behaves under sustained write contention once row counts approach the million the author cites.

Clarity's read

What the record supports and how the coverage leans. The claims behind it follow.

Reality

Evidence66
Adoption12
Hype gap+12
Incentives55
Confidence58
Why these scores

Claim ledger

Ranked by verification strength, evidence, and original report placement.

  1. [1]

    The author built a board of 1,000 tiles where anyone can pay to take a tile away from its current holder; tiles are sorted by a decaying 'heat' value, so the board reorders itself continuously.

    Reported SupportedView cited source
  2. [2]

    Heat is the sum of every payment a tile has received, each decaying exponentially: heat(t) = sum of A_i * 2^(-(t - t_i)/H), with half-life H of 3 days in the author's case.

    Reported SupportedView cited source
  3. [3]

    The obvious implementation is a scheduled job recomputing every row on a timer; the author notes that with a million rows the timer stops being a detail and becomes the whole system.

    Reported SupportedView cited source

Get this ledger inside ChatGPT. Ask about any of our stories and the answer brings its figure, each claim’s verdict and the sources. Free, no account.

Add to ChatGPTAdd to Claude

Sources

1 independent publisher whose own reporting we read for this story.

  1. dev.to

    1 article · August 24, 2026

    Ranking by exponential decay without a cron job

Share your take

Let Clarity write the post for you.

Signed-in readers get a short post drafted on this story in the register they choose — narrative, analytical, or a direct position — editable to the last word before it goes anywhere. The share buttons at the top of this story work without an account.

Topics and entities

Follow any of these and your For You feed starts watching them — no settings page required.

Topics

  • Serverless state and ownership splitFollow
  • Eliminating scheduled recompute jobsFollow
  • Durable Object concurrency semanticsFollow
  • Floating-point and log-space arithmeticFollow
  • Time-decay ranking systemsFollow
  • Secondary index design for ORDER BYFollow
Loading related stories