Hacker News new | ask | show | jobs
by aaronblohowiak 5758 days ago
This encourages having SQL queries initiated by the view, after the header has rendered. This seems antithetical to MVC to me.
3 comments

Not 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.

Not 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.

I 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.

These are my assumptions:

1.The performance goal is to return http & html headers as quickly as possible.

2. Business logic belongs in the models, as triggered by method invocations from the controller.

3. A good deal of time is spent on business logic and request handling (authentication / set-up / before_filter stuff.) Often, this requires network I/O to backend systems (or databases.) While some data retrieval is necessary only to render the view (and can thus be deferred gracefully,) other times your application logic depends the completion of these lengthy requests in order to complete the desired state modification.

4. Since we want to return the headers as quickly as possible, we can either a) figure out how to send the headers before the controller or b) figure out how to delay the processing until after the controller.

---

I think that that a) is better than b).

I like views that exclusively take data and format it for output. I like having the core business logic in the models, and I like having the system guards and request setup concentrated in the controllers. This way, I can have an exhaustive understanding of the tasks performed by an action without having to read through all of the views and their partials.

If we hide some of the processing within model actions and call those model actions from the view, then I no longer can assume that all major processing / I/O has happened by simply reading the controller and the model methods it invokes. Instead, I also have to read all of the views.

This violates the expectations I have about processing times and MVC.

If instead, we could detect the requested format and return immediately with the headers, then we could perform the overwhelming majority of the processing while the browser is busy downloading static assets.

There definitely is something that bothers me about using fibers in order to hide an important optimization detail from the developer, and I agree it would be better to have explicit control.

However I see the core team's point that they don't want to completely overhaul the API in a way that's going to break arguably a majority of existing apps, and at the same require the developer to pay attention to more details than may be necessary.

However on the topic of breaking expectations about processing times, I couldn't disagree more. To me, the main benefit of MVC (or any architecture choice) is in isolating responsibilities, not isolating code execution. It's not a big mental leap to conceive of a ActiveRecord relation as declaring some data that's needed that may or may not be actually queried, depending on if the view needs it. This helps your MVC separation, because it means you don't need to extract view logic from your template just to make sure the controller doesn't load something unnecessarily. Lazy execution of this form is a very powerful optimization technique, just ask any Haskeller.

Not really, in Rails, you'd do that stuff in a before filter.

(not really, boom, hat trick: http://img.skitch.com/20100908-tpruqq1pqsw3rsyh2ggn5qb7ac.pn...)

;)

I'll let the meme die now. Doing it in a before filter would have the same effect -- unnecessarily increasing the latency before the headers are sent to the web browser. Wether the time is spent in a before filter or a controller action, the key is that the time is being spent before the headers are delivered to the browser. If you are trying to minimize the time it takes to return the header, then you'll want to do that before almost anything else.

You can't send the headers unless you know if the person is authorized or not (and other common things you check in the before filters). So yes, this stuff would have to be checked before rendering a view.
I am willing to errantly send a 200 with a js redirect upon auth fail if it means faster load times.
In 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.
lazy 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.)
He'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.

Ah, 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.)

Well 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.
The 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.

There's other schools besides MVC, including "component-oriented."