# 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. Bitcask never fsyncs per
write (`DurabilityInner::sync()` is a no-op for it); durability comes from
write-buffer flush plus an explicit `flush()` / `close()`, or the periodic
`Flusher`. A process crash is safe (the log is in page cache), a power loss is
not — the loss window is the unflushed write buffer (1 MB per shard by default).
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.