hold targeter: add option to run parallel targeter processes
authorgmc <gmc@dcc99617-32d9-48b4-a31d-7c20da2025e4>
Mon, 22 Nov 2010 04:26:44 +0000 (04:26 +0000)
committergmc <gmc@dcc99617-32d9-48b4-a31d-7c20da2025e4>
Mon, 22 Nov 2010 04:26:44 +0000 (04:26 +0000)
Permit the hold targeter to divvy up the work and run more than one process
at a time by setting the opensrf.xml setting hold_targeter/parallel to a
value greater than one.  Doing so can significantly reduce the
time it takes to (re)target a large number of hold requests, although
only up to a point (in other words, if increasing the number of parallel
targeters beyond one, it is recommended to do so slowly.)

Signed-off-by: Galen Charlton <gmc@esilibrary.com>

git-svn-id: svn://svn.open-ils.org/ILS/trunk@18815 dcc99617-32d9-48b4-a31d-7c20da2025e4

Open-ILS/examples/opensrf.xml.example
Open-ILS/src/perlmods/OpenILS/Application/Storage/Publisher/action.pm
Open-ILS/src/support-scripts/hold_targeter.pl

index 429f1f1..4645929 100644 (file)
@@ -130,6 +130,16 @@ vim:et:ts=4:sw=4:
         </predue>
       </notifications>
 
+        <!-- Settings for the hold targeter cron job -->
+        <hold_targeter>
+            <!-- number of parallel processes to use during hold targeting;
+                 increasing can speed up (re)targeting a large number of
+                 hold requests, but with diminishing returns after a point;
+                 if increasing this value, it is recommend to do so slowly
+            -->
+            <parallel>1</parallel>
+        </hold_targeter>
+        
         <reporter>
             <!--
             Settings for the reporter daemon process 
index 4d590ab..fa98899 100644 (file)
@@ -321,6 +321,50 @@ __PACKAGE__->register_method(
        method          => 'nearest_hold',
 );
 
+sub targetable_holds {
+    my $self = shift;
+    my $client = shift;
+    my $check_expire = shift;
+
+    $check_expire ||= '12h';
+
+    local $OpenILS::Application::Storage::WRITE = 1;
+
+    # json_query can *almost* represent this query, but can't
+    # handle the CASE statement or the interval arithmetic
+    my $query = <<"    SQL";
+        SELECT ahr.id, mmsm.metarecord
+        FROM action.hold_request ahr
+        JOIN reporter.hold_request_record USING (id)
+        JOIN metabib.metarecord_source_map mmsm ON (bib_record = source)
+        WHERE capture_time IS NULL
+        AND (prev_check_time IS NULL or prev_check_time < (NOW() - ?::interval))
+        AND fulfillment_time IS NULL
+        AND cancel_time IS NULL
+        AND NOT frozen
+        ORDER BY CASE WHEN ahr.hold_type = 'F' THEN 0 ELSE 1 END, selection_depth DESC, request_time;
+    SQL
+    my $sth = action::hold_request->db_Main->prepare_cached($query);
+    $sth->execute($check_expire);
+
+    $client->respond( $_ ) for ( $sth->fetchall_arrayref );
+    return undef;
+}
+
+__PACKAGE__->register_method(
+    api_name    => 'open-ils.storage.action.hold_request.targetable_holds.id_list',
+    api_level   => 1,
+    stream      => 1,
+    method      => 'targetable_holds',
+    signature   => q/
+        Returns ordered list of hold request and metarecord IDs
+        for all hold requests that are available for initial targeting
+        or retargeting.
+        @param check interval
+        @return list of pairs of hold request and metarecord IDs
+/,
+);
+
 sub next_resp_group_id {
        my $self = shift;
        my $client = shift;
index 73a1988..84c5ba2 100755 (executable)
@@ -8,12 +8,14 @@ use strict;
 use warnings;
 use OpenSRF::Utils::JSON;
 use OpenSRF::System;
+use OpenSRF::Utils::SettingsClient;
+use OpenSRF::MultiSession;
 
 my $config = shift || die "bootstrap config required\n";
 my $lockfile = shift || "/tmp/hold_targeter-LOCK";
 
 if (-e $lockfile) {
-       die "I seem to be running already. If not remove $lockfile, try again\n";
+    die "I seem to be running already. If not remove $lockfile, try again\n";
 }
 
 open(F, ">$lockfile");
@@ -21,16 +23,50 @@ print F $$;
 close F;
 
 OpenSRF::System->bootstrap_client( config_file => $config );
+my $settings = OpenSRF::Utils::SettingsClient->new;
+my $parallel = $settings->config_value( hold_targeter => 'parallel' ) || 1; 
 
-my $r = OpenSRF::AppSession
-               ->create( 'open-ils.storage' )
-               ->request( 'open-ils.storage.action.hold_request.copy_targeter' => '24h' );
+if ($parallel == 1) {
 
-while (!$r->complete) { 
-    my $start = time;
-    $r->recv(timeout => 3600);
-    last if (time() - $start) >= 3600;
-};
+    my $r = OpenSRF::AppSession
+               ->create( 'open-ils.storage' )
+               ->request( 'open-ils.storage.action.hold_request.copy_targeter' => '24h' );
+
+    while (!$r->complete) { 
+        my $start = time;
+        $r->recv(timeout => 3600);
+        last if (time() - $start) >= 3600;
+    };
+
+} else {
+
+    my $multi_targeter = OpenSRF::MultiSession->new(
+        app => 'open-ils.storage', 
+        cap => $parallel, 
+        api_level => 1,
+        session_hash_function => sub {
+            my $ses = shift;
+            my $req = shift;
+            return $_[-1]; # last parameter is the ID of the metarecord associated with the
+                           # request's target; using this as the hash function value ensures
+                           # that parallel targeters won't try to simultaneously handle two
+                           # hold requests that have overlapping pools of copies that could
+                           # fill those requests
+        }
+    );
+
+    my $storage = OpenSRF::AppSession->create("open-ils.storage");
+    my $holds = $storage->request('open-ils.storage.action.hold_request.targetable_holds.id_list', '24h')->gather();
+    $storage->disconnect();
+
+    foreach my $hold (@$holds) {
+        $multi_targeter->request( 'open-ils.storage.action.hold_request.copy_targeter', '', $hold->[0], $hold->[1]);
+    }
+
+    $multi_targeter->session_wait(1);
+    $multi_targeter->disconnect;
+
+}
 
 unlink $lockfile;