]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/support-scripts/pingest.pl
LP 1768715: pingest supports max/min ID, duration, more ops
[working/Evergreen.git] / Open-ILS / src / support-scripts / pingest.pl
1 #!/usr/bin/perl
2 # ---------------------------------------------------------------
3 # Copyright © 2013,2014 Merrimack Valley Library Consortium
4 # Jason Stephenson <jstephenson@mvlc.org>
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15 # ---------------------------------------------------------------
16 # TODO: Document with POD.
17 # This guy parallelizes a reingest.
18 use strict;
19 use warnings;
20 use DBI;
21 use Getopt::Long;
22
23 # Globals for the command line options: --
24
25 # You will want to adjust the next two based on your database size,
26 # i.e. number of bib records as well as the number of cores on your
27 # database server.  Using roughly number of cores/2 doesn't seem to
28 # have much impact in off peak times.
29 my $batch_size = 10000; # records processed per batch
30 my $max_child  = 8;     # max number of parallel worker processes
31
32 my $skip_browse;  # Skip the browse reingest.
33 my $skip_attrs;   # Skip the record attributes reingest.
34 my $skip_search;  # Skip the search reingest.
35 my $skip_facets;  # Skip the facets reingest.
36 my $start_id;     # start processing at this bib ID.
37 my $end_id;       # stop processing when this bib ID is reached.
38 my $max_duration; # max processing duration in seconds
39 my $help;         # show help text
40
41 GetOptions(
42     'batch-size=i'   => \$batch_size,
43     'max-child=i'    => \$max_child,
44     'skip-browse'    => \$skip_browse,
45     'skip-attrs'     => \$skip_attrs,
46     'skip-search'    => \$skip_search,
47     'skip-facets'    => \$skip_facets,
48     'start-id=i'     => \$start_id,
49     'end-id=i'       => \$end_id,
50     'max-duration=i' => \$max_duration,
51     'help'           => \$help
52 );
53
54 sub help {
55     print <<HELP;
56
57     $0 --batch-size $batch_size --max-child $max_child \
58         --start-id 1 --end-id 500000 --duration 14400
59
60     --batch-size
61         Number of records to process per batch
62
63     --max-child
64         Max number of worker processes
65
66     --skip-browse
67     --skip-attrs
68     --skip-search
69     --skip-facets
70         Skip the selected reingest component
71
72     --start-id
73         Start processing at this record ID.
74
75     --end-id
76         Stop processing when this record ID is reached
77
78     --max-duration
79         Stop processing after this many total seconds have passed.
80
81     --help
82         Show this help text.
83
84 HELP
85     exit;
86 }
87
88 help() if $help;
89
90 my $where = "WHERE deleted = 'f'";
91 if ($start_id && $end_id) {
92     $where .= " AND id BETWEEN $start_id AND $end_id";
93 } elsif ($start_id) {
94     $where .= " AND id >= $start_id";
95 } elsif ($end_id) {
96     $where .= " AND id <= $end_id";
97 }
98
99 # "Gimme the keys!  I'll drive!"
100 my $q = <<END_OF_Q;
101 SELECT id
102 FROM biblio.record_entry
103 $where
104 ORDER BY id ASC
105 END_OF_Q
106
107 # Stuffs needed for looping, tracking how many lists of records we
108 # have, storing the actual list of records, and the list of the lists
109 # of records.
110 my ($count, $lists, $records) = (0,0,[]);
111 my @lol = ();
112 # To do the browse-only ingest:
113 my @blist = ();
114
115 my $start_epoch = time;
116
117 sub duration_expired {
118     return 1 if $max_duration && (time - $start_epoch) >= $max_duration;
119     return 0;
120 }
121
122 # All of the DBI->connect() calls in this file assume that you have
123 # configured the PGHOST, PGPORT, PGDATABASE, PGUSER, and PGPASSWORD
124 # variables in your execution environment.  If you have not, you have
125 # two options:
126 #
127 # 1) configure them
128 #
129 # 2) edit the DBI->connect() calls in this program so that it can
130 # connect to your database.
131 my $dbh = DBI->connect('DBI:Pg:');
132
133 my $results = $dbh->selectall_arrayref($q);
134 foreach my $r (@$results) {
135     my $record = $r->[0];
136     push(@blist, $record); # separate list of browse-only ingest
137     push(@$records, $record);
138     if (++$count == $batch_size) {
139         $lol[$lists++] = $records;
140         $count = 0;
141         $records = [];
142     }
143 }
144 $lol[$lists++] = $records if ($count); # Last batch is likely to be
145                                        # small.
146 $dbh->disconnect();
147
148 # We're going to reuse $count to keep track of the total number of
149 # batches processed.
150 $count = 0;
151
152 # @running keeps track of the running child processes.
153 my @running = ();
154
155 # We start the browse-only ingest before starting the other ingests.
156 browse_ingest(@blist) unless ($skip_browse);
157
158 # We loop until we have processed all of the batches stored in @lol
159 # or the maximum processing duration has been reached.
160 while ($count < $lists) {
161     my $duration_expired = duration_expired();
162
163     if (scalar(@lol) && scalar(@running) < $max_child && !$duration_expired) {
164         # Reuse $records for the lulz.
165         $records = shift(@lol);
166         if ($skip_search && $skip_facets && $skip_attrs) {
167             $count++;
168         } else {
169             reingest($records);
170         }
171     } else {
172         my $pid = wait();
173         if (grep {$_ == $pid} @running) {
174             @running = grep {$_ != $pid} @running;
175             $count++;
176             print "$count of $lists processed\n";
177         }
178     }
179
180     if ($duration_expired && scalar(@running) == 0) {
181         warn "Exiting on max_duration ($max_duration)\n";
182         exit(0);
183     }
184 }
185
186 # This subroutine forks a process to do the browse-only ingest on the
187 # @blist above.  It cannot be parallelized, but can run in parrallel
188 # to the other ingests.
189 sub browse_ingest {
190     my @list = @_;
191     my $pid = fork();
192     if (!defined($pid)) {
193         die "failed to spawn child";
194     } elsif ($pid > 0) {
195         # Add our browser to the list of running children.
196         push(@running, $pid);
197         # Increment the number of lists, because this list was not
198         # previously counted.
199         $lists++;
200     } elsif ($pid == 0) {
201         my $dbh = DBI->connect('DBI:Pg:');
202         my $sth = $dbh->prepare("SELECT metabib.reingest_metabib_field_entries(?, TRUE, FALSE, TRUE)");
203         foreach (@list) {
204             if ($sth->execute($_)) {
205                 my $crap = $sth->fetchall_arrayref();
206             } else {
207                 warn ("Browse ingest failed for record $_");
208             }
209             if (duration_expired()) {
210                 warn "browse_ingest() stopping on record $_ ".
211                     "after max duration reached\n";
212                 last;
213             }
214         }
215         $dbh->disconnect();
216         exit(0);
217     }
218 }
219
220 # Fork a child to do the other reingests:
221
222 sub reingest {
223     my $list = shift;
224     my $pid = fork();
225     if (!defined($pid)) {
226         die "Failed to spawn a child";
227     } elsif ($pid > 0) {
228         push(@running, $pid);
229     } elsif ($pid == 0) {
230         my $dbh = DBI->connect('DBI:Pg:');
231         reingest_attributes($dbh, $list) unless ($skip_attrs);
232         reingest_field_entries($dbh, $list)
233             unless ($skip_facets && $skip_search);
234         $dbh->disconnect();
235         exit(0);
236     }
237 }
238
239 # Reingest metabib field entries on a list of records.
240 sub reingest_field_entries {
241     my $dbh = shift;
242     my $list = shift;
243     my $sth = $dbh->prepare("SELECT metabib.reingest_metabib_field_entries(?, ?, TRUE, ?)");
244     # Because reingest uses "skip" options we invert the logic of do variables.
245     $sth->bind_param(2, ($skip_facets) ? 1 : 0);
246     $sth->bind_param(3, ($skip_search) ? 1 : 0);
247     foreach (@$list) {
248         $sth->bind_param(1, $_);
249         if ($sth->execute()) {
250             my $crap = $sth->fetchall_arrayref();
251         } else {
252             warn ("metabib.reingest_metabib_field_entries failed for record $_");
253         }
254     }
255 }
256
257 # Reingest record attributes on a list of records.
258 sub reingest_attributes {
259     my $dbh = shift;
260     my $list = shift;
261     my $sth = $dbh->prepare(<<END_OF_INGEST
262 SELECT metabib.reingest_record_attributes(id, NULL::TEXT[], marc)
263 FROM biblio.record_entry
264 WHERE id = ?
265 END_OF_INGEST
266     );
267     foreach (@$list) {
268         $sth->bind_param(1, $_);
269         if ($sth->execute()) {
270             my $crap = $sth->fetchall_arrayref();
271         } else {
272             warn ("metabib.reingest_record_attributes failed for record $_");
273         }
274     }
275 }