Skip to content

Commit b91a4fe

Browse files
authored
Merge pull request #383 from SomberNight/202606_clear_excess_undo_info
utxo db: incrementally clear excess undo infos as we go
2 parents 8e9c79b + 9835ef9 commit b91a4fe

2 files changed

Lines changed: 34 additions & 10 deletions

File tree

src/electrumx/server/db.py

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -398,10 +398,18 @@ def _flush_utxo_db(self, batch, flush_data: FlushData) -> None:
398398
batch_put(b'u' + hashX + suffix, value_sats)
399399
flush_data.adds.clear()
400400

401-
# New undo information
401+
# Add new undo information
402402
self.flush_undo_infos(batch_put, flush_data.undo_infos)
403403
flush_data.undo_infos.clear()
404-
404+
# Delete old undo information
405+
if not self.utxo_db.for_sync: # undo infos were only added if we are nearly caught up anyway
406+
old_min_height = max(0, self.min_undo_height(self.db_height))
407+
new_min_height = max(0, self.min_undo_height(flush_data.height))
408+
for h in range(old_min_height, new_min_height):
409+
key = self.undo_key(h)
410+
batch_delete(key)
411+
412+
# Log stats
405413
if self.utxo_db.for_sync:
406414
block_count = flush_data.height - self.db_height
407415
tx_count = flush_data.tx_count - self.db_tx_count
@@ -632,21 +640,23 @@ def write_raw_block(self, block: bytes, height: int) -> None:
632640
with util.open_truncate(self.raw_block_path(height)) as f:
633641
f.write(block)
634642
# Delete old blocks to prevent them accumulating
635-
try:
636-
del_height = self.min_undo_height(height) - 1
637-
os.remove(self.raw_block_path(del_height))
638-
except FileNotFoundError:
639-
pass
643+
del_height = self.min_undo_height(height) - 1
644+
if del_height >= 0:
645+
try:
646+
os.remove(self.raw_block_path(del_height))
647+
except FileNotFoundError:
648+
pass
640649

641650
def clear_excess_undo_info(self) -> None:
642651
'''Clear excess undo info. Only most recent N are kept.'''
643-
prefix = b'U'
652+
# delete aged undo_infos from utxo db
653+
# note: this is just a fallback cleanup path - normally already deleted by _flush_utxo_db()
644654
min_height = self.min_undo_height(self.db_height)
645655
keys = []
646-
for key, _hist in self.utxo_db.iterator(prefix=prefix):
656+
for key, uhist in self.utxo_db.iterator(prefix=b'U'):
647657
height = unpack_block_height(key[-BHEIGHT_LEN:])
648658
if height >= min_height:
649-
break
659+
break # can break as block_height is encoded as big endian
650660
keys.append(key)
651661

652662
if keys:
@@ -656,6 +666,7 @@ def clear_excess_undo_info(self) -> None:
656666
self.logger.info(f'deleted {len(keys):,d} stale undo entries')
657667

658668
# delete old block files
669+
# note: this is just a fallback cleanup path - normally already deleted by write_raw_block()
659670
prefix = self.raw_block_prefix()
660671
paths = [path for path in glob(f'{prefix}[0-9]*')
661672
if len(path) > len(prefix)

tests/server/test_storage.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,19 @@ class FakeError(Exception): pass
5656
assert db.get(b"a") == b"1"
5757

5858

59+
def test_batch_delete(db):
60+
db.put(b"a1", b"")
61+
db.put(b"a2", b"")
62+
db.put(b"a4", b"")
63+
db.put(b"a5", b"")
64+
assert list(db.iterator()) == [(b"a1", b""), (b"a2", b""), (b"a4", b""), (b"a5", b"")]
65+
with db.write_batch() as b:
66+
b.delete(b"a2")
67+
b.delete(b"a3")
68+
b.delete(b"a4")
69+
assert list(db.iterator()) == [(b"a1", b""), (b"a5", b"")]
70+
71+
5972
def test_iterator(db):
6073
"""
6174
The iterator should contain all key/value pairs starting with prefix

0 commit comments

Comments
 (0)