Skip to content
Kien

Choosing boring technology

· 5 min read

The default is a relational store, Redis, a queue, Node and TypeScript — not because they are the best tools in the abstract, but because 3am is not when you want to be learning a datastore's failure modes. Then the one time the exotic choice was right, argued on its merits, and the test I use to tell the two situations apart.

  • architecture
  • decision making
  • databases
  • operations
On this page
  1. What boring buys
  2. The one time the exotic choice was right
  3. The test I use
  4. Three questions before anything joins the stack

Most of what I have shipped runs on a relational database, Redis, a queue, and Node with TypeScript. That list is not a philosophy and it is certainly not taste. It is a bet that the thing which will actually hurt is not writing the code — it is the Tuesday at 3am when the code is running and something under it is not.

What boring buys

A technology you have operated for years comes with a catalogue of failure modes already in your head. You know what it looks like when the relational store is fine but a connection pool is exhausted, and how that differs from the store itself being unwell. You know what Redis does when memory fills, because you have already watched one search fill it. You know which of your queue's guarantees are real and which you assumed. None of that knowledge is impressive and all of it is load-bearing at exactly the moment you cannot look anything up.

The second thing it buys is the next engineer. On a platform with roughly 90 integrations, an engineer's first month is already spent learning the domain — which supplier means what by "a room", why one search is asynchronous and another is not. Every unfamiliar piece of infrastructure adds to that bill and competes with the part that is genuinely hard. A stack somebody already knows means their attention goes to the problem instead of the tooling.

And the third: boring things are debuggable by the tools everybody already has. When a query is slow, you read a plan. When a queue backs up, you count the jobs. There is no step where you first have to build the instrument.

The one time the exotic choice was right

Hotel identity does not fit any of that, and it is worth being precise about why, because "our data is complicated" is not an argument.

The same physical building carries a different identifier at every supplier. If supplier A's hotel matches supplier B's, and B's matches C's, then A's matches C's — identity is transitive. The default implementation is a pairwise mapping table: rows saying this id equals that id. It works until you ask the only question that matters, which is give me every identifier this building has. That question is a self-join of unknown depth. Not a big join, an unknown one: the number of hops depends on how the evidence happens to chain, and every supplier added makes the chains longer. You cannot write the query once and know its cost, and you cannot bound it without capping the depth, which means quietly deciding some hotels are not the same hotel after all.

A graph store answers that question natively. Variable-depth traversal from a node is not a clever use of the tool; it is the tool. So identity lookups run over Neo4j while the rows themselves stay where rows belong: an identity table of about 1.9 million rows carrying roughly 46 supplier-ID columns lives in the relational store, and the graph answers the traversal. That decision was made in 2022 and has held since, through every bridging campaign built on top of it.

It also charges rent. There are now two stores that must agree, which is a derived store and therefore a debt — a whole class of failure that did not exist while there was one. I would take the same decision again, and I would not pretend it was free.

The test I use

The problem's shape has to be what does not fit — not its size.

If a boring store is merely slower, that is not an argument for a new one. It is an argument for an index, a cache, a denormalised column, or a bigger machine, and all four of those are reversible in an afternoon by someone who was not in the original meeting. Nearly every "we need X because it is faster" proposal I have heard is really a schema or query problem wearing a costume.

The exotic tool wins when the question you need to ask cannot be expressed cleanly by the boring one — when the natural implementation is an unbounded recursion, or a query whose cost you cannot state, or an application-side loop reimplementing something the specialised engine does as its primary operation. Transitive identity qualifies. Almost nothing else I have been asked to adopt did.

Honesty about the argument's limits: I never benchmarked a pairwise-join implementation against the graph at production depth. The case was made on the shape of the query, not on a number, and if someone wants to attack the decision, that is the seam.

Three questions before anything joins the stack

Who operates it at 3am? Not who is excited about it — who gets paged. If the answer is one enthusiast, you have not adopted a technology, you have adopted a person, and people take holidays and change jobs.

How is it backed up, and who has restored one? A backup nobody has restored is a belief. The second store in the identity design is exactly where this question bites, because it is easy to treat the mirror as disposable right up to the day rebuilding it is on the critical path.

What happens to it during a rollback? Deploys go backwards; every push bumps a version and a bad release gets reverted. Code rolls back cleanly. Data does not, and a second store written by the new version does not roll back at all unless someone designed for it. If the answer to this question is a blank look, the technology is not ready to be adopted no matter how well it fits the shape.

Boring is not a virtue. It is a default that you should be able to argue yourself out of — once — with a reason you can write down in a sentence about the shape of the problem, and defend four years later when the person who wrote it has moved on.

Related posts

· 5 min read

"So you call other people's APIs" is the usual summary of my job, and it is wrong in an instructive way. Around 150 supplier codes on about 90 integrations, two documented offer patterns, two execution models behind one public contract, and suppliers that answer 200 with an error inside — a tour of what the work actually consists of, and why it is architecture.

  • integrations
  • architecture
  • API design
  • distributed systems

· 5 min read

Not the integration mechanics — the domain underneath them. Inventory that changes between search and book, prices with a shelf life, cancellation policies that are data rather than a flag, five product lines whose shapes genuinely differ, and supply that is other companies. The hard part is what you are selling, not how you call it.

  • travel tech
  • domain modelling
  • distributed systems
  • architecture