Ruby on Rails performance

On 30-nov-2005, at 12:15, Helmut Sedding wrote:

Hi,
So I would not say this “flush();” is the only way, but it
can help a lot. Most browser engines are built on the fact that
most internet connections are slow. They show the page
incrementally. Just sad that rails can’t come up with this feature.

This has to do with the way Rails (more specifically -
ActionController) work. Granted, you are not flushing output
incrementally but:

a) you can set headers anytime
b) your post-filtering is much more capable because you still can
modify the response as a whole
c) your page rendering is reversible (i.e. it’s not working as a
stream) which eases things greatly

If you really want to do something like this you will need to
integrate your own stream-based templating that’s going to be
synchronized with the actual output of the app, you will lose
response post-processing etc. But if you really want that, hell -
just render to string and send it away using streaming.


Julian ‘Julik’ Tarkhanov
me at julik.nl

Julian ‘Julik’ Tarkhanov wrote:

This has to do with the way Rails (more specifically -
your own stream-based templating that’s going to be synchronized with
the actual output of the app, you will lose response post-processing
etc. But if you really want that, hell - just render to string and send
it away using streaming.

ActionController::Streaming

Recalling the thread “Newbie question about fastcgi scaling” back in
August, I don’t think that this will work with FastCGI, either with
Apache, where the documentation for mod_cgi

http://www.fastcgi.com/mod_fastcgi/docs/mod_fastcgi.html

says that:

“FastCGI application output is buffered by default. This is not the case
for CGI scripts (under Apache 1.3). To override the default behavior,
use the -flush option (not available for dynamic applications).”

or with lighttpd, where Ben M. (to support the discussion in the
thread) did a test that indicated the same was happening.

If this was not the case, it would appear to be trivial to mount a
denial of service attack on a Rails application, by opening many
connections and reading content from them very slowly. With static
allocation of FastCGI processes (the favoured approach) the application
would stop handling requests as soon as all the processes were busy.
With dynamic allocation, things would slow down dramatically as the web
server spawned new processes (Rails has a long startup time), and memory
would eventually be exhausted (each FastCGI process uses tens of MB).

Thinking about this again now, I have the impression that serving large
(many MB) documents to unauthenticated users via send_data introduces
another hazard - if the web server is buffering the whole content while
the client downloads it, it would be easy to use many slow connections
to force memory exhaustion.

Has anyone looked into this?

regards

Justin

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


-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

On Dec 10, 2005, at 2:30 AM, Dan K. wrote:

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?

Please do implement. Modifying Action Pack to stream output is
straightforward. Its supporting libs (CGI, ERB, FastCGI) will take
more work, but could use the exercise.

Best,
jeremy
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.2 (Darwin)

iD8DBQFDmrYlAQHALep9HFYRAlIVAKCkuVoRXlBVvaKYqZju8HwykaXt0ACdGHew
keEBjJCPHPkUJP5VNWdWFqU=
=U8H2
-----END PGP SIGNATURE-----

If your rails apps are slow, check to see if you are running Apache web
server. I’ve found that since switching to Lighttpd, my apps are
significantly faster, I have noticed a HUGE difference.

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

On Dec 10, 2005, at 5:35 AM, Izidor J. wrote:

straightforward. Its supporting libs (CGI, ERB, FastCGI) will
for procedural preprocessors, such as PHP. But Rails is not
preprocessor. It is true object-oriented MVC framework, and
imposing streaming would cause much complexity and/or feature-removal.

Streaming could be a nice and interesting hack, but with all the
possible bugs it would introduce and nice features it would
prevent, I would really prefer if it would be developed as a
plugin, so it would not influence Rails features and software
quality…

Agreed; however, it’d require some healthy examination of Action Pack
and the libraries it relies on. If someone’s gung-ho for the
feature, I welcome their effort.

Best,
jeremy
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.2 (Darwin)

iD8DBQFDm0hcAQHALep9HFYRAtoIAJ0ZBflw5+kabSKTir0Ursbne4UKfACeMqCA
LxEiehazpvF84fllOmZlonM=
=lCI/
-----END PGP SIGNATURE-----

On Dec 10, 2005, at 12:04 PM, Jeremy K. wrote:

chunk of the view is rendered?

Please do implement. Modifying Action Pack to stream output is
straightforward. Its supporting libs (CGI, ERB, FastCGI) will take
more work, but could use the exercise.

I really like the way Rails does layouts. This is the first sane
approach to layouts I have seen. Especially the way views can define
variables such @page_title, which are then used in layout. This means
that views (layout contents) must be rendered first, and only after
them the final layout is rendered.

How would you do layouts without buffering? Is there any point having
streaming-output-data if most of the processing (know of any
application that does not use layouts?) needs to be buffered anyway ?

Streaming really prevents a lot of nice current and future features,
which depend on out-of-order modifications, and has somewhat dubious
benefits (what kind of monster pages you need to have to see
significant benefits?). Streaming is natural approach for procedural
preprocessors, such as PHP. But Rails is not preprocessor. It is true
object-oriented MVC framework, and imposing streaming would cause
much complexity and/or feature-removal.

Streaming could be a nice and interesting hack, but with all the
possible bugs it would introduce and nice features it would prevent,
I would really prefer if it would be developed as a plugin, so it
would not influence Rails features and software quality…

Of course, all this is IMHO…

izidor