Date: 2026-05-29
Time: 08:25
mayberotate — Conditional WAL File Rotationmayberotate is a size-based rotation guard. It checks whether the current WAL segment file has grown past the configured size limit (maxfile_size, default 10 MB) and, if so, triggers rotation to a new file. This prevents any single WAL file from growing unbounded, which matters for two reasons: it bounds the cost of replaying a single segment during crash recovery, and it lets truncate() delete entire files worth of old records rather than rewriting one monolithic file.
self.lock. Every call site (append, appendbatch, checkpoint) already holds the lock.self.fd points to a file whose current position is below maxfilesize — unless a single write exceeded the limit in one shot (more on this below).self.fd is never None after this method completes (assuming it wasn't None on entry). The rotate() call always opens a new file descriptor.None. It reads instance state only: self.fd (the open file handle) and self.maxfilesize (the rotation threshold).
None. The method communicates entirely through side effects on instance state.
1. Guard on self.fd: If the file descriptor is None (e.g., after close() or during truncate()), do nothing. This is a defensive check — in normal operation fd is always set when this is called.
2. Check file position: self._fd.tell() returns the current write offset — effectively the file size since the file is opened in append mode ("ab").
3. Compare against threshold: If the position is at or above maxfilesize, delegate to self.rotate().
That's it — three lines doing one thing.
When rotation triggers:
.wal file is created and opened for append.self.currentfile and self._fd are updated to point to the new segment.When rotation does not trigger: no side effects at all.
None explicit. If tell() fails (e.g., on a closed fd), the exception propagates to the caller. If rotate() fails to create the new file (permissions, disk full), that also propagates uncaught. The method trusts that fd is in a valid state when called.
Called as the final step in every write path — after dosync(), never before:
# In append():
self._fd.write(data)
self._do_sync()
self._maybe_rotate() # rotate AFTER the write is durable
# Same pattern in append_batch() and checkpoint()
The ordering is deliberate: the write and sync happen to the current file first, so the data is durable before rotation closes that file. If rotation were called before sync, the fsync inside rotate() would still cover it (since rotate flushes before closing), but the current ordering makes the intent clearer and keeps dosync's batch-counting logic coherent.
Caller obligation: callers must hold self._lock before calling this method. There is no internal locking.
self._rotate() — does the actual file creation and descriptor swap.self._fd.tell() — Python file object's position query. Works correctly with "ab" mode on POSIX: the OS sets the position to end-of-file on each write, so tell() reflects the true file size.1. A single write can exceed maxfilesize. If a batch is larger than the limit, maybe_rotate will rotate *after* that write, meaning one segment can temporarily exceed the cap. The check is >=, not a pre-write check, so this is by design — it's a soft limit.
2. _fd opened in append mode. tell() only equals file size if the file was opened with "ab". If someone changed the open mode, tell() could report a misleading position.
3. Called under lock. Nothing prevents a direct call without the lock — the method doesn't acquire it and doesn't assert it's held.
wal-rotation-is-post-write — Rotation is checked after each write is synced, never before; a single write can produce a segment larger than maxfile_size.wal-max-file-size-is-soft-limit — maxfile_size is a soft cap: the check triggers rotation for the *next* write, it does not prevent the current write from exceeding the limit.maybe-rotate-requires-lock — mayberotate must be called under self.lock but does not acquire or assert the lock itself; all three call sites (append, appendbatch, checkpoint) satisfy this obligation.rotation-preserves-fd-invariant — After mayberotate returns, self.fd is guaranteed non-None and open for writing (assuming it was non-None on entry), because rotate() always opens a new file.