# Known Caveats
## CAS and update block shard on I/O
`VarTree::cas`, `VarTree::update`, `VarMap::cas`, and `VarMap::update` hold the
shard mutex while reading the current value (`read_value_locked`). On a
block-cache miss this means a `pread` syscall happens under the lock — all
writes (and other CAS/update operations) targeting the same shard are blocked
until the read completes.
`ConstTree` and `ConstMap` variants of `cas` and `update` are **not** affected
because values are stored inline in the index (no disk I/O needed).
**Impact:** Under heavy CAS/update load with poor cache hit rate, write latency
for the affected shard can spike to disk-read latency.
**Mitigation:** Pre-warm the block cache (`warmup()`) before entering
CAS/update-heavy workloads, or increase cache capacity so working-set blocks
stay resident.
## migrate() memory for HashMap trees
`ConstMap::migrate` and `VarMap::migrate` collect all keys of each shard into a
`Vec` before iterating, because the shard index lock cannot be held across
`put`/`delete` calls (which re-acquire it).
For shards with hundreds of millions of keys this causes a transient memory
spike of `O(keys_per_shard * K)` bytes.
`ConstTree::migrate` and `VarTree::migrate` walk the lock-free SkipList
directly and are **not** affected.
**Mitigation:** If memory is tight, consider migrating in batches per shard
prefix range or switching to a SkipList-based tree type for the migration step.
## `enable_fsync` is a no-op on the Bitcask write path
`Config::enable_fsync` is read by FixedStore only and never itself triggers a
Bitcask per-write fsync (`DurabilityInner::sync()` is a no-op for it).
Configured `sync_batch_size` or `sync_interval` thresholds can trigger a
post-write Bitcask barrier; the background `Db` flush tick supplies the
deadline when writes are followed by silence. Dirty shards are fsynced each
tick pass. The loss window, when every pass completes and every barrier
succeeds, is `interval + 2 × pass duration` (default interval 1 s). A process
crash is still safe (the log is in page cache) even if the tick is disabled
with `flush: None`; a power loss then has no finite bound. `Db::close` is a
power-loss barrier for dirty Bitcask data and checkpoints the transaction log.
Use `Db::clean_shutdown` or `Db::shutdown` when the next FixedStore open should
also use clean recovery.
For a transaction the equivalent knob is per-transaction: `tx.durable()` fsyncs
the transaction log before the commit point. It is **off by default**, so an
ordinary `commit()` survives a process kill but not a power loss.
## A counted source must be opened through `Db`
A counter carrier persists `counters_dirty` into `db.info`, which the direct
openers (`open_hooked` and friends) have no `Db` to reach. They reject such a
hook with `DbError::CountersRequireManagedOpen`. The byte-level `ConstTree` /
`ConstMap` / `VarTree` / `VarMap` openers underneath take a `WriteHook<K>` with
no value type and no such check — a hand-assembled adapter wrapping a counted
carrier is reachable but unsupported.
## `LsmTree` sits outside three engine guarantees
`LsmTree` is not a transaction participant. Counters and unique namespaces
attached to it **silently never run** (no error, no delta, no claim); a change
stream and `watch()` are rejected at open. Attach neither `reg.counted` nor
`reg.unique_index` to an `LsmTree` source.
## Hooks inside `LsmTree::atomic` run under the stripe lock
Every other family buffers hook events and replays them after the lock drops.
`LsmTree::atomic` calls them synchronously while the stripe lock is held, so a
hook that re-enters the same collection deadlocks. Tracked in libfunc/armour#119.