X-Git-Url: http://www.git.stargrave.org/?a=blobdiff_plain;f=lib%2FPublicInbox%2FV2Writable.pm;h=c8334645d3cbcff5bf0b290bda75db7bd8113022;hb=d73d783ab2cf14ba28ca63723223d8c85a68cdd5;hp=f7a318e5b7f39f96342f65c6821a16c3edfa4b34;hpb=826e0992d956aa60711e779afeb8493884c41cc2;p=public-inbox.git diff --git a/lib/PublicInbox/V2Writable.pm b/lib/PublicInbox/V2Writable.pm index f7a318e5..c8334645 100644 --- a/lib/PublicInbox/V2Writable.pm +++ b/lib/PublicInbox/V2Writable.pm @@ -8,7 +8,6 @@ use strict; use v5.10.1; use parent qw(PublicInbox::Lock); use PublicInbox::SearchIdxShard; -use PublicInbox::IdxStack; use PublicInbox::Eml; use PublicInbox::Git; use PublicInbox::Import; @@ -20,7 +19,7 @@ use PublicInbox::Msgmap; use PublicInbox::Spawn qw(spawn popen_rd); use PublicInbox::SearchIdx qw(log2stack crlf_adjust is_ancestor check_size); use IO::Handle; # ->autoflush -use File::Temp qw(tempfile); +use File::Temp (); my $OID = qr/[a-f0-9]{40,}/; # an estimate of the post-packed size to the raw uncompressed size @@ -35,14 +34,13 @@ my $PACKING_FACTOR = 0.4; our $NPROC_MAX_DEFAULT = 4; sub detect_nproc () { - for my $nproc (qw(nproc gnproc)) { # GNU coreutils nproc - `$nproc 2>/dev/null` =~ /^(\d+)$/ and return $1; - } - # getconf(1) is POSIX, but *NPROCESSORS* vars are not for (qw(_NPROCESSORS_ONLN NPROCESSORS_ONLN)) { `getconf $_ 2>/dev/null` =~ /^(\d+)$/ and return $1; } + for my $nproc (qw(nproc gnproc)) { # GNU coreutils nproc + `$nproc 2>/dev/null` =~ /^(\d+)$/ and return $1; + } # should we bother with `sysctl hw.ncpu`? Those only give # us total processor count, not online processor count. @@ -116,13 +114,13 @@ sub new { total_bytes => 0, current_info => '', xpfx => $xpfx, - over => PublicInbox::OverIdx->new("$xpfx/over.sqlite3"), + oidx => PublicInbox::OverIdx->new("$xpfx/over.sqlite3"), lock_path => "$dir/inbox.lock", # limit each git repo (epoch) to 1GB or so rotate_bytes => int((1024 * 1024 * 1024) / $PACKING_FACTOR), last_commit => [], # git epoch -> commit }; - $self->{over}->{-no_fsync} = 1 if $v2ibx->{-no_fsync}; + $self->{oidx}->{-no_fsync} = 1 if $v2ibx->{-no_fsync}; $self->{shards} = count_shards($self) || nproc_shards($creat); bless $self, $class; } @@ -152,21 +150,15 @@ sub add { $self->{ibx}->with_umask(\&_add, $self, $eml, $check_cb); } -sub batch_bytes ($) { - my ($self) = @_; - ($self->{parallel} ? $self->{shards} : 1) * - $PublicInbox::SearchIdx::BATCH_BYTES; -} - # indexes a message, returns true if checkpointing is needed sub do_idx ($$$$) { my ($self, $msgref, $mime, $smsg) = @_; $smsg->{bytes} = $smsg->{raw_bytes} + crlf_adjust($$msgref); - $self->{over}->add_overview($mime, $smsg); + $self->{oidx}->add_overview($mime, $smsg); my $idx = idx_shard($self, $smsg->{num} % $self->{shards}); $idx->index_raw($msgref, $mime, $smsg); my $n = $self->{transact_bytes} += $smsg->{raw_bytes}; - $n >= batch_bytes($self); + $n >= $self->{batch_bytes}; } sub _add { @@ -227,7 +219,7 @@ sub v2_num_for { if ($altid && grep(/:file=msgmap\.sqlite3\z/, @$altid)) { my $num = $self->{mm}->num_for($mid); - if (defined $num && !$self->{over}->get_art($num)) { + if (defined $num && !$self->{oidx}->get_art($num)) { return ($num, $mid); } } @@ -282,11 +274,14 @@ sub idx_shard { sub _idx_init { # with_umask callback my ($self, $opt) = @_; $self->lock_acquire unless $opt && $opt->{-skip_lock}; - $self->{over}->create; + $self->{oidx}->create; # xcpdb can change shard count while -watch is idle my $nshards = count_shards($self); $self->{shards} = $nshards if $nshards && $nshards != $self->{shards}; + $self->{batch_bytes} = $opt->{batch_size} // + $PublicInbox::SearchIdx::BATCH_BYTES; + $self->{batch_bytes} *= $self->{shards} if $self->{parallel}; # need to create all shards before initializing msgmap FD # idx_shards must be visible to all forked processes @@ -386,7 +381,7 @@ sub rewrite_internal ($$;$$$) { } else { $im = $self->importer; } - my $over = $self->{over}; + my $oidx = $self->{oidx}; my $chashes = content_hashes($old_eml); my $removed = []; my $mids = mids($old_eml); @@ -400,7 +395,7 @@ sub rewrite_internal ($$;$$$) { foreach my $mid (@$mids) { my %gone; # num => [ smsg, $mime, raw ] my ($id, $prev); - while (my $smsg = $over->next_by_mid($mid, \$id, \$prev)) { + while (my $smsg = $oidx->next_by_mid($mid, \$id, \$prev)) { my $msg = get_blob($self, $smsg); if (!defined($msg)) { warn "broken smsg for $mid\n"; @@ -628,19 +623,19 @@ sub checkpoint ($;$) { $dbh->commit; # SQLite overview is third - $self->{over}->commit_lazy; + $self->{oidx}->commit_lazy; # Now deal with Xapian if ($wait) { my $barrier = $self->barrier_init(scalar @$shards); # each shard needs to issue a barrier command - $_->remote_barrier for @$shards; + $_->shard_barrier for @$shards; # wait for each Xapian shard $self->barrier_wait($barrier); } else { - $_->remote_commit for @$shards; + $_->shard_commit for @$shards; } # last_commit is special, don't commit these until @@ -660,6 +655,9 @@ sub checkpoint ($;$) { # public sub barrier { checkpoint($_[0], 1) }; +# true if locked and active +sub active { !!$_[0]->{im} } + # public sub done { my ($self) = @_; @@ -680,12 +678,12 @@ sub done { my $shards = delete $self->{idx_shards}; if ($shards) { for (@$shards) { - eval { $_->remote_close }; + eval { $_->shard_close }; $err .= "shard close: $@\n" if $@; } } - eval { $self->{over}->disconnect }; - $err .= "over disconnect: $@\n" if $@; + eval { $self->{oidx}->dbh_close }; + $err .= "over close: $@\n" if $@; delete $self->{bnote}; my $nbytes = $self->{total_bytes}; $self->{total_bytes} = 0; @@ -733,12 +731,14 @@ sub fill_alternates ($$) { } return unless $new; - my ($fh, $tmp) = tempfile('alt-XXXXXXXX', DIR => $info_dir); + my $fh = File::Temp->new(TEMPLATE => 'alt-XXXXXXXX', DIR => $info_dir); + my $tmp = $fh->filename; print $fh join("\n", sort { $alt{$b} <=> $alt{$a} } keys %alt), "\n" or die "print $tmp: $!\n"; chmod($mode, $fh) or die "fchmod $tmp: $!\n"; close $fh or die "close $tmp $!\n"; rename($tmp, $alt) or die "rename $tmp => $alt: $!\n"; + $fh->unlink_on_destroy(0); } sub git_init { @@ -819,18 +819,17 @@ sub import_init { sub diff ($$$) { my ($mid, $cur, $new) = @_; - my ($ah, $an) = tempfile('email-cur-XXXXXXXX', TMPDIR => 1); + my $ah = File::Temp->new(TEMPLATE => 'email-cur-XXXXXXXX', TMPDIR => 1); print $ah $cur->as_string or die "print: $!"; - close $ah or die "close: $!"; - my ($bh, $bn) = tempfile('email-new-XXXXXXXX', TMPDIR => 1); + $ah->flush or die "flush: $!"; PublicInbox::Import::drop_unwanted_headers($new); + my $bh = File::Temp->new(TEMPLATE => 'email-new-XXXXXXXX', TMPDIR => 1); print $bh $new->as_string or die "print: $!"; - close $bh or die "close: $!"; - my $cmd = [ qw(diff -u), $an, $bn ]; + $bh->flush or die "flush: $!"; + my $cmd = [ qw(diff -u), $ah->filename, $bh->filename ]; print STDERR "# MID conflict <$mid>\n"; my $pid = spawn($cmd, undef, { 1 => 2 }); waitpid($pid, 0) == $pid or die "diff did not finish"; - unlink($an, $bn); } sub get_blob ($$) { @@ -845,10 +844,10 @@ sub get_blob ($$) { sub content_exists ($$$) { my ($self, $mime, $mid) = @_; - my $over = $self->{over}; + my $oidx = $self->{oidx}; my $chashes = content_hashes($mime); my ($id, $prev); - while (my $smsg = $over->next_by_mid($mid, \$id, \$prev)) { + while (my $smsg = $oidx->next_by_mid($mid, \$id, \$prev)) { my $msg = get_blob($self, $smsg); if (!defined($msg)) { warn "broken smsg for $mid\n"; @@ -890,7 +889,7 @@ sub reindex_checkpoint ($$) { } # allow -watch or -mda to write... - $self->idx_init; # reacquire lock + $self->idx_init($sync->{-opt}); # reacquire lock $mm_tmp->atfork_parent if $mm_tmp; } @@ -918,9 +917,9 @@ sub index_oid { # cat_async callback } } if (!defined($num)) { # reuse if reindexing (or duplicates) - my $over = $self->{over}; + my $oidx = $self->{oidx}; for my $mid (@$mids) { - ($num, $mid0) = $over->num_mid0_for_oid($oid, $mid); + ($num, $mid0) = $oidx->num_mid0_for_oid($oid, $mid); last if defined $num; } } @@ -999,7 +998,7 @@ sub log_range ($$$$$) { my $opt = $sync->{-opt}; my $pr = $opt->{-progress} if (($opt->{verbose} || 0) > 1); my $cur = $sync->{ranges}->[$i] or do { - $pr->("$i.git indexing all of $tip") if $pr; + $pr->("$i.git indexing all of $tip\n") if $pr; return $tip; # all of it }; @@ -1108,10 +1107,10 @@ sub sync_prepare ($$$) { sub unindex_oid_remote ($$$) { my ($self, $oid, $mid) = @_; - my @removed = $self->{over}->remove_oid($oid, $mid); + my @removed = $self->{oidx}->remove_oid($oid, $mid); for my $num (@removed) { my $idx = idx_shard($self, $num % $self->{shards}); - $idx->remote_remove($oid, $num); + $idx->shard_remove($oid, $num); } } @@ -1122,11 +1121,11 @@ sub unindex_oid ($$;$) { # git->cat_async callback my $mm = $self->{mm}; my $mids = mids(PublicInbox::Eml->new($bref)); undef $$bref; - my $over = $self->{over}; + my $oidx = $self->{oidx}; foreach my $mid (@$mids) { my %gone; my ($id, $prev); - while (my $smsg = $over->next_by_mid($mid, \$id, \$prev)) { + while (my $smsg = $oidx->next_by_mid($mid, \$id, \$prev)) { $gone{$smsg->{num}} = 1 if $oid eq $smsg->{blob}; } my $n = scalar(keys(%gone)) or next; @@ -1198,21 +1197,20 @@ sub index_xap_only { # git->cat_async callback sub index_xap_step ($$$;$) { my ($self, $sync, $beg, $step) = @_; - my $ibx = $self->{ibx}; - my $all = $ibx->git; - my $over = $ibx->over; - my $batch_bytes = batch_bytes($self); - $step //= $self->{shards}; my $end = $sync->{art_end}; + return if $beg > $end; # nothing to do + + $step //= $self->{shards}; + my $ibx = $self->{ibx}; if (my $pr = $sync->{-opt}->{-progress}) { $pr->("Xapian indexlevel=$ibx->{indexlevel} ". "$beg..$end (% $step)\n"); } for (my $num = $beg; $num <= $end; $num += $step) { - my $smsg = $over->get_art($num) or next; + my $smsg = $ibx->over->get_art($num) or next; $smsg->{v2w} = $self; - $all->cat_async($smsg->{blob}, \&index_xap_only, $smsg); - if ($self->{transact_bytes} >= $batch_bytes) { + $ibx->git->cat_async($smsg->{blob}, \&index_xap_only, $smsg); + if ($self->{transact_bytes} >= $self->{batch_bytes}) { ${$sync->{nr}} = $num; reindex_checkpoint($self, $sync); } @@ -1235,7 +1233,7 @@ sub index_epoch ($$$) { $self->{current_info} = "$i.git $oid"; if ($f eq 'm') { my $arg = { %$sync, autime => $at, cotime => $ct }; - if ($sync->{index_max_size}) { + if ($sync->{max_size}) { $all->check_async($oid, \&check_size, $arg); } else { $all->cat_async($oid, \&index_oid, $arg); @@ -1253,8 +1251,9 @@ sub index_epoch ($$$) { } sub xapian_only { - my ($self, $opt, $sync) = @_; - my $seq = $opt->{sequentialshard}; + my ($self, $opt, $sync, $art_beg) = @_; + my $seq = $opt->{sequential_shard}; + $art_beg //= 0; local $self->{parallel} = 0 if $seq; $self->idx_init($opt); # acquire lock if (my $art_end = $self->{ibx}->mm->max) { @@ -1268,9 +1267,14 @@ sub xapian_only { $sync->{art_end} = $art_end; if ($seq || !$self->{parallel}) { my $shard_end = $self->{shards} - 1; - index_xap_step($self, $sync, $_) for (0..$shard_end); + for my $i (0..$shard_end) { + index_xap_step($self, $sync, $art_beg + $i); + if ($i != $shard_end) { + reindex_checkpoint($self, $sync); + } + } } else { # parallel (maybe) - index_xap_step($self, $sync, 0, 1); + index_xap_step($self, $sync, $art_beg, 1); } } $self->{ibx}->git->cat_async_wait; @@ -1281,20 +1285,21 @@ sub xapian_only { sub index_sync { my ($self, $opt) = @_; $opt //= $_[1] //= {}; - goto \&xapian_only if $opt->{xapianonly}; + goto \&xapian_only if $opt->{xapian_only}; my $pr = $opt->{-progress}; my $epoch_max; my $latest = git_dir_latest($self, \$epoch_max); return unless defined $latest; - my $seq = $opt->{sequentialshard}; + my $seq = $opt->{sequential_shard}; + my $art_beg; # the NNTP article number we start xapian_only at my $idxlevel = $self->{ibx}->{indexlevel}; local $self->{ibx}->{indexlevel} = 'basic' if $seq; $self->idx_init($opt); # acquire lock fill_alternates($self, $epoch_max); - $self->{over}->rethread_prepare($opt); + $self->{oidx}->rethread_prepare($opt); my $sync = { need_checkpoint => \(my $bool = 0), unindex_range => {}, # EPOCH => oid_old..oid_new @@ -1312,13 +1317,19 @@ sub index_sync { $self->{mm}->{dbh}->begin_work; $sync->{mm_tmp} = $self->{mm}->tmp_clone($self->{ibx}->{inboxdir}); + + # xapian_only works incrementally w/o --reindex + if ($seq && !$opt->{reindex}) { + $art_beg = $sync->{mm_tmp}->max; + $art_beg++ if defined($art_beg); + } } - if ($sync->{index_max_size} = $self->{ibx}->{index_max_size}) { + if ($sync->{max_size} = $opt->{max_size}) { $sync->{index_oid} = \&index_oid; } # work forwards through history index_epoch($self, $sync, $_) for (0..$epoch_max); - $self->{over}->rethread_done($opt); + $self->{oidx}->rethread_done($opt); $self->done; if (my $nr = $sync->{nr}) { @@ -1326,10 +1337,22 @@ sub index_sync { $pr->('all.git '.sprintf($sync->{-regen_fmt}, $$nr)) if $pr; } - if ($seq) { # deal with Xapian shards sequentially + # deal with Xapian shards sequentially + if ($seq && delete($sync->{mm_tmp})) { $self->{ibx}->{indexlevel} = $idxlevel; - delete $sync->{mm_tmp}; - xapian_only($self, $opt, $sync); + xapian_only($self, $opt, $sync, $art_beg); + } + + # --reindex on the command-line + if ($opt->{reindex} && !ref($opt->{reindex}) && $idxlevel ne 'basic') { + $self->lock_acquire; + my $s0 = PublicInbox::SearchIdx->new($self->{ibx}, 0, 0); + if (my $xdb = $s0->idx_acquire) { + my $n = $xdb->get_metadata('has_threadid'); + $xdb->set_metadata('has_threadid', '1') if $n ne '1'; + } + $s0->idx_release; + $self->lock_release; } # reindex does not pick up new changes, so we rerun w/o it: