The scariest deploy is the one that touches the database. Code you can roll back in seconds; a botched schema change can corrupt data you'll never get back.
The rule that saves us every time: never ship a breaking schema change and the code that depends on it in the same deploy. Split every migration into expand, migrate, contract — three safe steps instead of one risky leap.
Expand first: add the new column or table alongside the old one, nullable and unused, then deploy code that writes to both. Now backfill the historical rows in small, throttled batches so you never lock a hot table or saturate replication.
Once the new path is fully populated and the code reads from it, contract: stop writing the old column, confirm nothing still depends on it, and only then drop it — usually a release or two later. Each step is independently deployable and independently reversible.
It feels slower because it is more steps. But every one of those steps is boring and safe, and 'boring and safe' is exactly what you want when production data is on the line.