Function: delete in b-tree-storage-engine/btree.py

Date: 2026-05-29

Time: 07:38

BTree.delete(self, key)

Purpose

Removes a key-value pair from the disk-backed B-tree. This is the public API entry point for deletion — it coordinates the recursive tree traversal (delete), metadata bookkeeping (decrementing totalkeys), and WAL-based crash safety (atomic commit). Without this method, the B-tree would be append-only.

Contract

Parameters

| Parameter | Type | Description |

|-----------|------|-------------|

| key | str | The key to delete. Must match the string encoding used during insertion. |

Return Value

Returns boolTrue if the key was found and deleted, False if the key was not present. The caller does not need to handle exceptions under normal operation; a False return is the "not found" signal.

Algorithm

1. Reset I/O countersself.pm.resetcounters() zeros the pagesread/pages_written counters so post-operation stats reflect only this deletion.

2. Read metadata — fetches the current root page number, tree height, totalkeys, nextfree page, and free_head (free-list pointer) from page 0.

3. Recursive delete — calls _delete(root, key, height), which traverses the tree top-down:

4. Metadata update — if found is truthy (True or 'empty'), re-reads metadata to get the latest nextfree/freehead (which may have changed if delete freed a page), then writes updated metadata with totalkeys - 1 through the WAL.

5. WAL commitself.wal.commit(self.pm) calls fsync on the data file, then truncates and fsyncs the WAL. This is the atomic durability boundary — if a crash occurs before this point, WAL recovery replays the writes; after this point, the WAL is empty.

6. Returnbool(found) coerces 'empty' to True.

Side Effects

Error Handling

No explicit exception handling. Potential failure modes:

Critical Observation: Free-List / WAL Inconsistency

When delete removes an empty leaf at depth 2, it calls self.pm.freepage(childpage). This method writes directly to the data file via pm.writepage and pm.writemetanot through walwritepage/walwritemeta. This means the free-list mutation is not crash-safe. A crash after freepage but before wal.commit could leave the free list in an inconsistent state while WAL recovery replays the old page contents. The metadata page gets overwritten twice in this path: once by freepage (unprotected) and once by walwritemeta (protected), so the WAL-protected version wins on recovery — but the freed page's free-list node content may be lost.

Usage Patterns


tree = BTree('/tmp/mydb')
tree.put('user:123', b'{"name": "Alice"}')

deleted = tree.delete('user:123')   # True
deleted = tree.delete('user:123')   # False — already gone

tree.close()

Callers should check the return value if they need to distinguish "key removed" from "key was never there." There is no deleteifexists variant; the return value serves that purpose.

Dependencies

| Dependency | Role |

|------------|------|

| PageManager | Fixed-size page I/O to the data file |

| WAL | Write-ahead log for atomic durability |

| deserializeleaf, serializeleaf | Leaf page serialization |

| deserializeinternal, serializeinternal | Internal page serialization |

| bisectleft, bisectright | Binary search for key position |

| struct | Binary packing/unpacking of page headers and metadata |

Beliefs