Bichon Docs
Migration & Upgrade

Some releases change the search index format (new fields, tokenizer changes). The index on disk cannot be updated in place, so it must be rebuilt once from the previous index and the stored blobs. This page is the procedure for any such release — the steps do not change between versions.

The current build requires an index format 4 rebuild (it adds the sender display name and the pre-override address behind an alias). If you are upgrading from a build that did not have those fields, run this once.

What a reindex does

bichon-admin reindex rebuilds both Tantivy indexes, mail_metadata and attachment_metadata, into a temporary directory and swaps them in atomically:

  • Each message is read from the old index (metadata, tags, dates, mailbox/account, stable UID) and its body is re-extracted from the raw message blob.
  • The sender is re-resolved with the account's current "sender header override" rules, so rules added or removed after a message was archived take effect on the rebuilt index.
  • Messages that never got a stable IMAP UID are assigned one; existing UIDs are kept.
  • Attachments are re-indexed; on Pro, attachment text is re-extracted (skip it with --skip-attachment-text).

Not touched: the raw blobs in bichon-storage/blobs/, the metadata store memdb/ (except the UID counters), backups, logs and exports. Nothing is deleted — the previous index is kept aside.

Directory layout

<root>/                          # BICHON_ROOT_DIR, /data in the default Docker image
├── STORAGE_VERSION
├── memdb/
├── bichon-indices/              # the index root — the unit that gets rebuilt
│   ├── mail_metadata/
│   └── attachment_metadata/
├── bichon-storage/
│   └── blobs/

With custom directories, the server's --bichon-index-dir / --bichon-data-dir (env BICHON_INDEX_DIR / BICHON_DATA_DIR) are parent directories: the index lives at <index-dir>/bichon-indices, the blobs at <data-dir>/bichon-storage.

During a rebuild the index parent looks like this:

<index-parent>/
├── bichon-indices/                        # live index, untouched until the swap
├── bichon-indices.new/                    # temporary rebuild (removed on failure)
└── bichon-indices.old-YYYYMMDD-HHMMSS/    # the previous index, kept after success

The .old-* directory is your rollback point. It is never deleted automatically.

Before you start

  1. Stop the server. The tool takes a lock on the blob store; while the server runs it stops with "The service appears to be running (the blob store is locked)". In maintenance mode the container is already safe to docker exec into. If the server is running normally, docker stop it first.
  2. Check the paths against the server's startup configuration. If your deployment sets BICHON_INDEX_DIR / BICHON_DATA_DIR (or passes --bichon-index-dir / --bichon-data-dir), the reindex needs the same values. Env vars inside the container are inherited by docker exec; command-line flags are not — pass them again.
  3. Free disk space. The rebuild coexists with the live index: keep at least 1.15× the current index size free on the index volume.
  4. Storage version 2. The layout must already be v2 (STORAGE_VERSION = 2). If not, run the storage migration first.

Running it

Docker, default paths

docker exec -it <bichon-container> bichon-admin reindex --bichon-root-dir /data

Docker, custom paths

docker exec -it <bichon-container> bichon-admin reindex \
  --bichon-root-dir /data \
  --bichon-index-dir /ssd \
  --bichon-data-dir /hdd

Bare-metal installs

./bichon-admin reindex --bichon-root-dir /var/lib/bichon

Non-interactive (scripts, init containers, Kubernetes Jobs)

Without a terminal the confirmation prompt cannot be answered, so pass --yes explicitly:

bichon-admin reindex --yes \
  --bichon-root-dir /data \
  --bichon-index-dir /ssd \
  --bichon-data-dir /hdd

Without a terminal and without --yes, the command prints an error and exits with code 2 without rebuilding — it no longer silently cancels with exit code 0, so a Job cannot report success while doing nothing. --yes skips only the confirmation; every pre-check (data root, index presence, storage version, free disk space) still runs and can still fail the command.

Interactive menu

docker exec -it <bichon-container> bichon-admin

Pick Rebuild Search Indexes. It asks for the data root, the index and data parent directories (press Enter for the defaults under the root), then the writer memory budget and thread count ([256] and [3] by default — press Enter to accept). The environment variables are honoured too and skip the directory questions.

Options

FlagDescription
--bichon-root-dir (BICHON_ROOT_DIR)Data root. Also available as --root.
--bichon-index-dir (BICHON_INDEX_DIR)Index parent (default <root>). Also available as --index-parent.
--bichon-data-dir (BICHON_DATA_DIR)Blob parent (default <root>). Also available as --data-parent.
--forceRebuild even when the index is already at the current format
--yesSkip the Proceed with reindex? prompt. Required when stdin is not a terminal (init containers, Jobs, CI); otherwise the command exits with code 2
--skip-attachment-textSkip re-extracting attachment text (Pro; a no-op in Community)
--report <path>Where to write skipped/degraded documents (default <root>/reindex-skipped.ndjson)
--writer-memory-mb <MiB>Scripts / non-interactive only; the interactive flow asks for it (default 256)
--writer-threads <N>Scripts / non-interactive only; the interactive flow asks for it (default 3)

The writer memory budget and thread count are asked interactively, with the default shown in the prompt — press Enter to accept it. Input is validated as you type (15–4095 MiB per thread, at least one thread), and the offered thread default drops when the budget is too small for it. Scripts and docker exec without -it skip those two questions (defaults or the flags are used), but the final confirmation still requires --yes in that case.

On a memory-constrained host, lower the budget (128 or less). This only bounds tantivy's indexing arenas: the process also maps the old and new index files, which shows up as file-backed pages (reclaimable, not heap). The stable-IMAP-UID validation keeps a list of roughly 16 bytes per email.

Reading is strictly one message at a time: the raw EML is fetched from the blob store, the body is parsed and indexed, and that memory is released before the next message — so the read peak follows the largest single message, not the archive size. Only the UID validation list grows with the number of emails.

The command prints a REINDEX PLAN (paths, rebuild target, backup target, writer budget) and asks for confirmation (unless --yes is given), then shows one progress bar per index. Large archives take a while — do not restart the server until it finishes; the tool holds the data lock.

If it says "No index found ... nothing to reindex": the paths are wrong, but the exit code is 0. Check that custom --bichon-index-dir / --bichon-data-dir values were passed, and remember they are parents, not the bichon-indices directory itself.

If it says "reindex needs confirmation, but stdin is not a terminal": add --yes (exit code 2). This is the typical error in a Kubernetes Job or docker exec without -it.

After it finishes

REINDEX COMPLETE
  emails: ...
  attachments: ...
  skipped docs: ...
BACKUP: Previous indexes were kept (not deleted) at .../bichon-indices.old-...
→ You can start the service now.
  1. Restart the server (docker restart <bichon-container>).
  2. Confirm the maintenance page is gone and search returns results.
  3. Spot-check the sender column (Name <address>; alias recipients show a via <original address> hint), sender search by name, the contact pickers, attachment preview and tags.
  4. If skipped docs is greater than 0, read the skip report — see below.

Once you are satisfied, the .old-* directory can be deleted manually to free space. Keep it until then.

Skipped / degraded documents

<root>/reindex-skipped.ndjson (or --report) gets one JSON line per document that could not be carried over perfectly:

kindMeaning
missing_bodyThe raw message blob is missing; the message is indexed with an empty body and its old sender fields
skipped_document / unreadable_documentThe document could not be read/rebuilt — that message is not in the new index
mixed_uid_stateThe account's UID state was inconsistent before the rebuild (the rebuild still succeeds)

The file contains plain-text subjects and addresses and is created with owner-only permissions; treat it like archive data.

Rolling back

It failed before the swap

Nothing to do. The live index was never modified and the temporary bichon-indices.new directory was removed. Fix the cause and run the command again.

The swap failed halfway (rare)

The tool tries to rename the previous index back. If that also fails, it prints:

Rollback failed. Your previous indexes are safe at <index-parent>/bichon-indices.old-...

Stop the server, then rename that directory back to bichon-indices manually and start the server.

It succeeded, but the result is wrong

Stop the server and swap the directories back:

cd <index-parent>
mv bichon-indices bichon-indices.reindexed-$(date +%Y%m%d-%H%M%S)
mv bichon-indices.old-<timestamp> bichon-indices

Then start the server.

  • Community: the old index is still on the old format, so the server goes back into the 503 "rebuild required" maintenance page. That is expected — the rollback is for diagnosing and re-running the rebuild, not for serving the new version on the old index.
  • Pro/Enterprise: the server starts on the old index.
  • UID counters were persisted before the swap and only ever move forward, so a rollback never re-issues a UID; it may skip some numbers.
  • Do not delete .old-* until the new index is verified. A later reindex creates a new timestamped backup and leaves the old one alone.

Upgrades and the maintenance page

On the Community build, startup checks each index directory's INDEX_FORMAT marker. A missing or older marker puts the service into maintenance mode with a 503 page pointing at bichon-admin reindex instead of silently serving stale results. The Pro/Enterprise build does not currently show that page — after an upgrade that changes the index format, run the reindex proactively.

Whenever a release mentions an index-format change (or a tokenizer/field change), follow this same procedure: stop the service, reindex, verify, and delete the .old-* directory after a while.

On this page