[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)