@@ -22,6 +22,7 @@ import (
2222 "fmt"
2323 "io/fs"
2424 "iter"
25+ "math/bits"
2526 "net/http"
2627 "os"
2728 "path/filepath"
@@ -539,6 +540,17 @@ func (lrs *logResourceStorage) writeBundleIdempotent(ctx context.Context, index
539540 })
540541}
541542
543+ // writeTileIdempotent takes care of writing out the serialised tile file if it doesn't already exist.
544+ func (lrs * logResourceStorage ) writeTileIdempotent (ctx context.Context , level uint64 , index uint64 , partial uint8 , tile []byte ) error {
545+ return otel .TraceErr (ctx , "tessera.storage.posix.writeTileIdempotent" , tracer , func (ctx context.Context , span trace.Span ) error {
546+ tPath := layout .TilePath (level , index , partial )
547+ if err := lrs .s .createIdempotent (tPath , tile ); err != nil {
548+ return err
549+ }
550+ return nil
551+ })
552+ }
553+
542554// initialise ensures that the storage location is valid by loading the checkpoint from this location, or
543555// creating a zero-sized one if it doesn't already exist.
544556func (a * appender ) initialise (ctx context.Context ) error {
@@ -1201,7 +1213,7 @@ func (m *MirrorWriter) initialise(ctx context.Context) error {
12011213 return nil
12021214}
12031215
1204- // marshalTlogEntryBundle returns a tlog-tiles compatible serialization of the provided entrybundle .
1216+ // marshalTlogEntryBundle returns a tlog-tiles compatible serialization of the provided entry bundle .
12051217func marshalTlogEntryBundle (b * api.EntryBundle ) ([]byte , error ) {
12061218 // Prealloc the max size we could possibly write out (about 16MB for a full bundle of max size entries).
12071219 // If this causes problems we may want to default this to some lower
@@ -1351,5 +1363,151 @@ func (m *MirrorWriter) UpdateCheckpoint(ctx context.Context, fn func(old []byte)
13511363 return fmt .Errorf ("update function returned error: %w" , err )
13521364 }
13531365
1366+ _ , newSize , _ , err := parse .CheckpointUnsafe (newCP )
1367+ if err != nil {
1368+ return fmt .Errorf ("failed to parse checkpoint: %v" , err )
1369+ }
1370+
1371+ treeSize , err := m .IntegratedSize (ctx )
1372+ if err != nil {
1373+ return fmt .Errorf ("failed to get integrated size: %v" , err )
1374+ }
1375+ if err := m .ensureGeometry (ctx , newSize , treeSize ); err != nil {
1376+ return err
1377+ }
1378+
13541379 return m .s .createOverwrite (layout .CheckpointPath , newCP )
13551380}
1381+
1382+ // ensureGeometry checks that the partial tiles and entry bundles implied by the new size are
1383+ // present in the stored resources.
1384+ //
1385+ // If an implied partial resource is not already present, this function will attempt to create
1386+ // it from a strictly larger resource whose presence is implied by treeSize.
1387+ func (m * MirrorWriter ) ensureGeometry (ctx context.Context , cpSize , treeSize uint64 ) error {
1388+ if cpSize == 0 {
1389+ return nil
1390+ }
1391+ if cpSize > treeSize {
1392+ return fmt .Errorf ("new size %d is greater than integrated size %d" , cpSize , treeSize )
1393+ }
1394+ ml := maxLevel (cpSize )
1395+ idx := (cpSize - 1 ) >> layout .TileHeight
1396+ for l := uint64 (0 ); l <= uint64 (ml ); l , idx = l + 1 , idx >> layout .TileHeight {
1397+ treeP := layout .PartialTileSize (l , idx , treeSize )
1398+ cpP := layout .PartialTileSize (l , idx , cpSize )
1399+ if l == 0 {
1400+ if err := m .ensurePartialBundle (ctx , idx , cpP , treeP ); err != nil {
1401+ return err
1402+ }
1403+ }
1404+ if err := m .ensurePartialTile (ctx , l , idx , cpP , treeP ); err != nil {
1405+ return err
1406+ }
1407+ }
1408+ return nil
1409+ }
1410+
1411+ // maxLevel returns the maximum tile level for a tree of the given size.
1412+ func maxLevel (sz uint64 ) int {
1413+ if sz == 0 {
1414+ return 0
1415+ }
1416+ return (bits .Len64 (sz ) - 1 ) / layout .TileHeight
1417+ }
1418+
1419+ // ensurePartialBundle ensures that the partial entry bundle implied by the new size is
1420+ // present in the stored resources.
1421+ //
1422+ // If the implied partial entry bundle is not already present, this function will attempt to create
1423+ // it from the entry bundle implied by treeSize.
1424+ func (m * MirrorWriter ) ensurePartialBundle (ctx context.Context , idx uint64 , cpP , treeP uint8 ) error {
1425+ if cpP == treeP {
1426+ return nil
1427+ }
1428+
1429+ // Check if the entry bundle already exists.
1430+ _ , err := m .s .stat (m .logStorage .entriesPath (idx , cpP ))
1431+ switch {
1432+ case errors .Is (err , os .ErrNotExist ):
1433+ // Need to do the work.
1434+ case err != nil :
1435+ return fmt .Errorf ("failed to stat entry bundle @%d.%d: %v" , idx , cpP , err )
1436+ default :
1437+ // Already exists, we're done!
1438+ return nil
1439+ }
1440+
1441+ // Read bundle implied by the tree size...
1442+ d , err := m .logStorage .ReadEntryBundle (ctx , idx , treeP )
1443+ if err != nil {
1444+ return fmt .Errorf ("failed to read entry bundle @%d.%d: %v" , idx , treeP , err )
1445+ }
1446+ eb := & api.EntryBundle {}
1447+ if err := eb .UnmarshalText (d ); err != nil {
1448+ return fmt .Errorf ("failed to unmarshal entry bundle @%d.%d: %v" , idx , treeP , err )
1449+ }
1450+
1451+ // Then trim it down to the size implied by the checkpoint, and write it out.
1452+ // Handle cpP == 0 where a full-bundle is implied - we should never actually hit this case since cpP must
1453+ // equal treeP in this case, but it doesn't hurt to be defensive.
1454+ if cpP > 0 {
1455+ eb .Entries = eb .Entries [:cpP ]
1456+ }
1457+ d , err = marshalTlogEntryBundle (eb )
1458+ if err != nil {
1459+ return fmt .Errorf ("failed to marshal entry bundle @%d.%d: %v" , idx , cpP , err )
1460+ }
1461+ if err := m .logStorage .writeBundleIdempotent (ctx , idx , cpP , d ); err != nil {
1462+ return fmt .Errorf ("failed to write entry bundle @%d.%d: %v" , idx , cpP , err )
1463+ }
1464+ return nil
1465+ }
1466+
1467+ // ensurePartialTile ensures that the partial tile implied by the new size is
1468+ // present in the stored resources.
1469+ //
1470+ // If the implied partial tile is not already present, this function will attempt to create
1471+ // it from the tile implied by treeSize.
1472+ func (m * MirrorWriter ) ensurePartialTile (ctx context.Context , l uint64 , idx uint64 , cpP , treeP uint8 ) error {
1473+ if cpP == treeP {
1474+ return nil
1475+ }
1476+
1477+ // Check if the tile already exists.
1478+ _ , err := m .s .stat (layout .TilePath (l , idx , cpP ))
1479+ switch {
1480+ case errors .Is (err , os .ErrNotExist ):
1481+ // Need to do the work.
1482+ case err != nil :
1483+ return fmt .Errorf ("failed to stat tile @%d/%d.%d: %v" , l , idx , cpP , err )
1484+ default :
1485+ // Already exists, we're done!
1486+ return nil
1487+ }
1488+
1489+ // Read tile implied by the tree size...
1490+ d , err := m .logStorage .ReadTile (ctx , l , idx , treeP )
1491+ if err != nil {
1492+ return fmt .Errorf ("failed to read tile @%d/%d.%d: %v" , l , idx , treeP , err )
1493+ }
1494+ t := & api.HashTile {}
1495+ if err := t .UnmarshalText (d ); err != nil {
1496+ return fmt .Errorf ("failed to unmarshal tile @%d/%d.%d: %v" , l , idx , treeP , err )
1497+ }
1498+
1499+ // Then trim it down to the size implied by the checkpoint, and write it out.
1500+ // Handle cpP == 0 where a full-tile is implied - we should never actually hit this case since cpP must
1501+ // equal treeP in this case, but it doesn't hurt to be defensive.
1502+ if cpP > 0 {
1503+ t .Nodes = t .Nodes [:cpP ]
1504+ }
1505+ d , err = t .MarshalText ()
1506+ if err != nil {
1507+ return fmt .Errorf ("failed to marshal tile @%d/%d.%d: %v" , l , idx , cpP , err )
1508+ }
1509+ if err := m .logStorage .writeTileIdempotent (ctx , l , idx , cpP , d ); err != nil {
1510+ return fmt .Errorf ("failed to write tile @%d/%d.%d: %v" , l , idx , cpP , err )
1511+ }
1512+ return nil
1513+ }
0 commit comments