PostgreSQL Templates
supabase.io
supabase.io
We have recently open-sourced our core-server managing this (go): https://github.com/allaboutapps/integresql
Projects already utilizing Go and PostgreSQL may easily adapt this concept for their testing via https://github.com/allaboutapps/integresql-client-go
We try to approximate local/test and live as close as possible, therefore using the same database, with the same extensions in their exact same version is a hard requirement for us while implementing/testing locally.
However, I have no experience with H2 (and the modern Java ecosystem in general), so cannot talk about how close their emulation implementation resembles PostgreSQL behavior.
A full test suite run recreates the template db from flyway migrations; every test run clones the template db. One nice thing about this is that it's easy to examine the state of the database on test failures. One downside is that there's no "drop multiple databases" command so cleanup can take a while depending on how often you run the script.
Regarding cleanup: We run configure a max pool size in integresql after which "dirty"-flagged test databases are automatically removed (and then recreated on demand). We may also support auto-deletion (e.g. after a successful test) in the future, however currently it just does not seem necessary even when we have 5000+ test-db, it's just a disk-space concern.
Have you looked to tweak PSQL parameters to make it faster for test runs e.g. by disabling disk writes?
Regarding being language agnostic: YES, clients for languages other than go are very welcome. The integresql server actually provides a trivial RESTful JSON api and should be fairly easy to integrate with any language. This is our second iteration regarding this PostgreSQL test strategy, the last version came embedded with our legacy Node.js backend stack and wasn't able to handle tests in parallel, which became an additional hard requirement for us.
See our go-starter project for infos on how we are using integresql, e.g. our WithTestDatabase/WithTestServer utility function we use in all tests to "inject" the specific database: https://github.com/allaboutapps/go-starter/blob/master/inter...
A new database scaffolded from a predefined template becomes available in several milliseconds or few seconds VS. truncating, migrating up and seeding (fixtures) would always take several seconds. We initially experimented with recreating these test-databases directly via sql dumps, then binary dumps until we finally realized that PostgreSQL templates are the best option in these testing scenarios.
Sorry, I've got no benchmarks to share at this time.
This way every worker works on their own database. The speedup was 5x-7x as tests are now running in parallel.
My team uses a migration framework (in our case Flyway) such that we can easily create completely fresh DBs on a factory fresh(+ user roles) instance of Postgres. We do this for our testing as well. This means the schema tools are always tested as well. Is there a benefit to using templates? They're faster perhaps?
When migration or seed files have changed, rules rebuild the image. That way the price of migration (about 2 minutes for our Django app) isn't paid on every test run.
Creating a DB from template is 2 seconds.
Didn't make much sense to me either but whatever pays the bills I guess.
There's no reason to join across customers in this situation and it saves you from making that type of mistake.
Colocating this on the same database and using the same users/credentials just makes it easier operationally. It's about isolating the data.
Ok, so let's imagine your application requires 100 database tables.
If you have 1 customer, you have 100 tables. Every transaction performed in this database (including updates) will increase the age(datfrozenxid) by one. You now have 100 tables you need to vacuum.
If you only had one schema, that's no problem at all. Even with the defaults and a high number of writes, the default 3 autovacuum workers will quickly deal with it. Even though they can only vacuum a table at the time.
Now your business is growing. 1 customer becomes 10, 100... 10000?
At 10k customers, you now have 1 million tables (maybe more, as TOAST tables are counted). Every single transaction increases the 'age' of every table by one. It doesn't matter that 99% of all tables have low TXIDs, the overall DB "age" will be on the table with the highest count.
The default 3 autovacuum workers will definitely NOT cut it at this point(even with PG launching anti-wraparound autovaccums). You can augment by "manually" running vacuum, but in that case the overall database age will only decrease once it's done vacuuming all 1 million tables, so start early(before you reach the ~2.1 billion mark)
Alternatively, you could write a script that will sort tables by age and vacuum individually. This may not help as much as it seems, as the distribution will depend on how much work your autovacuum workers have been able to do so far. Not to mention, even getting the list of tables will be slow at that point.
So now you have to drastically increase the number of workers (and other settings, like work_mem) – which may also means higher costs – and you'll still have to watch this like a hawk (or rather, your monitoring system has to). There's no clear indication that the workers are falling behind, you can only get an approximation by trending TXID age.
You can make this work, but it is a pain. Even more so if you haven't prepared for it. For a handful of customers or a small number of tables or a small number of transactions this may not matter. Our production systems (~200 tables, excluding TOAST) started to fall behind after a few thousand customers. We have had at least one outage because we didn't even track this at the time and the database shutdown. 20/20, but nobody remembered to add this to the monitoring system.
Another unrelated problems with multiple schemas is: database migrations are much more complex now, you have to upgrade all schemas. This is both a blessing and a curse. Database dumps also tend to take forever.
createdb -T old_db new_db
Like so:
function save_database() {
mkdir -p /tmp/database-snapshots
DATABASE_NAME=$PROJECT_NAME
pg_dump -Fc "$DATABASE_NAME" >"/tmp/database-snapshots/$DATABASE_NAME.dump"
}
function restore_database() {
DATABASE_NAME=$PROJECT_NAME
DATABASE_DUMP="/tmp/database-snapshots/$DATABASE_NAME.dump"
if [ ! -f "$DATABASE_DUMP" ]; then
echo "No dump to restore"
return
else
psql postgres -c 'SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE pid <> pg_backend_pid()'
psql postgres -c "DROP DATABASE IF EXISTS \"$DATABASE_NAME\""
psql postgres -c "CREATE DATABASE \"$DATABASE_NAME\""
pg_restore -Fc -j 8 -d "$DATABASE_NAME" "$DATABASE_DUMP"
fi
}Stellar is a tool that wraps the template mechanism, and has some benchmarks: https://github.com/fastmonkeys/stellar
We've made Database Lab tool working on top of CoW file systems (ZFS, LVM) that is capable of provision multiterabyte Postgres instances in seconds. The goal is to use such clones to verify database migrations and optimize SQL queries against production-size data. See our repo: https://gitlab.com/postgres-ai/database-lab.