How not to structure database-backed web apps: performance bugs in the wild
blog.acolyer.org
blog.acolyer.org
It greatly depends on what you're doing, but for the majority of systems which are read heavy (and that most certainly includes "dynamic" sites like Amazon or Wikipedia), I hold to two major beliefs:
1 - Have very long TTLs on your internal cache servers with a way to proactively purge (message queues) and refresh in the background. Caching shouldn't be a compromise between freshness and performance. Have both!
2 - Generate message/payloads/views asynchronously in background workers and have your synchronous path as streamlined as possible (select 1 column from 1 table with indexes filters). Avoid serialization. Precaculate and denormalize. Any personalization or truly dynamic content can be done: 1 - By having the client make separate requests for that data 2 - Merging the data into the payload with some composition. 3 - Glueing bytes together (easier/safer with protocol buffers than json)
Do things asynchronously. Use message queues / streams.
Beyond that, GC becomes noticeable. For example, Go's net/http used to (might still) allocate much more than other 3rd party options.
key = calculate_cache_key()
if not cache.has(key):
data = expensive_calculation()
cache.store(key, data)
else:
data = cache.get(key)To frame it another way, what happens if 100 requests come in at the same time for that expensive value when it isn’t in the cache yet? The expensive calculation will be run 100 times at the same time.
Ideally, you’d rather refresh the cache value in the background once and never allow duplicate requests for it from the web.
If you’re running a language that makes it easier to deduplicate requests for certain data, the original approach will last longer. The CacheEx library in Elixir, for example, will only run the expensive calculation once, set the cache and then send the value back to everything that requested it while it was loading.
CacheEx gets the ability to check for the presence of the cache key in Erlang Term Storage (ETS) which is basically an in-memory cache. If the key is present, it just returns the value.
If it's not, it sends checks to see if a process exists with the cache key name. If there isn't one, it creates one to request the resource.
For any other requests that come in until the value has been created, they will be directed to the process that is getting the value.
When the process that was calculating things comes back, it will save the value to ETS and then also send it back to all of the queued processes that have been waiting for it.
In the case of Varnish, you'd be expecting it to send back the entire completed view...HTML and all. This isn't something that you need to worry about with Elixir because the view is never actually rendered in the application. It's broken down into pieces that are never duplicated in memory and then replayed directly to the socket...meaning you really only ever need to cache expensive data and not what it's transformed into.
Here's a good read on why this view layer is so fast, if you're curious. Most people report shock that their uncached performance with Elixir and Phoenix is on par with statically cached HTML. I didn't believe it until I saw it myself.
https://www.bignerdranch.com/blog/elixir-and-io-lists-part-2...
This gets more complicated if you have a distributed cache and/or distributed application servers. The typical solution there is to allow at most one computation per process/device.
For larger, more complex scenarios the techniques mentioned by GGP above work well, i.e. not doing expensive_calculation() in your app process at all.
The issue has a bunch of names, I know at least three: cache stampede, thundering herd and dogpile effect.
By default it handles the case of concurrent retrieval on the same key (the second one will just wait for the first one to finish and use that value rather than starting a duplicate computation). It also lets you configure more interesting things like eviction strategies, removal notifications, and statistics.
Last year was a lesson for me in why caches are a hard problem as I had to debug many cache issues from other people not thinking things through... (At least one of the issues was my own fault. :)) Since then whenever someone suggests we use a cache I instinctively pull out a set of questions[0] to ask. The three questions Guava has you consider can also lead you to using memcached or the like instead but my set tries to answer the question "Do you even need a cache?" and if so, generating helpful design documentation.
Is the code path as fast as it can possibly be without the cache? Do you have numbers?
Will the cache have a maximum size, or could it grow without bound?
If it grows without bound, either because of unbounded entries or because of unbounded memory for any particular entry, under what conditions will it consume all system memory?
If it has a maximum size, how does it evict things when it reaches that size?
Are you trying to cache something that could change?
If so, how is the cache invalidated?
How can it be invalidated / evicted manually by another thread or signal? (Debuggability, testability, inspectability/monitorability, hit rate and other statistics?)
Is there a race condition possibility for concurrent stores, retrieves, and evicts?
How constrained are your cache keys, that is, what does it need to know about to create one?
Do they need to take into account global info?
Do they need to take into account contextual information (like organization ID if your application server runs in a multi-tenant system, or user ID, or browser type, or requested-language)?
Or do they only depend on direct inputs to that code path?
[0] https://www.thejach.com/view/2017/6/caches_are_evil -- need to update it a bit but not much...It's fine if stale data is used deliberately, problems arise when stale data is assumed to be 'fresh'.
A nicer way to put it might be that finite TTLs should be viewed as an opportunity to optimize, not ideal or standard.
However, just because it's hard it doesn't mean we shouldn't do it the right way.
Wouldn't deploying a less-than-precise TTLs be an appropriate trade-off for non-transactional caches, caches subject to network partitions, caches that can't enroll in invalidation messages, caches that can't poll for change sets, caches that can't implement eviction policy, etc?
Certainly the cache should be transparent about the trade-offs it has made (such as not promising authoritative data if it's accepting eventual consistency via TTL).
It's impossible to create perfect systems, so it always makes sense to give yourself an extra out to protect you from unanticipated defects (otherwise known as a "belt and suspenders" approach).
As for the knowing where the performance problem is, I find that skill (that I think should be basic) is in fact very elusive in the engineers I encounter. When I interview people, even in what would be upper intermediate to senior level in terms of years of experience, alarmingly small number have ever done or could do (or even would do) performance profiling like described in the article. Yet they do describe how they changed this ORM for that ORM, this DB for that DB, this language for that language, in the name of higher performance.
I've seen or heard of:
- Teams spending weeks exchanging SQL DB for No SQL DB because of unsolvable performance problem. When hitting the same problem with NoSQL DB, they find that addition of a simple index is solution in both cases
- Teams spending weeks exchanging Hibernate for OpenJPA in a complex application, because of performance, without doing any performance analysis, because they've read article that says Hibernate is slow
- Teams choosing complex architectures they don't really understand, for performance reasons, without being able to articulate performance requirements of the system they're building
These days, whenever someone mentions performance as a reason for anything, I judge their competence based on their response to the question "And how are you measuring and monitoring it?"
I don't get the idea that one has to pick either SQL or NoSQL, but a lot of people seem to think this way. Why not use both? The SQL portion can more or less be treated as a rich index of relationships, and the NoSQL portion can handle performance when it makes more sense for data to be embedded in parent documents.
In fact, our current system has PG and several NoSQL databases, because each product has its own strengths.
How did these team members pass their job interviews?
By practicing algorithm puzzles?
The way that I do it now it's just use some simple functional test to GET / POST and measure the time in a pytest script, I'm sure there are better ways to do it.
Same is true with any abstraction – if you want to learn to use it right, first learn to make do without it.
I've written a few ORMs before, and I can do it with fairly succinct and concise codenquickly if needed. I still reach for the full ORM from the beginning, because swapping out layer is painful, and you always want to do it sooner than later.
Many years ago, a team member got asked to figure out what the performance implications would be if a specific application were to be changed from PostgreSQL to MongoDB (Mongo was very early at the time). That's a very difficult question to answer in general, but the way he decided to approach the problem was: create two programs. One would ask to connect to PG, grab the current time, run a query (once!), grab the time again, disconnect. And that's it. The other program would do the same for Mongo.
His 'findings' pointed out what some people were already expecting, that MongoDB was magically faster than PostgreSQL. Which was odd, as they both were running a single instance, with a simple data type, which PG should have zero problems handling.
I pointed out that he was measuring connection time too, which is slow on PG. He said that wasn't the case, because he started the timer after connecting to PG. I replied with "No, you are starting the timer after asking to connect to PG, you don't know if the connection is made at that point. Run a simple query first to be sure." He wouldn't accept that.
Turns out that not only that was correct (the mongo library would connect immediately, the PG one would defer until needed), but none of the DBs had indexes defined. So no numbers made any sense, the tested query was not based on any observed production queries, it would only be run one time, etc.
I have not seen a more meaningless 'performance' testing since. But the Powerpoint graphs looked pretty, were only management in the room, they would have likely be convinced to migrate.
All that, for a badly written application, that a single box with SQLite would have zero problems handling.
One the other side, I had a coworker implementing some simple, but effective, profiling of a Rails application. Slow path was traced to the caching code, specifically the code that generated the hash key. That was replaced with a better version and we got massive speedups.
Alright, I think I'll add some questions on profiling in my current team's interview process.
Some academics (Yang, Subramanian, Lu, Yan, Cheung) were able to produce massive improvements in about a dozen large, mature, battle tested open source projects using just a few lines of code.
This should give hope to all those tepidly trying to get into open source. Just go and take a look at the dozens of open source projects in Django or whatever and you could improve the performance by keeping an eye on the ORM.
Better yet you might find another thing that makes them even better with ease.
Of course, I should add, I think the really clever thing the academics did is to come up with this random link clicking program to time the worst load times of the projects. That whole setup was gold.
They didn't just change a single line in an application, they wrote a huge benchmark suite and dug through miles of code to find issues like this. I've no clue how much time they spent on it, it must've been months.
Maybe but the ORM lines should be pretty insulated. If you return the same results and only speed them up you should be okay.
To take an example, replacing any? with exists? doesn't seem to require testing everywhere in the project. I imagine that the average fix had lines like that.
I also suspect that the fixes were pareto distributed -- that a few fixes brought most of the benefit.
It's the equivalent of making a rest call and complaining about latency. Yeah, it's a method, why is it taking so long?
They blend the distinction between working in memory vs accessing the db.
From one side it is very convenient, however I really feel that such performance sensitive operation should be carefully considered and that most SQL should be written by hand.
If you like writing SQL by hand, by all means do so, but you will not automatically get better performance by handwritten SQL as compared to ORM generated SQL.
Even things like query hints can be easily applied in the ORM's I know. It is kind of hacky since it breaks abstraction layers - but so is query hints in SQL.
But of course there can be some special cases where you just have to drop down to raw SQL for some reason. All ORM's I know allow this.
Definitely writing plain SQL does not solve performance issues. However, it does force the developer to draw a clear line distinguishing where he is accessing the database and where he is working with in memory data structures.
Built in support for WHERE subqueries, on the other hand, is on our roadmap. Currently working to finish core MSSQL support first though. Hope that helps!
Where it might get trickier is when you have something like a Customer-Region-Vendor join, and you actually want it to go ahead and create objects for the all of the Customers and Regions and Vendors whose data was pulled out of that one select.
It even offsets some of the burden to the app, which is generally easier to scale than the database.
The idea is that you query the "root" table, loop over the results and build an array of IDs, then do additional queries to the other tables with a "where whatever_id IN (...)".
Most sites and web apps never even break a 100k/day hit mark for which I believe inefficiency may not be the biggest issue. But wasting a month tryig to write native Sql queries can hurt your project a lot more.
ORMs are not inherently inefficient. And in my time I have seen plenty of n+1 style queries written with raw SQL.
No they are not. Have you measured this in a real-world application? This overhead is negligible compared to the cost of the query itself. And without an ORM you still have to load the data into some kind of objects or data structures before you pass it to presentation, you will just have to write the code yourself.
But yeah, lazy loading in a loop will kill performance. So don't do that.
You may be able to write faster code, but unless you are Donald Knuth, you can't guarantee that any custom code you write will always be faster than some library.
In other words, you have no idea what you're talking about.
Could you please (re-)read https://news.ycombinator.com/newsguidelines.html and not use this site that way? The idea here is to post civilly and substantively, or not at all.
I agree that 99% of all projects never push beyond what relational databases are capable of. I've seen developers who were tasked with writing basically a todo list say that they're going with a nosql database because RDBMs don't scale.
Unless it would be the first contact a developer has with SQL, I really don't see how difference in time taken to query the database with an ORM vs SQL would be anything more than a couple of minutes, maybe an hour if you really have a hard time with it or your data model is really convoluted.
Still, inefficiency is a huge issue. I need to be close to desktop-like performance for data entry, and that requiere both insert/read performance (that are executed in the same block and compromise several queries) below 1 seconds.
Request/seconds is not the metric that matter.
Is the seconds/for user(s) main activity.
Of course full page cache is the first stopgap for this problem but you'll always have dynamic content.
But that said... writing native SQL queries wastes a month? Um, no. ORMs are convenient, but writing select, insert, update, and delete queries by hand isn't that hard. It's mildly verbose and you end up repeating yourself lots so it's annoying, particularly if you (like me) have the kind of programmer's mind that constantly wants to factor that out. But it's doable without adding a high factor to development time.
Rubocop already knows about `where.first? => find_by` for exmple: https://github.com/rubocop-hq/rubocop/blob/master/lib/ruboco...
I think, as the article suggests, that 'opaque' is as much the problem as the classic Object-Relational impedance mismatch, but I would posit that the opaqueness isn't just that the programmer can't _see_ what happens, but that they also don't _care_.
So I'd put my faith in tools instead. If programmers don't want to think about this, let the tools try instead.
The static analyser was interesting. Although static analysers have shallow comprehension, they were able to identify anti-patterns that were common enough to make it a useful linter. And of course their classic-mistakes approach can be applied to other languages too, even if each language and ORM needs its own corpus.
I sincerely hope that JetBrains and other IDEs bring this kind of analysis into their IDEs. I write a lot of non-Ruby code, and the JetBrain IDEs do keep suggesting nice 'code simplification' and 'generate boilerplate' checkers as I type.
But while they check my regex for well-formed-ness and colour my SQL but don't have any kind of meta analysis and comprehension. Missing opportunity.
You could take this further for normal non-DB code too - they could warn me when I have inefficient O(n) search in a loop and so on.
For phpstorm you have the EA extended plugins which gives lot of hints like that. I'm sure you could find the same kind of plugins or write one backed by a static analysis tool for other languages.
I could attempt to deconstruct the ORM problem, but Martin Fowler had a nice article [1] on replying to "ORM hate" a few years back which is worth the read for folks that question the usefulness of ORMs (and I believe questioning things is a healthy thing to do!)
Now consider that your DB is just like your UI. It's a view of your model objects. Just in the same way, there is no reason why your model objects should be related structurally to your DB representation of the data. And in exactly the same way there is no particular reason why you should have a way to automatically map to and from your DB layer to your model objects.
In many cases, it will be less complex to build a bespoke OR mapping rather than try to find a system that will do it generically.
I also don't see ORMs as exclusive. It's fine to use ORMs for 95% of your use cases, but drop down to raw SQL for the remaining 5% where it's not a good match. That's still a win for the goals I mentioned above.
Some are advertised that way. Entity Framework Code First, Migrations etc.
At our place, our DB dev team is larger than our api team, which in turn collectively dwarfs our front end teams. ORMs in this sort of environment have never really been given a chance, but I feel like it would be easy to justify them in many situations...
As an example of this, the influential DHH of Basecamp blogged saying just that: https://m.signalvnoise.com/conceptual-compression-means-begi... "Basecamp 3 has about 42,000 lines of code, and not a single fully formed SQL statement as part of application logic!"
I have never seen anybody claim ORMs would eliminate performance issues.
I'm wading through the tedious and boring process of writing a data access layer for an application at the moment. It's repetitive, there's lots of error-checking, and testing it is a pain in the arse. My big consolation, though, is that in a year's time when I need to optimise the queries because we're getting load on it, it'll be easy to understand and simple to change.
ORMs don't make code easier to understand, they make it less boring to write. Those are not the same things.
Any ORM I know allows you to easily drop down to raw SQL when you need it - but in the majority of cases you never need to. Writing everything manually in SQL with tedious boilerplate wrappers because you might need to manually optimize some queries at some point in the future seems like a massive violation of the YAGNI principle.
query.SQL("my complex query here")
If it's not, your ORM is broken and you should write a new one (they're really not that complex).Not my experience at all. Rather you have your straightforward ORM code with maybe a couple of "magic" hint annotations here and there where you need them. Your non-performance-critical queries are plain and simple, your performance-critical queries are less plain but remain readable. Whereas if you write boilerplate SQL the whole time all your queries are "readable" in theory, but there's just so much of them that you can never actually understand more than a fraction and have no way of knowing which differences are important and which are accidents.
(Unless, of course, you throw away the whole ORM infrastructure at the first sign of a performance problem and insist that you absolutely have to run custom SQL directly, disable entity caches, and so on. But don't do that.)
This isn't as terrible as you make it out to be. For example, general purpose programing languages don't prevent the programmer writing a O(2^n) algorithm; yet they are useful.
With an ORM you write the first working version of your product quickly, validate product-market fit, and maybe spend a small amount of time profiling and optimising eventually when you need to scale. That's a good tradeoff.
The experience of building apps with hand coded SQL, while not good for a project, is an excellent teacher.
The efficient way to handle it is to set a lower limit on the pkid (or other atomically increasing row) of the last record fetched, and fetch in ascending order e.g.
SELECT * from my_table where id > (last_row_id_seen) ORDER BY id asc limit 20;
Then you have an indexed jump to the correct rows to return. It's pretty straightforward to abstract a batched database iterator with this pattern for use application-wide (usually can be done in ~20-50 loc), but no ORM that I'm aware of supports this pattern natively.Do note that, by extension, it's impossible to efficiently batch iterate over tables that don't have a unique, orderable key in postgres and mysql.
Easiest way to prevent this is add enough filtering options that it doesn't inconvenience users to limit depth to page 10 or something
https://www.citusdata.com/blog/2016/03/30/five-ways-to-pagin...
Highly recommended read.
Unfortunately there aren't any solutions that generalize as well as LIMIT + OFFSET which is why we use it. In fact, other solutions usually take quit a bit of custom tailoring if you want Prev/Next and deep page jumping.
E.g. EXPOSE SERVICE(REST, GraphQL) BillOfMaterials (VARCHAR arg) AS (SELECT ... WHERE ... = arg etc.)
Pagination and sorting are pretty easy additions when you retrieve data using the ORM in the standard way, and now you need to add extra code to handle those specifically. I don't think you can use the Django admin with raw SQL (I never tried, but it doens't make too much sense, as it basically generates a set of views per table). Model methods don't make sense using SQL queries.
You should be able to write SQL if you want to be able to use the ORM effectively and I am certainly glad I knew it well before started using Django's ORM.
They populated their install by randomly filling in fields on the website. Which doesn't include any map editing! For OSM they suggest changing how the diary feature operates, which is a tiny, almost irrelevant part of the OSM website software stack. The OSM database has millions of geographic objects, and they talk about the diary system on the website.
> For example, when we profile the latest version of Openstreetmap, a collaborative editable map system, we find that a lot of time is spent on generating a location_name string for every diary based on the diary’s longitude, latitude, and language properties stored in the diary_entry table
The paper claims to have filed bug reports, and has URLs. But those links don't exist.
Paper: https://hyperloop-rails.github.io/220-HowNotStructure.pdf openstreetmap-website: https://github.com/openstreetmap/openstreetmap-website/ Claimed Issues submitted: https://github.com/hyperloop-rails/issues-summary
He's talking about the common Rails pattern of rendering a small Haml/ERB partial over & over in a loop. I've noticed big perf hits from this before, but never completely understood why. Rendering the exact same result directly in the loop body, without calling a second partial, gives a big speedup.
There is an extensive discussion here:
https://softwareengineering.stackexchange.com/questions/1571...
I would love to have some better understanding around this, although it sounds like no one has a clear idea of the cause.
In development, I've noticed that Rails re-reads the partial file off disk every iteration of the loop. I don't know if that happens in production too, but if so it would explain a lot.
This module can detect the n+1 queries problem automatically in Python ORMs: https://github.com/jmcarp/nplusone
Looking forward to seeing more automated tools like this in the Python/Django world.
However, if you understand how databases work, how to tune the driver and how to get the ORM tool to generate the same queries you'd otherwise write yourself, then you are fine.
For more details, check out these [14 High-Performance Persistence Tips](https://vladmihalcea.com/14-high-performance-java-persistenc...).
So if I do our equivalent of the ORM API Misuse case:
IF(RecordExists(SELECT * FROM schema.variants WHERE track_inventory = 0))
{
...
}
(RecordExists is a function that only checks whether the query returned something, and has been marked that way) the compiler will already reduce this to: IF(RecordExists(SELECT FROM schema.variants WHERE track_inventory = 0 LIMIT 1))
{
...
}
And likewise a function that selects all columns from a database and returns only one field, has the select reduced to only selecting that one column.The drawback, of course, is that any SQL features of the underlying database not exposed by the scripting language, are unreachable unless you fallback to sending raw query strings again.
It is just too easy to be rushed and bring in a heap of code, so I prefer to use SQL instead of ORM's.
All access to the database is done in a single module and they are wrapped in functions like below
def get_table_as_list(user_id, cols, tbl, where_clause, params_as_list, conn_str, order_by="1", maxrows='2000'):
"""
This should be the ONLY place that selects from the database
"""
db = get_db_conn(conn_str)
cur = db.cursor()
where_clause += ' AND user_id = %s'
sql = "SELECT " + cols + " FROM " + tbl + " WHERE " + where_clause + " ORDER BY " + order_by + " LIMIT " + maxrows
params_as_list.append(str(user_id))
cur.execute(sql, params_as_list)
res = list(cur.fetchall())
cur.close()
db.close()
return res
The database is designed and built first, then in the application the
definitions are done like below all_tables = [
{'tbl':'as_note',
'cols':['id','title','pinned', 'important','content','folder'],
'col_types':['id','Text','Checkbox','Checkbox', 'Note','Text'],
},
{'tbl':'as_task',
'cols':['id','Title','Pinned', 'Important','Notes','folder','Done'],
'col_types':['id','Text','Checkbox','Checkbox','Note','Text','Checkbox'],
}]
So far it is working well, and it is very simple to add new tables to
the schema and have them working in the application.Bonus questions:
What about maintenance or admin queries which aren't tied to a specific user_id?
What about sql injection?
Keeping all database access in one place to avoid having selects around the codebase.
> What about maintenance or admin queries which aren't tied to a specific user_id?
This is the web interface for users, all admin stuff is done elsewhere
> What about sql injection?
The selects are passed as parametised queries, so the where clause would be 'title = %s AND folder = %s'
I've been building apps with Django and Django's ORM for the last 10 years and found essentially zero overhead in most cases. Every once in a while there's a slow page, I open up the debug toolbar which shows me every SQL query that was used to generate the page in a nice waterfall diagram, I see something a little odd and change the order of some filters or add a `select_related`, or `prefetch_related` or discover that some third party library is making a dumb call (unavoidable problem of using third party libraries on any platform) and find a workaround for that. Every once in a blue moon, it appears easier to write a raw SQL statement than figure out what needs to be done to get the ORM to generate it, so I do that. Of course, Django's ORM makes it stupid easy to use a raw SQL statement: https://docs.djangoproject.com/en/2.0/topics/db/sql/
I've worked with a few other ORMs in Python and other languages over the years as well (in Go, Erlang, Elixir, Clojure, nodejs) and never really encountered any where the ORM had a noticeable performance overhead (dominated by the network latency back and forth from the database) and I've yet to work with one that I couldn't just do a raw query when needed. The closest I've seen is when I started using GORM in Go, I found it running slowly and discovered that it automatically adds a "soft delete" functionality so every query gets an additional "and not is_deleted" clause added. Disabled that feature and it was fine. That was my own fault though for starting to build before I finished reading the documentation, as it was pretty clearly explained in a later section. (OK, also the ORMs I was using in Perl and Java back in the late 90's/early 00's were also pretty terrible, but those were prehistoric times.)
You always have to be careful of N+1 problems, but that's not just an ORM thing. You run into that as soon as you have any abstraction in your code. Once you have refactored to `get_list_of_items(some criteria)` and `get_item_details(item)` functions/methods/whatever, whether it is using an ORM underneath or raw SQL, developers working on the app have to know that they can't loop over the results of the first and call the second on each of them. Tradeoffs between reusability and performance are nothing new though and not at all specific to web applications or ORMs.
The 10x slowdown I noticed was moving from native database functions (PHP's PDO or MySQLi extensions) to Doctrine2's underlying database abstraction layer, DBAL. Doing a prepared query (with the same SQL) in the native extension was 10x faster than DBAL, which uses the native extension under the wrapper. I never got to why - DBAL is doing more (fair enough) but not enough for this amount of slowdown.
As for dbal, I think the overhead is in statement preparation and parameter expansion (for array params) and it can be avoided by using the native functions, though that overhead was never 10x for me.
That is one issue. I find the interface the orm provides to also be inferior in every way compared to sql so I just don't get why people would add something that provides even the tiny overhead you describe to get an inferior interface for dealing with the database. Some orms I've used create their own sql like language, so now there is something new to learn (that's useless outside the orm) that provides nothing over actual sql but has all the drawbacks of the orm and sql. Even working with simple objects and queries is more difficult as I hardly know what the orm is doing. When I do look at what it generates, it's always been atrocious in the orms I've used. In addition, trying to wrap my mind around the concepts the orm builds poorly on top of the relational database is truly angering. Taking a wonderful interface to a database and turning it into a set of nonsensical object relationships actually makes it more difficult and more time consuming to write the app with the orm. This is unnecessary complexity for complexity's sake, something proponents of simplicity avoid.
So my experience has not only been that of horrible performance but also that of a horrible ui that slows development to a crawl and creates apps that need to be rewritten to work properly, the exact opposite of what orm proponents claim. One of the main reasons I want to move to a functional language is that orms don't exist there.
That's why I really like Promises/Futures in Javascript. You know exactly when you're executing a DB operation and you have to think about the implications more. It's not just a simple object accessor.
As long as you have a not-crazy person in charge of the initial design, you can allow—and even encourage—developers to make versioned changes to the database.
If you let us start from scratch with ORMs, it will suck for everyone.
There’s a fundamental mismatch between what developers think of as objects and how relational designers think of tables.
ORMs can smooth that over. But nothinking can undo the wrong-headed-ness of a developer thinking of a relational dB as an object store.
What does that spell? SLOW PERFORMANCE!
Todays programmers dont understand data. They understand frameworks. To find the nr of all cars that are out of insurance they write:
10 Nr=0
20 Hey framework, give me all cars!
Framework: Ok, here are 8001093 business objects representing all the cars in our DB. Each has all the attributes the car has. Color, mileage etc.
30 Thanks!
40 Foreach Cars as Car
50 If Car->insurance_end_date < FancyDateLib.now() Nr++
I see variants of this everywhere. And the performance impact is just untoppable. It's often several million times slower then a simple sql query.But beefy hardware with lots of ram and memory takes care of it. 'Our software is enterprise grade, so of course it cannot run on commodity hardware'.
We should fix our abstractions instead of telling people they don’t understand data. The latter is fine every so often, but doesn’t scale and isn’t even true most of the time.
If you scatter these throughout your code and then the database schema changes, you have a hell of a refactoring job to make sure everything still works. One advantage of an Orm is that if you keep your objects in line with your database the generated sql will stay correct.
Personally, I am more than happy to take that hit. I think it is a price well worth paying in order to have optimised queries that do exactly what I what them to do. To make it easier for myself though I will always try to keep my queries in one place in the code. Then all my code needs to know is it is getting clients_older_than(32) or whatever..
It maps 1:1 to the SQL statement that gets executed so there's no magic e.g. extra n+1 queries being introduced without you noticing.
But if you change your schema, re-generate, and immediate compile errors showing you where you're referencing something that's now been deleted, so it's not fragile like putting SQL in a String in your code (where if the schema changes, the compiler can't help you)
To me the line of argument around lacking static type checks always felt like FUD, but maybe it's best countered with counter-FUD...
If you've opened up your DB layer to accept strings (that you are supposed to build with a SqlBuilder statically referencing column and table names) you still have no guarantees that everything still works because some code somewhere might have just used a handwritten String instead. In other words you still need tests.
The static type checking value only helps against schema changes that rename or remove things, which is generally pretty rare, and depending on the level of autogeneration and query mapping might not even help if the column type changes. It doesn't help when the semantics change. For example (much more commonly) the introduction of a new column that is expected to be filtered on and/or required to be set a non-null value in inserts. So the static references are mainly reduced to being a mechanism to make finding users of a table easier, and hope that whoever is making the schema changes is going to look around for those users and update them accordingly. (When you don't own the table, as is usually the case in large software with many teams, the table owner similarly doesn't own your code, so that's kind of a vain hope. The best solution is what is done in open code with unknown consumers -- versioning. Stop renaming/deleting/changing the semantics of things, just provide new things, under different versions or namespaces if they really need to share names.) But you can accomplish this to the same effectiveness by creating a static reference to the table near where you execute queries on it, and use easy to read handwritten strings for the rest. In my experience though many queries are trivial enough that a SqlBuilder-esque pattern isn't much overhead, it's not a hard hit most of the time, and more complicated ones may belong as stored procedures if you've already invested in that direction.
If you go the full ORM route, which it sounds like you aren't suggesting since you mention optimized queries that do exactly what you tell them to do, the table owner may graciously update the central object builder to set a non-null default value for everyone (or specify one in the table def), which would maybe stop things from breaking immediately, but maybe wouldn't actually stop breakages (especially on the select side where data is retrieved that under the new semantics was meant to be filtered), so end users are still on their own for whether this new column matters to them or not. And that's just one type of schema change that's not a simple rename or column removal, there are many others.
Intellij is pretty good at highlighting and pointing out typo errors. The code will still compile though.
> no typing information
Given the popularity of javascript, I thought this would now be considered a feature ;)
The solution becomes really obvious if it happens once for 95% of people good at computers.
You might regret it a bit more if you do something like a filesystem read to get the query.
There's your regret. It hurts so much, doesn't it? :)
Not using an ORM requires writing lots of code to do what the ORM would otherwise do. Is that really appropriate today? Should we not take advantage of the available CPU/RAM? I say ORM is the correct choice in many cases. Not always.
I say ORM is the correct choice in many cases
I'm still undecided about this. Would be fun to compare some actual approaches. Which is your favorite ORM?For simple queries though (single-table), Entity Framework is just as fast.
Because I’m mostly working on dashboards and stuff, writing to the database isn’t much of a concern.
db.Query<Car>("SELECT * FROM Cars WHERE InsuranceEndDate < @Date", new { Date = DateTime.Now });
/edit: Sorry, missed the "number of". Well, you get the idea. It'd use 'QuerySingle' instead.jOOQ. Not really an ORM, but handles the tedium of mapping results to an object while letting me write type checked SQL. IMO, it is the best solution for dealing with an RDBMS. I wish every language had a jOOQ equivalent.
The ORM makes it easier to do things like Class.filter(insurance_end_date < today_date)
But hey what do I know, I am not berating the youf of today so I don’t belong in this skit.
Class.filter(insurance_end_date < today_date)
Which ORM is that? It would be cool to compare some approaches to common problems.You can ever traverse relations and still use the same syntax: https://docs.djangoproject.com/en/2.0/topics/db/queries/#loo...
Django has similar functionality through Q and F expressions.
Most ORMs have this functionality of specific language constructs to allow complex queries to be expressed without writing SQL, just with slightly different semantics.
SELECT * FROM DATA
for each r in result
if r.x > 12
do_something(r.y)
Exactly the same thing without any ORM. If anything ORMs should improve performance for novices since it makes it a lot easier for people to writer better queries that run on the database.Additionally, the ORM makes it much harder to see what is going on under the hood. For example you don't know if a value of an object is stored in the main table or lazy loaded from a related table. So you have no clue if echo "$user.name lives in $user.city" results in 0 sql queries or one or two.
I've never in the last 20+ years seen an ORM that doesn't allow you to log the SQL queries with a single configuration option.
If you have no idea what you are doing with an orm you probably won't be able to write decent SQL anyways.
Again this doesn't match my experience. A mediocre .NET developer who can access the database with LINQ is far more likely to run their queries on the database than a mediorce .NET developer who has to try to write SQL queries by hand.
And the truth is that modern ORMs are often pretty clever with their optimizations, and in many cases the ORM will often outperform an average developer doing the obvious thing is SQL.
This can be done on SQL itself, but it would be much harder.
To be fair though, ORMs do move us one layer further away from the actual DB, so people tend to be even less likely to understand how to write ORM calls which result in performant SQL calls.
SELECT * FROM DATA WHERE x > 12
where there's no corresponding index. There's also the infamous N+1 query pattern: // get list of ids
for each id in ids
r = SELECT * FROM DATA WHERE id=:id
if r.x > 12
do_something(r.y)
IMHO, both are signs of not fully understanding what the database does for you. In the same vein as the "learn JS before frameworks" argument, I'd argue devs should at least learn and understand SQL, indices, etc. before using ORMs.This was all dev 101 when I was coming up (yikes, almost 20 years ago now).
(You don't know the minutiae of pointer arithmetic!? You're not familiar with Docker, Vagrant, or AWS Lambda!? You can't construct a sed / awk one-liner with your eyes taped shut!? You don't know about concurrent skip lists!? You've never completed SICP or read Purely Functional Data Structures!? And so on.)
None of their developers knew about WHERE constraints but they did know how to join tables so they were looping over hundreds of millions of rows in classic ASP using essentially the code you have above, with a bunch of unnecessary type conversions cargo culted into the inner loop (IIRC string to int to string).
Added business lesson: we billed double for a rush job but since it was only about 20 hours to fix the code and build out the remaining features (about half of the site), the original contractor still made considerably more. Our sales guy wasn’t canny enough to realize that he should have offered to do it for, say, a third of the other company's price.
The problem is not ORMs, the problem is just people not thinking about the resources their query over some data takes, and trying to do things in memory that are better done in the database (as in your example), because databases are hard, and sql is not a particularly friendly language, so they take the easy way out. They'd do things like iterating over all instances just as much if not using a framework/ORM, because they understand their programming language and don't care to understand SQL, and also because they get away with it when there are 1000 cars in the fleet, and didn't anticipate having 1 million.
This is a problem that goes beyond the usual ORM antipatterns and bad database designs and becomes its own special kind of hell, because it makes code far harder to reason about and thus far harder to review, in a way that I don't remember encountering in Hibernate, Gorm, or anything else.
[0] count, length, and size
[1] includes, preload, and eager_load
IMO ORMs should be simple, unsurprising, and only have one or two methods which actually execute the sql and fetch data - they are there to make it simpler to build queries and map rows to objects, not to blur the line between fetching and manipulating data.
1 - http://diesel.rs
Fancy a couple of jOOQ stickers? :)
And while I have you here...your blog posts are always great and very informative. It's interesting to see the differences between RDBMSs (I really only have experience with MSSQL/MYSQL some PG), and how they do one thing or another.
Will do! Thanks for the nice words about the blog.
I feel like this brings up an annoying cultural problem in tech wgere developers hit intermediate skill levels and feel the need to proclaim their expertise by writing something in the you’re-doing-it-wrong genre but don’t have the breadth of experience to realize that they’re over-generalizing and so they go from “ActiveRecord has this problem” to “ORMs are bad” without asking whether anyone else has it better. I’ve read tons of similar posts where e.g. Java proved that exceptions, OOP, or static typing were bad or Twitter proved you shouldn’t use Rails.
They seem to spend solid third of their time googling for how to write a code. Write code, hit a jam, google, find something in stackoverflow, ruminate between what is found, copy paste and try if it works. They don't know their framework well enough to just write a code.
I'm a more of a embedded software/hardware guy and manager, but I can sit down and write pivot pivot query in plain sql without consulting any references.
I'm not saying these guys are doing something wrong. It may be that in the typical consultant work where you have existing old frameworks, new frameworks and new stuff coming in constantly, there is less value to master the tools that you are using. You write some glue, then move on.
You can differentiate the workforce. Programming by copy pasting example code withing frameworks may allow skipping the requirement to understand general algorithms.
Less required expertise means less pay and cheaper products. You hire 10 low-paid easily replaceable code monkeys who slap together pieces of software from ready components. It's like like assembly work. Then you hire one guy who knows things to supervise them and solve the problems when they can't figure them out. It's like blue collar assembly workers and engineers.
The issues with performance are almost always with the way the tool is used, like choosing a bad algorithms or the wrong data structure for a certain situation.
Lazy-loading a large child object in a tight loop over 100s of entries will always be slow because it's an improper approach, and nothing other than knowledge and competency will resolve that. It's rather amazing ORMs can be so controversial when the real issue is the developer, which speaks to the far bigger problem of expertise and quality in this industry.
But there are tons of other benefits:
1) Avoid all injection attacks by default by binding variables rather than interpolating their vakues
2) Write SQL code for you to automatically, so you always have balanced parentheses and no typos or errors mixing statements
3) Autogenerate classes and methods from the SQL model automatically so there is a place where you can add custom methods on objects
4) Model fields and relationships in a way the app can understand, so you can make meaningful error checks and manipulations in the app instead of relying on the database engine to give you a nice error message or manually copying the data type logic.
5) Play nice with version control, making sure to encapsulate the code in ONE PLACE instead of a million places when the schema or model changes.
6) Pissing people off on HN so we can better discuss and explain principles of architecting good softeare while talking anout the benefits of ORM
7) Let you write an adapter to move to eg a graph database which is far faster.
8) In fact, just making you avoid doing joins in the database by default is already a feature as you can make your app far more scalable w sharding and possibly think about making it byzantine fault tolerant and distributed!
See for example this:
And any sane ORM will perform the joins in the database by default.
Joins were good for the smaller websites, but they don't scale. By avoiding joins, you have shared-nothing models that can be partitioned horizontally aka sharding.
Now true, the latest and greatest databases such as CockroachDB go out of their way to try to do joins for you across partitions, even in an ACID manner, but then you have to use those. Better to avoid joins in the DB and do it in the app. You can then use a graph database instead of a relational database, going from O(log N) lookups to O(1) lookups for related data.
Oh and finally, the newest (and pretty cool) craze of BFT, Byzantine Fault Tolerance. You can't achieve that if you're doing joins across different publishers, because they're not supposed to be able to access each other's stuff "just like that".
Our ORM supports joins, even with multiple indexes, it even lets you define relationships and figures out the joins FOR YOU, but it is discouraged if you're building scalable sites.
By the way thank you for proving point #6 hehe1) Get the root record(s) from id(s)
2) See what related records it needs, combine them into a list of ids, partition list by shard
3) Ask each shard for the corresponding records
4) Repeat from 2 if necessary
5) Return this whole tree / graph to the user
Graph databases can do this in O(1) instead of O(log N) lookups.
Relational joins are just one way to achieve this, which can be made atomic in the ACID sense.
However, as you scale up your website, eg with 100,000,000 users, it would be silly to do massive joins. Google even says this in their docs now, for BigQuery.
Instead, design your systems from the beginnig to be as parallel as possible, if you think they will scale.
Look at the problems with Ethereum for example. Or Twitter fail whales of the past.
The relational model was designed to address the limitations of the network/graph database, especially to allow arbitrary (ad-hoc) querying and to decouple the physical storage from the logical model. But if you don't need all that, a graph database may be fine.
https://blog.acolyer.org/2017/07/07/do-we-need-specialized-g...
For the vast majority of use cases regular SQL databases blow graph databases out of the water. Unless you go for graph-specific algorithms like Shortest Path, and even then...
Most sites do not have to scale beyond this limitation (or can use database followers to throw a bit of money at the problem). Providing Google as an example is a bit exaggerated as almost nothing in the world has the scaling needs that google has.
It seems you either didn't use ORMs correctly or used a very poor one.
That's also true for code that one shouldn't use.
It's more stark that you're doing something crazy if you do a SELECT, instantiate objects from each returned row, then apply a filtering rule to that object, than if you're "just" operating on something that looks just like it's all local. Both to you, and to people reviewing your code.
I prefer working with an ORM, but it does take discipline to use an ORM properly; to make sure you know and use the facilities for building queries that return only the rows you want, and instantiate objects with only the columns you need.
I tend to agree with you that it will only be resolved by knowledge and competence, and throwing out ORMs won't solve that. But I also understand the frustration at how many ORM users basically seem to use it as a means to let them pretend there's no database server there, rather than as a tool to generate queries more easily.
More often than not I've seen developers fret over performance when it's not actually bothering users. This fetishization of performance seems to be a cultural thing in tech.
Conversely, data integrity, normalization and transactionality are usually given far too little weight.
Tell me where you've seen that, because I'd love to move there. From my experience, there is a common fetishization of non-performance. The "premature optimization" adage taken to its extreme - "we can buy more hardware", "developer time > machine time", "who cares about wasting electricity of millions of our users", etc.
I think you’re 100% right. ORMs are just tools, they can be used well or poorly. That’s up to the developer. As a former DBA, I also think they’re great tools. Aside from making the business logic easier to write, they also bring the business logic and the data closer together.
ala, this is why we have electron, php, and javascript. None of those things behave badly, but it's very easy to write something useable in those, but resource intensive, or insecure, or poorly maintainable.
I'm not dismissing ORMs - I use Sequel (the Ruby ORM) for almost all my database access. But I've also seen enough people use ORMs as an excuse to pretend they don't need to understand SQL or understand the database to understand why some people look at ORMs with suspicion.
A lot of code that is obviously bad when the database queries are plain for everyone to see are not so obviously bad when it's less clear if that method call translates to a database query or just extracts data locally.
Note that this is a general issue with this type of abstraction: It is a common complaint against transparent RPC wrappers as well that if they're too good at hiding that an object is remote it's easy for someone to carelessly cause massive amounts of unnecessary roundtrips.
On the flipside, I've seen a lot of people that can write efficient SQL queries, but then turn around and ruin their performance with a bespoke code model that inefficiently calls those SQL queries and poorly leaks the memory and database connections while doing so.
A good ORM makes it rather easy to fix an inefficient query of a fellow developer or previous self (almost all of the examples in this article are effectively one-line changes; many of the biggest performance gains I've made in ORM usage have been removing code and/or making it more readable), but fixing the mistakes of a bespoke model can be a huge challenge with a lot of surprises.
Then just turn on detailed SQL logging while you are coding and keep an eye on the queries generated by your ORM calls.
If you do this methodically for all your DAOs, then you should spot performance problems early when they can still be easily fixed.
I honestly don't get the fear some people have from SQL. If you can't do SQL good enough for 98% of use cases out there, you are not senior dev, not even experienced one. It's just one of those basic dev requirements that won't go away in next 50 years anyway.
Compilers will warn you if you do stupid common mistakes. Not all mistakes, but many of the stupid common ones. If you make stupid common mistakes with an ORM, why doesn't the ORM warn you?
(Greybeards may remember how perl's -W switch switch suddenly detected a frightful amount of performance problems using mostly simple tests. Almost 20 years ago now.)
E.g.
SELECT id FROM table [some conditions]
followed by a number of SELECT * FROM table WHERE id = ...
is one example of a anti-pattern that suggests that someone is doing an overly simplistic query followed by a loop. Similar with signs of triggering loading of related objects instead of a JOIN.Doing it on the emitted SQL would also make it quite easy to make it reasonably ORM agnostic - it doesn't need to be perfect, after all, so doing relatively crude pattern matching ought to be able to find at least the more basic problems.
That's overly generic.
The truth is that some tools encourage bad performance habits and a lax attitude about it whereas others don't. ORMs do.
This is no different than any other tool that makes things easy but with obvious limits. Proper decision making is still up to you. There really isn't much controversial here if you get past the whole "ORM" hype/hate cycle.
I don't believe in choices, people are flimsy. I believe in creating an environment that encourages good behavior.
...ok, people still make choices though, you're not controlling their minds. Perhaps educate your workforce so they make the right decisions by themselves, it's more effective and takes less effort than trying to coerce them through generalizations.
No, but as a PM you can dictate they don't use an ORM.
In Smalltalk, you would write such a query as:
(cars select: [ :car | car insurance endDate < now ]) size.
(There probably is a shortcut for just counting the elements, though admittedly you have to have each car for the query).So what Gemstone does is that when cars is a DB collection, it interprets the block and turns it into a query. So the code for iterating in-memory and the code for doing an optimized query is the same.
DB[:cars].where { insurance_end_date < Time.now }.count
(replacing "Time.now" with Sequel.function("NOW") if you want server side, or whatever date/time formatting function you want that'll return time/date in your desired format client side; client side it'll be evaluated once)I think most ORMs can do a reasonable job at this in languages where you have sufficient flexibility in overloading behaviour.
On top of the data/frameworks issue there's the one of data locality. Frameworks make the tradeoff decisions for you, but not always the right ones since the right tradeoffs are application-dependent. In one context using a cluster of commodity machines that do a mix of compute for your service and store and shuffle data around is a good idea. In another context a few beefy servers with the beef divided up between the app servers and database servers depending on where most of the compute most efficiently takes place is a better idea. (Though even with poor performance design, hardware is still good enough that a lot of things run just fine by having multiple services on one modest machine. That doesn't stop lots of programmers from reaching for frameworks that demand easily scaleable multi-machine systems even when there's no need and won't ever be need.)
In any case consistency matters: SQL strings are just one form of shipping code to the data to compute remotely instead of retrieving the data and computing locally. When you bring in an ORM, inconsistency is bound to follow with lots of computation that could have been in a query or stored procedure now living in the ORM framework and the application itself.
I can at least understand many people's annoyance with stored procedures though. Most DBs procedural extension languages suck badly enough that shipping JavaScript strings over to NoSQL stores can seem like a big improvement, and various levels of SQL standard conformance is all you typically get to use from the broader design pool of declarative languages.
select count(*) from cars where condition;
If programmers are doing what you say that they are then the programmers are the problem not the library.Furthermore most ORMs (certainly any that I would consider using!) allow escaped SQL to be used - and if the query gets much more complicated than a couple of where clauses I consider using this feature.
A decent ORM used well allows programmers to program faster on the simple stuff but still write fast code for the complex stuff.
You could replace everything you said with using SQL directly and just doing a select * from cars
I think the article alludes to much more important problems like querying in a loop, querying the same information again just because it's not in function/object scope
A lot of the mistakes I see are because developers don't learn SQL, use it wrong, then assume it's slow, then use NoSQL, then reenforce each other into believing NoSQL is saving their performance
Let's not make this a conversation on ORM or not, but how do we make sure developers understand how to properly make use of their relational databases, which for the last decade or so has been painted as old & cruddy compared to sexy NoSQL
As for performance, note that even when you know exactly what you're doing it is hard to wring good performance out of relational databases. This is why companies spend hundreds of thousands of dollars a year to hire "Database Administrators" to design and "tune" their database. (What other piece of software requires a highly paid, full time expert?) The most complicated databases are so complicated that we get "Junior DBAs" and "Senior DBAs" and multiple levels of certification.
And let's not forget what happens at extreme scales. At the lowest latencies and the largest data sets relational dbs are simply impossible to use. This might never be a problem for most businesses who are processing a few hundred messages a second (if that) but it should be on the mind of any startup that hopes to one day have millions of customers.
In the long run memory will become cheaper, faster and persistent. (Let us pray.) When that happens most everybody will abandon the big mess that are relational databases and just manipulate objects in memory as the gods intended. Then the relational model just becomes something to scare grand kids with.
It is possible i was overly harsh in my judgement, as assumed meant no real familiarity at all.
The places I've worked at that have written true Enterprise software, whether consulting, publicly traded healthcare companies, banks etc, have had extremely knowledgeable DBAs writing stored procedures for data access. The ORM usage was reserved for modifying the results of stored procedures or small things like account management. Nothing truly critical to the business was handled through an ORM directly pulling data from tables.
Just as a reminder, Hibernate is 17 years old :).
Also, modern ORM frameworks give ways to iterate on the data from the programmers language, but translated directly to SQL. See what [Diesel](http://diesel.rs/) does, for instance.
The paper talks about other issues, which comes from negligence or a lack of understanding of the performance costs of the queries. But this performance impact could aslo happen in a SQL-only environment (not using a framework won't help you to create indexes, or to add pagination to your queries).
As if the whole world was hosted on GitHub...
10 billion instructions to send a few kilobytes of data.
There's such a waste in back-end server design. If you're measuring response times are in seconds, and not in microseconds, you're doing something seriously wrong.
"It's taking too long on the server, let's offload everything to every single client out there."
Now we have two problems.
The months lost to the clueless developers massaging their horrible ORM code dominates in time.
In the long term properly factored code wins; and lightweight ORMs have a place. Heavy ORMs (Hibernate, old Entity Framework) should only be used if you absolutely must have specific features and are incapable of building that into a more logical place in your code.
Use a light weight ORM for simple CRUD actions (one with a small dependency graph). For performance critical create/update operations use stored procedures (just be careful with complex business logic; if you implement it, remember to test it). Use light weight ORM for mapping efficient SQL views to api/frontend. Use a workflow/async/job processing for anything which has to be long running or crosses system bounds (think credit card processing).
Does it matter? No - this screen will be accessed 30 times a day max. Better to have it work inefficiently and spend time on other tasks, than make it efficient - however much I want to.
0. https://www.doctrine-project.org/projects/doctrine-orm/en/2....
In my previous work we were generating over a 1000 queries for the front page (we were showing maybe 20-25 products with different options). Everything was done in nested loops, with new queries to the database each iteration.
The kid who had written that was apparently building his own framework when I looked up his webpage. Learn to use Django properly before you go building your own crap versions please.