Versioning data in Postgres? Testing a Git like approach
specfy.io
specfy.io
This is my cheat sheet:
ALTER SESSION SET NLS_TIMESTAMP_FORMAT = 'YYYY/MM/DD HH24:MI:SS';
select count(*) from dual
AS OF TIMESTAMP TO_TIMESTAMP('2010/01/01 00:00:00');
I was aware that Postgres had "time travel," but I don't know if it used this syntax, but I am aware that the feature has been removed. I don't know if "AS OF" is still supported. In Oracle, it has been quite helpful.Edit: Flashback came to Oracle in 9i, when "rollback segments" were replaced by the "undo tablespace" that could present any table as it appeared in the past, limited by the amount of "undo" available. The "FLASHBACK ANY TABLE" privilege is required to see this history on tables that you do not own, and that also conveys the privilege to fully revert tables to their previous contents. You must be a DBA to exercise flashback on tables owned by SYS.
I was working in a dual MySQL/Oracle environment 20 years ago exactly, and MySQL - still at version 3 o 4 - was a toy DB, compared. I was writing Oracle queries that could make your head spin, with optimizer hints, for example:
https://renenyffenegger.ch/notes/development/databases/Oracl...
Absolutely, and some of the things Tom Kyte (Oracle's resident DB performance guru) could do with Oracle were spectacular.
As you say, its a shame about the Oracle baggage, and in particular the steep price tag. Otherwise I'm certain it would be far more widely deployed as database.
You can also license it by user.
https://www.oracle.com/assets/technology-price-list-070617.p...
Try using row level security in postgres - it's awfully slow and you need to be a master architect and sql developer to make it run fast because it's too dumb on its own. With query planner hints I could at least guide it. Shame pg doesn't believe in that.
No they're not perfect but I need them rarely with MSSQL
> An optimizer will quickly run into NP-hard problems like join ordering
As a start, dynamic planning based on cardinality estimation based on stats. And then some. It explodes horribly, yes.
Assuming you're a good DBA, you can be better than the optimizer because you understand the whole system. Even databases are general-purpose machines. That's the whole point of adding indexes, etc.
I mean, has there ever been a database that auto-adds indexes based on profiler/optimizer feedback? (To answer my own question, apparently Azure SQL does).
Generally this seems like a nice balance to me - watch real queries, look for expensive plans and sniff out likely missing indexes based on the data distribution, but give the DBA final say on whether it's a good idea
I don't understand this, can you explain.
> Assuming you're a good DBA, you can be better than the optimizer because you understand the whole system
That's inaccurate. As a DBA I can understand the system better at higher level, but the database has statistics which gives it typically better understanding of the data distribution at a lower level. Indeed I could get that information and feed it into the query plan via hints, but that's going to be an enormous amount of my time, and I would have to do it every time the query is run whereas the database can keep an eye on the statistics as it varies and rebuild the query over time.
Equally, if an index is added you can expect the database to start using it immediately without adding hints. Etc
I am a fairly(?) skilled DBA who has a reasonable idea of what goes on underneath the hood, I do have some idea what I'm talking about.
Which is fun when you add a future schema migration that drops a column, test in locally and in some test cases, and then see it bomb in production because the column has an Index that you weren't aware of. (That's an easy thing to solve though, but definitely one of those cases where "The database is messing with its schema on its own" does do funny things)
Sometimes the planner needs a little help, and that's OK.
I very rarely used hints in my more database'y days - but every so often, they were needed, and often to tell the database something that seemed screamingly obvious to me.
I'm not sure where you got the notion that hints were something noobs would be working with? At least, that certainly wasn't my experience.
> I very rarely used hints in my more database'y days
evidence thereof!
> I'm not sure where you got the notion that hints were something noobs would be working with? At least, that certainly wasn't my experience.
It's been very, very much my experience. I worked in one place where nolock hints were applied everywhere, without them realising that READ UNCOMMITTED trans iso even existed, so had to show them that, and show them it could be specified at the application layer (not in the SQL). In another recent case I came across their use and I'm damn sure the person who used it didn't actually know what it did. Yeah, I've seen far too much hinting in my career.
I wouldn't post snooty and dismissive comments about this - assume that you are in a company of people who have been to more rodeos than you have.
> that changed its plan based on the column order in the SELECT clause.
Hmm? Could you elaborate; you mean the order of the cols or using ORDER BY? I assume you mean the latter but it sounded like the former.
When do you need a hint? why do you need a hint? If you can answer those questions wouldn't it be better to codify those answers into the query planner than a one off hint?
Which nicely sidesteps how hard it actually would be to first, understand the query planner to the degree needed to change it and second, the amount of effort it would take transform a one off hint into a general purpose optimization engine.
"Temporal tables (also known as system-versioned temporal tables) are a database feature that brings built-in support for providing information about data stored in the table at any point in time, rather than only the data that is correct at the current moment in time."
https://learn.microsoft.com/en-us/sql/relational-databases/t...
> The original version of PostgreSQL from the 1980s did not remove dead tuples. The idea was that keeping all the older versions allowed applications to execute “time-travel” queries to examine the database at a particular point in time [via https://ottertune.com/blog/the-part-of-postgresql-we-hate-th...]
Postgres deprecated support for time-travel in ~1997 and the more general notion of "system time" wasn't standardised until SQL:2011. This blog post is a good overview on the SQL:2011 spec + adoption in databases of (bi-)temporal versioning: https://illuminatedcomputing.com/posts/2019/08/sql2011-surve...
Temporal versioning is less sophisticated than git-like versioning (no branching etc.) but is usually more aligned with common end-user requirements. Kent Beck suggests this framing of "eventual business consistency": https://tidyfirst.substack.com/p/eventual-business-consisten...
I once spent some time trying to find a way to do bi-temporal versioning in Postgres.
The only thing I found was a half-dead abandonware external project and an associated presentation PDF from some conference the author once spoke at.
I was unaware that they previously had some form of temporal queries and deprecated it. That is a great shame.
> POSTQUEL allows users to save and query historical data and versions. By default, data in a relation is never deleted or updated. Conventional retrievals always access the current tuples in the relation. Historical data can be accessed by indicating the desired time when defining a tuple variable.
> [...] Finally, POSTGRES provides support for versions. A version can be created from a relation or a snapshot. Updates to a version do not modify the underlying relation and updates to the underlying relation will be visible through the version unless the value has been modified in the version.
I deployed NeonDB in a large enterprise client in Q2 of this year and, yes, the initial data migration is the hard part. We added 2x100gbe network cards directly on the VMware hosts where the data was so the migration to the Kubernetes cluster running NeonDB took hours instead of weeks.
I am not sure how useful this is for versioning data though. It seems like an orthogonal problem to me.
This is true in a sense, but these are commonly stored compressed and of course you can expect to have a pretty good compression ratio when changed files are mostly similar. And if they're not, you really have to store a copy anyway.
But I realized pg_largeobject (1) chunks data to around 2kB per chunk, and (2) stores each chunk on its own row, and (3) each row uses "oid" type as identifier, which is just 32 bit long. It's probably large enough for anything I would ever need, but for some reason I don't feel comfortable with only 32 bits as primary key.
4,294,967,295 * 2 = 8,589,934,590 KB
So, around 8.5 TB. Unless your hypothetical VCS is for a large library of videos, you should be fine
This was/is a very specific solution to our very specific set of problems. So not applicable to the general problem of "versioning a database".
It is still in use and still under active development – I'm actually fixing a few bugs with it just now.
I wrote much more about it here:
I thought logically git stored every file, but implementation-wise, it does git-object compaction. So in reality, it's not actually storing every file on disk, no?
I wish PostgreSQL would allow something as Oracle Flashback feature plus allowing to control how far history do you want to keep for specific tables, as typically only part of your data needs full auditing.
There are various schemes you can employ to keep "archived" data within postgres (e.g. offloading it to another instance accessible behind a FDW).
I've thought about this problem a lot, and I think every alternative solution I've seen is just an approximation of bitemporal schemas with some limitations that are typically not worth the cost:benefit trade-off.
The main difficulty is there isn't a standardized framework/FOSS solution for bitemporal data. I feel like it'll have to get into the SQL standard before we see widespread adoption.
create table test (id int primary key, attr int, deleted_at timestamp);
alter table test add default_filter is_active where deleted_at IS NULL;
then all selects like "select * from test where attr = 42" automatically gets added the above criteria and becomes "select * from test where attr = 42 AND deleted_at is NULL"
When you want old rows, you can override the default condition using something like:
select * from test where attr = 42 filter is_active (deleted_at IS NULL or deleted_at > '2010-01-01'::datetime)
You would then be able to have multiple criterias instead of just when row was deleted.
I run a GitHub Actions workflow every two hours which grabs the latest snapshot of the database, writes the key tables out as newline-delimited JSON and commits them to a Git repository.
https://github.com/simonw/simonwillisonblog-backup
This gives me a full revision history (1,500+ commits at this point) for all of my content and I didn't have to do anything extra in my PostgreSQL or Django app to get it.
If you need version tracking for audit purposes or to give you the ability to manually revert a mistake, and you're dealing with tens-of-thousands of rows, I think this is actually a pretty solid simple way to get that.
"A PostgreSQL Docker container that automatically upgrades your database" (2023) https://news.ycombinator.com/item?id=36748041 :
pgkit wraps Postgres PITR backup and recovery: https://github.com/SadeghHayeri/pgkit#pitr :
$ sudo pgkit pitr backup <name> <delay>
$ sudo pgkit pitr recover <name> <time>
$ sudo pgkit pitr recover <name> latestSort of. While Git’s canonical storage is indeed not diffs, it uses delta compression to, well, store diffs as a storage and transit optimization https://git-scm.com/book/en/v2/Git-Internals-Packfiles
https://higherlogics.blogspot.com/2015/10/versioning-domain-...
With this schema, you simply add an extra clause to each of your queries and they can simultaneously return the latest version or any past version.
https://stackoverflow.com/questions/7118432/how-to-reveal-ol...
This work is great but it feels wasteful given the features Postgres already has but probably the only reasonable solution is to rebuild it in user-space.
They all revolve around known patterns, https://en.wikipedia.org/wiki/Slowly_changing_dimension
I highly doubt there's a single way to do this that solves every scenario.
2022 https://news.ycombinator.com/item?id=31847416
Here's an alternative designed to work with an ORM, if you like that: https://www.youtube.com/watch?v=JsO551E7ySY&t=1094s.
I’d be very interested in a comparison of the two.
Separately though, it's great to see that there's still people trying to solve this problem at the extension level for Postgres. I was sad to discover that Postgres for a very long time had an official extension for time travel - https://www.postgresql.org/docs/11/contrib-spi.html#id-1.11.... - only to realize that it had been removed in https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit... - with a note that "it's easy to implement this separately" but no real hints that anyone was taking up the mantle.
As a simpler alternative, if you're only modifying data via Django and can tolerate some unreliability, https://django-simple-history.readthedocs.io/en/latest/ can get you much of the way there without a need for extensions. We've built on top of it with some domain-specific modifications to visualize and filter diffs, giving us at least a best-efforts audit-esque log system.
https://mariadb.com/kb/en/temporal-tables/
A quick Google turns up nothing for MySQL temporal.