Hi All,
I agree with others on this thread that there should be a way for a
view to render
the response incrementally, rather than the current behavior of
buffering the entire
response and sending it in a single step.
I disagree that the current approach results in higher throughput
than incremental
rendering would.
With the current approach you’re buffering the entire response in
memory for every request.
If you are trying to handle a large number of clients and you start
swapping because memory
is used up, your performance is going to suffer. Even if you don’t
swap, the server still
has to use more memory than necessary to keep track of everything and
that certainly isn’t
going to help performance any.
It takes more effort to keep track of the response body as you are
rendering it, than
just sending it out the pipe. Not just in terms of memory, but in
terms of programming
effort. In most cases its easier to render each chunk, send it to
the network, and
forget it.
Buffering at a lower level is useful, but let the webserver software
and the OS take
care of that. As a general rule, for maximum throughput its better
to send whatever
information you have out the network than holding onto it and sending
it later.
Think about what would happen if each intermediary in the chain from
the app to
the client waited until the entire response was received and buffered
in memory
and only then handed it off to the next point in the chain. For
example imagine
if at each of the following steps the entire response was received,
buffered, and
only passed onto the next chain until all of it had been received
in full:
- Rails Application
- SCGI/FastCGI
- Back-end Web server
- Front-end Reverse proxy server
- Cache module on proxy server
- GZIP Compression module on proxy server
- SSL/TLS encryption module on proxy server
- Web Proxy at client network gateway
- Web Client (3 steps: SSL/TLS decryption, GZIP decompression,
Rendering)
Of course, I’ve taken this example to the extreme just to illustrate
a point that
if each step completely buffered the response before passing it on to
the next step,
a full round trip request would take a really long time. In most
cases there would
likely not be this many steps… but even with just 2 or 3 of them
doing complete
buffering, its easy to see how latency AND throughput would be
negatively affected.
Just be glad that most of these steps do not do things serially, they
are designed
to handle information in chunks, processing them, and yes sometimes
even doing
some buffering – but only the smallest possible buffer that will
allow them to
do the job with reasonable performance.
In this thread, one of the approaches suggested send_data as a
solution. This would
work, but why the need to be so explicit? Why not have the ability
to pass in
a flag to render() that says you want the output flushed
incrementally as each
chunk of the view is rendered? This would not only cut down memory
usage, but
would reduce the TTFB (Time to First Byte) so that the clients can
begin rendering
the output immediately. Views could ignore the flag if they didn’t
have the
capability of rendering in chunks, but for those that do, there would
be a nice
performance advantage – I see no reason that existing Views couldn’t be
updated in the future to accommodate incremental rendering.
I’d gladly give up the ability to use after_filter for this
capability… even then,
it should be possible to create something like an after_stream_filter
that works
on the stream itself, allowing you to do transformations on the
output if you
really wanted to. But like I said, this should be an option, for
people who
rely on after_filter, they would still need the in-memory buffering.
Sorry for the length of this post, but I’m coming in late and I
wanted to address
several points in a single message than break it across multiple
replies.
–
Thanks,
Dan
Dan K. Email: [email protected]
Autopilot Marketing Inc. Phone: 1 (604) 820-0212
Web: http://www.autopilotmarketing.com