Immutable Data (2015)
kevinmahoney.co.uk
kevinmahoney.co.uk
Although the advantages are real, I can't say I have had much opportunity to implement schemas like this. The extra complexity is usually what gets in the way, and it can add difficulty to migrations.
I think it would be useful in certain scenarios, for specific parts of an application. Usually where the history is relevant to the user. I think using it more generally could be helped by some theoretical tooling for common patterns and data migrations.
We also have help from other quarters nowadays.
Databases often provide a time travel feature where we can query AS OF a certain date.
Some people went down the whole event sourcing / CQRS / Kafka route where there is an immutable audit log of updates.
Data warehousing has moved on such that we can implement “slowly changing data” there.
All in all, complicating our application logic, migrations and GDPR in order to maintain history in line of business applications might not be worthwhile.
This is a good overview of SQL:2011 temporal functionality support across the major players: https://illuminatedcomputing.com/posts/2019/08/sql2011-surve...
The idea is an indexed append-only log-structure and to use a functional tree structure (sharing unchanged nodes between revisions) plus a novel algorithm to balance incremental and full dumps of database pages using a sliding window instead.
Suppose that instead of a typical User table, you have a User_Revision table like suggested. Every time a user updates their account settings, you INSERT a new row there. If a user changes their email address, you get a row each time they update it.
Not only the company gets an history of email addresses, but also they are tied to each other. If this information gets leaked, the user is exposed to more vectors of attack.
(This is what Tildes use: https://tildes.net/settings/account_recovery , and I found it very genius)
It does not work if you actually want to send emails, for instance notifications.
I like Apple's approach to generating random throwaway emails.
I also like apple’s method, but it is not either-or.
Perhaps someone should deliberately leek a bogus database that contains the bcrypt hash of davey.jones.490@well-known-email-provider.com and see if that account starts receiving spam ... probably it won't.
There is (almost certainly) no associated e-mail account, but the following is the SHA-256 hash of firstname.lastname.threedigits, something like "davey.jones.490". I'm half-expecting someone to reply with the solution, though I think there are about 36 bits of entropy in my choice:
b8b6395265714064afb525f37e9969a7dbf09a4a2afa28812e917580be2dc667
Immutable design is extremely powerful, but it needs to be a first-class citizen to get full benefit. Clojure's data structures are a great study in this - they squeeze a shocking amount of efficiency out because they have guarantees that the underlying data is immutable (eg, copy-and-slightly-update a large object is effectively a free operation, we have old & new objects available for comparison and that is lovely). Mimicking the same style of programming in, say, Java would gain none of the performance advantages or the logical conveniences. I expect it would be an uncomfortable programmer experience.
Here I think it would be more effective to design the tables as-usual and keep a log of all the changes separately. There is a chance the log will get out of sync with the active tables but frankly if that is a problem go use something designed with immutability in mind and don't twist PostgreSQL into pretzel shapes.
Unfortunately Postgres doesn't have an API to get at those well designed internals. It implements SQL.
That it’s not better. Immutability is a tool, not a rule. Deploying immutability unanimously without regard for anything is a great way to create a terrible application.
See https://www.erlang.org/doc/apps/erts/time_correction.html#in... for more info. It's about time handling in the Erlang run-time system, but the issue it describes is universal.
And having an Erlang background, I would say timestamps are also tricky in the context of distributed systems.
This is relatively rare though.
I find date and time data so difficult to work with that I typically avoid using it for anything important unless absolutely necessary.
(2) To give an instance of where (1) becomes important, suppose you change some X to X' and then want to change it back. Suppose that after the change some entity was deleted—X foreign keys to a now-deleted value, X' does not. Most applications that try to shove both current state and history into one ubertable disable a bunch of constraint checking and other suchness, and permit this dubious feature of partially-rolling-back into an inconsistent state. But if you just DELETED the row when you said you had, then you would have gotten a foreign-key-error and your user would have copy-pasted you on their “unexpected error occurred” error message and you'd immediately be able to diagnose what foreign key constraint was blocking the undo, rather than mysterious failures several weeks later.
(3) Regardless of your stance on (1), once your application supports deletion, your relational integrity usually suffers because the technically correct value for all of the columns in a deleted-row is to make them all null. This is basically the problem that databases do not have sum types. A sum type in a database is not hard to create once you need it, create a row that has 3 columns which foreign-key to other tables, plus constraints that exactly one of these values is non-null. So the very lightweight construction if you are upset about denormalizing your data is for a Cat in your Cats table in your PetStore database to be a nullable pointer to a CatVersion. So that's how to proceed if you REALLY want to normalize.
(4) All of the above assumes that for every edit to an entity you will save a new row in the versions table, copying all of the other data. The problem is that inevitably some tables get super wide as they have to hold dozens of pieces of business data together, and it's never the ones that you initially expected. There is an easy fix for this as well, it is for versions to also be “mutable.” Whaaaaa??? Yes. Snapshots plus deltas. It's not really mutable because it's append-only.