ORMs are great, until they are not

Most teams adopt an ORM for good reasons. In the early sprints, having the database feel like a natural extension of the application — models mapping to tables, relationships expressed in code — lets teams move fast without needing deep SQL fluency on every story. The ORM earns its place well before the first release.

What I've observed is what happens after. The product ships, sprint priorities shift toward feature work and production issues, and the ORM quietly becomes tribal knowledge. The engineers who set it up carry a mental model of how it works and why certain decisions were made, but that understanding rarely survives team rotation — much like documentation, it gets treated as something someone will formalize eventually. New engineers learn just enough to be productive, the ORM fades into the background, and by the time something goes seriously wrong with the database, it's effectively a museum piece.


This is the Day 2 problem with ORMs — not that they're the wrong tool, but that they tend to be a Day 1 tool living in a Day 2 system.


The performance issues tend to announce themselves gradually, then all at once. The most common pattern I've seen is the N+1 query hiding in plain sight: a loop that fetches a collection and then lazily loads a related record for each row, one query at a time. At a few hundred records in development, it's invisible. At a few hundred thousand in production, it's a page timeout. The ORM generated the code, the code passed review, and nobody thought to look at the query log until users started complaining.


Volume exposes what structure couldn't. A query that uses a full table scan instead of an index might complete in milliseconds on a development dataset and take several seconds on production data. The ORM doesn't know the difference — it generated syntactically correct SQL, and that was the end of its responsibility. What's left is a team pulling query logs from Sumo Logic or digging through AWS Database Insights trying to correlate slow query reports with code that may be several sprints old. Tools like Quest Spotlight can surface the worst offenders quickly, but they hand you a list of symptoms, not a diagnosis. Actually tracing a slow query back to an ORM-generated call — and then understanding why the generated SQL doesn't match what the schema can support efficiently — requires a mental model of the database that the team may no longer have.


Schema drift is the underlying condition that makes all of this harder. Columns get added for features that shipped and were later changed, but the column stays. Indexes get created reactively during incidents and never revisited. Tables accumulate nullable fields that nobody is confident about removing. None of it surfaces as an error — it just means the system is harder to reason about when you need to move quickly, and the ORM's model definitions are no longer a reliable guide to what the database actually contains.


What I'd push back on is framing this as an ORM problem. It's a model maintenance problem, the same one that turns good documentation into outdated documentation. The remedy isn't abandoning the tool — it's being deliberate about what role it plays after the first release, and not assuming it still speaks for your schema just because it once did. The teams that navigate this well treat the schema as something they own explicitly, independent of what the ORM thinks it knows, and they stay close enough to the generated SQL to recognize when the abstraction is working against them.



---

Series: Databases & Data Systems (Unsexy, Critical Work)

* Part 3: ORMs are great, until they are not. 



Comments

Popular posts from this blog

AWS Re:Invent 2024

Tariffs are bad for you.