[HN Gopher] The Ten Rules of Schema Growth ___________________________________________________________________ The Ten Rules of Schema Growth Author : iamwil Score : 91 points Date : 2023-10-31 19:15 UTC (3 hours ago) (HTM) web link (blog.datomic.com) (TXT) w3m dump (blog.datomic.com) | packetlost wrote: | I wish there were open source, less Clojure-centric | implementations of Datomic, particularly as a plugin over | something like Postgres. It has many good ideas | yayitswei wrote: | There's XTDB which is still Clojure-centric but is at least | open-source. It supports SQL queries. | sweetsocks21 wrote: | I've been keeping an eye on https://github.com/cozodb/cozo | which is pretty close to something I've wanted, a sqlite | version of datalog/datomic. | Swizec wrote: | > 5. Never remove a name. > Removing a named schema component at | any level is a breaking change for programs that depend on that | name. Never remove a name. | | I agree with this in theory and have seen it go oh so very wrong | in practice. Tables with dozens of columns, some of which may be | unusued, invalid, actively deceiving, or at the very least | confusing. Then a new developer joins and goes "A-ha! _This_ is | the way to get my data. " ... except it's not and now their query | is lying to users, analysts, leadership, anyone who thinks | they're looking at the right data but isn't. | | You absolutely _have to_ make time to deprecate and remove parts | of the schema that are no longer valid. Even if it means breaking | a few eggs (hopefully during a thorough test run or phased | rollout) | tkiolp4 wrote: | But then why not addressing the real problem? If a table has a | few columns which are not used or invalid or deceiving, why did | we let developers introduce them? Lack of planning? Lack of | peer review? Lack of talent? | | I understand these "ten rules" as: as long as you have a decent | codebase and decent engineers, these ten rules will make your | life easier. | | These rules are nothing if you are dealing with crap codebases | (they can help, sure, but they will be just patches) | striking wrote: | Because sometimes you make assumptions that are seemingly | correct but eventually found to be wrong or based on flawed | inputs from sources beyond your control. | | Any system that ultimately relies on "engineers need to | always do the right thing" is a flawed, brittle, ineffectual | system. Because even the best engineers will make a mistake | somewhere, and because you can't exclusively hire "the best" | engineers. | | Let's spend our time figuring out how to recover from | mistakes rather than trying to pretend they'll never happen. | phtrivier wrote: | Even the best team make design decision that turns out | suboptimal when the requirement changes. | | Also, even the best team will sometimes make mistake. | | Db schemas are unforgiving. | __jem wrote: | I've worked with some databases that are 20+ years old and | have outlived multiple application iterations. There's always | going to be cruft in this kind of situation, it just comes | with territory of supporting applications with real | production users for a long time. | yxhuvud wrote: | Requirements change over time. Domain understanding change | over tim. Business change over time. Environments change over | time. Unless you are a seer with perfect precognition, most | of what you have done will be invalidated over time. | | Hence: make your code and data easy to change, but simple, as | you cannot predict in what way it will change. | Swizec wrote: | > Unless you are a seer with perfect precognition | | Even then ain't nobody in a 10 person seed-stage startup | got time, resources, or need to build the database you'll | want to have when you're a 600 person Series C monster. | BWStearns wrote: | This x100. The most miserable and frustrating periods of my | career have been in places that never deprecated anything. You | could spend hours doing something that looked quite sensible, | get a working draft that seemed to work, and then be told "oh | yeah, that's deprecated, that data isn't even populated | anymore, those rows just _happened_ to have data in dev." Then | you either start sanity checking everything before doing | anything and your velocity sucks, or just keep stepping on | landmines and losing whole afternoons. | | Edited to add: docs can help, but only so much. Environments | that cluttered also tend to have layers of docs that are | equally misleading. | __jem wrote: | Reclaiming the physical storage of an unused column is often a | costly and sometimes impossible operation, which is why many | legacy applications end up with the equivalent of | my_column_final_final_v2. Database administration requires | compromises like this sometimes in the name of uptime and data | integrity. Big migrations are always inherently a little risky, | and from the view of many DBAs, why even risk it just for a bit | of clean up? Your schema shouldn't be totally transparent to | your application's business logic anyway, so there are better | places to enforce naming hygiene. | koito17 wrote: | Interesting aside on rules 7 and 8: many Clojure users (myself | included) did not often use namespaced keywords until | clojure.spec was released, and in clojure.spec, you absolutely | need namespacing since specs reside in a global registry. Though | in the case of clojure.spec, we typically use the same namespace | as our current ns, and there is syntax sugar for this. e.g. | ::foo | | resolves to (keyword *ns* "foo") | | In Datomic, namespaces tend to represent your application's | models, like :person/date-of-birth. | | I find it very useful mostly for human readability, it offers a | way to distinguish what exactly :name refers to in your | application's model. It also helps with editor autocomplete since | you can type a namespace and see all keys up front, no need to | consult a keys spec itself (or the schema of your database in | Datomic). And when in doubt, in Datomic, you can always pull, and | it is not too hard to run a query that extracts all attributes | that exist in your database (this is actually an exercise in | Learn Datalog Today[1], highly recommend going through this | tutorial yourself if you want to play with databases like Datomic | or XTDB). | | [1] Exercise 2 in https://www.learndatalogtoday.org/chapter/4 | pphysch wrote: | > Data outlives code, and a valuable database supports many | applications over time. | | Probably an unpopular opinion, but I think having a central | database that _directly_ interfaces with multiple applications is | an enormous source of technical debt and other risks, and | unnecessary for most organizations. Read-only users are fine for | exploratory /analytical stuff, but multiple independent | writers/cooks is a recipe for disaster. | | I prefer an architecture where the central "database" is a | central, monolithic Django/Rails/NodeJS/Spring app that _totally_ | owns the actual database, and if someone needs access to the | data, you whip up an HTTPS API for them. | | Yes, it is a tiny bit of effort to "whip up an API" but it deals | with so many of the footguns implied by this article. "I need X+Y | tables formatted as Z JSON" is a 5 minute dev task in a modern | framework. | wrs wrote: | In that architecture, the central API effectively becomes "the | database", because apps depend on it just as much. Most of the | same rules would apply to the central API's schema. | pphysch wrote: | Conceptually, yes they are the same unit. Which is part of | the beauty of it; it's not a radical paradigm shift like | monolith->microservices or systemd->k8s. But the developer | experience/productivity of bending Django or Rails in | different ways is far superior to bending e.g. MySQL or | PostgreSQL*. HTTPS is plainly a much better, and more widely | available, integration protocol than N different SQL | dialects. | | *much respect to what the Crunchy Data folks have | accomplished | gwn7 wrote: | > I think having a central database that directly interfaces | with multiple applications is an enormous source of technical | debt and other risks, and unnecessary for most organizations. | | I think that the operative word here is "over time". So what is | meant is not necessarily supporting many applications at the | same time, but rather serially. | | So the message is supposed to be: Apps come and go as they can | be rewritten for so many reasons, but there will be a lot less | reasons to redesign / replace a "valuable" database. | timeagain wrote: | Agreed. When a company outgrows its monolith this component you | describe is normally the hardest part to figure out. All those | sneaky little joins and stored procedures and undocumented use | cases come pouring out. If your data layer has an API from the | start it makes everyone act honestly about data access and | provides more accountability. An API layer also allows for more | options when dealing with caching, noisy neighbors, retention, | consistency, and security. | vb-8448 wrote: | > is an enormous source of technical debt | | I totally agree with you, but I think in the real world (mostly | in monolithic apps, microservices shouldn't be affected) at | some point someone will try to access directly the database. | There are several reasons for doing this: API are too slow, | it's simpler and more immediate writing some SQL vs a http | client, the team responsible for APIs it's no more around and | similar. | plandis wrote: | In my experience, it's less risk to take the advice in the | article because even if you only have a monolith as the sole | producer/consumer you could still mess up and make a backwards | incompatible change. Humans are fallible, so try to minimize | human error. | | One way to minimize the human error is by only extending the | schema rather than changing it and forcing your monolith to | correctly make changes to existing queries. | | I'm not saying adding an API is bad, because it's not. I just | think it's solving a different set of problems. | d3ckard wrote: | Going to take the risk and politely say I do not agree with this | article at all. | | Alternative advice: _never_ allow more than one app to share the | db and expose data through APIs, not queries. Then you can | actually remove cruft and solve compatibility through API | versioning that you probably need to do anyway. Also, never | maintain more than two versions at the time. | n0w wrote: | You've got more than one app sharing a db when you deploy a new | version. Unless you're happy with downtime during deploys as | the cost of not having to manage how your schema evolves. | | These kinds of best practices make sense regardless of how many | apps access a db. | | Following the advice doesn't also prevent you from enforcing a | strict contract for external access and modification of the | data. | cogman10 wrote: | > You've got more than one app sharing a db when you deploy a | new version. Unless you're happy with downtime during deploys | as the cost of not having to manage how your schema evolves. | | 2 deploys is all it takes to solve this problem. | * 1 to deploy the new schema for the new version. * 1 | to remove the old schema. | | This sort of "tick tock" pattern for removing stuff is common | sense. Be it a database or a rest API, the first step is to | grow with a new one and the second is to kill the old one | which allows destructive schema actions without downtime. | koreth1 wrote: | 2 deploys isn't enough for robustness. It depends on what | the change is, but the full sequence is often more like | | * Add the new schema | | * Write to both the new and old schemas, keep reading from | the old one (can be combined with the previous step if | you're using something like Flyway) | | * Backfill the new schema; if there are conflicts, prefer | the data from the old schema | | * Keep writing to both schemas, but switch to reading from | the new one (can often be combined with the previous step) | | * Stop writing to the old schema | | * Remove the old schema | | Leave out any one of those steps and you can hit situations | where it's possible to lose data that's written while the | new code is rolling out. Though again, it depends on the | change; if you're, say, dropping a column that no client | ever reads or writes, obviously it gets simpler. | reissbaker wrote: | Yup, it depends on the change. Sometimes two deploys is | enough -- e.g. making a non-nullable column nullable -- | and sometimes you need a more involved process (e.g. | backfilling). | | Nonetheless, I agree with the OP that the article's | advice is pretty bad. If you ensure that multiple | apps/services aren't sharing the same DB tables, | refactoring your schema to better support business needs | or reduce tech debt is | | a. tractable, and | | b. good. | | The rules from the article make sense if you have a bunch | of different apps and services sharing a database + | schema, especially if the apps/services are maintained by | different teams. But... you really just shouldn't put | yourself in that situation in the first place. Share data | via APIs, not by direct access to the same tables. | candiddevmike wrote: | It's so simple! If you never delete or change things in your | schema, you never have to worry about changing it. | | The article is pretty devoid of actionable advice. | plandis wrote: | The article lists fairly sensible rules for backwards | compatibility and growth in my opinion. | candiddevmike wrote: | The central thesis revolves around continuous growth with | no advice given for removal/cleanup. This is not a sound | strategy for a database schema, at least for the SQL side. | Column bloat, trigger bloat, index bloat... Schemas cannot | continuously grow, there needs to be DROPs along the way. | plandis wrote: | Yes you eventually need to do the things you mention but | probably less frequently than a normal application needs | to add new columns or the like to support new use cases. | | The article thesis is essentially make breaking changes | as infrequently as possible. The easiest way to do that | is never change your data but that's a sure way to have | your competitors crush you as you stagnate. The next best | thing you can do is make sure existing producers and | consumers are not impacted when you make changes. For | most changes being made the advice in the article gives a | set of things you can do to achieve this goal. | | For times where your database itself is not scaling which | are the types of things you're mentioning, I think there | are other things you can do to, if not eliminate | backwards incompatibility, at least make the transition | easier. For example fronting your DB via an API and gate | all producers/consumers through that. If you're | frequently having to handle scaling issues perhaps it's | time to reevaluate your system design all together. | hyperpape wrote: | From the article: never break it | Never remove a name Never reuse a name | | Your point is a very reasonable statement, but you are | really disrespecting the author by putting a reasonable | statement in their mouth. They had every chance to say | the reasonable thing, and they clearly made a choice to | say the unreasonable thing. Respect that decision (and | tell them that they're wrong). | bob1029 wrote: | In cloud native arrangements, we are looking at the database as | the _ultimate_ integration tool. These schema are not owned by | any specific service. There is a totally separate universe | responsible for how all of that works outside code. Allowing | _code_ to drive schema (and permitting the implication that this | is the best way) is the biggest frustration I have with ORMs & | their higher-order consequences. | | In my view, if the schema must change so radically that | traditional migrations and other 'grow-only' techniques fall | apart, you are probably looking at a properly-dead canary and in | need of evacuating the entire coal mine. | | The Quote regarding flowcharts and tables applies here - if you | radically alter the foundation, everything built upon it | absolutely must adapt. Every flowchart into the trashcan | instantly. Don't even think about it. They're as good as a ball & | chain now. Allowing parts of the structure to dictate parts of | the foundation is where we find ourselves with circular firing | squads. | | Take things to the extreme - There is a reason you will start to | find roles like "Schema Owner" in large, legacy org charts. These | people _cannot_ see the code or they will become tainted. They | only have one class of allegiance - LOB owners. These are who | they engage to develop & refine schema over time. The schema | owner themselves has a _full time job_ that is entirely dedicated | to minimizing the impact of change over time to the org. This | person is ideally the most ancient wizard in the org chart and | has the prior Fred Brooks quote framed on their wall. | | You can make schema change a top-down event that touches the | entire organization. This happens quite often in banking when the | central system is _completely_ swapped for a different vendor & | tech stack. Most of a bank is just a SQL database, but every | vendor has a different schema that has to be adapted to. This is | known as "core conversion" in the industry and is one of the more | hellish experiences I have ever seen. If a bank with 4 decades of | digital records can pull something like this off with regularity, | there aren't many excuses that remain for a hole-in-the-wall SaaS | app with 6 months of customer data. ___________________________________________________________________ (page generated 2023-10-31 23:00 UTC)