Campaign ops runbook#
HPC ingest is partitioned writers, not multi-writer inside one LMDB
env. Each shard_XXXX/ is an independent ConCorpus. Route
traj_id % n_shards to that shard. One process owns each shard_id
across the job. If more writers share a shard id, each node keeps a
private tree, drains to a unique dest, then join-drained.
Default shard count is 64 (DEFAULT_N_SHARDS). Power-of-two counts
keep routing simple.
This page is the campaign ops runbook. Multi-shard writers are covered
by src/shard.rs tests (parallel_writers_different_shards,
join_drained_roots_*) and tests/cli_refuse.rs (mint/overwrite
refuses).
Layout#
<root>/
shards.json # { "n_shards": 64, "version": 1 }
shard_0000/data.mdb
shard_0001/data.mdb
...
shards.json is the manifest. shard-init writes it.
shard-ingest, drain, join-drained, compact-join, and
shard-select require it. Commands that only read (shard-select,
compact-join) use open_existing and do not mint a missing root.
One writer per shard (pattern A)#
Use this when the job can assign shard ids uniquely (SLURM_PROCID % n_shards, or one rank owns shard (S)).
# once, on a shared or node-local root
readcon-db shard-init /scratch/$USER/campaign --shards 64
# each writer: --start-id must satisfy start-id ≡ shard (mod n_shards)
readcon-db shard-ingest /scratch/$USER/campaign --shard $S --start-id $T \
--units '{"length":"angstrom","energy":"eV","time":"fs"}' run.con
shard-ingest refuses a missing manifest (does not mint). It refuses a
--start-id that does not route to --shard. After the first file it
skips traj ids that would land on another shard so one writer never
touches a foreign env.
Close every writer (drop the process, or rkrdb_close) before
drain. LMDB does not allow a snapshot while a write txn is open.
If every shard id has exactly one writer, drain to one dest that
does not already hold those data.mdb files:
readcon-db drain /scratch/$USER/campaign /lustre/proj/campaign/drained
readcon-db compact-join /lustre/proj/campaign/drained /lustre/proj/campaign_single
drain compact-snapshots data.mdb only (no lock.mdb) and refuses
dest overwrite. compact-join is the single-root join
(open_existing); dest must not exist.
Overlapping shard ids (pattern B)#
Use this when many nodes write the same shard ids (more writers than shards, or each node owns a full 0..N-1 set on burst-buffer).
Each node keeps a private tree, drains to a unique dest, then one join merges those dests:
# per node, on burst buffer (Frontier /mnt/bb, Aurora /tmp, ...)
readcon-db shard-init /mnt/bb/$USER/campaign --shards 64
readcon-db shard-ingest /mnt/bb/$USER/campaign --shard $S --start-id $T \
--units '{"length":"angstrom","energy":"eV","time":"fs"}' run.con
# close writers, then drain to a dest no other node uses
readcon-db drain /mnt/bb/$USER/campaign \
/lustre/orion/proj/campaign/node_$SLURM_NODEID
# once, after every node drained
readcon-db join-drained /lustre/orion/proj/campaign_single \
/lustre/orion/proj/campaign/node_*
join-drained requires shards.json on every source, refuses an
existing dest, and refuses traj-id collisions before creating dest.
Give writers disjoint traj id ranges (or disjoint shard sets) so join
does not collide.
drain to a dest that already has shard_XXXX/data.mdb is an error:
two node-local trees that share a shard id must not last-writer-wins.
Select and compact#
# fan-out select; does not mint a missing root
readcon-db shard-select /lustre/orion/proj/campaign/drained --symbol Cu
# one sharded root -> new single-env corpus (dest must not exist)
readcon-db compact-join /lustre/orion/proj/campaign/drained \
/lustre/orion/proj/campaign_single
# reverse: single-env -> new sharded root
readcon-db compact-split /lustre/orion/proj/campaign_single \
/lustre/orion/proj/campaign_reshard --shards 64
# extXYZ only when an external tool demands XYZ on disk
readcon-db compact-export-extxyz /lustre/orion/proj/campaign/drained \
subset.xyz --sharded --symbol Cu
compact-export-extxyz on a sharded root needs --sharded. Without the
flag it refuses (does not open the root as a single-env corpus). Dest
must not exist.
H5MD interchange is on the single-env dest after join:
# Python, dest must not exist
python -c 'from readcon_db import ConCorpus
ConCorpus("/lustre/orion/proj/campaign_single", readonly=True).export_h5md(1, "traj.h5")'
Units#
Stamp units on ingest (--units JSON, or C rkrdb_append_trajectory_units
/ Fortran db_append_units). Aliases canonicalize (A -> angstrom).
Missing units.time is CON fs. set_units converts stored numbers;
append/extend stamp incoming values. CON line-2 units is the
authority.
C / Fortran writers#
The C ABI and Fortran module open one corpus directory
(rkrdb_open / db_open). They do not implement shard routing.
Campaign partition stays on the CLI / Rust ShardedConCorpus.
A C or Fortran rank that owns shard (S) opens the shard directory
after shard-init:
/* traj_id % n_shards == 3 */
rkrdb_open("/scratch/campaign/shard_0003", &id);
rkrdb_append_trajectory_units(id, 3, "run.con",
"{\"length\":\"angstrom\",\"energy\":\"eV\",\"time\":\"fs\"}", &n);
rkrdb_close(id);
Then the same drain / join-drained / compact-join sequence as above.
Same-frame MPI: do not open the env on every rank. Rank 0 of the
caller communicator packs RCSO / RCSB and MPI_Bcasts on that
handle (include/readcon-db-mpi.h, Fortran INTEGER +
rkrdb_bcast_packed_frame_f). Shared open_readonly is the other
legal path when ranks touch different keys. See workflows.
Refuses (do not fight them)#
Command |
Refuse |
|---|---|
|
missing |
|
missing root / missing |
|
missing source |
|
dest exists; a source lacks |
|
dest exists; source missing |
|
sharded root without |
On refuse, dest is not created (or a partial dest is removed). Tests:
tests/cli_refuse.rs, src/shard.rs.
CLI from a prebuilt clib prefix#
Unpack readcon-db-clib-$VERSION-$target.tar.gz (see install).
bin/readcon-db is in that archive when the Release build produced it.
export PATH="$PREFIX/bin:$PATH"
export LD_LIBRARY_PATH="$PREFIX/lib:${LD_LIBRARY_PATH:-}"
readcon-db shard-init /scratch/$USER/campaign --shards 64