Proposal:Search Throttling

From MovableType

THIS HAS BEEN IMPLEMENTED IN MT 4.15

mt-search.cgi needs a way to throttle requests. Here is a proposal of the throttling implementation for now before app-level throttling architecture would be established.

It throttles by sigalarm by default.

  1. MT::App::Search::throttle_control calls prepare_throttle callback.
    1. by default, a callback whose priority is set to 5 checks the request just like the legacy mt-search did before. If it did not satisfy, it sets up 5 seconds alarm before die-ing.
  2. MT::App::Search processes request.
  3. If alarm fires, it dies with "Throttled" message.
  4. If alarm does not fire before the request successfully processed, take_down callback resets alarm.

Developers can write a plugin to change how to throttle like below. The plugin sets up two throttle controllers.

  • cb1 callback checks load average via uptime command. If the load is over the specified metric, it returns "0", by which MT::App::Search::throttle_control returns throttled response immediately.
  • cb2 callback sets up sigalarm in 20 seconds, instead of the default 5 seconds.
  • Both callbacks and the default callback respects priority, in that it does not do anything if $$result already had a value (which means higher priority callback already processed).
 package MT::Plugin::SearchThrottle1;
 use strict;
 use MT 4;
 use base 'MT::Plugin';
 our $VERSION = '1.0';
 my $plugin = MT::Plugin::SearchThrottle1->new({
     name        => "SearchThrottle1",
     version     => $VERSION,
     registry => {
         callbacks => {
             'cb1' => {
                 callback => 'MT::App::Search::prepare_throttle',
                 handler => \&throttle_by_uptime,
                 priority => 3,
             },
             'cb2' => {
                 callback => 'MT::App::Search::prepare_throttle',
                 handler => \&throttle_by_sigalarm,
                 priority => 4,
             }
         },
     },
 });
 MT->add_plugin($plugin);
 sub throttle_by_uptime {
     my ( $cb, $app, $result, $messages ) = @_;
     # Don't bother if a callback proiritized higher
     # set up its throttle already
     return $$result if defined $$result;
     my $uptime = `uptime`;
     my @uptime = split ',', $uptime;
     my $up5 = $uptime[3];
     chomp $up5;
     if ( $up5 > 0.1 ) {
         push @$messages, $app->translate('Under heavy load');
         $$result = 0;
     }
     1;
 }
 sub throttle_by_sigalarm {
     my ( $cb, $app, $result, $messages ) = @_;
     # Don't bother if a callback proiritized higher
     # set up its throttle already
     return $$result if defined $$result;
     $SIG{ALRM} = sub { $app->errtrans('Too busy.  Sorry.'); die; };
     $app->{__have_throttle} = 1;
     alarm(20);
     $$result = 1;
     1;
 }
 1;