A PostgreSQL Docker container that automatically upgrades your database
github.com
github.com
In a small startup...
* If the data is mission-critical and constantly changing, PostgreSQL is a rare infra thing for which I'd use a managed service like AWS RDS, rather just Debian Stable EC2 or my own containers. The first time I used RDS, the company couldn't afford to lose an hour of data (could destroy confidence in enterprise customer's pilot project), and without RDS, I didn't have time/resources to be nine-9s confident that we could do PITR if we ever needed to.
* If the data is less-critical or permits easy sufficient backups, and I don't mind a 0-2 year-old stable version of PG, I'd probably just use whatever PG version Debian Stable has locked in. And just hold my breath during Debian security updates to that version.
(Although I think I saw AWS has a cheaper entry level of pricing for RDS now, which I'll have to look into next time I have a concrete need. AWS pricing varies from no-brainers to lunacy, depending on specifics, and specifics can be tweaked with costs in mind.)
RDS, at least for MySQL, is, in my opinion, crap.
MySQL RDS acts like it's just regular MySQL minus SUPER privilege. So it will not accept various important inputs. For example, GTID state will be rejected. Even just pre-GTID log sequence numbers are barely supported. Various SQL SECURITY things are rejected. The suggestions for importing data using any of these features vary from scary nonsense (just ignore GTID numbers!) to absurd hacks (run sed on your mysqldump output to remove SQL SECURITY!).
The docs basically don't acknowledge that GTID matters in a RDS-to-or-from-non-RDS setup. The suggestions don't seem like they deserve to work. (Azure at least has some documentation for GTID, but it involves using fancy barely-documented APIs just to import your data.)
Replication will just break if you accidentally use a feature that RDS can't handle.
For something that could easily cost hundreds to thousands of dollars per month, I expected to be able to run an unmodified mysqldump and have RDS accept the output, process it correctly, and take my money. Nope, didn't happen.
The hard part of running a database, in my experience, isn't setting up or running it. The hard part isn't even configuring backups.
The hard part is noticing that your backups have been broken for years, before you actually need to restore from them. Yes, yes, you know how to do this correctly. But you delegated it to the sysadmin, the sysadmin subtly broke the backup scripts, the scripts have been silently doing nothing for 18 months, and then the sysadmin got a new job.
This is the main value proposition of RDS: Your data will be backed up, your backups will be restorable, and most of your normal admin tasks can be performed by pushing a button.
Well, usually...
https://news.ycombinator.com/item?id=13620622
Pretty sure I've read similar stories more recently. Probably still better odds of successful restore with RDS than with a home-rolled setup.
But there's no substitute for verifying backups.
Data wasn't mission critical and customers could live with 5min downtimes for upgrades once a year.
No problem for years. When we had hosted Mongo, we had more problems.
If I have the money I'll use a managed database. But running Postgres in a startup up to $10m ARR / ~1TB data seems not like a problem.
Same goes of course for snapshots and backups in the cloud. I have several clients which had backup problems because of misconfiguration in AWS/GCP.
I personally feel like upgrading a database should be an explicit admin process, and isn't something I want my db container entrypoint automagically handling.
This container came about for the Redash project (https://github.com/getredash/redash), which had been stuck on PostgreSQL 9.5 (!) for years.
Moving to a newer PostgreSQL version is easy enough for new installations, but deploying that kind of change to an existing userbase isn't so pretty.
For people familiar with the command line, PostgreSQL, and Docker then its no big deal.
But a large number of Redash deployments seem to have been done by people not skilled in those things. "We deployed it from the Digital Ocean droplet / AWS image / (etc)".
For those situations, something that takes care of the database upgrade process automatically is the better approach. :)
I agree it should be explicitly invoked and not automated, for something almost everyone needs to do, it sure is a hard task.
mkdir /var/lib/postgresql/data/old
mv -v /var/lib/postgresql/data/* /var/lib/postgresql/data/old/
mkdir /var/lib/postgresql/data/new
You should create the both dirs first, then check if they do really exist and only then move the files. Shit happens and it's better be safe than sorry.Also I would replace ../old and ../new with $OLD and $NEW to have a bit less clutter in the next block with the explicit upgrade calls, ie:
/usr/local/bin/pg_upgrade --link -d $OLD -D $NEW -b /usr/local-pg9.5/bin -B /usr/local/bin
Overall it's nice idea, just need some more safety checking before starting the process.Using variable names better ($OLD, $NEW) is a good idea too. Should cut down any potential typo risk as well. :)
Add: are sure about "${NEW}"/* in this?
444 mv -v "${NEW}"/* "${PGDATA}" "${NEW}"/*
But it's specifically to do wildcard expansion of the quoted string, and the shell interpreter is happy with it. I'm open to suggestions for improvements though. :)---
This is confusing to me:
... you can just make the list of files (maybe even write it to the file, maybe
even write a batch file which would move them) and use it for moving the data
from pgdata to old. This saves an unnecessary move.
I'm not understanding what you're meaning here.I understand the "make a list of files" bit, but I'm not grokking why doing that is an improvement, and I'm not seeing where there's an unnecessary move that could be eliminated?
The pg_upgrade process is pretty much:
1. Initialise a fresh data directory using the new PostgreSQL version
2. Run pg_upgrade, pointing at both the old and new data directories
3. Start the database using the new data directory
For the "automatic upgrade container" purposes, we need to do everything under the single mount point so the "--link" option to pg_upgrade is effective and uses hard links.Thus the "move things into an 'old' directory" first, then the "move the converted 'new' data into place" bit afterwards.
If this was PowerShell then I would just get the list of files in $PGDATA, create folders and then move the files in the list, ie
$files = gci $PGDATA
try {
New-item "$PGDATA/old" -erroraction stop
New-item "$PGDATA/new" -erroraction stop
}
catch {
throw "Failed to create the necessary dirs"
}
try {
$files | Move-Item -Destination "$PGDATA/old" -erroraction stop
}
catch {
# throw "Failed to move pg_data, your databases are now borked, good luck"
gci "$PGDATA/new" | move-item $PGDATA
}
It's way more streamlined and if creating the dirs would fail (especially `new`) then it would fail before moving the data. And you don't need to `set +e` in this part.I tried to replicate this in Linux and... it's a mess.
`find` includes the directory in the list, `ls -1` does the thing but bash stores it's output as a single string, redirecting it to the file get this file included in the list... I even tried xargs, but quickly abandoned the idea. Though if you can create the redirected output file in some other place than $PGDATA (`/tmp` perharps?) then `ls -1` trick would work.
:D
---
Oh. Now I understand the purpose of grabbing a file list first. That would allow for creating both old + new dirs first, prior to any move attempt.
I'll think it over. I'm kind of on the fence about it at first though. Lets see what I reckon after sleeping on it.
Thanks for following up btw. :)
I think I got everything right, and it passes the test harness, but please give it a look over if you have a few minutes. :)
But just to be clear — the point of this is to migrate an existing Postgres DB from the original version to a newer version? This is about the data volume itself, not automatically upgrading the Postgres binary when a new version is released?
So, you’d basically pass a flag that says --migrate-db when starting the container to kick start changing the data on disk? So when you start a new Postgres 15 container, you could pass it a volume with Postgres 13 data and it would auto update the on disk DB data.
The upgrade-of-your-data happens automatically when the container starts, prior to starting the database server.
You’re using the work “upgrade” differently than how I normally think about it when talking about version changes. You’re talking about data-on-disk, not just the program version.
(I realize that when talking about Postgres, the two are linked, but that’s not the case for most programs)
I'm just going with what I thought of, but am happy to adjust things as makes sense. :)
Still uses "upgrade", but highlights the fact that you're changing the artifact and not just the software.
I'll email the HN mods and ask them if they can do it. :)
Also copied that wording to the GitHub repo page, Docker Hub page, and will probably use it everywhere else that needs a description too. :D
> pg_upgrade (formerly called pg_migrator) allows data stored in PostgreSQL data files to be upgraded to a later PostgreSQL major version without the data dump/restore typically required for major version upgrades
I've had issues in the past with PostgreSQL as a backend for NextCloud (all running in docker) and blindly pulling the latest PostgreSQL and then wondering why it didn't work when the major version jumped. (It's easy enough to fix once you figure out why it's not running - just do a manual export from the previous version and import the data into the newer version).
However, does this container automatically backup the data before upgrading in case you discover that the newer version isn't compatible with whatever is using it?
Nope. This container is the official Docker Postgres 15.3-alpine3.18 image + the older versions of PostgreSQL compiled into it and some pg_upgrade scripting added to the docker entrypoint script to run the upgrade before starting PostgreSQL.
It goes out of it's way to use the "--link" option when running pg_upgrade, to upgrade in-place and therefore avoid making an additional copy of the data.
That being said, this is a pretty new project (about a week old on GitHub), and having some support for making an (optional) backup isn't a bad idea.
I'll have to think on a good way to make that work. Probably needs to check for some environment variable as a toggle or something, for the people who want it... (unsure yet).
Chapter 26. Backup and Restore > 26.3. Continuous Archiving and Point-in-Time Recovery (PITR) > 26.3.4. Recovering Using a Continuous Archive Backup: https://www.postgresql.org/docs/current/continuous-archiving...
IIRC there are fancier ways than pg_dump to do Postgres backups that aren't postgres native PITR?
gh topic postgresql-backup: https://github.com/topics/postgresql-backup
- pgsql-backup.sh: https://github.com/fukawi2/pgsql-backup/blob/develop/src/pgs...
- https://github.com/SadeghHayeri/pgkit#backup https://github.com/SadeghHayeri/pgkit/blob/main/pgkit/cli/co... :
$ sudo pgkit pitr backup <name> <delay>
> Recover: This command is used to recover a delayed replica to a specified point in time between now and the database's delay amount. The time can be given in the YYYY-mm-ddTHH:MM format. The latest keyword can also be used to recover the database up to the latest transaction available.: $ sudo pgkit pitr recover <name> <time>
$ sudo pgkit pitr recover <name> latest
> The database will then start replaying the WAL files. It's progress can be tracked through the log files at /var/log/postgresql/.- "PostgreSQL-Disaster-Recovery-With-Barman" https://github.com/softwarebrahma/PostgreSQL-Disaster-Recove... :
> The solution architecture chosen here is a 'Traditional backup with WAL streaming' architecture implementation (Backup via rsync/SSH + WAL streaming). This is chosen as it provides incremental backup/restore & a bunch of other features.
Glossary of backup terms: https://en.wikipedia.org/wiki/Glossary_of_backup_terms
Continuous Data Protection > Continuous vs near continuous: https://en.wikipedia.org/wiki/Continuous_Data_Protection#Con...
Thanks heaps. :)
Copying TBs of data around seems like it would delay the start of the database by a lot.
Then it starts PostgreSQL 15.3 as per normal.
It's intended as being a drop-in replacement for existing (alpine based) PostgreSQL containers.
That being said, it's still pretty new so don't use it on production data until you've tested it first (etc).
--
Btw, it uses the "--link" option when it runs pg_upgrade so should be reasonably suitable even for databases of a fairly large size. That option means it processes the database files "in place" to avoid needing to make a 2nd copy.
At the moment it only process a single data directory, so if you have multiple then it's not yet suitable.
We have been maintaining a manual migration script for our docker users for the DB part, while the app part does migrations automatically already (Django), so making the db part more built-in makes a lot of sense.
Our case doesn't really need live/fully auto, just an easy mode for admins during upgrade cycles, and our scripts were pretty generic, so a project like this makes a lot of sense. There are a few modes we support - local / same-server, cross-node, etc - so am curious.
the closest thing to this is helm which like everything in the k8s ecosystem is either a useful tool or a single feature wrapped in a cursed configuration language, depending on your perspective
not hard to imagine a single command to snapshot the volume, boot the new version, run a migration command, revert if something fails
But I was bitten in the rear by Wallabag the other day. Didn't work anymore. Ended up having to do a doctrine upgrade. Then it all worked. Same concept I guess. No automatic upgrade does allow you to pause and evaluate what you're doing, and take backups.
Now I say it allows you. Whether you (or I) do so... after all, that is what automatic backups are for...
Now when did I last validate those?
So (for example):
pgautoupgrade/pgautoupgrade:15-alpine3.8
That one would upgrade the database to PostgreSQL 15, and not further.Simultaneously, there would be other other versions available too. For example:
pgautoupgrade/pgautoupgrade:16-alpine3.8
This one would upgrade the database to PostgreSQL 16, and not further.That should be pretty straight forward to do. :)
pgautoupgrade/pgautoupgrade:15-alpine3.8-v1
The "-v1" text fragment on the end is a version number because I'm still improving the scripting for the upgrade part. So that'll likely increment over time (-v2, -v3, -v4, etc) until it stabilises.This covers one of the issues.
I've not done stuff with kubernetes yet though, so I have no idea how it's done there.
Essentially the same, except that K8s gives you a wide variety of storage backend integrations (Storage classes + storage providers) which can attach "anything" (local volumes on the node, NFS, NAS, Cloud Volumes, ...) depending on your local environment and needs.
Feels like the database would take a massive performance hit using network backed storage unless the software is aware of that fact.
I'm just pointing out how it's commonly done. Of course people add things like replication, distributed filesystems, (etc) to the mix to suit their needs. :)
Or is that when you have a layer of cache inbetween like Redis?
A container is nothing but a process with restrictions on which filesystem subtrees kt can see, what resources (CPU, memory) it may use and which networks it can access, with some tooling to manage self-contained images of directory structures.
Major versions still require a pg_dump (or a scary migration using postgres' anemic logical replication) unless some advancement has happened on the postgres side I'm unaware of.
Nope. It's entirely for upgrading between major PostgreSQL versions. :)
It uses the PostgreSQL "pg_upgrade" utility to do the data upgrade behind the scenes: