]> Sergey Matveev's repositories - public-inbox.git/commitdiff
extindex: -xcpdb and -compact support
authorEric Wong <e@80x24.org>
Fri, 30 Jul 2021 12:18:55 +0000 (12:18 +0000)
committerEric Wong <e@80x24.org>
Sat, 31 Jul 2021 00:14:20 +0000 (00:14 +0000)
Since extindex uses Xapian shards in a similar way to
v2 inboxes, we'll support -xcpdb (reshard+upgrade) and
-compact all the same to give admins tuning+upgrade
options.

lib/PublicInbox/Admin.pm
lib/PublicInbox/Search.pm
lib/PublicInbox/V2Writable.pm
lib/PublicInbox/Xapcmd.pm
script/public-inbox-compact
script/public-inbox-xcpdb
t/extsearch.t
t/indexlevels-mirror.t

index d5f867a2f6a45136424655ac21d71eac0e69524d..2534958b121e691ecee1e963fde736d03383c30f 100644 (file)
@@ -28,6 +28,27 @@ sub setup_signals {
        };
 }
 
+sub resolve_eidxdir {
+       my ($cd) = @_;
+       my $try = $cd // '.';
+       my $root_dev_ino;
+       while (1) { # favor v2, first
+               if (-f "$try/ei.lock") {
+                       return rel2abs_collapsed($try);
+               } elsif (-d $try) {
+                       my @try = stat _;
+                       $root_dev_ino //= do {
+                               my @root = stat('/') or die "stat /: $!\n";
+                               "$root[0]\0$root[1]";
+                       };
+                       return undef if "$try[0]\0$try[1]" eq $root_dev_ino;
+                       $try .= '/..'; # continue, cd up
+               } else {
+                       die "`$try' is not a directory\n";
+               }
+       }
+}
+
 sub resolve_inboxdir {
        my ($cd, $ver) = @_;
        my $try = $cd // '.';
@@ -107,11 +128,24 @@ sub resolve_inboxes ($;$$) {
                $cfg or die "--all specified, but $cfgfile not readable\n";
                @$argv and die "--all specified, but directories specified\n";
        }
-
+       my (@old, @ibxs, @eidx);
+       if ($opt->{-eidx_ok}) {
+               require PublicInbox::ExtSearchIdx;
+               my $i = -1;
+               @$argv = grep {
+                       $i++;
+                       if (defined(my $ei = resolve_eidxdir($_))) {
+                               $ei = PublicInbox::ExtSearchIdx->new($ei, $opt);
+                               push @eidx, $ei;
+                               undef;
+                       } else {
+                               1;
+                       }
+               } @$argv;
+       }
        my $min_ver = $opt->{-min_inbox_version} || 0;
        # lookup inboxes by st_dev + st_ino instead of {inboxdir} pathnames,
        # pathnames are not unique due to symlinks and bind mounts
-       my (@old, @ibxs);
        if ($opt->{all}) {
                $cfg->each_inbox(sub {
                        my ($ibx) = @_;
@@ -161,7 +195,7 @@ sub resolve_inboxes ($;$$) {
                die "-V$min_ver inboxes not supported by $0\n\t",
                    join("\n\t", @old), "\n";
        }
-       @ibxs;
+       $opt->{-eidx_ok} ? (\@ibxs, \@eidx) : @ibxs;
 }
 
 # TODO: make Devel::Peek optional, only used for daemon
index 7e19e616a2180e28fe7470698b3d555360ee4c93..e80a5944371ed418d105b9c373b00a1f2282440b 100644 (file)
@@ -187,7 +187,7 @@ sub xdir ($;$) {
        my ($self, $rdonly) = @_;
        if ($rdonly || !defined($self->{shard})) {
                $self->{xpfx};
-       } else { # v2 only:
+       } else { # v2 + extindex only:
                "$self->{xpfx}/$self->{shard}";
        }
 }
index 025487d2155c70fdafbc4b86a4e277739532901e..1288f47ba47013fb363030d4119d9e8e1149efe7 100644 (file)
@@ -54,14 +54,14 @@ sub nproc_shards ($) {
 
 sub count_shards ($) {
        my ($self) = @_;
+       # always load existing shards in case core count changes:
+       # Also, shard count may change while -watch is running
        if (my $ibx = $self->{ibx}) {
-               # always load existing shards in case core count changes:
-               # Also, shard count may change while -watch is running
                my $srch = $ibx->search or return 0;
                delete $ibx->{search};
                $srch->{nshard} // 0
        } else { # ExtSearchIdx
-               $self->{nshard} ||= scalar($self->xdb_shards_flat);
+               $self->{nshard} = scalar($self->xdb_shards_flat);
        }
 }
 
index 9791f02cf76aaf7ecff9d8f8419d5b2843a41ec1..e37eece5937f52bead48cb24aa8cd18737eea40c 100644 (file)
@@ -61,14 +61,14 @@ sub commit_changes ($$$$) {
        }
 
        # trigger ->check_inodes in read-only daemons
-       syswrite($im->{lockfh}, '.') if $over_chg;
+       syswrite($im->{lockfh}, '.') if $over_chg && $im;
 
        remove_tree(@old_shard);
        $tmp = undef;
        if (!$opt->{-coarse_lock}) {
                $opt->{-skip_lock} = 1;
-
-               if ($im->can('count_shards')) {
+               $im //= $ibx if $ibx->can('eidx_sync');
+               if ($im->can('count_shards')) { # v2w or eidx
                        my $pr = $opt->{-progress};
                        my $n = $im->count_shards;
                        if (defined $reshard && $n != $reshard) {
@@ -83,7 +83,11 @@ sub commit_changes ($$$$) {
                }
                my $env = $opt->{-idx_env};
                local %ENV = (%ENV, %$env) if $env;
-               PublicInbox::Admin::index_inbox($ibx, $im, $opt);
+               if ($ibx->can('eidx_sync')) {
+                       $ibx->eidx_sync($opt);
+               } else {
+                       PublicInbox::Admin::index_inbox($ibx, $im, $opt);
+               }
        }
 }
 
@@ -103,9 +107,10 @@ sub runnable_or_die ($) {
        which($exe) or die "$exe not found in PATH\n";
 }
 
-sub prepare_reindex ($$$) {
-       my ($ibx, $im, $opt) = @_;
-       if ($ibx->version == 1) {
+sub prepare_reindex ($$) {
+       my ($ibx, $opt) = @_;
+       if ($ibx->can('eidx_sync')) { # no prep needed for ExtSearchIdx
+       } elsif ($ibx->version == 1) {
                my $dir = $ibx->search->xdir(1);
                my $xdb = $PublicInbox::Search::X{Database}->new($dir);
                if (my $lc = $xdb->get_metadata('last_commit')) {
@@ -172,9 +177,14 @@ sub prepare_run {
        my ($ibx, $opt) = @_;
        my $tmp = {}; # old shard dir => File::Temp->newdir object or undef
        my @queue; # ([old//src,newdir]) - list of args for cpdb() or compact()
-       my $old;
-       if (my $srch = $ibx->search) {
+       my ($old, $misc_ok);
+       if ($ibx->can('eidx_sync')) {
+               $misc_ok = 1;
+               $old = $ibx->xdir(1);
+       } elsif (my $srch = $ibx->search) {
                $old = $srch->xdir(1);
+       }
+       if (defined $old) {
                -d $old or die "$old does not exist\n";
        }
        my $reshard = $opt->{reshard};
@@ -184,7 +194,7 @@ sub prepare_run {
 
        # we want temporary directories to be as deep as possible,
        # so v2 shards can keep "xap$SCHEMA_VERSION" on a separate FS.
-       if ($old && $ibx->version == 1) {
+       if (defined($old) && $ibx->can('version') && $ibx->version == 1) {
                if (defined $reshard) {
                        warn
 "--reshard=$reshard ignored for v1 $ibx->{inboxdir}\n";
@@ -196,7 +206,7 @@ sub prepare_run {
                $tmp->{$old} = $wip;
                nodatacow_dir($wip->dirname);
                push @queue, [ $old, $wip ];
-       } elsif ($old) {
+       } elsif (defined $old) {
                opendir my $dh, $old or die "Failed to opendir $old: $!\n";
                my @old_shards;
                while (defined(my $dn = readdir($dh))) {
@@ -204,6 +214,7 @@ sub prepare_run {
                                push @old_shards, $dn;
                        } elsif ($dn eq '.' || $dn eq '..') {
                        } elsif ($dn =~ /\Aover\.sqlite3/) {
+                       } elsif ($dn eq 'misc' && $misc_ok) {
                        } else {
                                warn "W: skipping unknown dir: $old/$dn\n"
                        }
@@ -239,19 +250,19 @@ sub check_compact () { runnable_or_die($XAPIAN_COMPACT) }
 
 sub _run { # with_umask callback
        my ($ibx, $cb, $opt) = @_;
-       my $im = $ibx->importer(0);
-       $im->lock_acquire;
+       my $im = $ibx->can('importer') ? $ibx->importer(0) : undef;
+       ($im // $ibx)->lock_acquire;
        my ($tmp, $queue) = prepare_run($ibx, $opt);
 
        # fine-grained locking if we prepare for reindex
        if (!$opt->{-coarse_lock}) {
-               prepare_reindex($ibx, $im, $opt);
-               $im->lock_release;
+               prepare_reindex($ibx, $opt);
+               ($im // $ibx)->lock_release;
        }
 
-       $ibx->cleanup;
+       $ibx->cleanup if $ibx->can('cleanup');
        process_queue($queue, $cb, $opt);
-       $im->lock_acquire if !$opt->{-coarse_lock};
+       ($im // $ibx)->lock_acquire if !$opt->{-coarse_lock};
        commit_changes($ibx, $im, $tmp, $opt);
 }
 
@@ -259,11 +270,16 @@ sub run {
        my ($ibx, $task, $opt) = @_; # task = 'cpdb' or 'compact'
        my $cb = \&$task;
        PublicInbox::Admin::progress_prepare($opt ||= {});
-       defined(my $dir = $ibx->{inboxdir}) or die "no inboxdir defined\n";
-       -d $dir or die "inboxdir=$dir does not exist\n";
+       my $dir;
+       for my $fld (qw(inboxdir topdir)) {
+               my $d = $ibx->{$fld} // next;
+               -d $d or die "$fld=$d does not exist\n";
+               $dir = $d;
+               last;
+       }
        check_compact() if $opt->{compact} && $ibx->search;
 
-       if (!$opt->{-coarse_lock}) {
+       if (!$ibx->can('eidx_sync') && !$opt->{-coarse_lock}) {
                # per-epoch ranges for v2
                # v1:{ from => $OID }, v2:{ from => [ $OID, $OID, $OID ] } }
                $opt->{reindex} = { from => $ibx->version == 1 ? '' : [] };
@@ -393,7 +409,7 @@ sub cpdb ($$) { # cb_spawn callback
        PublicInbox::SearchIdx::load_xapian_writable() or die;
        my $XapianDatabase = $PublicInbox::Search::X{Database};
        if (ref($old) eq 'ARRAY') {
-               ($cur_shard) = ($new =~ m!xap[0-9]+/([0-9]+)\b!);
+               ($cur_shard) = ($new =~ m!(?:xap|ei)[0-9]+/([0-9]+)\b!);
                defined $cur_shard or
                        die "BUG: could not extract shard # from $new";
                $reshard = $opt->{reshard};
index ab1d1e5e760bc5771b804faa22eccf2b7636cba0..6e34aaeb44649180fd4ee32ba9d6190aa5458b3c 100755 (executable)
@@ -4,9 +4,9 @@
 use strict;
 use v5.10.1;
 use Getopt::Long qw(:config gnu_getopt no_ignore_case auto_abbrev);
-my $opt = { compact => 1, -coarse_lock => 1 };
+my $opt = { compact => 1, -coarse_lock => 1, -eidx_ok => 1 };
 my $help = <<EOF; # the following should fit w/o scrolling in 80x24 term:
-usage: public-inbox-compact INBOX_DIR
+usage: public-inbox-compact <INBOX_DIR|EXTINDEX_DIR>
 
   Compact Xapian DBs in an inbox
 
@@ -29,9 +29,13 @@ PublicInbox::Admin::progress_prepare($opt);
 
 require PublicInbox::InboxWritable;
 require PublicInbox::Xapcmd;
-my @ibxs = PublicInbox::Admin::resolve_inboxes(\@ARGV, $opt);
-unless (@ibxs) { print STDERR $help; exit 1 }
-foreach (@ibxs) {
-       my $ibx = PublicInbox::InboxWritable->new($_);
+my $cfg = PublicInbox::Config->new;
+my ($ibxs, $eidxs) = PublicInbox::Admin::resolve_inboxes(\@ARGV, $opt, $cfg);
+unless ($ibxs) { print STDERR $help; exit 1 }
+for my $ibx (@$ibxs) {
+       $ibx = PublicInbox::InboxWritable->new($ibx);
        PublicInbox::Xapcmd::run($ibx, 'compact', $opt);
 }
+for my $eidx (@$eidxs) {
+       PublicInbox::Xapcmd::run($eidx, 'compact', $opt);
+}
index 768dc2badb75f7e3f017dbda7aadac53f6a0f15e..81d1a85bc289703a6e78d0471e43764d953005de 100755 (executable)
@@ -5,7 +5,7 @@ use strict;
 use v5.10.1;
 use Getopt::Long qw(:config gnu_getopt no_ignore_case auto_abbrev);
 my $help = <<EOF; # the following should fit w/o scrolling in 80x24 term:
-usage: public-inbox-xcpdb [options] INBOX_DIR
+usage: public-inbox-xcpdb [options] <INBOX_DIR|EXTINDEX_DIR>
 
   upgrade or reshard Xapian DB(s) used by public-inbox
 
@@ -26,7 +26,7 @@ index options (see public-inbox-index(1) man page for full description):
 
 See public-inbox-xcpdb(1) man page for full documentation.
 EOF
-my $opt = { quiet => -1, compact => 0, fsync => 1 };
+my $opt = { quiet => -1, compact => 0, fsync => 1, -eidx_ok => 1 };
 GetOptions($opt, qw(
        fsync|sync! compact|c reshard|R=i
        max_size|max-size=s batch_size|batch-size=s
@@ -41,8 +41,8 @@ PublicInbox::Admin::require_or_die('-search');
 
 require PublicInbox::Config;
 my $cfg = PublicInbox::Config->new;
-my @ibxs = PublicInbox::Admin::resolve_inboxes(\@ARGV, $opt, $cfg) or
-       die $help;
+my ($ibxs, $eidxs) = PublicInbox::Admin::resolve_inboxes(\@ARGV, $opt, $cfg);
+unless ($ibxs) { print STDERR $help; exit 1 }
 my $idx_env = PublicInbox::Admin::index_prepare($opt, $cfg);
 
 # we only set XAPIAN_FLUSH_THRESHOLD for index, since cpdb doesn't
@@ -56,8 +56,12 @@ if ($opt->{'sequential-shard'} && ($opt->{jobs} // 1) > 1) {
 
 require PublicInbox::InboxWritable;
 require PublicInbox::Xapcmd;
-foreach (@ibxs) {
-       my $ibx = PublicInbox::InboxWritable->new($_);
-       # we rely on --no-renumber to keep docids synched for NNTP
+# we rely on --no-renumber to keep docids synched for NNTP(artnum) + IMAP(UID)
+for my $ibx (@$ibxs) {
+       $ibx = PublicInbox::InboxWritable->new($ibx);
        PublicInbox::Xapcmd::run($ibx, 'cpdb', $opt);
 }
+
+for my $eidx (@$eidxs) {
+       PublicInbox::Xapcmd::run($eidx, 'cpdb', $opt);
+}
index 1f62e80c8c1a2e7adcbcbbc3ff71d7506f5dee2d..d933b9485281791745f2df2854c9d1802aab003b 100644 (file)
@@ -422,4 +422,36 @@ for my $j (1, 3, 6) {
        like($dirs[-1], qr!/ei[0-9]+/$max\z!, '-j works');
 }
 
+SKIP: {
+       my $d = "$home/extindex-j1";
+       my $o = { 2 => \(my $err = '') };
+       ok(run_script([qw(-xcpdb -R4), $d]), 'xcpdb R4');
+       my @dirs = glob("$d/ei*/?");
+       for my $i (0..3) {
+               is(grep(m!/ei[0-9]+/$i\z!, @dirs), 1, "shard [$i] created");
+       }
+       for my $i (4..5) {
+               is(grep(m!/ei[0-9]+/$i\z!, @dirs), 0, "no shard [$i]");
+       }
+
+       ok(run_script([qw(-xcpdb -R2), $d]), 'xcpdb -R2');
+       @dirs = glob("$d/ei*/?");
+       for my $i (0..1) {
+               is(grep(m!/ei[0-9]+/$i\z!, @dirs), 1, "shard [$i] kept");
+       }
+       for my $i (2..3) {
+               is(grep(m!/ei[0-9]+/$i\z!, @dirs), 0, "no shard [$i]");
+       }
+       skip 'xapian-compact missing', 4 unless have_xapian_compact;
+       ok(run_script([qw(-compact), $d], undef, $o), 'compact');
+       # n.b. stderr contains xapian-compact output
+
+       my @d2 = glob("$d/ei*/?");
+       is_deeply(\@d2, \@dirs, 'dirs consistent after compact');
+       ok(run_script([qw(-extindex --dedupe --all), $d]),
+               '--dedupe works after compact');
+       ok(run_script([qw(-extindex --gc), $d], undef, $o),
+               '--gc works after compact');
+}
+
 done_testing;
index bd140cc427ba7c1a980c728bc89189344067547b..e606e79b183e8a96ac64555c3cfe1b9d6778723e 100644 (file)
@@ -158,7 +158,8 @@ my $import_index_incremental = sub {
        SKIP: {
                skip 'xapian-compact missing', 1 if !have_xapian_compact;
                my $cmd = [ qw(-compact), $mirror ];
-               ok(run_script($cmd, undef, { 2 => \$err}), "compact $level");
+               ok(run_script($cmd, undef, { 2 => \$err}), "compact $level")
+                       or diag $err;
        }
 };