Chapter 9 of 14

Before the Scary Migration

Every developer has run a migration they were not sure about and felt the small cold moment afterwards. This chapter removes it, in two commands, and explains why this particular feature could only come from the thing that installed your database.

The problem

A destructive migration cannot be undone by migrate:rollback. The down() method restores the schema; it cannot restore the column you dropped, because the data went with it. And in local development the data is often worth more than it sounds: three weeks of hand-made test cases covering the edge conditions you had to reason about, which a fresh seed does not contain.

So people do one of two things. They avoid the experiment — writing the migration defensively, never trying the cleaner schema — or they run it and lose an afternoon rebuilding fixtures. Both are the same tax, paid differently.

The hard way

You can do this yourself: mysqldump to a file, remember where you put it, remember the flags, restore with the inverse command, remember which of the four dumps in your Downloads folder was the right one. It works, and the number of people who actually do it before every risky migration is approximately zero, because the ceremony is longer than the migration.

Two commands

grove db snapshot --db myapp --note "before migrate"
# ...run the scary migration...
grove db list
grove db restore <id>

Data restored exactly as it was. The note is not decoration — a week later, grove db list with five snapshots in it is unreadable without one.

“Exactly” has been true since 1.9.0, and it is worth knowing why it was not before. A MySQL dump recreates the tables it holds and says nothing about any others, so a restore after a migration that created a table left that table in place — the one case a snapshot before a migration exists for. Each database in the dump is now dropped and recreated on the way in. If you are on an older Grove, upgrade before you rely on this chapter.

Snapshots live under Grove's own directory rather than in your project, which keeps a database dump out of your repository and out of your build context. MySQL and PostgreSQL snapshots are plain SQL dumps — readable, greppable, restorable by hand if you ever want to. An ElyraSQL snapshot is a hot, consistent copy of its single database file, taken while it serves.

A database per branch

A snapshot answers “put it back”. There is a second, quieter version of the same problem: you switch from a feature branch to main, and the feature branch's migrations are still in the database. Now main is running against a schema it has never seen, and the errors look like bugs in main.

grove db branches on

Since 1.9.0 a project can have a database per git branch. Check out a branch and its data is swapped in; go back to main and main's data comes back exactly as it was left. The first checkout of a new branch starts it from the data you were on.

The design decision worth noticing: the database keeps its name, and only its contents move. So the app, php artisan in a terminal, a test run and a database client all see the checked-out branch without being told. Pointing the app at a different database per branch would have reached only the requests Grove proxies, and your terminal would have kept migrating the old one. On MySQL the swap is a single atomic RENAME TABLE, measured at 43–46 ms for a 70 MB schema.

Why only a supervisor can do this

This is the chapter that makes the argument for the whole course, so it is worth stating directly.

A tool that merely connects to your database can dump it. Only a tool that owns it knows which engine is running, on which port, with which credentials, for which project — without being told, every time, by you. Grove installed the database, so grove db snapshot needs no connection string: it already knows.

That is the same property that made grove env correct in chapter 6 and per-site PHP routing possible in chapter 5. Each is small on its own. Together they are the difference between a collection of tools and an environment.

Make it a habit

Snapshot before a migration you have not run before. Snapshot before running someone else's migration on your data. Snapshot before migrate:fresh --seed on a database you have been building up by hand.

It costs seconds and it changes what you are willing to try — which is the real benefit. The best version of a schema is usually the third one you attempt, and you only get to the third if the first two were cheap.

This is also what makes an agent safe to let near your database. If you have a coding agent running migrations, a snapshot beforehand turns “what did it just do” from a question into a command. Chapter 14 comes back to this.

What you learned

  • down() restores the schema, not the data. Hand-made local fixtures are worth more than they sound.
  • Two commands, with a note, because five unlabelled snapshots are as good as none.
  • Snapshots live in Grove's directory, not in your repository or your build context.
  • Owning the database is what makes it one command — the same property behind grove env and per-site PHP.
  • Cheap experiments change what you attempt. The best schema is usually the third one.
Next: in Chapter 10 the thing you cannot build from separate tools: the full timeline of a request, the queries it caused, and a replay button.