AlgoJudge documentation0.1

Restore

Putting a dump back, what is checked before anything is touched, and why you rehearse it.

./scripts/restore.sh backups/algojudge-20260830-040000.dump

It closes the installation, stops the Server and the Client, restores, checks that something actually arrived, starts them again, waits for healthy, and reopens.

This destroys the current database

pg_restore --clean --if-exists drops every object the dump carries before recreating it. What is in the installation now is gone. The script asks first — you type the word restore — unless you pass --yes.

What it reads before it does anything

The .meta file beside the dump:

  • the checksum. A dump that does not match it has been altered or damaged since it was taken, and the restore refuses.
  • whether the dump covered the files. A coverage=INCOMPLETE dump restores to an installation whose rows point at bytes that are not there; restore the file store from the same window too.
  • which schema it holds.

With no .meta beside it, nothing can be checked — not the checksum, not the schema, not the coverage — and the script says so. Continuing is a decision.

The schema is the step that bites

An older dump under a newer Server is not an error. With MIGRATE_ON_START=true the Server brings the restored schema forward on its next start, which is usually what somebody restoring last week's backup wants.

It is one-way, and you are told before rather than after. If you meant to go back to an older Server as well, set SERVER_TAG to its version first.

Why the Server and the Client are stopped rather than drained

Nothing should hold a connection to a database whose tables are being dropped. PostgreSQL stays up, because it is what is being restored into.

Neither Runner is stopped, deliberately: they hold nothing a restore touches, and stopping the sandboxing one would abandon whatever it is evaluating. Both simply fail to renew their leases while the Server is down and give their jobs up, and the Server requeues those — so a restore of a few minutes costs nothing. A long one costs the external Runner's pending set, which is the duplicate submission An external judge describes.

It will not report success on a restore that did nothing

Two checks, and the second is the one that works.

The script counts the tables afterwards and fails if there are fewer than two. That catches an empty database and cannot catch a silent no-op: the Server creates the whole schema at its first start, so every table is already there before a restore begins.

So the identity of every table is taken before and compared after. --clean --if-exists drops each relation and creates it again, which gives every restored table a new one, while a restore that did nothing leaves the old ones exactly where they were. From outside, that is the only difference there is.

The second check exists because of a real failure, found while this repository was being written: a Compose invocation failed, its one-line error was reported as a pg_restore warning, and the restore did nothing at all while every check afterwards passed on data that had never been touched at all.

state/restore.log holds what pg_restore said. Some of it is routine — an extension the role may not drop, a comment on something it does not own — and the run does not stop on the first one, because a half-restored database is strictly worse than a complete one with warnings. One of those lines being relation does not exist on a table you care about is not routine.

Rehearse it

A backup with no tested restore is a hypothesis. The honest rehearsal is on a spare host: clone the repository, copy .env and one dump, docker compose up -d --wait, ./scripts/restore.sh, then sign in and look at something you recognise.

The real test is the one that caught the silent no-op

Change something first, then restore, then check the change is gone. A restore that did nothing passes every other check there is.

On this page