Skip to content

Commit

Permalink
Lazily delete sub-trees
Browse files Browse the repository at this point in the history
why:
  This gives some control of the memory used to keep the deleted vertices
  in the cached layers. For larger sub-trees, keys and vertices might be
  on the persistent backend to a large extend. This would pull an amount
  of extra information from the backend into the cached layer.

  For lazy deleting it is enough to remember sub-trees by a small set of
  (at most 16) sub-roots to be processed when storing persistent data.
  Marking the tree root deleted immediately allows to let most of the code
  base work as before.
  • Loading branch information
mjfh committed Aug 13, 2024
1 parent 59f236f commit 9ed7f79
Show file tree
Hide file tree
Showing 10 changed files with 582 additions and 92 deletions.
9 changes: 9 additions & 0 deletions nimbus/db/aristo/aristo_constants.nim
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,15 @@ const
## LRU cache size for accounts that have storage, see `.accLeaves` and
## `.stoLeaves` fields of the main descriptor.

DELETE_SUBTREE_VERTICES_MAX* = 25
## Maximum number of vertices for a tree to be deleted instantly. If the
## tree is larger, only the sub-tree root will be deleted immediately and
## subsequent entries will be deleted not until the cache layers are saved
## to the backend.
##
## Set to zero to disable in which case all sub-trees are deleted
## immediately.

static:
# must stay away from `VertexID(1)` and `VertexID(2)`
doAssert 2 < LEAST_FREE_VID
Expand Down
127 changes: 127 additions & 0 deletions nimbus/db/aristo/aristo_delete/delete_debug.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# nimbus-eth1
# Copyright (c) 2023-2024 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
# http://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or
# http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed
# except according to those terms.

{.push raises: [].}

import
std/[math, strformat, times],
chronicles,
".."/[aristo_desc, aristo_get, aristo_profile]

export
aristo_profile.toStr

type
SubTreeStats* = tuple
nVtxs: int ## Number of vertices in sub-tree
nLeafs: int ## Number of leafs in sub-tree
depthMax: int ## Maximal vertex path length
nStoCache: int ## Size of storage leafs cache
elapsed: Duration ## Time spent analysing

SubTreeStatsAccu* = tuple
count: int ## Number of entries
sVtxs, qVtxs: float ## Sum and square sum of `.nVtxs`
sLeafs, qLeafs: float ## Sum and square sum of `.nLeafs`
sDepth, qDepth: float ## Sum and square sum of `.depthMax`
sElapsed: Duration ## Sum of `.elapsed`

SubTreeDist* = tuple
count: int ## Number of entries
mVtxs, dVtxs: float ## Mean and std deviation of `.nVtxs`
mLeafs, dLeafs: float ## Mean and std deviation of `.nLeafs`
mDepth, dDepth: float ## Mean and std deviation of `.depthMax`

# ------------------------------------------------------------------------------
# Prival helper
# ------------------------------------------------------------------------------

proc analyseSubTreeImpl(
db: AristoDbRef; # Database, top layer
rvid: RootedVertexID; # Root vertex
depth: int; # Recursion depth
stats: var SubTreeStats; # Statistics
) =
let (vtx, _) = db.getVtxRc(rvid).valueOr:
return

stats.nVtxs.inc

if stats.depthMax < depth:
stats.depthMax = depth

case vtx.vType:
of Branch:
for n in 0..15:
if vtx.bVid[n].isValid:
db.analyseSubTreeImpl((rvid.root,vtx.bVid[n]), depth+1, stats)
of Leaf:
stats.nLeafs.inc


func evalDist(count: int; sum, sqSum: float): tuple[mean, stdDev: float] =
result.mean = sum / count.float

let
sqMean = sqSum / count.float
meanSq = result.mean * result.mean

# Mathematically, `meanSq <= sqMean` but there might be rounding errors
# if `meanSq` and `sqMean` are approximately the same.
sigma = sqMean - min(meanSq,sqMean)

result.stdDev = sigma.sqrt

# ------------------------------------------------------------------------------
# Public analysis tools
# ------------------------------------------------------------------------------

proc analyseSubTree*(
db: AristoDbRef; # Database, top layer
rvid: RootedVertexID; # Root vertex
minVtxs: int; # Accumulate if `minVtxs` <= `.nVtxs`
accu: var SubTreeStatsAccu; # For accumulated statistics
): SubTreeStats =
let start = getTime()
db.analyseSubTreeImpl(rvid, 1, result)
result.nStoCache = db.stoLeaves.len

if minVtxs <= result.nVtxs:
accu.count.inc
accu.sVtxs += result.nVtxs.float
accu.qVtxs += (result.nVtxs * result.nVtxs).float
accu.sLeafs += result.nLeafs.float
accu.qLeafs += (result.nLeafs * result.nLeafs).float
accu.sDepth += result.depthMax.float
accu.qDepth += (result.depthMax * result.depthMax).float

result.elapsed = getTime() - start
accu.sElapsed += result.elapsed # Unconditionally collecrd


func stats*(a: SubTreeStatsAccu): SubTreeDist =
result.count = a.count
(result.mVtxs, result.dVtxs) = evalDist(a.count, a.sVtxs, a.qVtxs)
(result.mLeafs, result.dLeafs) = evalDist(a.count, a.sLeafs, a.qLeafs)
(result.mDepth, result.dDepth) = evalDist(a.count, a.sDepth, a.qDepth)

func strStats*(
a: SubTreeStatsAccu;
): tuple[count, vtxs, leafs, depth, elapsed: string] =
let w = a.stats()
result.count = $w.count
result.elapsed = a.sElapsed.toStr
result.vtxs = &"{w.mVtxs:.1f}[{w.dVtxs:.1f}]"
result.leafs = &"{w.mLeafs:.1f}[{w.dLeafs:.1f}]"
result.depth = &"{w.mDepth:.1f}[{w.dDepth:.1f}]"

# ------------------------------------------------------------------------------
# End
# ------------------------------------------------------------------------------
93 changes: 6 additions & 87 deletions nimbus/db/aristo/aristo_delete/delete_subtree.nim
Original file line number Diff line number Diff line change
Expand Up @@ -8,94 +8,13 @@
# at your option. This file may not be copied, modified, or distributed
# except according to those terms.

{.push raises: [].}
import ../aristo_constants

import
eth/common,
".."/[aristo_desc, aristo_get, aristo_layers],
./delete_helpers
when DELETE_SUBTREE_VERTICES_MAX == 0:
import ./delete_subtree_now as del_sub
else:
import ./delete_subtree_lazy as del_sub

# ------------------------------------------------------------------------------
# Private heplers
# ------------------------------------------------------------------------------
export del_sub

proc delSubTreeNow(
db: AristoDbRef;
rvid: RootedVertexID;
): Result[void,AristoError] =
## Delete sub-tree now
let (vtx, _) = db.getVtxRc(rvid).valueOr:
if error == GetVtxNotFound:
return ok()
return err(error)

if vtx.vType == Branch:
for n in 0..15:
if vtx.bVid[n].isValid:
? db.delSubTreeNow((rvid.root,vtx.bVid[n]))

db.disposeOfVtx(rvid)

ok()


proc delStoTreeNow(
db: AristoDbRef; # Database, top layer
rvid: RootedVertexID; # Root vertex
accPath: Hash256; # Accounts cache designator
stoPath: NibblesBuf; # Current storage path
): Result[void,AristoError] =
## Implementation of *delete* sub-trie.

let (vtx, _) = db.getVtxRc(rvid).valueOr:
if error == GetVtxNotFound:
return ok()
return err(error)

case vtx.vType
of Branch:
for i in 0..15:
if vtx.bVid[i].isValid:
? db.delStoTreeNow(
(rvid.root, vtx.bVid[i]), accPath,
stoPath & vtx.ePfx & NibblesBuf.nibble(byte i))

of Leaf:
let stoPath = Hash256(data: (stoPath & vtx.lPfx).getBytes())
db.layersPutStoLeaf(AccountKey.mixUp(accPath, stoPath), nil)

db.disposeOfVtx(rvid)

ok()

# ------------------------------------------------------------------------------
# Public functions
# ------------------------------------------------------------------------------

proc delSubTreeImpl*(
db: AristoDbRef;
root: VertexID;
): Result[void,AristoError] =
discard db.getVtxRc((root, root)).valueOr:
if error == GetVtxNotFound:
return ok()
return err(error)
db.delSubTreeNow (root,root)


proc delStoTreeImpl*(
db: AristoDbRef; # Database, top layer
rvid: RootedVertexID; # Root vertex
accPath: Hash256;
): Result[void,AristoError] =
## Implementation of *delete* sub-trie.
discard db.getVtxRc(rvid).valueOr:
if error == GetVtxNotFound:
return ok()
return err(error)
db.delStoTreeNow(rvid, accPath, NibblesBuf())

# ------------------------------------------------------------------------------
# End
# ------------------------------------------------------------------------------

Loading

0 comments on commit 9ed7f79

Please sign in to comment.