crimson/os/seastore/journal: dispatch records greedily while io slots are free
`RecordSubmitter` dispatches a small record (or the pending batch) only
once the journal is fully drained (`state == IDLE`): `submit()` takes
the direct-write path only at IDLE, and `decrement_io_with_flush()`
flushes the pending batch only after *all* outstanding io has
completed. The remaining dispatch triggers never fire for 4K-class
records: `preferred_fullness` measures the padding ratio of a single
block, and small records do not reach the batch capacity.
At low-to-mid client queue depth this degenerates into strict
alternation: one journal io in flight, each batch carrying only the
records that arrived during the previous device write. Measured with
4K randwrite QD20 against a single crimson OSD (circular-bounded
journal): io_depth 1.07, 1.9 records/io, ~2.2ms `submit_record`
latency -- 1.5x the raw device write+flush latency, since each record
first waits ~0.5x for the in-flight io to drain. `io_depth_limit`
(default 5) is never approached.
Dispatch whenever the device has a free io slot instead:
- `submit()`: take the direct-write path whenever `state != FULL`;
records are written greedily while slots are free, and batching
kicks in once `io_depth_limit` is reached.
- `decrement_io_with_flush()`: flush the pending batch into the freed
slot instead of waiting for IDLE.
Ordering is unaffected: a batch's `write_base` is assigned at dispatch
from `journal_allocator.get_written_to()`, so the on-disk layout
follows submission order; completions are reported in order via the
`device_submission` exit turnstile and the exclusive `finalize` phase,
so a record is never acked before all earlier records are durable.
Replay stops at the first invalid record, and anything beyond such a
hole was never acked. The same concurrency already occurs through the
`SUBMIT_FULL` path under high load; this extends it to the
low-fullness regime.
## Benchmark
Ceph 21.3.0 dev (RelWithDebInfo), single crimson OSD, 4 reactors
pinned to 4 cores, fio rbd engine on 4 disjoint cores, size-1 pool,
24 GiB prefilled image, consumer NVMe without PLP (~0.7ms fdatasync).