@@ -380,8 +380,10 @@ func (c *collection) hardDeleteDocumentBlocks(
380380 return err
381381 }
382382
383+ cache := blockCache {}
384+
383385 for _ , key := range keysToDelete {
384- err = c .deleteBlocks (ctx , systemstore , docID , key .Cid , prunedOwners )
386+ err = c .deleteBlocks (ctx , systemstore , docID , key .Cid , prunedOwners , cache )
385387 if err != nil {
386388 return NewErrTruncateDeleteBlocks (err , key .Cid .String ())
387389 }
@@ -457,7 +459,7 @@ func (c *collection) hardDeleteCollectionBlocks(
457459 // the document blocks and their owner edges are deleted earlier in truncate (see the
458460 // hardDeleteDocKeysAndHeadstore pass), so the collection-commit DAG walked here only
459461 // re-encounters already-deleted document composites.
460- err = c .deleteBlocks (ctx , nil , "" , key .Cid , nil )
462+ err = c .deleteBlocks (ctx , nil , "" , key .Cid , nil , nil )
461463 if err != nil {
462464 return NewErrTruncateDeleteBlocks (err , key .Cid .String ())
463465 }
@@ -479,6 +481,12 @@ func (c *collection) hardDeleteCollectionBlocks(
479481 return nil
480482}
481483
484+ // blockCache holds blocks already read while deleting one document, so a block reachable from more
485+ // than one of the document's headstore entries is read and decoded once rather than once per entry.
486+ // Blocks are content-addressed and immutable, so one read under any transaction is valid to follow
487+ // links from under another. A nil entry records a block that was not found; a nil cache disables it.
488+ type blockCache map [cid.Cid ]* coreblock.Block
489+
482490// deleteBlocks deletes the block of the given cid and all the blocks it links to, if
483491// a block with this cid is found.
484492//
@@ -489,6 +497,7 @@ func (c *collection) deleteBlocks(
489497 docID string ,
490498 currentCid cid.Cid ,
491499 prunedOwners map [string ]struct {},
500+ cache blockCache ,
492501) error {
493502 blockstore := datastore .NewMultistore (c .db .rootstore , c .db .lockSet , c .db .blockStoreChunkSize ).Blockstore ()
494503
@@ -545,7 +554,7 @@ func (c *collection) deleteBlocks(
545554 block * coreblock.Block
546555 }
547556
548- coreBlock , isFound , err := getBlock (readCtx , blockstore , currentCid )
557+ coreBlock , isFound , err := getBlock (readCtx , blockstore , cache , currentCid )
549558 if err != nil {
550559 return err
551560 }
@@ -591,7 +600,7 @@ func (c *collection) deleteBlocks(
591600 currentBlock := toDelete [i ]
592601
593602 if currentBlock .block == nil {
594- coreBlock , isFound , err := getBlock (readCtx , blockstore , currentBlock .id )
603+ coreBlock , isFound , err := getBlock (readCtx , blockstore , cache , currentBlock .id )
595604 if err != nil {
596605 return err
597606 }
@@ -653,7 +662,16 @@ func (c *collection) deleteBlocks(
653662 return nil
654663}
655664
656- func getBlock (ctx context.Context , blockstore datastore.Blockstore , id cid.Cid ) (* coreblock.Block , bool , error ) {
665+ func getBlock (
666+ ctx context.Context ,
667+ blockstore datastore.Blockstore ,
668+ cache blockCache ,
669+ id cid.Cid ,
670+ ) (* coreblock.Block , bool , error ) {
671+ if block , ok := cache [id ]; ok {
672+ return block , block != nil , nil
673+ }
674+
657675 rawBlock , err := blockstore .Get (ctx , id )
658676 if errors .Is (err , ipld.ErrNotFound {}) {
659677 // We are looping through the links in a simple way that may result in us
@@ -662,6 +680,9 @@ func getBlock(ctx context.Context, blockstore datastore.Blockstore, id cid.Cid)
662680 // (another call to `deleteBlocks`).
663681 //
664682 // If we encounter such a block, we can skip over the error and continue.
683+ if cache != nil {
684+ cache [id ] = nil
685+ }
665686 return nil , false , nil
666687 }
667688 if err != nil {
@@ -673,5 +694,9 @@ func getBlock(ctx context.Context, blockstore datastore.Blockstore, id cid.Cid)
673694 return nil , false , err
674695 }
675696
697+ if cache != nil {
698+ cache [id ] = decodedBlock
699+ }
700+
676701 return decodedBlock , true , nil
677702}
0 commit comments