5 ms·
I can't help but feel this problem is indicative of incidental complexity in how we develop web applications. Not saying the PHP glory days were better, but the
by acjohnson55 4y ago
I can't help but feel this problem is indicative of incidental complexity in how we develop web applications. Not saying the PHP glory days were better, but there's something to be said for removing the layers of abstraction between the data and the presentation. Make the database query using SQL directly, and then inject the results into the HTML template to be delivered to the browser. Obviously, there were many issues here, like how easy it was to leave applications open to SQL injection attacks.
But it has been interesting to see the tide turn back towards server-side rendering, relying on partial DOM replacement for client-side updates. For web apps that don't have massive numbers of UI states (like a document editor), it seems like people are rethinking the wisdom thick client-side JavaScript applications, which seem to be one of the main motivators for REST API layers, and the need to efficiently fulfill N+1 queries.
Although, I do remember dealing with the N+1 problem when doing Django server-side apps more than a decade ago, before the dominance of client-side apps. I guess it was more the rise of MVC architecture and the active record pattern (https://en.wikipedia.org/wiki/Active_record_pattern https://en.wikipedia.org/wiki/Active_record_pattern) that brought the N+1 problem, more so than client-side apps.
- Arch-TK 4y agoThis is specifically just a problem with all ORMs. They attempt to solve the object-relational mismatch and fail because these two concepts (sets of tuples and graphs) are completely orthogonal.
- jbverschoor 4y agoNo it's not.. you can and will easily have the same problem with SQL if you're not fetching beforehand.. I bet wordpress (or many plugins) is/are so slow exactly because of this. ORMs will even make it easier to write more efficient queries is many cases.
- hinkley 4y agoThere are too many questions to make any absolute statements. You hint at a couple here. Specialization of requests reduces data transport, but increases testing surface area and drastically reduces caching options - including within the database. Years ago we discovered that our bottleneck in Oracle was being caused by query cache evictions (worse, in 9i only queries currently in the cache could run, so new queries were blocking in the query planner. I'm not sure if this was ever fixed.) One of the important uses of etags within your infrastructure is to allow you to incorporate data manipulation into your caches. If your functions are deterministic you can store the results of a calculation, instead of the source data for that calculation. Cache hits then don't just save bandwidth, they also save computation, which reduces the sequential parts of the response, fighting Amdahl's Law.
- Arch-TK 4y agoIf you intentionally write poor code with plain SQL you will have the same problem as the auto-generated poor code of a lot of ORMs? Yes? I don't think Wordpress is relevant here in any way. If the code is poorly written and has N+1 queries problems then it is either poorly written or is trying to half-ass an ORM. Lastly, I would like a lot of citations to prove to me that ORMs produce more efficient queries. What produces more efficient queries in all my experience is a good understanding of relational databases, better query planners and better database design.
- chc 4y agoThis was very much a problem for WordPress back in its "glory days." I had to go in and optimize a lot of stuff to get our website's performance to be sub-5s. (I haven't worked with WordPress in years, so I can't comment on its current state, but I wouldn't be surprised if this is still a thing.)
- fabian2k 4y agoThis is not a problem with all ORMs. This is caused by lazy loading the related data when it is accessed, which is not how all ORMs work. If your ORM requires you to define the included related data explicitly, you won't have this particular problem.
- Arch-TK 4y agoAnd an ORM which eagerly loads all related data before it is accessed will have a different but related problem. I'm actually not aware of any ORM which tries to do more than trivial guessing of when data needs to be eagerly loaded. There is no easy way of solving this problem within an ORM without either causing a different problem or without leaking the abstraction a lot and requiring lots of additional complexity to annotate what you want the ORM to actually do.
- Lapsa 4y agoreal fun begins when random Cartesian products shits up all your RAM
- fabian2k 4y agoIn EF Core for example you have to specify which relations to include, either using ".Include()" or transforming the entire query with a "Select()". In the latter case this works pretty much automatically as long as you stay within the kinds of expressions EF Core can translate to SQL. There is a different performance problem lurking with this design though. You have to decide whether to tell EF Core to do a query per relation or everything in one query. The single query is very efficient for many simple cases and ensures consistency, but it can fail spectacularly if you have a very large number of relations. This is a pretty significant footgun, but if you really don't want to be bothered with this you can just always use split queries. ORMs can't really abstract over all performance considerations. They are convenient, but they can't fully hide what the database is doing under the hood.
- deleted 4y ago[deleted]
- hgsgm 4y agoTuples are Objects Graphs are Relations. ORM (or GTM if you like) helps you navigate your Graph of Tuples.
- Arch-TK 4y agoSets of tuples and graphs of objects are fundamentally incompatible and I have not found a single ORM which didn't completely fail to pave over this fundamental incompatibility. The object-relational impedance mismatch is not just something I came up with to shit on ORM. It is a well understood and widely known phenomenon which ORM users often like to insist on pretending doesn't exist. To claim that sets of tuples are the same as object graphs is like claiming that you have solved the halting problem. Feel free to claim that the impedance mismatch is small enough that a sufficiently advanced ORM might successfully abstract it away, I don't think this claim is defensible either but on the other hand claiming that the OR impedance mismatch doesn't exist makes me think you have no idea what problem an ORM actually solves. It certainly makes me wonder how you would justify the size and complexity of every ORM out there.
- 8note 4y agoIsn't an adjacency list a set of 2-ples? That's a set of tuples that defines a graph
- Arch-TK 4y agoTry re-creating a relational database in a graph database or vice versa by applying this approach and report back. I promise you that if you put a graph in a SQL database or vice versa you will end up with absolutely abysmal performance unless you explicitly go out of your way to special case everything into being performant.
- nightski 4y agoThe problem isn't the mismatch of the data structures. Imo it comes down to lazy loading and that's about it. Using lazy loading to navigate relationships is where the impedance mismatch manifests and it is completely avoidable (basically a do at your own risk, danger ahead type of scenario).
- avereveard 4y agoproper orm allow you to specify how to process relationships, best one on a per query basis and not on a per model basis. then they aggregate the table resulting from the join presenting a deduplicated view of the root object and all the related one fetched.
- Arch-TK 4y agoThis is a major abstraction leak and is very rarely as simple as you describe it. The only time I've seen it be this simple is with people who design their databases entirely around an ORM and in that case they're already leaving a large amount of performance on the table which you can no longer recover by reaching behind the scenes to help the ORM figure out what it needs to do.
- ramchip 4y agoI really like the compromise taken by Ecto (in Elixir): higher level than writing SQL directly, but without lazy loading, callbacks, and other features that make it difficult to see what a piece of code is really doing.
- serverholic 4y agoAt my first job out of school we didn't use an ORM and just queried with raw SQL. I had never heard of the N+1 query problem because it wasn't something we really had to worry about. After that I joined a startup that used rails and I had quite the education in ORMs and N+1 queries. The ORM felt quite restricting and I felt a lot less confident in my code.
- acdha 4y agoI saw this tons back then, too. Even back in the paleo-web you’d see people writing nested for loops, sometimes obscured by functions or a class structure but sometimes just the adjacent HTML. As you noted, this is one of the reasons why client-side JavaScript often falls far short of the envisioned benefits. In both cases, however, I would suggest that while the trend is real and should inform your architecture the most important thing is to routinely use your monitoring. Different apps have different performance challenges but they all need to be observable and I’ve seen so many times where people wasted tons of time and resources shooting in the dark because they didn’t have granular monitoring or thought it was too hard. One of the best example I had was a while back when I inherited a Django codebase which was too slow. One of the developers had spent a couple days rewriting everything in Jinja2 because “Django is slow”, introducing the bugs you’d expect and leading to lots of new custom code to maintain (such as 3 versions of the human size formatting function). Performance didn’t budge. Six months later, I saw the MySQL query counter spiraling up on every page view, installed Django Debug Toolbar, looked at the queries, and spent an afternoon reverting all of the templates and fixing the N+1 queries which had always been the problem. That was tens of thousands of lines of code churn all wasted for what was eventually maybe 50 lines changed against the original codebase. One thing I introduced which worked well was a test hook which failed based on the query counts for a view. That caught most N+1 issues and is conveniently easy to implement with almost any model.
- mireq2 4y agoI spent couple days rewriting everything from Django to Jinja2 and it was really big difference with 10x speedup (from 500ms for invoice list to 50ms). It was big e-commerce system with every ORM query optimized, selecting only required fields, prefetching related relations or selecting related. Django templates are really really slow and hard to debug. Rewrite was really great decision in this case.
- acdha 4y agoI’ve never seen a difference that pronounced (I’m guessing that you had a lot of template logic?) but that sounds like you did it the right way: based on profiling.
- pmontra 4y ago> Make the database query using SQL directly, and then inject the results into the HTML template to be delivered to the browser. This is what I do for the most complex queries, where translating them to the ORM would be a time consuming pain. What I do normally is think in SQL and write in the ORM language. After all the API of the database is SQL, not the ORM, and I already know SQL. In this way it's obvious that you have to write includes(:comments), because it's a join, and it's obvious that the original object.votes.count would generate one query each time it is called. I write the separate queries and compose the results instead. I'm not using BatchLoader or other gems. Using directly the ORM is enough. If this query turns out to be important for the performances of the application, I'll think about a way to write it with a single query in SQL to make the database do all the work. Then translate it in ActiveRecord or leave it in SQL if it takes too long. Sometimes it's not obvious how to do it, a problem common to all ORMs of all languages. Edit: somebody called saila gave an example of such a query in the comments one hour before I wrote my reply.
- imtringued 4y agoA lot of complex computed properties can easily be made efficient by having "WITH" statements. The with query starts with the ID of the object to which the computed property is attached to. So in this case you would do the same thing as the author and write WITH comment_vote_count as select comment_id, COUNT(vote_id) from comment left join vote ... The ORM would just treat this like a normal table and do a join based on the comment ID except the ORM provides a convenient API to do this, which it currently does not...
- brightball 4y agoThere's a Ruby gem called Bullet that identifies and warns developers about N+1 problems. You can also have it fail tests if detected. I don't know if the approach is possible with every ORM or if it's just leveraging some Ruby perks, but I can't think of a good reason why you wouldn't use the equivalent everywhere. https://github.com/flyerhzm/bullet https://github.com/flyerhzm/bullet