Skip to content
C.W.K.
Stream
Lesson 01 of 04 · published

Permanent Versus Transient: A Retry Policy Needs a Diagnosis First

~12 min · reliability, error-handling, taxonomy, cost

Level 0Unsorted
0 XP0/36 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete

One Policy Cannot Serve Two Failures

Processing an article can fail because the page is behind a paywall, because it is genuinely not an article, or because the service doing the work hiccuped. The first two will fail identically every time you try. The third succeeds on the next attempt.

Any single retry policy is therefore wrong for one of them. Retry everything, and a paywalled page is fetched forever at real cost — a paywall retried is a paywall twice. Stamp everything as failed, and one momentary outage permanently marks a perfectly readable article as broken, with no path back. The classification has to precede the policy.

Stamp the Permanent, Leave the Transient Unmarked

The rule that falls out is pleasingly asymmetric. Permanent failures — extraction refused, or a verdict that the page contains no article — record a timestamp and a short reason. Workers skip them, opening one never auto-retries, and the card shows an honest marker.

Transient failures record nothing. The absence of a stamp is what makes the next attempt happen naturally, with no backoff table and no scheduler. The system tries again the next time someone wants the thing, which is exactly the right cadence and requires no machinery at all.

Always Provide the Manual Override

A permanent verdict is a judgment, and judgments are sometimes wrong — as the next lesson shows in detail. So there must be an explicit way to clear the stamp and try again: a retry control that a person presses deliberately.

The distinction that keeps this coherent is between automatic and deliberate. Automatic retry of a permanent failure is the waste the taxonomy exists to prevent. Deliberate retry is a person overriding a verdict, which is always allowed, and it also functions as the debugging tool you will want the moment you suspect a misclassification.

Record the Reason, Not Just the Fact

The stamp should carry a short reason string alongside the timestamp. A boolean tells you an article failed; a reason tells you whether every failure last week was the same kind — and that pattern is what surfaces a systematic misdiagnosis rather than a scatter of unlucky pages.

It also makes the next repair possible. When you eventually fix the underlying cause, a reason lets you clear exactly the affected stamps instead of choosing between clearing all of them and clearing none.

Classify before you retry. "Should we retry?" has no answer without "what kind of failure was that?" — and a system that cannot tell the two apart will be wrong about one of them permanently, at whichever cost that class carries.

Code

Stamp the permanent, leave the transient unmarked, and keep a manual override·python
async def clean_article(con, article_id: int) -> str | None:
    try:
        extracted = await extract(article_url(con, article_id))
    except ExtractionRefused as exc:
        # PERMANENT: paywall, dead page, or a no-article verdict.
        # Stamped, so workers skip it and an open never auto-retries.
        # A paywall retried is a paywall twice.
        store.stamp_clean_failed(con, article_id, reason=str(exc)[:200])
        return None
    except (TimeoutError, ServiceUnavailable):
        # TRANSIENT: the utility door was down, the vessel hiccuped.
        # Stamp NOTHING. The absence of a stamp is the retry policy --
        # the next attempt happens when someone next wants the thing,
        # with no backoff table and no scheduler.
        return None

    if _looks_like_no_article(extracted):
        store.stamp_clean_failed(con, article_id, reason="no-article verdict")
        return None

    store.cache_clean_text(con, article_id, extracted)
    return extracted


def retry_clean(con, article_id: int):
    """The deliberate override. A permanent verdict is a judgment, and
    judgments are sometimes wrong -- so clearing the stamp must always
    be one explicit human action away."""
    store.clear_clean_failure(con, article_id)   # reason + timestamp
    return schedule_clean(con, article_id, force=True)

External links

Exercise

Find a retry in your system and ask what it does with a failure that will never succeed. Then find a failure path that records a permanent state and ask what happens if the diagnosis was wrong. If either question has no good answer, the classification step is missing — write down the two or three failure classes that path can actually produce.
Hint
Most retry loops catch a broad exception type and treat everything inside it identically. Listing what can actually be raised there usually reveals two or three genuinely different situations that were being handled as one, and the split is often a single except clause.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.