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.