7 ms·
Automatic Flushing: The Rails 3.1 Plan
- zbanks 16y agoCool idea. It's one of those "cheap" speed boosts which are always nice to find/have. It'd be nice to see this implemented in Django as well...
- deleted 16y ago[deleted]
- aaronblohowiak 16y agoThis encourages having SQL queries initiated by the view, after the header has rendered. This seems antithetical to MVC to me.
- jtgeibel 16y agoIn some cases the query is lazy loaded with a call to .each or something similar in the view already. In other cases, the query is run in the controller before anything is rendered. I think the main goal is to improve client side performance (by downloading scripts and other referenced files sooner) and see the most benefit on pages that load multiple models, for instance a sidebar showing popular posts and recent activity.
- aaronblohowiak 16y agolazy loading is bad for overall performance, though. Retrieving all of the models with eager-loading will avoid exploding query counts and lower the total time to render a page (and lower the load on your servers.)
- dasil003 16y agoHe's not talking about eager loading associations. It's AR 3.0 backed by arel that allows ALL queries to be lazily loaded, as in, they are declared in the controller cleanly, but they only run in the view (and furthermore they only run if the view needs them). Including assocations is an orthogonal issue. As far as I know they get the same benefit out of the box in Rails 3. For example, I think you can say: @posts = Post.where(:published => true).include(:comments) And it will still load it all eagerly, but it won't do it until you actually iterate over @posts.
- aaronblohowiak 16y agoAh, yes. I see what you are saying now. I was unaware that the abstract relational algebra was also able to defer joins. That is really an impressive bit of kit. While this does satisfy the objection to association loading, you still have the general problem of delaying the flushing until after all controller processing has completed. for nontrivial applications, this may indeed take quite some time (talking with disparate backends for a SOA, for example.)
- dasil003 16y agoWell what would you prefer? A complete re-architecture of Rails to support SOAs? Declaring head and body renders separately? They did the hard work for ActiveRecord. Most Rails apps still use ActiveRecord and have a relatively straightforward architecture. If you want the same benefit for your SOA architecture, it's not that hard, just build a middleware layer that defers the queries like arel does. Even better, follow the facebook approach, render a page shell and load the content via AJAX. That will ultimately get you the best performance by far, allowing faster response times, piecemeal loading, maximal offloading of processing to the client side, and opens up interesting avenues for caching possibilities.
- aaronblohowiak 16y agoThe ability (though of course not the requirement) to declare head and body renders separately would allow you to return the header before your processing, regardless of wether that processing is more intensive in the controller or the main body view. Deferring only works if having a stub of a request is sufficient to proceed to the next step in the execution path. Unless you are going to implement your own conditionals (which, admittedly, is doable in ruby,) then you are going to force the evaluation of the request as soon as you want to use it to make a decision.
- collint 16y agoNot really, in Rails, you might have this controller code: @things = Thing.where(:it => "good") And this view code: <% for thing in @things %> <%= thing.name %> <% end %> But the SQL query doesn't fire in the controller. It gets kicked in the view when you "for x in y" Concerns still wonderfully separated.
- aaronblohowiak 16y agoNot really, in Rails, you might have to do more than just retrieve some models. For instance, you might have to load up the current user, grab some stuff from memcache, check with your SSO system to validate the session, and then retrieve the data pertinent to the current request. Then, you might have to make some data modifications (which will create transactions and hit your db.) Finally, the view rendering can begin. In only the trivial cases can you defer the actual SQL queries from being performed before the view is rendered.
- dasil003 16y agoI don't get your point. Just because you may have to do some things in a before_filter or whatever doesn't mean you need to do all things there. As far as accessing stuff out of memcached is concerned, there's no reason that can't also be deferred to the view. Claiming that lazy loaded queries is only a benefit for "trival cases" is a strawman. It's a hugely powerful functionality for ActiveRecord that you can utilize in many ways, and would be very hard to implement without low level support. Cached attributes can often easily be made available via concise single model methods that operate transparently without the controller OR the view needing to know they are cache-backed. Plus, even if you are loading stuff out of memcached in the controller, it's going to be fast, because that's the whole point of memcached. ActiveRecord meanwhile, normally takes a huge percentage of rendering time. Being able to defer those queries while still allowing the controller to declare them is actually a huge combination of performance flexibility and separation of concerns. Previously, if you wanted to defer them "cleanly", you'd have to create model methods, but even there you would have to pass params through somehow or generally do something uglier than what you have to do now.
- fizx 16y agoThere's other schools besides MVC, including "component-oriented."
- Twisol 16y agoSo how does this work with Rack? Unless I'm mistaken, you have to return the body all at once, which entirely negates the benefits here. I don't see Rails mandating that an asynchronous server be used (i.e. Thin, Mongrel2, etc.), so I'm rather confused.
- judofyr 16y agoIn Rack you need to return a body which responds to #each (which yields strings); it doesn't need to return the body all at once: class Dummy def initialize(controller) @controller = controller end def each @controller.render.each { |part| yield part } end end @body = Dummy.new(self)
- deleted 16y ago[deleted]
- Twisol 16y agoAaaah, and the work is done within #each and not within #call. I see. The only issue is if you have a middleware that modifies the output, because unless you're careful and/or you're doing something extremely minor near the start of the page, it'll all be processed in the middleware rather than the server. So the server still gets it all in one piece, and so does the client.
- judofyr 16y agoI’ve been doing some research for this earlier, and my conclusion was: This is very hard, if not impossible, to implement automatically. The main problem is that it’s impossible to handle exceptions correctly without making the whole stack aware of it. Currently, when an exception occurs, the system can simply change the response (since the response hasn’t been sent to the client yet, but is only buffered inside the system). With this approach, a response can be in x different states: before flushing, after the 1st flushing, … and after the xth flushing. And after the 1st flushing, the status, headers and some content has been sent to the client. Imagine that something raises an exception after the 1st flushing. Then a 200 status has already been sent, togeher with some headers and some content. First of all, the system has to make sure the HTML is valid and at least give the user some feedback. It’s not impossible, but still a quite hard problem (because ERB doesn’t give us any hint of where tags are open/closed). The system also need to take care of all the x different state and return correct HTML in all of them. Another issue is that we’re actually sending an error page with a 200 status. This means that the response is cacheable with whatever caching rules you decied earlier in the controller (before you knew that an error will occur). Suddenly you have your 500.html cached all over the placed, at the client-side, in your reverse proxy and everywhere. Let’s not forget that exceptions don’t always render the error page, but do other things as well. For instance, sometimes an exception is raised to tell the system that the user needs to be authenticated or doesn’t have permission to do something. These are often implemented as Rack middlewares, but with automatic flushing they also need to take care of each x states. And if it for instance needs to redirect the user, it can’t change the status/headers to a 302/Location if it’s already in the 1st state, and therefore needs to inject a <script>window.location=’foo’</script> in a cacheable 200 response. Of course, the views shouldn’t really raise any exceptions because it should be dumb. However, in Rails it’s very usual in Rails to defer the expensive method calls to the view. The controllers sets everything up, but it’s not until it needs to be rendered that it’s actually called. This increases the possibilty that an exception is raised in the rendering phrase. Maybe I’m just not smart enough, but I just can’t come up with a way to tackle all of these problems (completely automated) without requiring any changes in the app.
- raggi 16y agoIt does require changes in the app, but app authors who need this kind of performance benefit will be willing to accept that hit. The solution is far better than the alternatives: - Allowing users to flush manually (people screw this up real bad) - Changing the rack spec (allowing for #each on the body to be lazily yielded, and terminating on nil or the like) - Moving to an always async stack (totally kills most users) Yes, there are plenty of issues with this, and I agree with your concern, but it is also something which can have a marked effect on performance for users. It's also worth noting that a well componentised partial can render an error in-place of the partial itself, for example, rendering a page that contains the whole layout, and a single red box of errors (say a render of the _new partial can be added to the buffer after a _create fails, instead of rendering the success box). Yes, that requires some refactoring of the application (rather than using for example, the standard 302 approach). It's also worth noting that a larger class of applications that would find this actually useful should generally have reasonable test coverage and code maturity. Whilst this isn't always the case, we also don't protect users from eval, and other evil tools, in ruby or rails.
- briandoll 16y agoFrom Yehuda on twitter: "BTW: Those who have brought up issues with exceptions/status codes re: flushing, you're right, but it's not specific to the fiber solution"