Neon’s point-in-time restore (PITR) lets you rewind your database to any specific moment, effectively undoing accidental DROP TABLE statements or bad data migrations.

Let’s see it in action. Imagine you have a users table.

CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100),
    email VARCHAR(100) UNIQUE
);

INSERT INTO users (name, email) VALUES
('Alice', 'alice@example.com'),
('Bob', 'bob@example.com');

Now, suppose you accidentally run a DROP TABLE users; command.

DROP TABLE users;

Your users table is gone. This is where PITR comes in. You can restore to a point before the DROP TABLE command was executed.

The core of Neon’s PITR lies in its immutable storage and WAL (Write-Ahead Logging) archiving. When you make a change in Neon, it’s written to the WAL. These WAL records are continuously streamed and stored. A restore operation essentially replays these WAL records up to a specified timestamp, reconstructing the database state at that exact moment.

Here’s how you’d do it using the Neon CLI:

First, you need the project_id and branch_name of your Neon project. You can find these in your Neon console URL or by running neonctl status. Let’s assume your project_id is my-project-123 and your branch_name is main.

You’ll also need the target timestamp for your restore. This is a critical piece of information. You can get the current time from your system using date -u +'%Y-%m-%dT%H:%M:%SZ'. For instance, if you want to restore to 2023-10-27 10:30:00 UTC, you’d use 2023-10-27T10:30:00Z.

To initiate a point-in-time restore, you’ll use the neonctl command:

neonctl restore \
  --project-id my-project-123 \
  --branch-name main \
  --restore-to-time '2023-10-27T10:29:59Z' \
  --new-branch-name main-restored \
  --new-endpoint-name main-restored-ep

Let’s break down these arguments:

  • --project-id: Your Neon project’s unique identifier.
  • --branch-name: The branch you want to restore from.
  • --restore-to-time: The exact timestamp (UTC) up to which you want to restore. Crucially, this is the latest point you want to be present in the restored database. If you specify 2023-10-27T10:29:59Z, any data changes that occurred after this exact second will not be in the restored database.
  • --new-branch-name: A name for the new branch that will be created with the restored data. It’s best practice to create a new branch rather than overwriting an existing one.
  • --new-endpoint-name: A name for the new endpoint that will be created to access the restored branch.

After running this command, Neon will:

  1. Create a new branch (e.g., main-restored).
  2. Provision a new endpoint for that branch (e.g., main-restored-ep).
  3. Begin the process of replaying WAL records from the specified restore-to-time onto this new branch.

This process might take some time depending on the size of your database and the amount of WAL data that needs to be replayed. You can monitor the progress in the Neon console. Once complete, you’ll have a new endpoint pointing to a database that looks exactly as it did at 2023-10-27T10:29:59Z.

The most surprising aspect of Neon’s PITR is how it handles large amounts of historical data. Unlike traditional databases that might require complex snapshot management and downtime for restores, Neon’s architecture, leveraging immutable storage and WAL streaming, allows for very granular and efficient restores without impacting your primary database operations. The system doesn’t need to "rewind" the physical storage; it constructs the desired state by replaying the logical changes recorded in the WAL.

Once your restored branch is active and you’ve verified its data, you can then drop the original, problematic branch or endpoint if it’s no longer needed.

The next problem you’ll likely encounter is understanding how to manage multiple restored branches and endpoints efficiently, especially in a production environment.

Want structured learning?

Take the full Neon course →