Skip to content

4.2.0

Latest

Choose a tag to compare

@cberner cberner released this 17 Aug 19:09
· 24 commits to master since this release

New features

  • Add an experimental cursor API, behind the experimental_cursor feature flag:
    Table::lower_bound_mut() and Table::upper_bound_mut() return a CursorMut pointing at a gap
    between entries, modeled on the standard library's BTreeMap cursors. Inserting sorted data
    through its insert_before() method can be around 3x faster than calling insert() with the
    same data; insert_after() inserts through the gap in descending order at the same speed.
    ReadableTable::lower_bound() and ReadableTable::upper_bound() return a read-only Cursor.
    The feature is unstable and may change incompatibly, or be removed, in any release.
  • Add ReadOnlyTable::get_owned(), ReadOnlyTable::range_owned(),
    ReadOnlyMultimapTable::get_owned(), and ReadOnlyMultimapTable::range_owned(), which
    return the new OwnedAccessGuard, OwnedRange, OwnedMultimapValue, and
    OwnedMultimapRange types. These keep the read transaction alive until they are dropped,
    including the guards yielded by the iterators, which may outlive the iterator that produced
    them.
  • Add Table::entry() and the associated Entry, OccupiedEntry, and VacantEntry
    types, mirroring std::collections::BTreeMap::entry. Supports or_insert,
    or_insert_with, or_insert_with_key, and_modify, and the usual OccupiedEntry
    / VacantEntry accessors.
  • Add ExtractIf::close() to explicitly finalize an extract iterator without removing unread
    entries.

Optimizations

  • Improve write performance: Durability::None commits are about 2x faster, and writes that do not
    split a page are about 15% faster for single-key Durability::None commits and about 6% faster
    for batched writes.
  • Improve write performance when tables of a single WriteTransaction are modified concurrently
    from multiple threads. Writes to separate tables previously serialized on internal locks and
    could be slower than writing from a single thread; they now scale with the number of threads.
    Up to about 4x faster.
  • Optimize Table::pop_first() and Table::pop_last() to be about 2x faster.
  • Optimize inserting in ascending key order. A table loaded in key order occupies about half as
    much space, and loads faster.
  • Optimize Table::retain(), Table::retain_in(), Table::extract_if(), and
    Table::extract_from_if(). Benchmarks on large tables show a 30-100x speedup for retaining and
    an 18-65x speedup for extracting, depending on the fraction of entries affected. Iterating an
    extract iterator from both ends no longer degrades removal batching.
  • Avoid unnecessary write amplification when removing a value that is not present from a multimap
    table. Such a removal is now a no-op.

Minor improvements

  • Table::retain(), Table::retain_in(), Table::extract_if(), and Table::extract_from_if()
    now poison the write transaction if their predicate panics or an internal error prevents
    removals from being applied, causing WriteTransaction::commit() to return
    CommitError::TransactionPoisoned. After an extract iterator returns an error, later calls keep
    returning an error instead of continuing.
  • Enable file space reclamation during non-durable transactions performed while a savepoint exists.
  • Reuse pages freed by a durable write transaction in the next write transaction when no live read
    transaction or savepoint still needs them. Previously, pages were not reused for one additional
    transaction.
  • compact() now returns CompactionError::PersistentSavepointExists or
    CompactionError::EphemeralSavepointExists instead of the misleading
    CompactionError::TransactionInProgress when a savepoint blocks compaction.
  • StorageBackend::close() is now called when opening a database fails and when an I/O error occurs
    while dropping a Database, allowing backends to release their resources on both paths.

Bug fixes

  • Return StorageError::Corrupted instead of panicking when opening or repairing a database with a
    corrupted persistent savepoint record or malformed freed-page record.
  • Return StorageError::Corrupted instead of aborting the process when branch pages form a cycle or
    arbitrarily long chain, or when a corrupted page number could cause a multi-terabyte allocation.
  • Fix WriteTransaction::stats() returning garbage statistics, or panicking when debug assertions
    are enabled, when called while a table is open and modified in the same transaction.
  • Fix a deadlock when a Database was dropped while a WriteTransaction was live. A live
    WriteTransaction now keeps the database open: the transaction remains usable after the
    Database is dropped, and the database closes when the transaction commits, aborts, or is
    dropped.
  • Fix a panic, including one raised while dropping a Database, after check_integrity() returned
    an error. Such a database now refuses to begin a write transaction or to re-run the check,
    returning StorageError::Corrupted, and is no longer recorded as cleanly shut down.
  • Harden against errors and panics raised part way through WriteTransaction::commit(): the
    database now refuses further write transactions until it is closed and reopened (which
    repairs it), instead of risking corruption from continued use after the failed commit.
  • Fix a case where check_integrity() failed to repair the database when the table length was
    corrupted. Such a file previously passed the check and then panicked, including from
    Database::drop.
  • Fix a potential deadlock when removing a value from a multimap table causes its value-set to
    shrink from a subtree back to inline storage, while another table of the same write transaction
    is used concurrently from a different thread.
  • Fix crashes while growing or resizing the database file that could leave it permanently
    unopenable or reported as corrupted on subsequent opens, even though every committed transaction
    was intact and fully recoverable.
  • Fix cases where the database file could grow instead of reusing freed space, and where
    compact() could grow the file or stop before fully shrinking it.
  • Fix Table::get_mut() and Entry::and_modify() to enforce the maximum value size limit.
    Previously these paths could bypass the limit that Table::insert() and the entry() accessors
    enforce.
  • Fix a panic in insert() when a single leaf page accumulated 65536 entries via in-place
    appends, e.g. by inserting a large value and then many small values in ascending key order
    within the same transaction.
  • Fix check_integrity() incorrectly reporting a healthy database as corrupted (and panicking
    in debug builds) after a persistent savepoint was deleted or restored, or when an ephemeral
    Savepoint was dropped from another thread while the same write transaction was committing.
  • Fix a leak of database space when an ephemeral Savepoint was created from one thread while the
    same write transaction was first accessing a table from another thread, and that savepoint was
    later restored. The leaked space was only reclaimed by a full repair.
  • Fix a panic when opening a database file that was externally extended to an invalid size; such
    files are now rejected with StorageError::Corrupted.
  • Fix a hang on Windows when opening a truncated or corrupt database file. Reads past the end of the
    file now return an error instead of looping forever.
  • Fix check_integrity() so that it now returns DatabaseError::TransactionInProgress when an
    ephemeral Savepoint is still alive. Previously the check could invalidate the pages such a
    savepoint referenced while leaving it marked valid, so restoring it afterward could corrupt the
    database.
  • Fix Database::check_integrity() silently discarding transactions committed with
    Durability::None that had not yet been made durable by a later commit; a passing check now
    preserves them (making them durable) instead of rolling them back.
  • Fix a bug that could silently roll back or corrupt durably committed transactions if a crash
    occurred while recovering from an earlier crash. Triggering it required two crashes -- one
    interrupting a commit and another during the subsequent repair on the next open -- and it did
    not affect transactions committed with two-phase commit.
  • Fix new composite types (Option, Vec, tuples, and arrays) of a user-defined type sharing a
    type identity with the same composite of a built-in type when the two happened to have the same
    name. A table using such a composite of a user type can no longer be silently opened under the
    built-in composite (and vice versa); the mismatch is now reported as TableError::TableTypeMismatch.
    Existing databases created by older versions remain readable. If an older database already used
    such a colliding composite name, its stored type identity remains ambiguous and may still open
    under either spelling.