@@ -37,17 +37,13 @@ declareCounter portal_pruning_counter,
37
37
" Number of pruning events which occured during the node's uptime" ,
38
38
labels = [" protocol_id" ]
39
39
40
- declareGauge portal_pruning_deleted_elements ,
41
- " Number of elements deleted in the last pruning" , labels = [" protocol_id" ]
40
+ declareGauge portal_pruning_used_size ,
41
+ " Total used size after the last pruning" , labels = [" protocol_id" ]
42
42
43
- const
44
- contentDeletionFraction = 0.05 # # 5% of the content will be deleted when the
45
- # # storage capacity is hit and radius gets adjusted.
43
+ declareGauge portal_pruning_size,
44
+ " Total size after the last pruning" , labels = [" protocol_id" ]
46
45
47
46
type
48
- RowInfo =
49
- tuple [contentId: array [32 , byte ], payloadLength: int64 , distance: array [32 , byte ]]
50
-
51
47
ContentDB * = ref object
52
48
backend: SqStoreRef
53
49
kv: KvStoreRef
60
56
vacuumStmt: SqliteStmt [NoParams , void ]
61
57
contentCountStmt: SqliteStmt [NoParams , int64 ]
62
58
contentSizeStmt: SqliteStmt [NoParams , int64 ]
63
- getAllOrderedByDistanceStmt: SqliteStmt [array [32 , byte ], RowInfo ]
64
59
deleteOutOfRadiusStmt: SqliteStmt [(array [32 , byte ], array [32 , byte ]), void ]
65
60
largestDistanceStmt: SqliteStmt [array [32 , byte ], array [32 , byte ]]
66
61
@@ -234,12 +229,6 @@ proc new*(
234
229
let contentCountStmt =
235
230
db.prepareStmt (" SELECT COUNT(key) FROM kvstore;" , NoParams , int64 )[]
236
231
237
- let getAllOrderedByDistanceStmt = db.prepareStmt (
238
- " SELECT key, length(value), xorDistance(?, key) as distance FROM kvstore ORDER BY distance DESC" ,
239
- array [32 , byte ],
240
- RowInfo ,
241
- )[]
242
-
243
232
let deleteOutOfRadiusStmt = db.prepareStmt (
244
233
" DELETE FROM kvstore WHERE isInRadius(?, key, ?) == 0" ,
245
234
(array [32 , byte ], array [32 , byte ]),
@@ -261,7 +250,6 @@ proc new*(
261
250
vacuumStmt: vacuumStmt,
262
251
contentSizeStmt: contentSizeStmt,
263
252
contentCountStmt: contentCountStmt,
264
- getAllOrderedByDistanceStmt: getAllOrderedByDistanceStmt,
265
253
deleteOutOfRadiusStmt: deleteOutOfRadiusStmt,
266
254
largestDistanceStmt: largestDistanceStmt,
267
255
)
@@ -280,7 +268,6 @@ proc close*(db: ContentDB) =
280
268
db.vacuumStmt.disposeSafe ()
281
269
db.contentCountStmt.disposeSafe ()
282
270
db.contentSizeStmt.disposeSafe ()
283
- db.getAllOrderedByDistanceStmt.disposeSafe ()
284
271
db.deleteOutOfRadiusStmt.disposeSafe ()
285
272
db.largestDistanceStmt.disposeSafe ()
286
273
discard db.kv.close ()
@@ -325,36 +312,6 @@ proc del*(db: ContentDB, key: ContentId) =
325
312
326
313
# # Pruning related calls
327
314
328
- proc deleteContentFraction * (
329
- db: ContentDB , target: UInt256 , fraction: float64
330
- ): (UInt256 , int64 , int64 , int64 ) =
331
- # # Deletes at most `fraction` percent of content from the database.
332
- # # The content furthest from the provided `target` is deleted first.
333
- # TODO : The usage of `db.contentSize()` for the deletion calculation versus
334
- # `db.usedSize()` for the pruning threshold leads sometimes to some unexpected
335
- # results of how much content gets up deleted.
336
- doAssert (fraction > 0 and fraction < 1 , " Deleted fraction should be > 0 and < 1" )
337
-
338
- let totalContentSize = db.contentSize ()
339
- let bytesToDelete = int64 (fraction * float64 (totalContentSize))
340
- var deletedElements: int64 = 0
341
-
342
- var ri: RowInfo
343
- var deletedBytes: int64 = 0
344
- let targetBytes = target.toBytesBE ()
345
- for e in db.getAllOrderedByDistanceStmt.exec (targetBytes, ri):
346
- if deletedBytes + ri.payloadLength <= bytesToDelete:
347
- db.del (ri.contentId)
348
- deletedBytes = deletedBytes + ri.payloadLength
349
- inc deletedElements
350
- else :
351
- return (
352
- UInt256 .fromBytesBE (ri.distance),
353
- deletedBytes,
354
- totalContentSize,
355
- deletedElements,
356
- )
357
-
358
315
proc reclaimSpace * (db: ContentDB ): void =
359
316
# # Runs sqlite VACUUM commands which rebuilds the db, repacking it into a
360
317
# # minimal amount of disk space.
@@ -390,9 +347,33 @@ proc forcePrune*(db: ContentDB, localId: UInt256, radius: UInt256) =
390
347
db.reclaimAndTruncate ()
391
348
notice " Finished database pruning"
392
349
393
- proc putAndPrune * (db: ContentDB , key: ContentId , value: openArray [byte ]): PutResult =
394
- db.put (key, value)
350
+ proc prune * (db: ContentDB ) =
351
+ # # Decrease the radius with `radiusDecreasePercentage` and prune the content
352
+ # # outside of the new radius.
353
+ const radiusDecreasePercentage = 5
354
+ # The amount here is somewhat arbitrary but should be big enough to not
355
+ # constantly require pruning. If it is too small, it would adjust the radius
356
+ # so often that the network might not be able to keep up with the current
357
+ # radius of the node. At the same time, it would iterate over the content also
358
+ # way to often. If the amount is too big it could render the node unresponsive
359
+ # for too long.
360
+
361
+ let newRadius = db.dataRadius div 100 * (100 - radiusDecreasePercentage)
362
+
363
+ info " Pruning content outside of radius" ,
364
+ oldRadius = db.dataRadius, newRadius = newRadius
365
+ db.deleteContentOutOfRadius (db.localId, newRadius)
366
+ db.dataRadius = newRadius
395
367
368
+ let usedSize = db.usedSize ()
369
+ let size = db.size ()
370
+ portal_pruning_counter.inc ()
371
+ portal_pruning_used_size.set (usedSize)
372
+ portal_pruning_size.set (size)
373
+
374
+ info " Finished pruning content" , usedSize, size, storageCapacity = db.storageCapacity
375
+
376
+ proc putAndPrune * (db: ContentDB , key: ContentId , value: openArray [byte ]) =
396
377
# The used size is used as pruning threshold. This means that the database
397
378
# size will reach the size specified in db.storageCapacity and will stay
398
379
# around that size throughout the node's lifetime, as after content deletion
@@ -404,55 +385,12 @@ proc putAndPrune*(db: ContentDB, key: ContentId, value: openArray[byte]): PutRes
404
385
# static radius.
405
386
# When not using the `forcePrune` functionality, pruning to the required
406
387
# capacity will not be very effictive and free pages will not be returned.
407
- let dbSize = db.usedSize ()
408
-
409
- if dbSize < int64 (db.storageCapacity):
410
- return PutResult (kind: ContentStored )
411
- else :
412
- # Note:
413
- # An approach of a deleting a full fraction is chosen here, in an attempt
414
- # to not continuously require radius updates, which could have a negative
415
- # impact on the network. However this should be further investigated, as
416
- # doing a large fraction deletion could cause a temporary node performance
417
- # degradation. The `contentDeletionFraction` might need further tuning or
418
- # one could opt for a much more granular approach using sql statement
419
- # in the trend of:
420
- # "SELECT key FROM kvstore ORDER BY xorDistance(?, key) DESC LIMIT 1"
421
- # Potential adjusting the LIMIT for how many items require deletion.
422
- let (distanceOfFurthestElement, deletedBytes, totalContentSize, deletedElements) =
423
- db.deleteContentFraction (db.localId, contentDeletionFraction)
424
-
425
- let deletedFraction = float64 (deletedBytes) / float64 (totalContentSize)
426
- info " Deleted content fraction" , deletedBytes, deletedElements, deletedFraction
427
-
428
- return PutResult (
429
- kind: DbPruned ,
430
- distanceOfFurthestElement: distanceOfFurthestElement,
431
- deletedFraction: deletedFraction,
432
- deletedElements: deletedElements,
433
- )
388
+ db.put (key, value)
434
389
435
- proc adjustRadius (
436
- db: ContentDB , deletedFraction: float64 , distanceOfFurthestElement: UInt256
437
- ) =
438
- # Invert fraction as the UInt256 implementation does not support
439
- # multiplication by float
440
- let invertedFractionAsInt = int64 (1.0 / deletedFraction)
441
- let scaledRadius = db.dataRadius div u256 (invertedFractionAsInt)
442
-
443
- # Choose a larger value to avoid the situation where the
444
- # `distanceOfFurthestElement is very close to the local id so that the local
445
- # radius would end up too small to accept any more data to the database.
446
- # If scaledRadius radius will be larger it will still contain all elements.
447
- let newRadius = max (scaledRadius, distanceOfFurthestElement)
448
-
449
- info " Database radius adjusted" ,
450
- oldRadius = db.dataRadius, newRadius = newRadius, distanceOfFurthestElement
451
-
452
- # Both scaledRadius and distanceOfFurthestElement are smaller than current
453
- # dataRadius, so the radius will constantly decrease through the node its
454
- # lifetime.
455
- db.dataRadius = newRadius
390
+ while db.usedSize () >= int64 (db.storageCapacity):
391
+ # Note: This should typically only happen once, but if the content is not
392
+ # distributed uniformly over the id range, it could happen multiple times.
393
+ db.prune ()
456
394
457
395
proc createGetHandler * (db: ContentDB ): DbGetHandler =
458
396
return (
@@ -477,21 +415,7 @@ proc createStoreHandler*(db: ContentDB, cfg: RadiusConfig): DbStoreHandler =
477
415
of Dynamic :
478
416
# In case of dynamic radius, the radius gets adjusted based on the
479
417
# to storage capacity and content gets pruned accordingly.
480
- let res = db.putAndPrune (contentId, content)
481
- if res.kind == DbPruned :
482
- portal_pruning_counter.inc ()
483
- portal_pruning_deleted_elements.set (res.deletedElements.int64 )
484
-
485
- if res.deletedFraction > 0.0 :
486
- db.adjustRadius (res.deletedFraction, res.distanceOfFurthestElement)
487
- else :
488
- # Note:
489
- # This can occur when the furthest content is bigger than the fraction
490
- # size. This is unlikely to happen as it would require either very
491
- # small storage capacity or a very small `contentDeletionFraction`
492
- # combined with some big content.
493
- info " Database pruning attempt resulted in no content deleted"
494
- return
418
+ db.putAndPrune (contentId, content)
495
419
of Static :
496
420
# If the radius is static, it may never be adjusted, database capacity
497
421
# is disabled and no pruning is ever done.
0 commit comments