locked_user beautyfulman has asked for the wisdom of the Perl Monks concerning the following question:

Replies are listed 'Best First'.
Re: PSGI/Plack unsatisfactory performance
by Your Mother (Archbishop) on Dec 07, 2021 at 05:35 UTC

    I’m one of the ones, probably the one you’re calling out, who has suggested uwsgi repeatedly. Stability is not free but it’s the only professional answer. Starman was close to useless for me in the *multiple* times I fought to use it; I prefer Perl solutions. I think I even filed a bug report on core dumps related to some bad hardcoded port stuff once and never got an answer. Never heard of, nor tried, the other options but they also appear unstable. unit from the nginx people might be worth trying, I still haven’t.

    “Hello world” tests are essentially useless in real world terms, unless your app has no templates, no DB/Model layers, and no processing of any kind. Then it works, but at that point, it’s a static app and every raw webserver will beat it easily.

    I am skeptical that even with mod_php, or whatever is the standard now, you are seeing better than 80x improved performance. It’s probably some sort of webserver caching since the page/request never changes. mod_perl, as suggested already, will be faster but it’s a mistake, and a dead end likely to get pulled out from under you in the future, in my view.

            That’s fantastic. If you have the time and patience, I encourage you to write-up your approach in as much detail as possible to post here. Deployment stuff is possibly the hardest part—outside security—of getting web apps right and it sounds like you’re hitting on winning combinations.

      Re: PSGI/Plack unsatisfactory performance
      by NERDVANA (Priest) on Dec 07, 2021 at 22:19 UTC

        If you get *any* connections dropped, something has gone wrong. You have 100 concurrent requests, so any server with a listen() backlock of at least 100 should serve every request without dropping any.

        I suspect the "something wrong" is that you ran with the default "max requests", which for Starman is 1000. This means after 1000 pages served, it will kill and start a new worker. While the worker is restarting, perhaps that loses a connection?

        Jmeter looks like an unpleasant pile of Java and GUI with a very long manual, so I'll make some examples with 'ab' instead.

        My laptop is a Intel(R) Core(TM) i7-8550U CPU @ 1.80GHz, 4 core / 8 thread.
        Here's my app:

        $ perl -v This is perl 5, version 34, subversion 0 (v5.34.0) built for x86_64-li +nux $ echo "sub { [ 200, [], ['OK'] ] }" > app.psgi

        I'll try Gazelle first:

        $ man Gazelle | head Gazelle(3) User Contributed Perl Documentation Gazell +e(3) NAME Gazelle - a Preforked Plack Handler for performance freaks SYNOPSIS $ plackup -s Gazelle --port 5003 --max-reqs-per-child 50000 + \ -E production -a app.psgi $ plackup -s Gazelle --port 5003 --max-reqs-per-child 50000 -E product +ion -a app.psgi & $ ab -n 10000 -c 100 http://localhost:5003/ (snip) Document Path: / Document Length: 2 bytes Concurrency Level: 100 Time taken for tests: 0.430 seconds Complete requests: 10000 Failed requests: 0 Total transferred: 940000 bytes HTML transferred: 20000 bytes Requests per second: 23277.36 [#/sec] (mean) Time per request: 4.296 [ms] (mean) Time per request: 0.043 [ms] (mean, across all concurrent reques +ts) Transfer rate: 2136.79 [Kbytes/sec] received

        So, mine is running 4x faster with no dropped requests. On a laptop.

        Now Starman:

        $ starman --workers 8 --max-requests 1000000 & $ ab -n 10000 -c 100 http://localhost:5000/ (snip) Concurrency Level: 100 Time taken for tests: 0.585 seconds Complete requests: 10000 Failed requests: 0 Total transferred: 770000 bytes HTML transferred: 20000 bytes Requests per second: 17102.06 [#/sec] (mean) Time per request: 5.847 [ms] (mean) Time per request: 0.058 [ms] (mean, across all concurrent reques +ts) Transfer rate: 1285.99 [Kbytes/sec] received

        Now Feersum:

        $ plackup -s Feersum --pre-fork=8 --access-log=/dev/null app.psgi & $ ab -n 10000 -c 100 http://localhost:5000/ (snip) Concurrency Level: 100 Time taken for tests: 0.542 seconds Complete requests: 10000 Failed requests: 0 Total transferred: 400000 bytes HTML transferred: 20000 bytes Requests per second: 18437.56 [#/sec] (mean) Time per request: 5.424 [ms] (mean) Time per request: 0.054 [ms] (mean, across all concurrent reques +ts) Transfer rate: 720.22 [Kbytes/sec] received

        I think all of these are performing professionally for me, when you consider that the request overhead is extremely small vs. the time for a database request. All of these are intended to be combined with a front-end like apache or nginx, which is what you would use to serve static content. In fact, most of them warn you they *need* to be combined with a frontend to get safe HTTP sanity checking. If perl is only used for the dynamic content, the performance overhead of the app server is even less important, because the database will dominate.

        Update:
        As a followup, I ran it on a small-but-fast server and got 31810/sec for Gazelle, on 6 cores. But when I went to a different somewhat-slower server with 24 cores, I got only 8000-9000/sec no matter how many cores I asked it to use or which server module I plugged into plackup. The second server is using the official docker image for Perl 5.34. I really don't know what to make of these results.

            Except php is the short bus of programming languages, so having it run fast doesn't really interest me. PHP gets to make optimizations that perl doesn't, like storing pointers directly to the functions instead of looking them up from a symbol table, but then that also ties your hands when you want to do things like override a function or wrap it with a method modifier, which are easy in perl and impossible in php. If there was any one language I wish I could eliminate from my life, it would be php, but wordpress/drupal popularity makes that hard. It's even faster these days because Facebook pours money into it; its a shame the r&d doesn't go toward a more deserving language.

            I feel like there is something going wrong with the "accept" loops in Gazelle. I checked on the implementation, and it looks very much like it forks, and then each worker calls accept() on the same listening socket, and then they *should* be able to receive new connections in parallel. Yet, on the slower server, the pool of workers were unable to beat the performance of a single worker. I'm aware of the "stampede" effect where a listen socket becoming readable wakes all the workers instead of just one, but with Gazelle's loop implemented in C, that should still be low enough overhead that they should be able to run in parallel even for a tiny request. I'd be interested to see it if you or anyone else decide to chase this down to some microsecond-level traces. It's a lot of effort though, to shave off a mere 3-5ms per request. It wouldn't make any difference to any of the apps I maintain.

      Re: PSGI/Plack unsatisfactory performance
      by trwww (Priest) on Dec 07, 2021 at 01:09 UTC

        mod_perl

        It makes me sad people aren't using it.

        I just did a hello world test on my system, a bare apache with mod_php 7, and then that same apache with mod_perl loaded instead. I got 1,500 requests per second more with mod_perl

        The following was performed on a relatively low traffic Intel E3-1230 v6 with 64gb of RAM

        mod_php for a baseline:

        config:

        ServerRoot "/opt/apps/apache" PidFile /home/me/modapache/apache/logs/modphp.pid Listen 11208 LoadModule authn_file_module modules/mod_authn_file.so LoadModule authn_core_module modules/mod_authn_core.so LoadModule authz_host_module modules/mod_authz_host.so LoadModule authz_groupfile_module modules/mod_authz_groupfile.so LoadModule authz_user_module modules/mod_authz_user.so LoadModule authz_core_module modules/mod_authz_core.so LoadModule access_compat_module modules/mod_access_compat.so LoadModule auth_basic_module modules/mod_auth_basic.so LoadModule reqtimeout_module modules/mod_reqtimeout.so LoadModule filter_module modules/mod_filter.so LoadModule mime_module modules/mod_mime.so LoadModule log_config_module modules/mod_log_config.so LoadModule env_module modules/mod_env.so LoadModule headers_module modules/mod_headers.so LoadModule setenvif_module modules/mod_setenvif.so LoadModule version_module modules/mod_version.so LoadModule unixd_module modules/mod_unixd.so LoadModule status_module modules/mod_status.so LoadModule autoindex_module modules/mod_autoindex.so LoadModule dir_module modules/mod_dir.so LoadModule alias_module modules/mod_alias.so LoadModule rewrite_module modules/mod_rewrite.so LoadModule php7_module modules/libphp7.so ServerAdmin me@example.com <Directory /> AllowOverride none Require all denied </Directory> DocumentRoot "/home/me/modapache/root" <Directory "/home/me/modapache/root"> Options Indexes FollowSymLinks AllowOverride All Require all granted </Directory> <IfModule dir_module> DirectoryIndex index.php index.html </IfModule> <Files ".ht*"> Require all denied </Files> ErrorLog "/home/me/modapache/apache/logs/modphp.error_log" LogLevel warn <IfModule log_config_module> LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agen +t}i\"" combined LogFormat "%h %l %u %t \"%r\" %>s %b" common <IfModule logio_module> # You need to enable mod_logio.c to use %I and %O LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Ag +ent}i\" %I %O" combinedio </IfModule> CustomLog "/home/me/modapache/apache/logs/modphp.access_log" commo +n </IfModule> <IfModule headers_module> RequestHeader unset Proxy early </IfModule> <IfModule mime_module> TypesConfig conf/mime.types AddType application/x-httpd-php .php AddType application/x-compress .Z AddType application/x-gzip .gz .tgz </IfModule>

        script:

        <?php echo '<p>Hello World</p>'; ?>

        test:

        $ curl -v http://127.0.0.1:11208/hello.php < HTTP/1.1 200 OK < Server: Apache/2.4.25 (Unix) PHP/7.4.2 < <p>Hello World</p>

        apache bench:

        $ /opt/apps/apache/bin/ab -n 250000 -c 3 http://127.0.0.1:11208/hello. +php This is ApacheBench, Version 2.3 <$Revision: 1757674 $> Server Software: Apache/2.4.25 Server Hostname: 127.0.0.1 Server Port: 11208 Document Path: /hello.php Document Length: 18 bytes Concurrency Level: 3 Time taken for tests: 14.386 seconds Complete requests: 250000 Failed requests: 0 Total transferred: 54500000 bytes HTML transferred: 4500000 bytes Requests per second: 17377.86 [#/sec] (mean) Time per request: 0.173 [ms] (mean) Time per request: 0.058 [ms] (mean, across all concurrent reques +ts) Transfer rate: 3699.58 [Kbytes/sec] received

        now mod_perl:

        config:

        ServerRoot "/opt/apps/apache" Listen 127.0.0.1:11208 LoadModule authn_file_module modules/mod_authn_file.so LoadModule authn_core_module modules/mod_authn_core.so LoadModule authz_host_module modules/mod_authz_host.so LoadModule authz_groupfile_module modules/mod_authz_groupfile.so LoadModule authz_user_module modules/mod_authz_user.so LoadModule authz_core_module modules/mod_authz_core.so LoadModule access_compat_module modules/mod_access_compat.so LoadModule auth_basic_module modules/mod_auth_basic.so LoadModule reqtimeout_module modules/mod_reqtimeout.so LoadModule filter_module modules/mod_filter.so LoadModule mime_module modules/mod_mime.so LoadModule log_config_module modules/mod_log_config.so LoadModule env_module modules/mod_env.so LoadModule headers_module modules/mod_headers.so LoadModule setenvif_module modules/mod_setenvif.so LoadModule version_module modules/mod_version.so LoadModule unixd_module modules/mod_unixd.so LoadModule status_module modules/mod_status.so LoadModule autoindex_module modules/mod_autoindex.so LoadModule dir_module modules/mod_dir.so LoadModule alias_module modules/mod_alias.so LoadModule perl_module modules/mod_perl.so LoadModule apreq_module modules/mod_apreq2.so ServerAdmin me@example.com <Directory /> AllowOverride none Require all denied </Directory> DocumentRoot "/home/me/modapache/root" <Directory "/home/me/modapache/root"> Options Indexes FollowSymLinks AllowOverride None Require all granted </Directory> <IfModule dir_module> DirectoryIndex index.html </IfModule> <Files ".ht*"> Require all denied </Files> ErrorLog "/home/me/modapache/apache/logs/modperl.error_log" LogLevel warn <IfModule log_config_module> LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agen +t}i\"" combined LogFormat "%h %l %u %t \"%r\" %>s %b" common <IfModule logio_module> LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Ag +ent}i\" %I %O" combinedio </IfModule> CustomLog "/home/me/modapache/apache/logs/modperl.access_log" comm +on </IfModule> <IfModule mime_module> TypesConfig conf/mime.types AddType application/x-compress .Z AddType application/x-gzip .gz .tgz </IfModule> PidFile /home/me/modapache/apache/logs/modperl.pid <Perl> use lib qw(/home/me/modapache/lib); use MyApache2::Rocks (); </Perl> <Location /> SetHandler modperl PerlResponseHandler MyApache2::Rocks </Location> <Location /robots.txt> SetHandler None </Location> <Location /favicon.ico> SetHandler None </Location>

        script:

        package MyApache2::Rocks; use strict; use warnings; use Apache2::RequestRec (); use Apache2::RequestIO (); use Apache2::Const -compile => qw(OK); sub handler { my $r = shift; $r->content_type('text/plain'); $r->print( "mod_perl 2.0 rocks!\n" ); return Apache2::Const::OK; } 1;

        test:

        $ curl -v http://127.0.0.1:11208/foo/bar < HTTP/1.1 200 OK < Server: Apache/2.4.25 (Unix) mod_apreq2-20090110/2.8.0 mod_perl/2.0. +10 Perl/v5.24.1 < mod_perl 2.0 rocks!

        apache bench:

        $ /opt/apps/apache/bin/ab -n 250000 -c 3 http://127.0.0.1:11208/foo/ba +r This is ApacheBench, Version 2.3 <$Revision: 1757674 $> Server Software: Apache/2.4.25 Server Hostname: 127.0.0.1 Server Port: 11208 Document Path: /foo/bar Document Length: 20 bytes Concurrency Level: 3 Time taken for tests: 13.204 seconds Complete requests: 250000 Failed requests: 0 Total transferred: 51500000 bytes HTML transferred: 5000000 bytes Requests per second: 18934.11 [#/sec] (mean) Time per request: 0.158 [ms] (mean) Time per request: 0.053 [ms] (mean, across all concurrent reques +ts) Transfer rate: 3809.01 [Kbytes/sec] received

          mod_perl It makes me sad people aren't using it.

          Hear! Hear!

          I reproduced your script on my lowly kimsufi and online servers; I'm getting awful numbers compared to yours, do you have any idea why that is? something in the hardware maybe?

          KIMSUFI Intel ATOM N2800 2c/4t 1,86GHz 4Go DDR3 1066MHz Server Software: Apache Server Hostname: vincentveyron.com Server Port: 80 Document Path: /rocks Document Length: 20 bytes Concurrency Level: 3 Time taken for tests: 2.081 seconds Complete requests: 2500 Failed requests: 0 Total transferred: 437500 bytes HTML transferred: 50000 bytes Requests per second: 1201.57 [#/sec] (mean) Time per request: 2.497 [ms] (mean) Time per request: 0.832 [ms] (mean, across all concurrent reques +ts) Transfer rate: 205.35 [Kbytes/sec] received ONLINE Intel(R) Atom(TM) CPU C2338 @ 1.74GHz 4Go Server Software: Apache Server Hostname: vincentveyron.com Server Port: 80 Document Path: /rocks Document Length: 20 bytes Concurrency Level: 3 Time taken for tests: 83.870 seconds Complete requests: 25000 Failed requests: 0 Total transferred: 4375000 bytes HTML transferred: 500000 bytes Requests per second: 298.08 [#/sec] (mean) Time per request: 10.064 [ms] (mean) Time per request: 3.355 [ms] (mean, across all concurrent reques +ts) Transfer rate: 50.94 [Kbytes/sec] received

          https://compta.libremen.com

          Logiciel libre de comptabilité générale en partie double

        Re: PSGI/Plack unsatisfactory performance
        by Anonymous Monk on Dec 07, 2021 at 08:41 UTC
          with the imaginary code you didn't show I measured 5 trillion requests a nanosecond. SHOW TEH CODEZ