Repository: benbjohnson/litestream
Pull request #309 (2022-02-08 19:53:54)

Update index & offset encoding
Previously, the index & offsets were encoded as 8-character hex strings, however, this limits the maximum value to a `uint32`. This is normally not an issue, however, indices could go over the maximum value of 4 billion over time and the offset could exceed this value for an especially large WAL update. For safety, these encodings have been updated to 16-character hex encodings.


Patch:
diff --git a/abs/replica_client.go b/abs/replica_client.go
index 551f638..394c76a 100644
--- a/abs/replica_client.go
+++ b/abs/replica_client.go
@@ -190,8 +190,6 @@ func (c *ReplicaClient) WriteSnapshot(ctx context.Context, generation string, in
 	internal.OperationTotalCounterVec.WithLabelValues(ReplicaClientType, "PUT").Inc()
 	internal.OperationBytesCounterVec.WithLabelValues(ReplicaClientType, "PUT").Add(float64(rc.N()))
 
-	// log.Printf("%s(%s): snapshot: creating %s/%08x t=%s", r.db.Path(), r.Name(), generation, index, time.Since(startTime).Truncate(time.Millisecond))
-
 	return litestream.SnapshotInfo{
 		Generation: generation,
 		Index:      index,
diff --git a/db.go b/db.go
index 791d80d..a8c59de 100644
--- a/db.go
+++ b/db.go
@@ -703,7 +703,7 @@ func (db *DB) cleanWAL(ctx context.Context) error {
 			return err
 		}
 
-		db.Logger.Printf("remove shadow index: %s/%08x", generation, index)
+		db.Logger.Printf("remove shadow index: %s/%s", generation, FormatIndex(index))
 	}
 
 	return nil
diff --git a/file_replica_client.go b/file_replica_client.go
index a8873f0..2eef73a 100644
--- a/file_replica_client.go
+++ b/file_replica_client.go
@@ -100,7 +100,7 @@ func (c *FileReplicaClient) WALSegmentPath(generation string, index int, offset
 	if err != nil {
 		return "", err
 	}
-	return filepath.Join(dir, FormatIndex(index), fmt.Sprintf("%08x.wal.lz4", offset)), nil
+	return filepath.Join(dir, FormatIndex(index), fmt.Sprintf("%s.wal.lz4", FormatOffset(offset))), nil
 }
 
 // Generations returns a list of available generation names.
diff --git a/gcs/replica_client.go b/gcs/replica_client.go
index 0a45b2b..7f5d91c 100644
--- a/gcs/replica_client.go
+++ b/gcs/replica_client.go
@@ -160,8 +160,6 @@ func (c *ReplicaClient) WriteSnapshot(ctx context.Context, generation string, in
 	internal.OperationTotalCounterVec.WithLabelValues(ReplicaClientType, "PUT").Inc()
 	internal.OperationBytesCounterVec.WithLabelValues(ReplicaClientType, "PUT").Add(float64(n))
 
-	// log.Printf("%s(%s): snapshot: creating %s/%08x t=%s", r.db.Path(), r.Name(), generation, index, time.Since(startTime).Truncate(time.Millisecond))
-
 	return litestream.SnapshotInfo{
 		Generation: generation,
 		Index:      index,
diff --git a/internal/internal.go b/internal/internal.go
index 681726a..0c70d4d 100644
--- a/internal/internal.go
+++ b/internal/internal.go
@@ -15,6 +15,12 @@ import (
 	"github.com/prometheus/client_golang/prometheus/promauto"
 )
 
+// Platform-independent maximum integer sizes.
+const (
+	MaxUint = ^uint(0)
+	MaxInt  = int(MaxUint >> 1)
+)
+
 // ReadCloser wraps a reader to also attach a separate closer.
 type ReadCloser struct {
 	r io.Reader
@@ -170,14 +176,14 @@ func ParseSnapshotPath(s string) (index int, err error) {
 		return 0, fmt.Errorf("invalid snapshot path")
 	}
 
-	i32, _ := strconv.ParseUint(a[1], 16, 32)
-	if i32 > math.MaxInt32 {
+	i64, _ := strconv.ParseUint(a[1], 16, 64)
+	if i64 > uint64(MaxInt) {
 		return 0, fmt.Errorf("index too large in snapshot path %q", s)
 	}
-	return int(i32), nil
+	return int(i64), nil
 }
 
-var snapshotPathRegex = regexp.MustCompile(`^([0-9a-f]{8})\.snapshot\.lz4$`)
+var snapshotPathRegex = regexp.MustCompile(`^([0-9a-f]{16})\.snapshot\.lz4$`)
 
 // ParseWALSegmentPath parses the index/offset from a segment filename. Used by path-based replicas.
 func ParseWALSegmentPath(s string) (index int, offset int64, err error) {
@@ -186,18 +192,18 @@ func ParseWALSegmentPath(s string) (index int, offset int64, err error) {
 		return 0, 0, fmt.Errorf("invalid wal segment path")
 	}
 
-	i32, _ := strconv.ParseUint(a[1], 16, 32)
-	if i32 > math.MaxInt32 {
+	i64, _ := strconv.ParseUint(a[1], 16, 64)
+	if i64 > uint64(MaxInt) {
 		return 0, 0, fmt.Errorf("index too large in wal segment path %q", s)
 	}
 	off64, _ := strconv.ParseUint(a[2], 16, 64)
 	if off64 > math.MaxInt64 {
 		return 0, 0, fmt.Errorf("offset too large in wal segment path %q", s)
 	}
-	return int(i32), int64(off64), nil
+	return int(i64), int64(off64), nil
 }
 
-var walSegmentPathRegex = regexp.MustCompile(`^([0-9a-f]{8})\/([0-9a-f]{8})\.wal\.lz4$`)
+var walSegmentPathRegex = regexp.MustCompile(`^([0-9a-f]{16})\/([0-9a-f]{16})\.wal\.lz4$`)
 
 // Shared replica metrics.
 var (
diff --git a/replica.go b/replica.go
index cc5652d..50e822e 100644
--- a/replica.go
+++ b/replica.go
@@ -514,7 +514,7 @@ func (r *Replica) Snapshot(ctx context.Context) (info SnapshotInfo, err error) {
 		return info, err
 	}
 
-	r.Logger.Printf("snapshot written %s/%08x", pos.Generation, pos.Index)
+	r.Logger.Printf("snapshot written %s/%s", pos.Generation, FormatIndex(pos.Index))
 
 	return info, nil
 }
@@ -580,9 +580,9 @@ func (r *Replica) deleteSnapshotsBeforeIndex(ctx context.Context, generation str
 		}
 
 		if err := r.client.DeleteSnapshot(ctx, info.Generation, info.Index); err != nil {
-			return fmt.Errorf("delete snapshot %s/%08x: %w", info.Generation, info.Index, err)
+			return fmt.Errorf("delete snapshot %s/%s: %w", info.Generation, FormatIndex(info.Index), err)
 		}
-		r.Logger.Printf("snapshot deleted %s/%08x", generation, index)
+		r.Logger.Printf("snapshot deleted %s/%s", generation, FormatIndex(index))
 	}
 
 	return itr.Close()
diff --git a/replica_client.go b/replica_client.go
index 3bf01b1..ba6d448 100644
--- a/replica_client.go
+++ b/replica_client.go
@@ -88,7 +88,7 @@ func FindSnapshotForIndex(ctx context.Context, client ReplicaClient, generation
 	if n == 0 {
 		return 0, ErrNoSnapshots
 	} else if snapshotIndex == -1 {
-		return 0, fmt.Errorf("no snapshots available at or before index %08x", index)
+		return 0, fmt.Errorf("no snapshots available at or before index %s", FormatIndex(index))
 	}
 	return snapshotIndex, nil
 }
@@ -349,7 +349,7 @@ func Restore(ctx context.Context, client ReplicaClient, filename, generation str
 
 	// Copy snapshot to output path.
 	tmpPath := filename + ".tmp"
-	logger.Printf("%srestoring snapshot %s/%08x to %s", opt.LogPrefix, generation, snapshotIndex, tmpPath)
+	logger.Printf("%srestoring snapshot %s/%s to %s", opt.LogPrefix, generation, FormatIndex(snapshotIndex), tmpPath)
 	if err := RestoreSnapshot(ctx, client, tmpPath, generation, snapshotIndex, opt.Mode, opt.Uid, opt.Gid); err != nil {
 		return fmt.Errorf("cannot restore snapshot: %w", err)
 	}
@@ -380,7 +380,7 @@ func Restore(ctx context.Context, client ReplicaClient, filename, generation str
 		if err = ApplyWAL(ctx, tmpPath, walPath); err != nil {
 			return fmt.Errorf("cannot apply wal: %w", err)
 		}
-		logger.Printf("%sapplied wal %s/%08x elapsed=%s", opt.LogPrefix, generation, walIndex, time.Since(startTime).String())
+		logger.Printf("%sapplied wal %s/%s elapsed=%s", opt.LogPrefix, generation, FormatIndex(walIndex), time.Since(startTime).String())
 	}
 
 	// Copy file to final location.
diff --git a/s3/replica_client.go b/s3/replica_client.go
index a9e3e63..182d713 100644
--- a/s3/replica_client.go
+++ b/s3/replica_client.go
@@ -253,8 +253,6 @@ func (c *ReplicaClient) WriteSnapshot(ctx context.Context, generation string, in
 	internal.OperationTotalCounterVec.WithLabelValues(ReplicaClientType, "PUT").Inc()
 	internal.OperationBytesCounterVec.WithLabelValues(ReplicaClientType, "PUT").Add(float64(rc.N()))
 
-	// log.Printf("%s(%s): snapshot: creating %s/%08x t=%s", r.db.Path(), r.Name(), generation, index, time.Since(startTime).Truncate(time.Millisecond))
-
 	return litestream.SnapshotInfo{
 		Generation: generation,
 		Index:      index,
diff --git a/sftp/replica_client.go b/sftp/replica_client.go
index 8b651e9..fd0912c 100644
--- a/sftp/replica_client.go
+++ b/sftp/replica_client.go
@@ -282,8 +282,6 @@ func (c *ReplicaClient) WriteSnapshot(ctx context.Context, generation string, in
 	internal.OperationTotalCounterVec.WithLabelValues(ReplicaClientType, "PUT").Inc()
 	internal.OperationBytesCounterVec.WithLabelValues(ReplicaClientType, "PUT").Add(float64(n))
 
-	// log.Printf("%s(%s): snapshot: creating %s/%08x t=%s", r.db.Path(), r.Name(), generation, index, time.Since(startTime).Truncate(time.Millisecond))
-
 	return litestream.SnapshotInfo{
 		Generation: generation,
 		Index:      index,
diff --git a/testdata/wal-time-bounds/ok/generations/0000000000000000/wal/00000000/00000000.wal.lz4 b/testdata/wal-time-bounds/ok/generations/0000000000000000/wal/0000000000000000/0000000000000000.wal.lz4
similarity index 100%
rename from testdata/wal-time-bounds/ok/generations/0000000000000000/wal/00000000/00000000.wal.lz4
rename to testdata/wal-time-bounds/ok/generations/0000000000000000/wal/0000000000000000/0000000000000000.wal.lz4
diff --git a/testdata/wal-time-bounds/ok/generations/0000000000000000/wal/00000000/00000001.wal.lz4 b/testdata/wal-time-bounds/ok/generations/0000000000000000/wal/0000000000000000/0000000000000001.wal.lz4
similarity index 100%
rename from testdata/wal-time-bounds/ok/generations/0000000000000000/wal/00000000/00000001.wal.lz4
rename to testdata/wal-time-bounds/ok/generations/0000000000000000/wal/0000000000000000/0000000000000001.wal.lz4
diff --git a/testdata/wal-time-bounds/ok/generations/0000000000000000/wal/00000001/00000000.wal.lz4 b/testdata/wal-time-bounds/ok/generations/0000000000000000/wal/0000000000000001/0000000000000000.wal.lz4
similarity index 100%
rename from testdata/wal-time-bounds/ok/generations/0000000000000000/wal/00000001/00000000.wal.lz4
rename to testdata/wal-time-bounds/ok/generations/0000000000000000/wal/0000000000000001/0000000000000000.wal.lz4
diff --git a/wal_downloader.go b/wal_downloader.go
index b87c245..c490384 100644
--- a/wal_downloader.go
+++ b/wal_downloader.go
@@ -272,7 +272,7 @@ func (d *WALDownloader) downloader(ctx context.Context) error {
 // of the on-disk file on success.
 func (d *WALDownloader) downloadWAL(ctx context.Context, index int, offsets []int64) (string, error) {
 	// Open handle to destination WAL path.
-	walPath := fmt.Sprintf("%s-%08x-wal", d.prefix, index)
+	walPath := fmt.Sprintf("%s-%s-wal", d.prefix, FormatIndex(index))
 	f, err := internal.CreateFile(walPath, d.Mode, d.Uid, d.Gid)
 	if err != nil {
 		return "", err
@@ -285,7 +285,7 @@ func (d *WALDownloader) downloadWAL(ctx context.Context, index int, offsets []in
 		if err := func() error {
 			// Ensure next offset is our current position in the file.
 			if written != offset {
-				return fmt.Errorf("missing WAL offset: generation=%s index=%08x offset=%08x", d.generation, index, written)
+				return fmt.Errorf("missing WAL offset: generation=%s index=%s offset=%s", d.generation, FormatIndex(index), FormatOffset(written))
 			}
 
 			rd, err := d.client.WALSegmentReader(ctx, Pos{Generation: d.generation, Index: index, Offset: offset})
@@ -331,5 +331,5 @@ type WALNotFoundError struct {
 
 // Error returns the error string.
 func (e *WALNotFoundError) Error() string {
-	return fmt.Sprintf("wal not found: generation=%s index=%08x", e.Generation, e.Index)
+	return fmt.Sprintf("wal not found: generation=%s index=%s", e.Generation, FormatIndex(e.Index))
 }


Test patch:
diff --git a/cmd/litestream/main.go b/cmd/litestream/main.go
index fa43f4d..490ba18 100644
--- a/cmd/litestream/main.go
+++ b/cmd/litestream/main.go
@@ -718,7 +718,7 @@ var _ flag.Value = (*indexVar)(nil)
 
 // String returns an 8-character hexadecimal value.
 func (v *indexVar) String() string {
-	return fmt.Sprintf("%08x", int(*v))
+	return litestream.FormatIndex(int(*v))
 }
 
 // Set parses s into an integer from a hexadecimal value.
diff --git a/cmd/litestream/restore_test.go b/cmd/litestream/restore_test.go
index 9469b5a..6744963 100644
--- a/cmd/litestream/restore_test.go
+++ b/cmd/litestream/restore_test.go
@@ -27,10 +27,10 @@ func TestRestoreCommand(t *testing.T) {
 		// STDOUT has timing info so we need to grep per line.
 		lines := strings.Split(stdout.String(), "\n")
 		for i, substr := range []string{
-			`restoring snapshot 0000000000000000/00000000 to ` + filepath.Join(tempDir, "db.tmp"),
-			`applied wal 0000000000000000/00000000 elapsed=`,
-			`applied wal 0000000000000000/00000001 elapsed=`,
-			`applied wal 0000000000000000/00000002 elapsed=`,
+			`restoring snapshot 0000000000000000/0000000000000000 to ` + filepath.Join(tempDir, "db.tmp"),
+			`applied wal 0000000000000000/0000000000000000 elapsed=`,
+			`applied wal 0000000000000000/0000000000000001 elapsed=`,
+			`applied wal 0000000000000000/0000000000000002 elapsed=`,
 			`renaming database from temporary location`,
 		} {
 			if !strings.Contains(lines[i], substr) {
@@ -54,7 +54,7 @@ func TestRestoreCommand(t *testing.T) {
 		// STDOUT has timing info so we need to grep per line.
 		lines := strings.Split(stdout.String(), "\n")
 		for i, substr := range []string{
-			`restoring snapshot 0000000000000001/00000001 to ` + filepath.Join(tempDir, "db.tmp"),
+			`restoring snapshot 0000000000000001/0000000000000001 to ` + filepath.Join(tempDir, "db.tmp"),
 			`no wal files found, snapshot only`,
 			`renaming database from temporary location`,
 		} {
@@ -78,7 +78,7 @@ func TestRestoreCommand(t *testing.T) {
 
 		lines := strings.Split(stdout.String(), "\n")
 		for i, substr := range []string{
-			`restoring snapshot 0000000000000000/00000000 to ` + filepath.Join(tempDir, "db.tmp"),
+			`restoring snapshot 0000000000000000/0000000000000000 to ` + filepath.Join(tempDir, "db.tmp"),
 			`no wal files found, snapshot only`,
 			`renaming database from temporary location`,
 		} {
@@ -102,7 +102,7 @@ func TestRestoreCommand(t *testing.T) {
 
 		lines := strings.Split(stdout.String(), "\n")
 		for i, substr := range []string{
-			`restoring snapshot 0000000000000001/00000000 to ` + filepath.Join(tempDir, "db.tmp"),
+			`restoring snapshot 0000000000000001/0000000000000000 to ` + filepath.Join(tempDir, "db.tmp"),
 			`no wal files found, snapshot only`,
 			`renaming database from temporary location`,
 		} {
@@ -256,7 +256,7 @@ func TestRestoreCommand(t *testing.T) {
 
 	t.Run("ErrInvalidReplicaURL", func(t *testing.T) {
 		m, _, _, _ := newMain()
-		err := m.Run(context.Background(), []string{"restore", "-o", "/tmp/db", "xyz://xyz"})
+		err := m.Run(context.Background(), []string{"restore", "-o", filepath.Join(t.TempDir(), "db"), "xyz://xyz"})
 		if err == nil || err.Error() != `unknown replica type in config: "xyz"` {
 			t.Fatalf("unexpected error: %s", err)
 		}
diff --git a/cmd/litestream/snapshots.go b/cmd/litestream/snapshots.go
index c274f3e..c8309ba 100644
--- a/cmd/litestream/snapshots.go
+++ b/cmd/litestream/snapshots.go
@@ -83,10 +83,10 @@ func (c *SnapshotsCommand) Run(ctx context.Context, args []string) (ret error) {
 
 	fmt.Fprintln(w, "replica\tgeneration\tindex\tsize\tcreated")
 	for _, info := range infos {
-		fmt.Fprintf(w, "%s\t%s\t%08x\t%d\t%s\n",
+		fmt.Fprintf(w, "%s\t%s\t%s\t%d\t%s\n",
 			info.replicaName,
 			info.Generation,
-			info.Index,
+			litestream.FormatIndex(info.Index),
 			info.Size,
 			info.CreatedAt.Format(time.RFC3339),
 		)
diff --git a/cmd/litestream/testdata/generations/no-database/Makefile b/cmd/litestream/testdata/generations/no-database/Makefile
index 793e5cd..61fea09 100644
--- a/cmd/litestream/testdata/generations/no-database/Makefile
+++ b/cmd/litestream/testdata/generations/no-database/Makefile
@@ -1,4 +1,4 @@
 .PHONY: default
 default:
-	TZ=UTC touch -ct 200001010000 replica/generations/0000000000000000/snapshots/00000000.snapshot.lz4
-	TZ=UTC touch -ct 200001020000 replica/generations/0000000000000001/snapshots/00000000.snapshot.lz4
+	TZ=UTC touch -ct 200001010000 replica/generations/0000000000000000/snapshots/0000000000000000.snapshot.lz4
+	TZ=UTC touch -ct 200001020000 replica/generations/0000000000000001/snapshots/0000000000000000.snapshot.lz4
diff --git a/cmd/litestream/testdata/generations/no-database/replica/generations/0000000000000000/snapshots/00000000.snapshot.lz4 b/cmd/litestream/testdata/generations/no-database/replica/generations/0000000000000000/snapshots/0000000000000000.snapshot.lz4
similarity index 100%
rename from cmd/litestream/testdata/generations/no-database/replica/generations/0000000000000000/snapshots/00000000.snapshot.lz4
rename to cmd/litestream/testdata/generations/no-database/replica/generations/0000000000000000/snapshots/0000000000000000.snapshot.lz4
diff --git a/cmd/litestream/testdata/generations/no-database/replica/generations/0000000000000001/snapshots/00000000.snapshot.lz4 b/cmd/litestream/testdata/generations/no-database/replica/generations/0000000000000001/snapshots/0000000000000000.snapshot.lz4
similarity index 100%
rename from cmd/litestream/testdata/generations/no-database/replica/generations/0000000000000001/snapshots/00000000.snapshot.lz4
rename to cmd/litestream/testdata/generations/no-database/replica/generations/0000000000000001/snapshots/0000000000000000.snapshot.lz4
diff --git a/cmd/litestream/testdata/generations/ok/Makefile b/cmd/litestream/testdata/generations/ok/Makefile
index 51f5394..d18db15 100644
--- a/cmd/litestream/testdata/generations/ok/Makefile
+++ b/cmd/litestream/testdata/generations/ok/Makefile
@@ -1,9 +1,9 @@
 .PHONY: default
 default:
 	TZ=UTC touch -ct 200001030000 db
-	TZ=UTC touch -ct 200001010000 replica/generations/0000000000000000/snapshots/00000000.snapshot.lz4
-	TZ=UTC touch -ct 200001020000 replica/generations/0000000000000000/snapshots/00000001.snapshot.lz4
-	TZ=UTC touch -ct 200001010000 replica/generations/0000000000000000/wal/00000000/00000000.wal.lz4
-	TZ=UTC touch -ct 200001020000 replica/generations/0000000000000000/wal/00000000/00000001.wal.lz4
-	TZ=UTC touch -ct 200001030000 replica/generations/0000000000000000/wal/00000001/00000000.wal.lz4
-	TZ=UTC touch -ct 200001010000 replica/generations/0000000000000001/snapshots/00000000.snapshot.lz4
+	TZ=UTC touch -ct 200001010000 replica/generations/0000000000000000/snapshots/0000000000000000.snapshot.lz4
+	TZ=UTC touch -ct 200001020000 replica/generations/0000000000000000/snapshots/0000000000000001.snapshot.lz4
+	TZ=UTC touch -ct 200001010000 replica/generations/0000000000000000/wal/0000000000000000/0000000000000000.wal.lz4
+	TZ=UTC touch -ct 200001020000 replica/generations/0000000000000000/wal/0000000000000000/0000000000000001.wal.lz4
+	TZ=UTC touch -ct 200001030000 replica/generations/0000000000000000/wal/0000000000000001/0000000000000000.wal.lz4
+	TZ=UTC touch -ct 200001010000 replica/generations/0000000000000001/snapshots/0000000000000000.snapshot.lz4
diff --git a/cmd/litestream/testdata/generations/ok/replica/generations/0000000000000000/snapshots/00000000.snapshot.lz4 b/cmd/litestream/testdata/generations/ok/replica/generations/0000000000000000/snapshots/0000000000000000.snapshot.lz4
similarity index 100%
rename from cmd/litestream/testdata/generations/ok/replica/generations/0000000000000000/snapshots/00000000.snapshot.lz4
rename to cmd/litestream/testdata/generations/ok/replica/generations/0000000000000000/snapshots/0000000000000000.snapshot.lz4
diff --git a/cmd/litestream/testdata/generations/ok/replica/generations/0000000000000000/snapshots/00000001.snapshot.lz4 b/cmd/litestream/testdata/generations/ok/replica/generations/0000000000000000/snapshots/0000000000000001.snapshot.lz4
similarity index 100%
rename from cmd/litestream/testdata/generations/ok/replica/generations/0000000000000000/snapshots/00000001.snapshot.lz4
rename to cmd/litestream/testdata/generations/ok/replica/generations/0000000000000000/snapshots/0000000000000001.snapshot.lz4
diff --git a/cmd/litestream/testdata/generations/ok/replica/generations/0000000000000000/wal/00000000/00000000.wal.lz4 b/cmd/litestream/testdata/generations/ok/replica/generations/0000000000000000/wal/0000000000000000/0000000000000000.wal.lz4
similarity index 100%
rename from cmd/litestream/testdata/generations/ok/replica/generations/0000000000000000/wal/00000000/00000000.wal.lz4
rename to cmd/litestream/testdata/generations/ok/replica/generations/0000000000000000/wal/0000000000000000/0000000000000000.wal.lz4
diff --git a/cmd/litestream/testdata/generations/ok/replica/generations/0000000000000000/wal/00000000/00000001.wal.lz4 b/cmd/litestream/testdata/generations/ok/replica/generations/0000000000000000/wal/0000000000000000/0000000000000001.wal.lz4
similarity index 100%
rename from cmd/litestream/testdata/generations/ok/replica/generations/0000000000000000/wal/00000000/00000001.wal.lz4
rename to cmd/litestream/testdata/generations/ok/replica/generations/0000000000000000/wal/0000000000000000/0000000000000001.wal.lz4
diff --git a/cmd/litestream/testdata/generations/ok/replica/generations/0000000000000000/wal/00000001/00000000.wal.lz4 b/cmd/litestream/testdata/generations/ok/replica/generations/0000000000000000/wal/0000000000000001/0000000000000000.wal.lz4
similarity index 100%
rename from cmd/litestream/testdata/generations/ok/replica/generations/0000000000000000/wal/00000001/00000000.wal.lz4
rename to cmd/litestream/testdata/generations/ok/replica/generations/0000000000000000/wal/0000000000000001/0000000000000000.wal.lz4
diff --git a/cmd/litestream/testdata/generations/ok/replica/generations/0000000000000001/snapshots/00000000.snapshot.lz4 b/cmd/litestream/testdata/generations/ok/replica/generations/0000000000000001/snapshots/0000000000000000.snapshot.lz4
similarity index 100%
rename from cmd/litestream/testdata/generations/ok/replica/generations/0000000000000001/snapshots/00000000.snapshot.lz4
rename to cmd/litestream/testdata/generations/ok/replica/generations/0000000000000001/snapshots/0000000000000000.snapshot.lz4
diff --git a/cmd/litestream/testdata/generations/replica-name/Makefile b/cmd/litestream/testdata/generations/replica-name/Makefile
index f6a5eae..aaab4b1 100644
--- a/cmd/litestream/testdata/generations/replica-name/Makefile
+++ b/cmd/litestream/testdata/generations/replica-name/Makefile
@@ -1,5 +1,5 @@
 .PHONY: default
 default:
 	TZ=UTC touch -ct 200001030000 db
-	TZ=UTC touch -ct 200001010000 replica0/generations/0000000000000000/snapshots/00000000.snapshot.lz4
-	TZ=UTC touch -ct 200001020000 replica1/generations/0000000000000001/snapshots/00000000.snapshot.lz4
+	TZ=UTC touch -ct 200001010000 replica0/generations/0000000000000000/snapshots/0000000000000000.snapshot.lz4
+	TZ=UTC touch -ct 200001020000 replica1/generations/0000000000000001/snapshots/0000000000000000.snapshot.lz4
diff --git a/cmd/litestream/testdata/generations/replica-name/replica0/generations/0000000000000000/snapshots/00000000.snapshot.lz4 b/cmd/litestream/testdata/generations/replica-name/replica0/generations/0000000000000000/snapshots/0000000000000000.snapshot.lz4
similarity index 100%
rename from cmd/litestream/testdata/generations/replica-name/replica0/generations/0000000000000000/snapshots/00000000.snapshot.lz4
rename to cmd/litestream/testdata/generations/replica-name/replica0/generations/0000000000000000/snapshots/0000000000000000.snapshot.lz4
diff --git a/cmd/litestream/testdata/generations/replica-name/replica0/generations/0000000000000000/snapshots/00000001.snapshot.lz4 b/cmd/litestream/testdata/generations/replica-name/replica0/generations/0000000000000000/snapshots/0000000000000001.snapshot.lz4
similarity index 100%
rename from cmd/litestream/testdata/generations/replica-name/replica0/generations/0000000000000000/snapshots/00000001.snapshot.lz4
rename to cmd/litestream/testdata/generations/replica-name/replica0/generations/0000000000000000/snapshots/0000000000000001.snapshot.lz4
diff --git a/cmd/litestream/testdata/generations/replica-name/replica0/generations/0000000000000000/wal/00000000/00000000.wal.lz4 b/cmd/litestream/testdata/generations/replica-name/replica0/generations/0000000000000000/wal/0000000000000000/0000000000000000.wal.lz4
similarity index 100%
rename from cmd/litestream/testdata/generations/replica-name/replica0/generations/0000000000000000/wal/00000000/00000000.wal.lz4
rename to cmd/litestream/testdata/generations/replica-name/replica0/generations/0000000000000000/wal/0000000000000000/0000000000000000.wal.lz4
diff --git a/cmd/litestream/testdata/generations/replica-name/replica0/generations/0000000000000000/wal/00000000/00000001.wal.lz4 b/cmd/litestream/testdata/generations/replica-name/replica0/generations/0000000000000000/wal/0000000000000000/0000000000000001.wal.lz4
similarity index 100%
rename from cmd/litestream/testdata/generations/replica-name/replica0/generations/0000000000000000/wal/00000000/00000001.wal.lz4
rename to cmd/litestream/testdata/generations/replica-name/replica0/generations/0000000000000000/wal/0000000000000000/0000000000000001.wal.lz4
diff --git a/cmd/litestream/testdata/generations/replica-name/replica0/generations/0000000000000000/wal/00000001/00000000.wal.lz4 b/cmd/litestream/testdata/generations/replica-name/replica0/generations/0000000000000000/wal/0000000000000001/0000000000000000.wal.lz4
similarity index 100%
rename from cmd/litestream/testdata/generations/replica-name/replica0/generations/0000000000000000/wal/00000001/00000000.wal.lz4
rename to cmd/litestream/testdata/generations/replica-name/replica0/generations/0000000000000000/wal/0000000000000001/0000000000000000.wal.lz4
diff --git a/cmd/litestream/testdata/generations/replica-name/replica1/generations/0000000000000001/snapshots/00000000.snapshot.lz4 b/cmd/litestream/testdata/generations/replica-name/replica1/generations/0000000000000001/snapshots/0000000000000000.snapshot.lz4
similarity index 100%
rename from cmd/litestream/testdata/generations/replica-name/replica1/generations/0000000000000001/snapshots/00000000.snapshot.lz4
rename to cmd/litestream/testdata/generations/replica-name/replica1/generations/0000000000000001/snapshots/0000000000000000.snapshot.lz4
diff --git a/cmd/litestream/testdata/generations/replica-url/Makefile b/cmd/litestream/testdata/generations/replica-url/Makefile
index 3125ed2..01edcf5 100644
--- a/cmd/litestream/testdata/generations/replica-url/Makefile
+++ b/cmd/litestream/testdata/generations/replica-url/Makefile
@@ -1,9 +1,9 @@
 .PHONY: default
 default:
-	TZ=UTC touch -ct 200001010000 replica/generations/0000000000000000/snapshots/00000000.snapshot.lz4
-	TZ=UTC touch -ct 200001020000 replica/generations/0000000000000000/snapshots/00000001.snapshot.lz4
-	TZ=UTC touch -ct 200001010000 replica/generations/0000000000000000/wal/00000000/00000000.wal.lz4
-	TZ=UTC touch -ct 200001020000 replica/generations/0000000000000000/wal/00000000/00000001.wal.lz4
-	TZ=UTC touch -ct 200001030000 replica/generations/0000000000000000/wal/00000001/00000000.wal.lz4
-	TZ=UTC touch -ct 200001020000 replica/generations/0000000000000001/snapshots/00000000.snapshot.lz4
+	TZ=UTC touch -ct 200001010000 replica/generations/0000000000000000/snapshots/0000000000000000.snapshot.lz4
+	TZ=UTC touch -ct 200001020000 replica/generations/0000000000000000/snapshots/0000000000000001.snapshot.lz4
+	TZ=UTC touch -ct 200001010000 replica/generations/0000000000000000/wal/0000000000000000/0000000000000000.wal.lz4
+	TZ=UTC touch -ct 200001020000 replica/generations/0000000000000000/wal/0000000000000000/0000000000000001.wal.lz4
+	TZ=UTC touch -ct 200001030000 replica/generations/0000000000000000/wal/0000000000000001/0000000000000000.wal.lz4
+	TZ=UTC touch -ct 200001020000 replica/generations/0000000000000001/snapshots/0000000000000000.snapshot.lz4
 
diff --git a/cmd/litestream/testdata/generations/replica-url/replica/generations/0000000000000000/snapshots/00000000.snapshot.lz4 b/cmd/litestream/testdata/generations/replica-url/replica/generations/0000000000000000/snapshots/0000000000000000.snapshot.lz4
similarity index 100%
rename from cmd/litestream/testdata/generations/replica-url/replica/generations/0000000000000000/snapshots/00000000.snapshot.lz4
rename to cmd/litestream/testdata/generations/replica-url/replica/generations/0000000000000000/snapshots/0000000000000000.snapshot.lz4
diff --git a/cmd/litestream/testdata/generations/replica-url/replica/generations/0000000000000000/snapshots/00000001.snapshot.lz4 b/cmd/litestream/testdata/generations/replica-url/replica/generations/0000000000000000/snapshots/0000000000000001.snapshot.lz4
similarity index 100%
rename from cmd/litestream/testdata/generations/replica-url/replica/generations/0000000000000000/snapshots/00000001.snapshot.lz4
rename to cmd/litestream/testdata/generations/replica-url/replica/generations/0000000000000000/snapshots/0000000000000001.snapshot.lz4
diff --git a/cmd/litestream/testdata/generations/replica-url/replica/generations/0000000000000000/wal/00000000/00000000.wal.lz4 b/cmd/litestream/testdata/generations/replica-url/replica/generations/0000000000000000/wal/0000000000000000/0000000000000000.wal.lz4
similarity index 100%
rename from cmd/litestream/testdata/generations/replica-url/replica/generations/0000000000000000/wal/00000000/00000000.wal.lz4
rename to cmd/litestream/testdata/generations/replica-url/replica/generations/0000000000000000/wal/0000000000000000/0000000000000000.wal.lz4
diff --git a/cmd/litestream/testdata/generations/replica-url/replica/generations/0000000000000000/wal/00000000/00000001.wal.lz4 b/cmd/litestream/testdata/generations/replica-url/replica/generations/0000000000000000/wal/0000000000000000/0000000000000001.wal.lz4
similarity index 100%
rename from cmd/litestream/testdata/generations/replica-url/replica/generations/0000000000000000/wal/00000000/00000001.wal.lz4
rename to cmd/litestream/testdata/generations/replica-url/replica/generations/0000000000000000/wal/0000000000000000/0000000000000001.wal.lz4
diff --git a/cmd/litestream/testdata/generations/replica-url/replica/generations/0000000000000000/wal/00000001/00000000.wal.lz4 b/cmd/litestream/testdata/generations/replica-url/replica/generations/0000000000000000/wal/0000000000000001/0000000000000000.wal.lz4
similarity index 100%
rename from cmd/litestream/testdata/generations/replica-url/replica/generations/0000000000000000/wal/00000001/00000000.wal.lz4
rename to cmd/litestream/testdata/generations/replica-url/replica/generations/0000000000000000/wal/0000000000000001/0000000000000000.wal.lz4
diff --git a/cmd/litestream/testdata/generations/replica-url/replica/generations/0000000000000001/snapshots/00000000.snapshot.lz4 b/cmd/litestream/testdata/generations/replica-url/replica/generations/0000000000000001/snapshots/0000000000000000.snapshot.lz4
similarity index 100%
rename from cmd/litestream/testdata/generations/replica-url/replica/generations/0000000000000001/snapshots/00000000.snapshot.lz4
rename to cmd/litestream/testdata/generations/replica-url/replica/generations/0000000000000001/snapshots/0000000000000000.snapshot.lz4
diff --git a/cmd/litestream/testdata/restore/latest-replica/Makefile b/cmd/litestream/testdata/restore/latest-replica/Makefile
index a8a9885..24334a3 100644
--- a/cmd/litestream/testdata/restore/latest-replica/Makefile
+++ b/cmd/litestream/testdata/restore/latest-replica/Makefile
@@ -1,6 +1,6 @@
 .PHONY: default
 default:
-	TZ=UTC touch -ct 200001010000 replica0/generations/0000000000000000/snapshots/00000000.snapshot.lz4
-	TZ=UTC touch -ct 200001020000 replica1/generations/0000000000000002/snapshots/00000000.snapshot.lz4
-	TZ=UTC touch -ct 200001030000 replica0/generations/0000000000000001/snapshots/00000000.snapshot.lz4
+	TZ=UTC touch -ct 200001010000 replica0/generations/0000000000000000/snapshots/0000000000000000.snapshot.lz4
+	TZ=UTC touch -ct 200001020000 replica1/generations/0000000000000002/snapshots/0000000000000000.snapshot.lz4
+	TZ=UTC touch -ct 200001030000 replica0/generations/0000000000000001/snapshots/0000000000000000.snapshot.lz4
 
diff --git a/cmd/litestream/testdata/restore/latest-replica/replica0/generations/0000000000000000/snapshots/00000000.snapshot.lz4 b/cmd/litestream/testdata/restore/latest-replica/replica0/generations/0000000000000000/snapshots/0000000000000000.snapshot.lz4
similarity index 100%
rename from cmd/litestream/testdata/restore/latest-replica/replica0/generations/0000000000000000/snapshots/00000000.snapshot.lz4
rename to cmd/litestream/testdata/restore/latest-replica/replica0/generations/0000000000000000/snapshots/0000000000000000.snapshot.lz4
diff --git a/cmd/litestream/testdata/restore/latest-replica/replica0/generations/0000000000000001/snapshots/00000000.snapshot.lz4 b/cmd/litestream/testdata/restore/latest-replica/replica0/generations/0000000000000001/snapshots/0000000000000000.snapshot.lz4
similarity index 100%
rename from cmd/litestream/testdata/restore/latest-replica/replica0/generations/0000000000000001/snapshots/00000000.snapshot.lz4
rename to cmd/litestream/testdata/restore/latest-replica/replica0/generations/0000000000000001/snapshots/0000000000000000.snapshot.lz4
diff --git a/cmd/litestream/testdata/restore/latest-replica/replica1/generations/0000000000000002/snapshots/00000000.snapshot.lz4 b/cmd/litestream/testdata/restore/latest-replica/replica1/generations/0000000000000002/snapshots/0000000000000000.snapshot.lz4
similarity index 100%
rename from cmd/litestream/testdata/restore/latest-replica/replica1/generations/0000000000000002/snapshots/00000000.snapshot.lz4
rename to cmd/litestream/testdata/restore/latest-replica/replica1/generations/0000000000000002/snapshots/0000000000000000.snapshot.lz4
diff --git a/cmd/litestream/testdata/restore/ok/00000002.db b/cmd/litestream/testdata/restore/ok/0000000000000002.db
similarity index 100%
rename from cmd/litestream/testdata/restore/ok/00000002.db
rename to cmd/litestream/testdata/restore/ok/0000000000000002.db
diff --git a/cmd/litestream/testdata/restore/ok/README b/cmd/litestream/testdata/restore/ok/README
index 9450f45..48c0fd4 100644
--- a/cmd/litestream/testdata/restore/ok/README
+++ b/cmd/litestream/testdata/restore/ok/README
@@ -5,9 +5,9 @@ To reproduce this testdata, run sqlite3 and execute:
 	INSERT INTO t (x) VALUES (1);
 	INSERT INTO t (x) VALUES (2);
 
-	sl3 split -o generations/0000000000000000/wal/00000000 db-wal
-	cp db generations/0000000000000000/snapshots/00000000.snapshot
-	lz4 -c --rm generations/0000000000000000/snapshots/00000000.snapshot
+	sl3 split -o generations/0000000000000000/wal/0000000000000000 db-wal
+	cp db generations/0000000000000000/snapshots/0000000000000000.snapshot
+	lz4 -c --rm generations/0000000000000000/snapshots/0000000000000000.snapshot
 
 
 Then execute:
@@ -15,7 +15,7 @@ Then execute:
 	PRAGMA wal_checkpoint(TRUNCATE);
 	INSERT INTO t (x) VALUES (3);
 
-	sl3 split -o generations/0000000000000000/wal/00000001 db-wal
+	sl3 split -o generations/0000000000000000/wal/0000000000000001 db-wal
 
 
 Then execute:
@@ -24,13 +24,13 @@ Then execute:
 	INSERT INTO t (x) VALUES (4);
 	INSERT INTO t (x) VALUES (5);
 
-	sl3 split -o generations/0000000000000000/wal/00000002 db-wal
+	sl3 split -o generations/0000000000000000/wal/0000000000000002 db-wal
 
 
 Finally, obtain the final snapshot:
 
 	PRAGMA wal_checkpoint(TRUNCATE);
 
-	cp db 00000002.db
+	cp db 0000000000000002.db
 	rm db*
 
diff --git a/cmd/litestream/testdata/restore/ok/replica/generations/0000000000000000/snapshots/00000000.snapshot.lz4 b/cmd/litestream/testdata/restore/ok/replica/generations/0000000000000000/snapshots/0000000000000000.snapshot.lz4
similarity index 100%
rename from cmd/litestream/testdata/restore/ok/replica/generations/0000000000000000/snapshots/00000000.snapshot.lz4
rename to cmd/litestream/testdata/restore/ok/replica/generations/0000000000000000/snapshots/0000000000000000.snapshot.lz4
diff --git a/cmd/litestream/testdata/restore/ok/replica/generations/0000000000000000/wal/00000000/00000000.wal.lz4 b/cmd/litestream/testdata/restore/ok/replica/generations/0000000000000000/wal/0000000000000000/0000000000000000.wal.lz4
similarity index 100%
rename from cmd/litestream/testdata/restore/ok/replica/generations/0000000000000000/wal/00000000/00000000.wal.lz4
rename to cmd/litestream/testdata/restore/ok/replica/generations/0000000000000000/wal/0000000000000000/0000000000000000.wal.lz4
diff --git a/cmd/litestream/testdata/restore/ok/replica/generations/0000000000000000/wal/00000000/00002050.wal.lz4 b/cmd/litestream/testdata/restore/ok/replica/generations/0000000000000000/wal/0000000000000000/0000000000002050.wal.lz4
similarity index 100%
rename from cmd/litestream/testdata/restore/ok/replica/generations/0000000000000000/wal/00000000/00002050.wal.lz4
rename to cmd/litestream/testdata/restore/ok/replica/generations/0000000000000000/wal/0000000000000000/0000000000002050.wal.lz4
diff --git a/cmd/litestream/testdata/restore/ok/replica/generations/0000000000000000/wal/00000000/00003068.wal.lz4 b/cmd/litestream/testdata/restore/ok/replica/generations/0000000000000000/wal/0000000000000000/0000000000003068.wal.lz4
similarity index 100%
rename from cmd/litestream/testdata/restore/ok/replica/generations/0000000000000000/wal/00000000/00003068.wal.lz4
rename to cmd/litestream/testdata/restore/ok/replica/generations/0000000000000000/wal/0000000000000000/0000000000003068.wal.lz4
diff --git a/cmd/litestream/testdata/restore/ok/replica/generations/0000000000000000/wal/00000001/00000000.wal.lz4 b/cmd/litestream/testdata/restore/ok/replica/generations/0000000000000000/wal/0000000000000001/0000000000000000.wal.lz4
similarity index 100%
rename from cmd/litestream/testdata/restore/ok/replica/generations/0000000000000000/wal/00000001/00000000.wal.lz4
rename to cmd/litestream/testdata/restore/ok/replica/generations/0000000000000000/wal/0000000000000001/0000000000000000.wal.lz4
diff --git a/cmd/litestream/testdata/restore/ok/replica/generations/0000000000000000/wal/00000002/00000000.wal.lz4 b/cmd/litestream/testdata/restore/ok/replica/generations/0000000000000000/wal/0000000000000002/0000000000000000.wal.lz4
similarity index 100%
rename from cmd/litestream/testdata/restore/ok/replica/generations/0000000000000000/wal/00000002/00000000.wal.lz4
rename to cmd/litestream/testdata/restore/ok/replica/generations/0000000000000000/wal/0000000000000002/0000000000000000.wal.lz4
diff --git a/cmd/litestream/testdata/restore/ok/replica/generations/0000000000000000/wal/00000002/00001038.wal.lz4 b/cmd/litestream/testdata/restore/ok/replica/generations/0000000000000000/wal/0000000000000002/0000000000001038.wal.lz4
similarity index 100%
rename from cmd/litestream/testdata/restore/ok/replica/generations/0000000000000000/wal/00000002/00001038.wal.lz4
rename to cmd/litestream/testdata/restore/ok/replica/generations/0000000000000000/wal/0000000000000002/0000000000001038.wal.lz4
diff --git a/cmd/litestream/testdata/restore/replica-name/replica0/generations/0000000000000000/snapshots/00000000.snapshot.lz4 b/cmd/litestream/testdata/restore/replica-name/replica0/generations/0000000000000000/snapshots/0000000000000000.snapshot.lz4
similarity index 100%
rename from cmd/litestream/testdata/restore/replica-name/replica0/generations/0000000000000000/snapshots/00000000.snapshot.lz4
rename to cmd/litestream/testdata/restore/replica-name/replica0/generations/0000000000000000/snapshots/0000000000000000.snapshot.lz4
diff --git a/cmd/litestream/testdata/restore/replica-name/replica1/generations/0000000000000001/snapshots/00000001.snapshot.lz4 b/cmd/litestream/testdata/restore/replica-name/replica1/generations/0000000000000001/snapshots/0000000000000001.snapshot.lz4
similarity index 100%
rename from cmd/litestream/testdata/restore/replica-name/replica1/generations/0000000000000001/snapshots/00000001.snapshot.lz4
rename to cmd/litestream/testdata/restore/replica-name/replica1/generations/0000000000000001/snapshots/0000000000000001.snapshot.lz4
diff --git a/cmd/litestream/testdata/restore/replica-url/replica/generations/0000000000000000/snapshots/00000000.snapshot.lz4 b/cmd/litestream/testdata/restore/replica-url/replica/generations/0000000000000000/snapshots/0000000000000000.snapshot.lz4
similarity index 100%
rename from cmd/litestream/testdata/restore/replica-url/replica/generations/0000000000000000/snapshots/00000000.snapshot.lz4
rename to cmd/litestream/testdata/restore/replica-url/replica/generations/0000000000000000/snapshots/0000000000000000.snapshot.lz4
diff --git a/cmd/litestream/testdata/snapshots/ok/Makefile b/cmd/litestream/testdata/snapshots/ok/Makefile
index 866903e..739022a 100644
--- a/cmd/litestream/testdata/snapshots/ok/Makefile
+++ b/cmd/litestream/testdata/snapshots/ok/Makefile
@@ -1,6 +1,6 @@
 .PHONY: default
 default:
-	TZ=UTC touch -ct 200001010000 replica/generations/0000000000000000/snapshots/00000000.snapshot.lz4
-	TZ=UTC touch -ct 200001020000 replica/generations/0000000000000000/snapshots/00000001.snapshot.lz4
-	TZ=UTC touch -ct 200001030000 replica/generations/0000000000000001/snapshots/00000000.snapshot.lz4
+	TZ=UTC touch -ct 200001010000 replica/generations/0000000000000000/snapshots/0000000000000000.snapshot.lz4
+	TZ=UTC touch -ct 200001020000 replica/generations/0000000000000000/snapshots/0000000000000001.snapshot.lz4
+	TZ=UTC touch -ct 200001030000 replica/generations/0000000000000001/snapshots/0000000000000000.snapshot.lz4
 
diff --git a/cmd/litestream/testdata/snapshots/ok/replica/generations/0000000000000000/snapshots/00000000.snapshot.lz4 b/cmd/litestream/testdata/snapshots/ok/replica/generations/0000000000000000/snapshots/0000000000000000.snapshot.lz4
similarity index 100%
rename from cmd/litestream/testdata/snapshots/ok/replica/generations/0000000000000000/snapshots/00000000.snapshot.lz4
rename to cmd/litestream/testdata/snapshots/ok/replica/generations/0000000000000000/snapshots/0000000000000000.snapshot.lz4
diff --git a/cmd/litestream/testdata/snapshots/ok/replica/generations/0000000000000000/snapshots/00000001.snapshot.lz4 b/cmd/litestream/testdata/snapshots/ok/replica/generations/0000000000000000/snapshots/0000000000000001.snapshot.lz4
similarity index 100%
rename from cmd/litestream/testdata/snapshots/ok/replica/generations/0000000000000000/snapshots/00000001.snapshot.lz4
rename to cmd/litestream/testdata/snapshots/ok/replica/generations/0000000000000000/snapshots/0000000000000001.snapshot.lz4
diff --git a/cmd/litestream/testdata/snapshots/ok/replica/generations/0000000000000001/snapshots/00000000.snapshot.lz4 b/cmd/litestream/testdata/snapshots/ok/replica/generations/0000000000000001/snapshots/0000000000000000.snapshot.lz4
similarity index 100%
rename from cmd/litestream/testdata/snapshots/ok/replica/generations/0000000000000001/snapshots/00000000.snapshot.lz4
rename to cmd/litestream/testdata/snapshots/ok/replica/generations/0000000000000001/snapshots/0000000000000000.snapshot.lz4
diff --git a/cmd/litestream/testdata/snapshots/ok/stdout b/cmd/litestream/testdata/snapshots/ok/stdout
index 270a252..604aefc 100644
--- a/cmd/litestream/testdata/snapshots/ok/stdout
+++ b/cmd/litestream/testdata/snapshots/ok/stdout
@@ -1,4 +1,4 @@
-replica  generation        index     size  created
-file     0000000000000001  00000000  93    2000-01-03T00:00:00Z
-file     0000000000000000  00000001  93    2000-01-02T00:00:00Z
-file     0000000000000000  00000000  93    2000-01-01T00:00:00Z
+replica  generation        index             size  created
+file     0000000000000001  0000000000000000  93    2000-01-03T00:00:00Z
+file     0000000000000000  0000000000000001  93    2000-01-02T00:00:00Z
+file     0000000000000000  0000000000000000  93    2000-01-01T00:00:00Z
diff --git a/cmd/litestream/testdata/snapshots/replica-name/Makefile b/cmd/litestream/testdata/snapshots/replica-name/Makefile
index 050a241..c739902 100644
--- a/cmd/litestream/testdata/snapshots/replica-name/Makefile
+++ b/cmd/litestream/testdata/snapshots/replica-name/Makefile
@@ -1,4 +1,4 @@
 .PHONY: default
 default:
-	TZ=UTC touch -ct 200001010000 replica0/generations/0000000000000000/snapshots/00000000.snapshot.lz4
-	TZ=UTC touch -ct 200001020000 replica1/generations/0000000000000001/snapshots/00000000.snapshot.lz4
+	TZ=UTC touch -ct 200001010000 replica0/generations/0000000000000000/snapshots/0000000000000000.snapshot.lz4
+	TZ=UTC touch -ct 200001020000 replica1/generations/0000000000000001/snapshots/0000000000000000.snapshot.lz4
diff --git a/cmd/litestream/testdata/snapshots/replica-name/replica0/generations/0000000000000000/snapshots/00000000.snapshot.lz4 b/cmd/litestream/testdata/snapshots/replica-name/replica0/generations/0000000000000000/snapshots/0000000000000000.snapshot.lz4
similarity index 100%
rename from cmd/litestream/testdata/snapshots/replica-name/replica0/generations/0000000000000000/snapshots/00000000.snapshot.lz4
rename to cmd/litestream/testdata/snapshots/replica-name/replica0/generations/0000000000000000/snapshots/0000000000000000.snapshot.lz4
diff --git a/cmd/litestream/testdata/snapshots/replica-name/replica0/generations/0000000000000000/snapshots/00000001.snapshot.lz4 b/cmd/litestream/testdata/snapshots/replica-name/replica0/generations/0000000000000000/snapshots/0000000000000001.snapshot.lz4
similarity index 100%
rename from cmd/litestream/testdata/snapshots/replica-name/replica0/generations/0000000000000000/snapshots/00000001.snapshot.lz4
rename to cmd/litestream/testdata/snapshots/replica-name/replica0/generations/0000000000000000/snapshots/0000000000000001.snapshot.lz4
diff --git a/cmd/litestream/testdata/snapshots/replica-name/replica1/generations/0000000000000001/snapshots/00000000.snapshot.lz4 b/cmd/litestream/testdata/snapshots/replica-name/replica1/generations/0000000000000001/snapshots/0000000000000000.snapshot.lz4
similarity index 100%
rename from cmd/litestream/testdata/snapshots/replica-name/replica1/generations/0000000000000001/snapshots/00000000.snapshot.lz4
rename to cmd/litestream/testdata/snapshots/replica-name/replica1/generations/0000000000000001/snapshots/0000000000000000.snapshot.lz4
diff --git a/cmd/litestream/testdata/snapshots/replica-name/stdout b/cmd/litestream/testdata/snapshots/replica-name/stdout
index 42c074e..276224a 100644
--- a/cmd/litestream/testdata/snapshots/replica-name/stdout
+++ b/cmd/litestream/testdata/snapshots/replica-name/stdout
@@ -1,2 +1,2 @@
-replica   generation        index     size  created
-replica1  0000000000000001  00000000  93    2000-01-02T00:00:00Z
+replica   generation        index             size  created
+replica1  0000000000000001  0000000000000000  93    2000-01-02T00:00:00Z
diff --git a/cmd/litestream/testdata/snapshots/replica-url/Makefile b/cmd/litestream/testdata/snapshots/replica-url/Makefile
index f300c83..233e922 100644
--- a/cmd/litestream/testdata/snapshots/replica-url/Makefile
+++ b/cmd/litestream/testdata/snapshots/replica-url/Makefile
@@ -1,5 +1,5 @@
 .PHONY: default
 default:
-	TZ=UTC touch -ct 200001010000 replica/generations/0000000000000000/snapshots/00000000.snapshot.lz4
-	TZ=UTC touch -ct 200001020000 replica/generations/0000000000000000/snapshots/00000001.snapshot.lz4
-	TZ=UTC touch -ct 200001030000 replica/generations/0000000000000001/snapshots/00000000.snapshot.lz4
+	TZ=UTC touch -ct 200001010000 replica/generations/0000000000000000/snapshots/0000000000000000.snapshot.lz4
+	TZ=UTC touch -ct 200001020000 replica/generations/0000000000000000/snapshots/0000000000000001.snapshot.lz4
+	TZ=UTC touch -ct 200001030000 replica/generations/0000000000000001/snapshots/0000000000000000.snapshot.lz4
diff --git a/cmd/litestream/testdata/snapshots/replica-url/replica/generations/0000000000000000/snapshots/00000000.snapshot.lz4 b/cmd/litestream/testdata/snapshots/replica-url/replica/generations/0000000000000000/snapshots/0000000000000000.snapshot.lz4
similarity index 100%
rename from cmd/litestream/testdata/snapshots/replica-url/replica/generations/0000000000000000/snapshots/00000000.snapshot.lz4
rename to cmd/litestream/testdata/snapshots/replica-url/replica/generations/0000000000000000/snapshots/0000000000000000.snapshot.lz4
diff --git a/cmd/litestream/testdata/snapshots/replica-url/replica/generations/0000000000000000/snapshots/00000001.snapshot.lz4 b/cmd/litestream/testdata/snapshots/replica-url/replica/generations/0000000000000000/snapshots/0000000000000001.snapshot.lz4
similarity index 100%
rename from cmd/litestream/testdata/snapshots/replica-url/replica/generations/0000000000000000/snapshots/00000001.snapshot.lz4
rename to cmd/litestream/testdata/snapshots/replica-url/replica/generations/0000000000000000/snapshots/0000000000000001.snapshot.lz4
diff --git a/cmd/litestream/testdata/snapshots/replica-url/replica/generations/0000000000000001/snapshots/00000000.snapshot.lz4 b/cmd/litestream/testdata/snapshots/replica-url/replica/generations/0000000000000001/snapshots/0000000000000000.snapshot.lz4
similarity index 100%
rename from cmd/litestream/testdata/snapshots/replica-url/replica/generations/0000000000000001/snapshots/00000000.snapshot.lz4
rename to cmd/litestream/testdata/snapshots/replica-url/replica/generations/0000000000000001/snapshots/0000000000000000.snapshot.lz4
diff --git a/cmd/litestream/testdata/snapshots/replica-url/stdout b/cmd/litestream/testdata/snapshots/replica-url/stdout
index 270a252..604aefc 100644
--- a/cmd/litestream/testdata/snapshots/replica-url/stdout
+++ b/cmd/litestream/testdata/snapshots/replica-url/stdout
@@ -1,4 +1,4 @@
-replica  generation        index     size  created
-file     0000000000000001  00000000  93    2000-01-03T00:00:00Z
-file     0000000000000000  00000001  93    2000-01-02T00:00:00Z
-file     0000000000000000  00000000  93    2000-01-01T00:00:00Z
+replica  generation        index             size  created
+file     0000000000000001  0000000000000000  93    2000-01-03T00:00:00Z
+file     0000000000000000  0000000000000001  93    2000-01-02T00:00:00Z
+file     0000000000000000  0000000000000000  93    2000-01-01T00:00:00Z
diff --git a/cmd/litestream/testdata/wal/ok/Makefile b/cmd/litestream/testdata/wal/ok/Makefile
index 2bb5a8e..6522d94 100644
--- a/cmd/litestream/testdata/wal/ok/Makefile
+++ b/cmd/litestream/testdata/wal/ok/Makefile
@@ -1,7 +1,7 @@
 .PHONY: default
 default:
-	TZ=UTC touch -ct 200001010000 replica/generations/0000000000000000/wal/00000000/00000000.wal.lz4
-	TZ=UTC touch -ct 200001020000 replica/generations/0000000000000000/wal/00000000/00000001.wal.lz4
-	TZ=UTC touch -ct 200001030000 replica/generations/0000000000000000/wal/00000001/00000000.wal.lz4
-	TZ=UTC touch -ct 200001040000 replica/generations/0000000000000001/wal/00000000/00000000.wal.lz4
+	TZ=UTC touch -ct 200001010000 replica/generations/0000000000000000/wal/0000000000000000/0000000000000000.wal.lz4
+	TZ=UTC touch -ct 200001020000 replica/generations/0000000000000000/wal/0000000000000000/0000000000000001.wal.lz4
+	TZ=UTC touch -ct 200001030000 replica/generations/0000000000000000/wal/0000000000000001/0000000000000000.wal.lz4
+	TZ=UTC touch -ct 200001040000 replica/generations/0000000000000001/wal/0000000000000000/0000000000000000.wal.lz4
 
diff --git a/cmd/litestream/testdata/wal/ok/replica/generations/0000000000000000/wal/00000000/00000000.wal.lz4 b/cmd/litestream/testdata/wal/ok/replica/generations/0000000000000000/wal/0000000000000000/0000000000000000.wal.lz4
similarity index 100%
rename from cmd/litestream/testdata/wal/ok/replica/generations/0000000000000000/wal/00000000/00000000.wal.lz4
rename to cmd/litestream/testdata/wal/ok/replica/generations/0000000000000000/wal/0000000000000000/0000000000000000.wal.lz4
diff --git a/cmd/litestream/testdata/wal/ok/replica/generations/0000000000000000/wal/00000000/00000001.wal.lz4 b/cmd/litestream/testdata/wal/ok/replica/generations/0000000000000000/wal/0000000000000000/0000000000000001.wal.lz4
similarity index 100%
rename from cmd/litestream/testdata/wal/ok/replica/generations/0000000000000000/wal/00000000/00000001.wal.lz4
rename to cmd/litestream/testdata/wal/ok/replica/generations/0000000000000000/wal/0000000000000000/0000000000000001.wal.lz4
diff --git a/cmd/litestream/testdata/wal/ok/replica/generations/0000000000000000/wal/00000001/00000000.wal.lz4 b/cmd/litestream/testdata/wal/ok/replica/generations/0000000000000000/wal/0000000000000001/0000000000000000.wal.lz4
similarity index 100%
rename from cmd/litestream/testdata/wal/ok/replica/generations/0000000000000000/wal/00000001/00000000.wal.lz4
rename to cmd/litestream/testdata/wal/ok/replica/generations/0000000000000000/wal/0000000000000001/0000000000000000.wal.lz4
diff --git a/cmd/litestream/testdata/wal/ok/replica/generations/0000000000000001/wal/00000000/00000000.wal.lz4 b/cmd/litestream/testdata/wal/ok/replica/generations/0000000000000001/wal/0000000000000000/0000000000000000.wal.lz4
similarity index 100%
rename from cmd/litestream/testdata/wal/ok/replica/generations/0000000000000001/wal/00000000/00000000.wal.lz4
rename to cmd/litestream/testdata/wal/ok/replica/generations/0000000000000001/wal/0000000000000000/0000000000000000.wal.lz4
diff --git a/cmd/litestream/testdata/wal/ok/stdout b/cmd/litestream/testdata/wal/ok/stdout
index 90a58f5..f420a7b 100644
--- a/cmd/litestream/testdata/wal/ok/stdout
+++ b/cmd/litestream/testdata/wal/ok/stdout
@@ -1,5 +1,5 @@
-replica  generation        index     offset    size  created
-file     0000000000000001  00000000  00000000  93    2000-01-04T00:00:00Z
-file     0000000000000000  00000001  00000000  93    2000-01-03T00:00:00Z
-file     0000000000000000  00000000  00000001  93    2000-01-02T00:00:00Z
-file     0000000000000000  00000000  00000000  93    2000-01-01T00:00:00Z
+replica  generation        index             offset            size  created
+file     0000000000000001  0000000000000000  0000000000000000  93    2000-01-04T00:00:00Z
+file     0000000000000000  0000000000000001  0000000000000000  93    2000-01-03T00:00:00Z
+file     0000000000000000  0000000000000000  0000000000000001  93    2000-01-02T00:00:00Z
+file     0000000000000000  0000000000000000  0000000000000000  93    2000-01-01T00:00:00Z
diff --git a/cmd/litestream/testdata/wal/replica-name/Makefile b/cmd/litestream/testdata/wal/replica-name/Makefile
index 5556bc8..2347a2f 100644
--- a/cmd/litestream/testdata/wal/replica-name/Makefile
+++ b/cmd/litestream/testdata/wal/replica-name/Makefile
@@ -1,6 +1,6 @@
 .PHONY: default
 default:
-	TZ=UTC touch -ct 200001010000 replica0/generations/0000000000000000/wal/00000000/00000000.wal.lz4
-	TZ=UTC touch -ct 200001020000 replica1/generations/0000000000000000/wal/00000000/00000001.wal.lz4
-	TZ=UTC touch -ct 200001030000 replica1/generations/0000000000000000/wal/00000001/00000000.wal.lz4
-	TZ=UTC touch -ct 200001040000 replica1/generations/0000000000000001/wal/00000000/00000000.wal.lz4
+	TZ=UTC touch -ct 200001010000 replica0/generations/0000000000000000/wal/0000000000000000/0000000000000000.wal.lz4
+	TZ=UTC touch -ct 200001020000 replica1/generations/0000000000000000/wal/0000000000000000/0000000000000001.wal.lz4
+	TZ=UTC touch -ct 200001030000 replica1/generations/0000000000000000/wal/0000000000000001/0000000000000000.wal.lz4
+	TZ=UTC touch -ct 200001040000 replica1/generations/0000000000000001/wal/0000000000000000/0000000000000000.wal.lz4
diff --git a/cmd/litestream/testdata/wal/replica-name/replica0/generations/0000000000000000/wal/00000000/00000000.wal.lz4 b/cmd/litestream/testdata/wal/replica-name/replica0/generations/0000000000000000/wal/0000000000000000/0000000000000000.wal.lz4
similarity index 100%
rename from cmd/litestream/testdata/wal/replica-name/replica0/generations/0000000000000000/wal/00000000/00000000.wal.lz4
rename to cmd/litestream/testdata/wal/replica-name/replica0/generations/0000000000000000/wal/0000000000000000/0000000000000000.wal.lz4
diff --git a/cmd/litestream/testdata/wal/replica-name/replica0/generations/0000000000000000/wal/00000000/00000001.wal.lz4 b/cmd/litestream/testdata/wal/replica-name/replica0/generations/0000000000000000/wal/0000000000000000/0000000000000001.wal.lz4
similarity index 100%
rename from cmd/litestream/testdata/wal/replica-name/replica0/generations/0000000000000000/wal/00000000/00000001.wal.lz4
rename to cmd/litestream/testdata/wal/replica-name/replica0/generations/0000000000000000/wal/0000000000000000/0000000000000001.wal.lz4
diff --git a/cmd/litestream/testdata/wal/replica-name/replica0/generations/0000000000000000/wal/00000001/00000000.wal.lz4 b/cmd/litestream/testdata/wal/replica-name/replica0/generations/0000000000000000/wal/0000000000000001/0000000000000000.wal.lz4
similarity index 100%
rename from cmd/litestream/testdata/wal/replica-name/replica0/generations/0000000000000000/wal/00000001/00000000.wal.lz4
rename to cmd/litestream/testdata/wal/replica-name/replica0/generations/0000000000000000/wal/0000000000000001/0000000000000000.wal.lz4
diff --git a/cmd/litestream/testdata/wal/replica-name/replica1/generations/0000000000000001/wal/00000000/00000000.wal.lz4 b/cmd/litestream/testdata/wal/replica-name/replica1/generations/0000000000000001/wal/0000000000000000/0000000000000000.wal.lz4
similarity index 100%
rename from cmd/litestream/testdata/wal/replica-name/replica1/generations/0000000000000001/wal/00000000/00000000.wal.lz4
rename to cmd/litestream/testdata/wal/replica-name/replica1/generations/0000000000000001/wal/0000000000000000/0000000000000000.wal.lz4
diff --git a/cmd/litestream/testdata/wal/replica-name/stdout b/cmd/litestream/testdata/wal/replica-name/stdout
index 2e9f9d9..80756dc 100644
--- a/cmd/litestream/testdata/wal/replica-name/stdout
+++ b/cmd/litestream/testdata/wal/replica-name/stdout
@@ -1,2 +1,2 @@
-replica   generation        index     offset    size  created
-replica1  0000000000000001  00000000  00000000  93    2000-01-04T00:00:00Z
+replica   generation        index             offset            size  created
+replica1  0000000000000001  0000000000000000  0000000000000000  93    2000-01-04T00:00:00Z
diff --git a/cmd/litestream/testdata/wal/replica-url/Makefile b/cmd/litestream/testdata/wal/replica-url/Makefile
index 2bb5a8e..6522d94 100644
--- a/cmd/litestream/testdata/wal/replica-url/Makefile
+++ b/cmd/litestream/testdata/wal/replica-url/Makefile
@@ -1,7 +1,7 @@
 .PHONY: default
 default:
-	TZ=UTC touch -ct 200001010000 replica/generations/0000000000000000/wal/00000000/00000000.wal.lz4
-	TZ=UTC touch -ct 200001020000 replica/generations/0000000000000000/wal/00000000/00000001.wal.lz4
-	TZ=UTC touch -ct 200001030000 replica/generations/0000000000000000/wal/00000001/00000000.wal.lz4
-	TZ=UTC touch -ct 200001040000 replica/generations/0000000000000001/wal/00000000/00000000.wal.lz4
+	TZ=UTC touch -ct 200001010000 replica/generations/0000000000000000/wal/0000000000000000/0000000000000000.wal.lz4
+	TZ=UTC touch -ct 200001020000 replica/generations/0000000000000000/wal/0000000000000000/0000000000000001.wal.lz4
+	TZ=UTC touch -ct 200001030000 replica/generations/0000000000000000/wal/0000000000000001/0000000000000000.wal.lz4
+	TZ=UTC touch -ct 200001040000 replica/generations/0000000000000001/wal/0000000000000000/0000000000000000.wal.lz4
 
diff --git a/cmd/litestream/testdata/wal/replica-url/replica/generations/0000000000000000/wal/00000000/00000000.wal.lz4 b/cmd/litestream/testdata/wal/replica-url/replica/generations/0000000000000000/wal/0000000000000000/0000000000000000.wal.lz4
similarity index 100%
rename from cmd/litestream/testdata/wal/replica-url/replica/generations/0000000000000000/wal/00000000/00000000.wal.lz4
rename to cmd/litestream/testdata/wal/replica-url/replica/generations/0000000000000000/wal/0000000000000000/0000000000000000.wal.lz4
diff --git a/cmd/litestream/testdata/wal/replica-url/replica/generations/0000000000000000/wal/00000000/00000001.wal.lz4 b/cmd/litestream/testdata/wal/replica-url/replica/generations/0000000000000000/wal/0000000000000000/0000000000000001.wal.lz4
similarity index 100%
rename from cmd/litestream/testdata/wal/replica-url/replica/generations/0000000000000000/wal/00000000/00000001.wal.lz4
rename to cmd/litestream/testdata/wal/replica-url/replica/generations/0000000000000000/wal/0000000000000000/0000000000000001.wal.lz4
diff --git a/cmd/litestream/testdata/wal/replica-url/replica/generations/0000000000000000/wal/00000001/00000000.wal.lz4 b/cmd/litestream/testdata/wal/replica-url/replica/generations/0000000000000000/wal/0000000000000001/0000000000000000.wal.lz4
similarity index 100%
rename from cmd/litestream/testdata/wal/replica-url/replica/generations/0000000000000000/wal/00000001/00000000.wal.lz4
rename to cmd/litestream/testdata/wal/replica-url/replica/generations/0000000000000000/wal/0000000000000001/0000000000000000.wal.lz4
diff --git a/cmd/litestream/testdata/wal/replica-url/replica/generations/0000000000000001/wal/00000000/00000000.wal.lz4 b/cmd/litestream/testdata/wal/replica-url/replica/generations/0000000000000001/wal/0000000000000000/0000000000000000.wal.lz4
similarity index 100%
rename from cmd/litestream/testdata/wal/replica-url/replica/generations/0000000000000001/wal/00000000/00000000.wal.lz4
rename to cmd/litestream/testdata/wal/replica-url/replica/generations/0000000000000001/wal/0000000000000000/0000000000000000.wal.lz4
diff --git a/cmd/litestream/testdata/wal/replica-url/stdout b/cmd/litestream/testdata/wal/replica-url/stdout
index 90a58f5..f420a7b 100644
--- a/cmd/litestream/testdata/wal/replica-url/stdout
+++ b/cmd/litestream/testdata/wal/replica-url/stdout
@@ -1,5 +1,5 @@
-replica  generation        index     offset    size  created
-file     0000000000000001  00000000  00000000  93    2000-01-04T00:00:00Z
-file     0000000000000000  00000001  00000000  93    2000-01-03T00:00:00Z
-file     0000000000000000  00000000  00000001  93    2000-01-02T00:00:00Z
-file     0000000000000000  00000000  00000000  93    2000-01-01T00:00:00Z
+replica  generation        index             offset            size  created
+file     0000000000000001  0000000000000000  0000000000000000  93    2000-01-04T00:00:00Z
+file     0000000000000000  0000000000000001  0000000000000000  93    2000-01-03T00:00:00Z
+file     0000000000000000  0000000000000000  0000000000000001  93    2000-01-02T00:00:00Z
+file     0000000000000000  0000000000000000  0000000000000000  93    2000-01-01T00:00:00Z
diff --git a/cmd/litestream/wal.go b/cmd/litestream/wal.go
index 1124c03..fc54840 100644
--- a/cmd/litestream/wal.go
+++ b/cmd/litestream/wal.go
@@ -108,11 +108,11 @@ func (c *WALCommand) Run(ctx context.Context, args []string) (ret error) {
 
 	fmt.Fprintln(w, "replica\tgeneration\tindex\toffset\tsize\tcreated")
 	for _, info := range infos {
-		fmt.Fprintf(w, "%s\t%s\t%08x\t%08x\t%d\t%s\n",
+		fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%d\t%s\n",
 			info.replicaName,
 			info.Generation,
-			info.Index,
-			info.Offset,
+			litestream.FormatIndex(info.Index),
+			litestream.FormatOffset(info.Offset),
 			info.Size,
 			info.CreatedAt.Format(time.RFC3339),
 		)
diff --git a/file_replica_client_test.go b/file_replica_client_test.go
index 65dcb11..1a1405f 100644
--- a/file_replica_client_test.go
+++ b/file_replica_client_test.go
@@ -78,7 +78,7 @@ func TestReplicaClient_SnapshotPath(t *testing.T) {
 	t.Run("OK", func(t *testing.T) {
 		if got, err := litestream.NewFileReplicaClient("/foo").SnapshotPath("0123456701234567", 1000); err != nil {
 			t.Fatal(err)
-		} else if want := "/foo/generations/0123456701234567/snapshots/000003e8.snapshot.lz4"; got != want {
+		} else if want := "/foo/generations/0123456701234567/snapshots/00000000000003e8.snapshot.lz4"; got != want {
 			t.Fatalf("SnapshotPath()=%v, want %v", got, want)
 		}
 	})
@@ -118,7 +118,7 @@ func TestReplicaClient_WALSegmentPath(t *testing.T) {
 	t.Run("OK", func(t *testing.T) {
 		if got, err := litestream.NewFileReplicaClient("/foo").WALSegmentPath("0123456701234567", 1000, 1001); err != nil {
 			t.Fatal(err)
-		} else if want := "/foo/generations/0123456701234567/wal/000003e8/000003e9.wal.lz4"; got != want {
+		} else if want := "/foo/generations/0123456701234567/wal/00000000000003e8/00000000000003e9.wal.lz4"; got != want {
 			t.Fatalf("WALPath()=%v, want %v", got, want)
 		}
 	})
diff --git a/internal/internal_test.go b/internal/internal_test.go
index 5b661de..9d2c49b 100644
--- a/internal/internal_test.go
+++ b/internal/internal_test.go
@@ -15,12 +15,12 @@ func TestParseSnapshotPath(t *testing.T) {
 		index int
 		err   error
 	}{
-		{"00bc614e.snapshot.lz4", 12345678, nil},
-		{"xxxxxxxx.snapshot.lz4", 0, fmt.Errorf("invalid snapshot path")},
-		{"00bc614.snapshot.lz4", 0, fmt.Errorf("invalid snapshot path")},
-		{"00bc614e.snapshot.lz", 0, fmt.Errorf("invalid snapshot path")},
-		{"00bc614e.snapshot", 0, fmt.Errorf("invalid snapshot path")},
-		{"00bc614e", 0, fmt.Errorf("invalid snapshot path")},
+		{"0000000000bc614e.snapshot.lz4", 12345678, nil},
+		{"xxxxxxxxxxxxxxxx.snapshot.lz4", 0, fmt.Errorf("invalid snapshot path")},
+		{"0000000000bc614.snapshot.lz4", 0, fmt.Errorf("invalid snapshot path")},
+		{"0000000000bc614e.snapshot.lz", 0, fmt.Errorf("invalid snapshot path")},
+		{"0000000000bc614e.snapshot", 0, fmt.Errorf("invalid snapshot path")},
+		{"0000000000bc614e", 0, fmt.Errorf("invalid snapshot path")},
 		{"", 0, fmt.Errorf("invalid snapshot path")},
 	} {
 		t.Run("", func(t *testing.T) {
@@ -41,20 +41,22 @@ func TestParseWALSegmentPath(t *testing.T) {
 		offset int64
 		err    error
 	}{
-		{"00bc614e/000003e8.wal.lz4", 12345678, 1000, nil},
-		{"00000000/00000000.wal", 0, 0, fmt.Errorf("invalid wal segment path")},
-		{"00000000/00000000", 0, 0, fmt.Errorf("invalid wal segment path")},
-		{"00000000/", 0, 0, fmt.Errorf("invalid wal segment path")},
-		{"00000000", 0, 0, fmt.Errorf("invalid wal segment path")},
+		{"0000000000bc614e/00000000000003e8.wal.lz4", 12345678, 1000, nil},
+		{"0000000000000000/0000000000000000.wal", 0, 0, fmt.Errorf("invalid wal segment path")},
+		{"0000000000000000/0000000000000000", 0, 0, fmt.Errorf("invalid wal segment path")},
+		{"0000000000000000/", 0, 0, fmt.Errorf("invalid wal segment path")},
+		{"0000000000000000", 0, 0, fmt.Errorf("invalid wal segment path")},
 		{"", 0, 0, fmt.Errorf("invalid wal segment path")},
 	} {
 		t.Run("", func(t *testing.T) {
 			index, offset, err := internal.ParseWALSegmentPath(tt.s)
 			if got, want := index, tt.index; got != want {
 				t.Errorf("index=%#v, want %#v", got, want)
-			} else if got, want := offset, tt.offset; got != want {
+			}
+			if got, want := offset, tt.offset; got != want {
 				t.Errorf("offset=%#v, want %#v", got, want)
-			} else if got, want := err, tt.err; !reflect.DeepEqual(got, want) {
+			}
+			if got, want := err, tt.err; !reflect.DeepEqual(got, want) {
 				t.Errorf("err=%#v, want %#v", got, want)
 			}
 		})
diff --git a/litestream.go b/litestream.go
index 5614457..c91c648 100644
--- a/litestream.go
+++ b/litestream.go
@@ -6,12 +6,14 @@ import (
 	"errors"
 	"fmt"
 	"io"
+	"math"
 	"os"
 	"path/filepath"
 	"strconv"
 	"strings"
 	"time"
 
+	"github.com/benbjohnson/litestream/internal"
 	"github.com/mattn/go-sqlite3"
 )
 
@@ -302,7 +304,7 @@ func (p Pos) String() string {
 	if p.IsZero() {
 		return ""
 	}
-	return fmt.Sprintf("%s/%08x:%08x", p.Generation, p.Index, p.Offset)
+	return fmt.Sprintf("%s/%s:%s", p.Generation, FormatIndex(p.Index), FormatOffset(p.Offset))
 }
 
 // IsZero returns true if p is the zero value.
@@ -426,32 +428,36 @@ func IsGenerationName(s string) bool {
 	return true
 }
 
-// FormatIndex formats an index as an 8-character hex value.
+// FormatIndex formats an index as a hex value.
 func FormatIndex(index int) string {
-	return fmt.Sprintf("%08x", index)
+	return fmt.Sprintf("%016x", index)
 }
 
 // ParseIndex parses a hex-formatted index into an integer.
 func ParseIndex(s string) (int, error) {
-	v, err := strconv.ParseUint(s, 16, 32)
+	v, err := strconv.ParseUint(s, 16, 64)
 	if err != nil {
 		return -1, fmt.Errorf("cannot parse index: %q", s)
+	} else if v > uint64(internal.MaxInt) {
+		return -1, fmt.Errorf("index too large: %q", s)
 	}
 	return int(v), nil
 }
 
-// FormatOffset formats an offset as an 8-character hex value.
+// FormatOffset formats an offset as a hex value.
 func FormatOffset(offset int64) string {
-	return fmt.Sprintf("%08x", offset)
+	return fmt.Sprintf("%016x", offset)
 }
 
 // ParseOffset parses a hex-formatted offset into an integer.
 func ParseOffset(s string) (int64, error) {
-	v, err := strconv.ParseInt(s, 16, 32)
+	v, err := strconv.ParseUint(s, 16, 64)
 	if err != nil {
-		return -1, fmt.Errorf("cannot parse index: %q", s)
+		return -1, fmt.Errorf("cannot parse offset: %q", s)
+	} else if v > math.MaxInt64 {
+		return -1, fmt.Errorf("offset too large: %q", s)
 	}
-	return v, nil
+	return int64(v), nil
 }
 
 // removeDBFiles deletes the database and related files (journal, shm, wal).
diff --git a/replica_client_test.go b/replica_client_test.go
index 65d5d81..fedf9f2 100644
--- a/replica_client_test.go
+++ b/replica_client_test.go
@@ -19,7 +19,7 @@ func TestFindSnapshotForIndex(t *testing.T) {
 		if snapshotIndex, err := litestream.FindSnapshotForIndex(context.Background(), client, "0000000000000000", 0x000007d0); err != nil {
 			t.Fatal(err)
 		} else if got, want := snapshotIndex, 0x000003e8; got != want {
-			t.Fatalf("index=%08x, want %08x", got, want)
+			t.Fatalf("index=%s, want %s", litestream.FormatIndex(got), litestream.FormatIndex(want))
 		}
 	})
 
@@ -28,14 +28,14 @@ func TestFindSnapshotForIndex(t *testing.T) {
 		if snapshotIndex, err := litestream.FindSnapshotForIndex(context.Background(), client, "0000000000000000", 0x000003e8); err != nil {
 			t.Fatal(err)
 		} else if got, want := snapshotIndex, 0x000003e8; got != want {
-			t.Fatalf("index=%08x, want %08x", got, want)
+			t.Fatalf("index=%s, want %s", litestream.FormatIndex(got), litestream.FormatIndex(want))
 		}
 	})
 
 	t.Run("ErrNoSnapshotsBeforeIndex", func(t *testing.T) {
 		client := litestream.NewFileReplicaClient(filepath.Join("testdata", "find-snapshot-for-index", "no-snapshots-before-index"))
 		_, err := litestream.FindSnapshotForIndex(context.Background(), client, "0000000000000000", 0x000003e8)
-		if err == nil || err.Error() != `no snapshots available at or before index 000003e8` {
+		if err == nil || err.Error() != `no snapshots available at or before index 00000000000003e8` {
 			t.Fatalf("unexpected error: %#v", err)
 		}
 	})
@@ -499,7 +499,7 @@ func TestRestore(t *testing.T) {
 		client := litestream.NewFileReplicaClient(testDir)
 		if err := litestream.Restore(context.Background(), client, filepath.Join(tempDir, "db"), "0000000000000000", 0, 2, litestream.NewRestoreOptions()); err != nil {
 			t.Fatal(err)
-		} else if !fileEqual(t, filepath.Join(testDir, "00000002.db"), filepath.Join(tempDir, "db")) {
+		} else if !fileEqual(t, filepath.Join(testDir, "0000000000000002.db"), filepath.Join(tempDir, "db")) {
 			t.Fatalf("file mismatch")
 		}
 	})
@@ -511,7 +511,7 @@ func TestRestore(t *testing.T) {
 		client := litestream.NewFileReplicaClient(testDir)
 		if err := litestream.Restore(context.Background(), client, filepath.Join(tempDir, "db"), "0000000000000000", 0, 0, litestream.NewRestoreOptions()); err != nil {
 			t.Fatal(err)
-		} else if !fileEqual(t, filepath.Join(testDir, "00000000.db"), filepath.Join(tempDir, "db")) {
+		} else if !fileEqual(t, filepath.Join(testDir, "0000000000000000.db"), filepath.Join(tempDir, "db")) {
 			t.Fatalf("file mismatch")
 		}
 	})
@@ -525,7 +525,7 @@ func TestRestore(t *testing.T) {
 		opt.Parallelism = 0
 		if err := litestream.Restore(context.Background(), client, filepath.Join(tempDir, "db"), "0000000000000000", 0, 2, opt); err != nil {
 			t.Fatal(err)
-		} else if !fileEqual(t, filepath.Join(testDir, "00000002.db"), filepath.Join(tempDir, "db")) {
+		} else if !fileEqual(t, filepath.Join(testDir, "0000000000000002.db"), filepath.Join(tempDir, "db")) {
 			t.Fatalf("file mismatch")
 		}
 	})
diff --git a/testdata/find-latest-generation/ok/Makefile b/testdata/find-latest-generation/ok/Makefile
index c71ce14..45a7e01 100644
--- a/testdata/find-latest-generation/ok/Makefile
+++ b/testdata/find-latest-generation/ok/Makefile
@@ -1,7 +1,7 @@
 .PHONY: default
 default:
-	TZ=UTC touch -ct 200001010000 generations/0000000000000000/snapshots/00000000.snapshot.lz4
-	TZ=UTC touch -ct 200001020000 generations/0000000000000000/snapshots/00000001.snapshot.lz4
-	TZ=UTC touch -ct 200001010000 generations/0000000000000001/snapshots/00000000.snapshot.lz4
-	TZ=UTC touch -ct 200001030000 generations/0000000000000001/snapshots/00000001.snapshot.lz4
-	TZ=UTC touch -ct 200001010000 generations/0000000000000002/snapshots/00000000.snapshot.lz4
+	TZ=UTC touch -ct 200001010000 generations/0000000000000000/snapshots/0000000000000000.snapshot.lz4
+	TZ=UTC touch -ct 200001020000 generations/0000000000000000/snapshots/0000000000000001.snapshot.lz4
+	TZ=UTC touch -ct 200001010000 generations/0000000000000001/snapshots/0000000000000000.snapshot.lz4
+	TZ=UTC touch -ct 200001030000 generations/0000000000000001/snapshots/0000000000000001.snapshot.lz4
+	TZ=UTC touch -ct 200001010000 generations/0000000000000002/snapshots/0000000000000000.snapshot.lz4
diff --git a/testdata/find-latest-generation/ok/generations/0000000000000000/snapshots/00000000.snapshot.lz4 b/testdata/find-latest-generation/ok/generations/0000000000000000/snapshots/0000000000000000.snapshot.lz4
similarity index 100%
rename from testdata/find-latest-generation/ok/generations/0000000000000000/snapshots/00000000.snapshot.lz4
rename to testdata/find-latest-generation/ok/generations/0000000000000000/snapshots/0000000000000000.snapshot.lz4
diff --git a/testdata/find-latest-generation/ok/generations/0000000000000000/snapshots/00000001.snapshot.lz4 b/testdata/find-latest-generation/ok/generations/0000000000000000/snapshots/0000000000000001.snapshot.lz4
similarity index 100%
rename from testdata/find-latest-generation/ok/generations/0000000000000000/snapshots/00000001.snapshot.lz4
rename to testdata/find-latest-generation/ok/generations/0000000000000000/snapshots/0000000000000001.snapshot.lz4
diff --git a/testdata/find-latest-generation/ok/generations/0000000000000001/snapshots/00000000.snapshot.lz4 b/testdata/find-latest-generation/ok/generations/0000000000000001/snapshots/0000000000000000.snapshot.lz4
similarity index 100%
rename from testdata/find-latest-generation/ok/generations/0000000000000001/snapshots/00000000.snapshot.lz4
rename to testdata/find-latest-generation/ok/generations/0000000000000001/snapshots/0000000000000000.snapshot.lz4
diff --git a/testdata/find-latest-generation/ok/generations/0000000000000001/snapshots/00000001.snapshot.lz4 b/testdata/find-latest-generation/ok/generations/0000000000000001/snapshots/0000000000000001.snapshot.lz4
similarity index 100%
rename from testdata/find-latest-generation/ok/generations/0000000000000001/snapshots/00000001.snapshot.lz4
rename to testdata/find-latest-generation/ok/generations/0000000000000001/snapshots/0000000000000001.snapshot.lz4
diff --git a/testdata/find-latest-generation/ok/generations/0000000000000002/snapshots/00000000.snapshot.lz4 b/testdata/find-latest-generation/ok/generations/0000000000000002/snapshots/0000000000000000.snapshot.lz4
similarity index 100%
rename from testdata/find-latest-generation/ok/generations/0000000000000002/snapshots/00000000.snapshot.lz4
rename to testdata/find-latest-generation/ok/generations/0000000000000002/snapshots/0000000000000000.snapshot.lz4
diff --git a/testdata/find-snapshot-for-index/no-snapshots-before-index/generations/0000000000000000/snapshots/00000bb8.snapshot.lz4 b/testdata/find-snapshot-for-index/no-snapshots-before-index/generations/0000000000000000/snapshots/0000000000000bb8.snapshot.lz4
similarity index 100%
rename from testdata/find-snapshot-for-index/no-snapshots-before-index/generations/0000000000000000/snapshots/00000bb8.snapshot.lz4
rename to testdata/find-snapshot-for-index/no-snapshots-before-index/generations/0000000000000000/snapshots/0000000000000bb8.snapshot.lz4
diff --git a/testdata/find-snapshot-for-index/ok/generations/0000000000000000/snapshots/00000000.snapshot.lz4 b/testdata/find-snapshot-for-index/ok/generations/0000000000000000/snapshots/0000000000000000.snapshot.lz4
similarity index 100%
rename from testdata/find-snapshot-for-index/ok/generations/0000000000000000/snapshots/00000000.snapshot.lz4
rename to testdata/find-snapshot-for-index/ok/generations/0000000000000000/snapshots/0000000000000000.snapshot.lz4
diff --git a/testdata/find-snapshot-for-index/ok/generations/0000000000000000/snapshots/000003e8.snapshot.lz4 b/testdata/find-snapshot-for-index/ok/generations/0000000000000000/snapshots/00000000000003e8.snapshot.lz4
similarity index 100%
rename from testdata/find-snapshot-for-index/ok/generations/0000000000000000/snapshots/000003e8.snapshot.lz4
rename to testdata/find-snapshot-for-index/ok/generations/0000000000000000/snapshots/00000000000003e8.snapshot.lz4
diff --git a/testdata/find-snapshot-for-index/ok/generations/0000000000000000/snapshots/00000bb8.snapshot.lz4 b/testdata/find-snapshot-for-index/ok/generations/0000000000000000/snapshots/0000000000000bb8.snapshot.lz4
similarity index 100%
rename from testdata/find-snapshot-for-index/ok/generations/0000000000000000/snapshots/00000bb8.snapshot.lz4
rename to testdata/find-snapshot-for-index/ok/generations/0000000000000000/snapshots/0000000000000bb8.snapshot.lz4
diff --git a/testdata/generation-time-bounds/ok/Makefile b/testdata/generation-time-bounds/ok/Makefile
index e29f9e4..7f2ad77 100644
--- a/testdata/generation-time-bounds/ok/Makefile
+++ b/testdata/generation-time-bounds/ok/Makefile
@@ -1,8 +1,8 @@
 .PHONY: default
 default:
-	TZ=UTC touch -ct 200001010000 generations/0000000000000000/snapshots/00000000.snapshot.lz4
-	TZ=UTC touch -ct 200001020000 generations/0000000000000000/snapshots/00000001.snapshot.lz4
-	TZ=UTC touch -ct 200001010000 generations/0000000000000000/wal/00000000/00000000.wal.lz4
-	TZ=UTC touch -ct 200001020000 generations/0000000000000000/wal/00000000/00000001.wal.lz4
-	TZ=UTC touch -ct 200001030000 generations/0000000000000000/wal/00000001/00000000.wal.lz4
+	TZ=UTC touch -ct 200001010000 generations/0000000000000000/snapshots/0000000000000000.snapshot.lz4
+	TZ=UTC touch -ct 200001020000 generations/0000000000000000/snapshots/0000000000000001.snapshot.lz4
+	TZ=UTC touch -ct 200001010000 generations/0000000000000000/wal/0000000000000000/0000000000000000.wal.lz4
+	TZ=UTC touch -ct 200001020000 generations/0000000000000000/wal/0000000000000000/0000000000000001.wal.lz4
+	TZ=UTC touch -ct 200001030000 generations/0000000000000000/wal/0000000000000001/0000000000000000.wal.lz4
 
diff --git a/testdata/generation-time-bounds/ok/generations/0000000000000000/snapshots/00000000.snapshot.lz4 b/testdata/generation-time-bounds/ok/generations/0000000000000000/snapshots/0000000000000000.snapshot.lz4
similarity index 100%
rename from testdata/generation-time-bounds/ok/generations/0000000000000000/snapshots/00000000.snapshot.lz4
rename to testdata/generation-time-bounds/ok/generations/0000000000000000/snapshots/0000000000000000.snapshot.lz4
diff --git a/testdata/generation-time-bounds/ok/generations/0000000000000000/snapshots/00000001.snapshot.lz4 b/testdata/generation-time-bounds/ok/generations/0000000000000000/snapshots/0000000000000001.snapshot.lz4
similarity index 100%
rename from testdata/generation-time-bounds/ok/generations/0000000000000000/snapshots/00000001.snapshot.lz4
rename to testdata/generation-time-bounds/ok/generations/0000000000000000/snapshots/0000000000000001.snapshot.lz4
diff --git a/testdata/generation-time-bounds/ok/generations/0000000000000000/wal/00000000/00000000.wal.lz4 b/testdata/generation-time-bounds/ok/generations/0000000000000000/wal/0000000000000000/0000000000000000.wal.lz4
similarity index 100%
rename from testdata/generation-time-bounds/ok/generations/0000000000000000/wal/00000000/00000000.wal.lz4
rename to testdata/generation-time-bounds/ok/generations/0000000000000000/wal/0000000000000000/0000000000000000.wal.lz4
diff --git a/testdata/generation-time-bounds/ok/generations/0000000000000000/wal/00000000/00000001.wal.lz4 b/testdata/generation-time-bounds/ok/generations/0000000000000000/wal/0000000000000000/0000000000000001.wal.lz4
similarity index 100%
rename from testdata/generation-time-bounds/ok/generations/0000000000000000/wal/00000000/00000001.wal.lz4
rename to testdata/generation-time-bounds/ok/generations/0000000000000000/wal/0000000000000000/0000000000000001.wal.lz4
diff --git a/testdata/generation-time-bounds/ok/generations/0000000000000000/wal/00000001/00000000.wal.lz4 b/testdata/generation-time-bounds/ok/generations/0000000000000000/wal/0000000000000001/0000000000000000.wal.lz4
similarity index 100%
rename from testdata/generation-time-bounds/ok/generations/0000000000000000/wal/00000001/00000000.wal.lz4
rename to testdata/generation-time-bounds/ok/generations/0000000000000000/wal/0000000000000001/0000000000000000.wal.lz4
diff --git a/testdata/generation-time-bounds/snapshots-only/Makefile b/testdata/generation-time-bounds/snapshots-only/Makefile
index 6405068..aa5978b 100644
--- a/testdata/generation-time-bounds/snapshots-only/Makefile
+++ b/testdata/generation-time-bounds/snapshots-only/Makefile
@@ -1,5 +1,5 @@
 .PHONY: default
 default:
-	TZ=UTC touch -ct 200001010000 generations/0000000000000000/snapshots/00000000.snapshot.lz4
-	TZ=UTC touch -ct 200001020000 generations/0000000000000000/snapshots/00000001.snapshot.lz4
+	TZ=UTC touch -ct 200001010000 generations/0000000000000000/snapshots/0000000000000000.snapshot.lz4
+	TZ=UTC touch -ct 200001020000 generations/0000000000000000/snapshots/0000000000000001.snapshot.lz4
 
diff --git a/testdata/generation-time-bounds/snapshots-only/generations/0000000000000000/snapshots/00000000.snapshot.lz4 b/testdata/generation-time-bounds/snapshots-only/generations/0000000000000000/snapshots/0000000000000000.snapshot.lz4
similarity index 100%
rename from testdata/generation-time-bounds/snapshots-only/generations/0000000000000000/snapshots/00000000.snapshot.lz4
rename to testdata/generation-time-bounds/snapshots-only/generations/0000000000000000/snapshots/0000000000000000.snapshot.lz4
diff --git a/testdata/generation-time-bounds/snapshots-only/generations/0000000000000000/snapshots/00000001.snapshot.lz4 b/testdata/generation-time-bounds/snapshots-only/generations/0000000000000000/snapshots/0000000000000001.snapshot.lz4
similarity index 100%
rename from testdata/generation-time-bounds/snapshots-only/generations/0000000000000000/snapshots/00000001.snapshot.lz4
rename to testdata/generation-time-bounds/snapshots-only/generations/0000000000000000/snapshots/0000000000000001.snapshot.lz4
diff --git a/testdata/max-index/no-wal/generations/0000000000000000/snapshots/00000000.snapshot.lz4 b/testdata/max-index/no-wal/generations/0000000000000000/snapshots/0000000000000000.snapshot.lz4
similarity index 100%
rename from testdata/max-index/no-wal/generations/0000000000000000/snapshots/00000000.snapshot.lz4
rename to testdata/max-index/no-wal/generations/0000000000000000/snapshots/0000000000000000.snapshot.lz4
diff --git a/testdata/max-index/no-wal/generations/0000000000000000/snapshots/00000001.snapshot.lz4 b/testdata/max-index/no-wal/generations/0000000000000000/snapshots/0000000000000001.snapshot.lz4
similarity index 100%
rename from testdata/max-index/no-wal/generations/0000000000000000/snapshots/00000001.snapshot.lz4
rename to testdata/max-index/no-wal/generations/0000000000000000/snapshots/0000000000000001.snapshot.lz4
diff --git a/testdata/max-index/ok/generations/0000000000000000/snapshots/00000000.snapshot.lz4 b/testdata/max-index/ok/generations/0000000000000000/snapshots/0000000000000000.snapshot.lz4
similarity index 100%
rename from testdata/max-index/ok/generations/0000000000000000/snapshots/00000000.snapshot.lz4
rename to testdata/max-index/ok/generations/0000000000000000/snapshots/0000000000000000.snapshot.lz4
diff --git a/testdata/max-index/ok/generations/0000000000000000/snapshots/00000001.snapshot.lz4 b/testdata/max-index/ok/generations/0000000000000000/snapshots/0000000000000001.snapshot.lz4
similarity index 100%
rename from testdata/max-index/ok/generations/0000000000000000/snapshots/00000001.snapshot.lz4
rename to testdata/max-index/ok/generations/0000000000000000/snapshots/0000000000000001.snapshot.lz4
diff --git a/testdata/max-index/ok/generations/0000000000000000/wal/00000000/00000000.wal.lz4 b/testdata/max-index/ok/generations/0000000000000000/wal/0000000000000000/0000000000000000.wal.lz4
similarity index 100%
rename from testdata/max-index/ok/generations/0000000000000000/wal/00000000/00000000.wal.lz4
rename to testdata/max-index/ok/generations/0000000000000000/wal/0000000000000000/0000000000000000.wal.lz4
diff --git a/testdata/max-index/ok/generations/0000000000000000/wal/00000000/00001234.wal.lz4 b/testdata/max-index/ok/generations/0000000000000000/wal/0000000000000000/0000000000001234.wal.lz4
similarity index 100%
rename from testdata/max-index/ok/generations/0000000000000000/wal/00000000/00001234.wal.lz4
rename to testdata/max-index/ok/generations/0000000000000000/wal/0000000000000000/0000000000001234.wal.lz4
diff --git a/testdata/max-index/ok/generations/0000000000000000/wal/00000001/00000000.wal.lz4 b/testdata/max-index/ok/generations/0000000000000000/wal/0000000000000001/0000000000000000.wal.lz4
similarity index 100%
rename from testdata/max-index/ok/generations/0000000000000000/wal/00000001/00000000.wal.lz4
rename to testdata/max-index/ok/generations/0000000000000000/wal/0000000000000001/0000000000000000.wal.lz4
diff --git a/testdata/max-index/ok/generations/0000000000000000/wal/00000002/00000000.wal.lz4 b/testdata/max-index/ok/generations/0000000000000000/wal/0000000000000002/0000000000000000.wal.lz4
similarity index 100%
rename from testdata/max-index/ok/generations/0000000000000000/wal/00000002/00000000.wal.lz4
rename to testdata/max-index/ok/generations/0000000000000000/wal/0000000000000002/0000000000000000.wal.lz4
diff --git a/testdata/max-index/snapshot-later-than-wal/generations/0000000000000000/snapshots/00000000.snapshot.lz4 b/testdata/max-index/snapshot-later-than-wal/generations/0000000000000000/snapshots/0000000000000000.snapshot.lz4
similarity index 100%
rename from testdata/max-index/snapshot-later-than-wal/generations/0000000000000000/snapshots/00000000.snapshot.lz4
rename to testdata/max-index/snapshot-later-than-wal/generations/0000000000000000/snapshots/0000000000000000.snapshot.lz4
diff --git a/testdata/max-index/snapshot-later-than-wal/generations/0000000000000000/snapshots/00000001.snapshot.lz4 b/testdata/max-index/snapshot-later-than-wal/generations/0000000000000000/snapshots/0000000000000001.snapshot.lz4
similarity index 100%
rename from testdata/max-index/snapshot-later-than-wal/generations/0000000000000000/snapshots/00000001.snapshot.lz4
rename to testdata/max-index/snapshot-later-than-wal/generations/0000000000000000/snapshots/0000000000000001.snapshot.lz4
diff --git a/testdata/max-index/snapshot-later-than-wal/generations/0000000000000000/wal/00000000/00000000.wal.lz4 b/testdata/max-index/snapshot-later-than-wal/generations/0000000000000000/wal/0000000000000000/0000000000000000.wal.lz4
similarity index 100%
rename from testdata/max-index/snapshot-later-than-wal/generations/0000000000000000/wal/00000000/00000000.wal.lz4
rename to testdata/max-index/snapshot-later-than-wal/generations/0000000000000000/wal/0000000000000000/0000000000000000.wal.lz4
diff --git a/testdata/max-index/snapshot-later-than-wal/generations/0000000000000000/wal/00000000/00001234.wal.lz4 b/testdata/max-index/snapshot-later-than-wal/generations/0000000000000000/wal/0000000000000000/0000000000001234.wal.lz4
similarity index 100%
rename from testdata/max-index/snapshot-later-than-wal/generations/0000000000000000/wal/00000000/00001234.wal.lz4
rename to testdata/max-index/snapshot-later-than-wal/generations/0000000000000000/wal/0000000000000000/0000000000001234.wal.lz4
diff --git a/testdata/max-snapshot-index/ok/Makefile b/testdata/max-snapshot-index/ok/Makefile
index d7b4d6c..47e186b 100644
--- a/testdata/max-snapshot-index/ok/Makefile
+++ b/testdata/max-snapshot-index/ok/Makefile
@@ -1,6 +1,6 @@
 .PHONY: default
 default:
-	TZ=UTC touch -ct 200001020000 generations/0000000000000000/snapshots/00000000.snapshot.lz4
-	TZ=UTC touch -ct 200001010000 generations/0000000000000001/snapshots/00000000.snapshot.lz4
-	TZ=UTC touch -ct 200001030000 generations/0000000000000001/snapshots/00000001.snapshot.lz4
-	TZ=UTC touch -ct 200001010000 generations/0000000000000002/snapshots/00000000.snapshot.lz4
+	TZ=UTC touch -ct 200001020000 generations/0000000000000000/snapshots/0000000000000000.snapshot.lz4
+	TZ=UTC touch -ct 200001010000 generations/0000000000000001/snapshots/0000000000000000.snapshot.lz4
+	TZ=UTC touch -ct 200001030000 generations/0000000000000001/snapshots/0000000000000001.snapshot.lz4
+	TZ=UTC touch -ct 200001010000 generations/0000000000000002/snapshots/0000000000000000.snapshot.lz4
diff --git a/testdata/max-snapshot-index/ok/generations/0000000000000000/snapshots/00000000.snapshot.lz4 b/testdata/max-snapshot-index/ok/generations/0000000000000000/snapshots/0000000000000000.snapshot.lz4
similarity index 100%
rename from testdata/max-snapshot-index/ok/generations/0000000000000000/snapshots/00000000.snapshot.lz4
rename to testdata/max-snapshot-index/ok/generations/0000000000000000/snapshots/0000000000000000.snapshot.lz4
diff --git a/testdata/max-snapshot-index/ok/generations/0000000000000000/snapshots/000003e8.snapshot.lz4 b/testdata/max-snapshot-index/ok/generations/0000000000000000/snapshots/00000000000003e8.snapshot.lz4
similarity index 100%
rename from testdata/max-snapshot-index/ok/generations/0000000000000000/snapshots/000003e8.snapshot.lz4
rename to testdata/max-snapshot-index/ok/generations/0000000000000000/snapshots/00000000000003e8.snapshot.lz4
diff --git a/testdata/max-snapshot-index/ok/generations/0000000000000000/snapshots/000007d0.snapshot.lz4 b/testdata/max-snapshot-index/ok/generations/0000000000000000/snapshots/00000000000007d0.snapshot.lz4
similarity index 100%
rename from testdata/max-snapshot-index/ok/generations/0000000000000000/snapshots/000007d0.snapshot.lz4
rename to testdata/max-snapshot-index/ok/generations/0000000000000000/snapshots/00000000000007d0.snapshot.lz4
diff --git a/testdata/max-wal-index/ok/generations/0000000000000000/wal/00000000/00000000.wal.lz4 b/testdata/max-wal-index/ok/generations/0000000000000000/wal/0000000000000000/0000000000000000.wal.lz4
similarity index 100%
rename from testdata/max-wal-index/ok/generations/0000000000000000/wal/00000000/00000000.wal.lz4
rename to testdata/max-wal-index/ok/generations/0000000000000000/wal/0000000000000000/0000000000000000.wal.lz4
diff --git a/testdata/max-wal-index/ok/generations/0000000000000000/wal/00000000/00001234.wal.lz4 b/testdata/max-wal-index/ok/generations/0000000000000000/wal/0000000000000000/0000000000001234.wal.lz4
similarity index 100%
rename from testdata/max-wal-index/ok/generations/0000000000000000/wal/00000000/00001234.wal.lz4
rename to testdata/max-wal-index/ok/generations/0000000000000000/wal/0000000000000000/0000000000001234.wal.lz4
diff --git a/testdata/max-wal-index/ok/generations/0000000000000000/wal/00000001/00000000.wal.lz4 b/testdata/max-wal-index/ok/generations/0000000000000000/wal/0000000000000001/0000000000000000.wal.lz4
similarity index 100%
rename from testdata/max-wal-index/ok/generations/0000000000000000/wal/00000001/00000000.wal.lz4
rename to testdata/max-wal-index/ok/generations/0000000000000000/wal/0000000000000001/0000000000000000.wal.lz4
diff --git a/testdata/replica-client-time-bounds/ok/Makefile b/testdata/replica-client-time-bounds/ok/Makefile
index d7b4d6c..47e186b 100644
--- a/testdata/replica-client-time-bounds/ok/Makefile
+++ b/testdata/replica-client-time-bounds/ok/Makefile
@@ -1,6 +1,6 @@
 .PHONY: default
 default:
-	TZ=UTC touch -ct 200001020000 generations/0000000000000000/snapshots/00000000.snapshot.lz4
-	TZ=UTC touch -ct 200001010000 generations/0000000000000001/snapshots/00000000.snapshot.lz4
-	TZ=UTC touch -ct 200001030000 generations/0000000000000001/snapshots/00000001.snapshot.lz4
-	TZ=UTC touch -ct 200001010000 generations/0000000000000002/snapshots/00000000.snapshot.lz4
+	TZ=UTC touch -ct 200001020000 generations/0000000000000000/snapshots/0000000000000000.snapshot.lz4
+	TZ=UTC touch -ct 200001010000 generations/0000000000000001/snapshots/0000000000000000.snapshot.lz4
+	TZ=UTC touch -ct 200001030000 generations/0000000000000001/snapshots/0000000000000001.snapshot.lz4
+	TZ=UTC touch -ct 200001010000 generations/0000000000000002/snapshots/0000000000000000.snapshot.lz4
diff --git a/testdata/replica-client-time-bounds/ok/generations/0000000000000000/snapshots/00000000.snapshot.lz4 b/testdata/replica-client-time-bounds/ok/generations/0000000000000000/snapshots/0000000000000000.snapshot.lz4
similarity index 100%
rename from testdata/replica-client-time-bounds/ok/generations/0000000000000000/snapshots/00000000.snapshot.lz4
rename to testdata/replica-client-time-bounds/ok/generations/0000000000000000/snapshots/0000000000000000.snapshot.lz4
diff --git a/testdata/replica-client-time-bounds/ok/generations/0000000000000001/snapshots/00000000.snapshot.lz4 b/testdata/replica-client-time-bounds/ok/generations/0000000000000001/snapshots/0000000000000000.snapshot.lz4
similarity index 100%
rename from testdata/replica-client-time-bounds/ok/generations/0000000000000001/snapshots/00000000.snapshot.lz4
rename to testdata/replica-client-time-bounds/ok/generations/0000000000000001/snapshots/0000000000000000.snapshot.lz4
diff --git a/testdata/replica-client-time-bounds/ok/generations/0000000000000001/snapshots/00000001.snapshot.lz4 b/testdata/replica-client-time-bounds/ok/generations/0000000000000001/snapshots/0000000000000001.snapshot.lz4
similarity index 100%
rename from testdata/replica-client-time-bounds/ok/generations/0000000000000001/snapshots/00000001.snapshot.lz4
rename to testdata/replica-client-time-bounds/ok/generations/0000000000000001/snapshots/0000000000000001.snapshot.lz4
diff --git a/testdata/replica-client-time-bounds/ok/generations/0000000000000002/snapshots/00000000.snapshot.lz4 b/testdata/replica-client-time-bounds/ok/generations/0000000000000002/snapshots/0000000000000000.snapshot.lz4
similarity index 100%
rename from testdata/replica-client-time-bounds/ok/generations/0000000000000002/snapshots/00000000.snapshot.lz4
rename to testdata/replica-client-time-bounds/ok/generations/0000000000000002/snapshots/0000000000000000.snapshot.lz4
diff --git a/testdata/restore/bad-permissions/00000000.db b/testdata/restore/bad-permissions/0000000000000000.db
similarity index 100%
rename from testdata/restore/bad-permissions/00000000.db
rename to testdata/restore/bad-permissions/0000000000000000.db
diff --git a/testdata/restore/bad-permissions/README b/testdata/restore/bad-permissions/README
index 9450f45..48c0fd4 100644
--- a/testdata/restore/bad-permissions/README
+++ b/testdata/restore/bad-permissions/README
@@ -5,9 +5,9 @@ To reproduce this testdata, run sqlite3 and execute:
 	INSERT INTO t (x) VALUES (1);
 	INSERT INTO t (x) VALUES (2);
 
-	sl3 split -o generations/0000000000000000/wal/00000000 db-wal
-	cp db generations/0000000000000000/snapshots/00000000.snapshot
-	lz4 -c --rm generations/0000000000000000/snapshots/00000000.snapshot
+	sl3 split -o generations/0000000000000000/wal/0000000000000000 db-wal
+	cp db generations/0000000000000000/snapshots/0000000000000000.snapshot
+	lz4 -c --rm generations/0000000000000000/snapshots/0000000000000000.snapshot
 
 
 Then execute:
@@ -15,7 +15,7 @@ Then execute:
 	PRAGMA wal_checkpoint(TRUNCATE);
 	INSERT INTO t (x) VALUES (3);
 
-	sl3 split -o generations/0000000000000000/wal/00000001 db-wal
+	sl3 split -o generations/0000000000000000/wal/0000000000000001 db-wal
 
 
 Then execute:
@@ -24,13 +24,13 @@ Then execute:
 	INSERT INTO t (x) VALUES (4);
 	INSERT INTO t (x) VALUES (5);
 
-	sl3 split -o generations/0000000000000000/wal/00000002 db-wal
+	sl3 split -o generations/0000000000000000/wal/0000000000000002 db-wal
 
 
 Finally, obtain the final snapshot:
 
 	PRAGMA wal_checkpoint(TRUNCATE);
 
-	cp db 00000002.db
+	cp db 0000000000000002.db
 	rm db*
 
diff --git a/testdata/restore/bad-permissions/generations/0000000000000000/snapshots/00000000.snapshot.lz4 b/testdata/restore/bad-permissions/generations/0000000000000000/snapshots/0000000000000000.snapshot.lz4
similarity index 100%
rename from testdata/restore/bad-permissions/generations/0000000000000000/snapshots/00000000.snapshot.lz4
rename to testdata/restore/bad-permissions/generations/0000000000000000/snapshots/0000000000000000.snapshot.lz4
diff --git a/testdata/restore/ok/00000002.db b/testdata/restore/ok/0000000000000002.db
similarity index 100%
rename from testdata/restore/ok/00000002.db
rename to testdata/restore/ok/0000000000000002.db
diff --git a/testdata/restore/ok/README b/testdata/restore/ok/README
index 9450f45..48c0fd4 100644
--- a/testdata/restore/ok/README
+++ b/testdata/restore/ok/README
@@ -5,9 +5,9 @@ To reproduce this testdata, run sqlite3 and execute:
 	INSERT INTO t (x) VALUES (1);
 	INSERT INTO t (x) VALUES (2);
 
-	sl3 split -o generations/0000000000000000/wal/00000000 db-wal
-	cp db generations/0000000000000000/snapshots/00000000.snapshot
-	lz4 -c --rm generations/0000000000000000/snapshots/00000000.snapshot
+	sl3 split -o generations/0000000000000000/wal/0000000000000000 db-wal
+	cp db generations/0000000000000000/snapshots/0000000000000000.snapshot
+	lz4 -c --rm generations/0000000000000000/snapshots/0000000000000000.snapshot
 
 
 Then execute:
@@ -15,7 +15,7 @@ Then execute:
 	PRAGMA wal_checkpoint(TRUNCATE);
 	INSERT INTO t (x) VALUES (3);
 
-	sl3 split -o generations/0000000000000000/wal/00000001 db-wal
+	sl3 split -o generations/0000000000000000/wal/0000000000000001 db-wal
 
 
 Then execute:
@@ -24,13 +24,13 @@ Then execute:
 	INSERT INTO t (x) VALUES (4);
 	INSERT INTO t (x) VALUES (5);
 
-	sl3 split -o generations/0000000000000000/wal/00000002 db-wal
+	sl3 split -o generations/0000000000000000/wal/0000000000000002 db-wal
 
 
 Finally, obtain the final snapshot:
 
 	PRAGMA wal_checkpoint(TRUNCATE);
 
-	cp db 00000002.db
+	cp db 0000000000000002.db
 	rm db*
 
diff --git a/testdata/restore/ok/generations/0000000000000000/snapshots/00000000.snapshot.lz4 b/testdata/restore/ok/generations/0000000000000000/snapshots/0000000000000000.snapshot.lz4
similarity index 100%
rename from testdata/restore/ok/generations/0000000000000000/snapshots/00000000.snapshot.lz4
rename to testdata/restore/ok/generations/0000000000000000/snapshots/0000000000000000.snapshot.lz4
diff --git a/testdata/restore/ok/generations/0000000000000000/wal/00000000/00000000.wal.lz4 b/testdata/restore/ok/generations/0000000000000000/wal/0000000000000000/0000000000000000.wal.lz4
similarity index 100%
rename from testdata/restore/ok/generations/0000000000000000/wal/00000000/00000000.wal.lz4
rename to testdata/restore/ok/generations/0000000000000000/wal/0000000000000000/0000000000000000.wal.lz4
diff --git a/testdata/restore/ok/generations/0000000000000000/wal/00000000/00002050.wal.lz4 b/testdata/restore/ok/generations/0000000000000000/wal/0000000000000000/0000000000002050.wal.lz4
similarity index 100%
rename from testdata/restore/ok/generations/0000000000000000/wal/00000000/00002050.wal.lz4
rename to testdata/restore/ok/generations/0000000000000000/wal/0000000000000000/0000000000002050.wal.lz4
diff --git a/testdata/restore/ok/generations/0000000000000000/wal/00000000/00003068.wal.lz4 b/testdata/restore/ok/generations/0000000000000000/wal/0000000000000000/0000000000003068.wal.lz4
similarity index 100%
rename from testdata/restore/ok/generations/0000000000000000/wal/00000000/00003068.wal.lz4
rename to testdata/restore/ok/generations/0000000000000000/wal/0000000000000000/0000000000003068.wal.lz4
diff --git a/testdata/restore/ok/generations/0000000000000000/wal/00000001/00000000.wal.lz4 b/testdata/restore/ok/generations/0000000000000000/wal/0000000000000001/0000000000000000.wal.lz4
similarity index 100%
rename from testdata/restore/ok/generations/0000000000000000/wal/00000001/00000000.wal.lz4
rename to testdata/restore/ok/generations/0000000000000000/wal/0000000000000001/0000000000000000.wal.lz4
diff --git a/testdata/restore/ok/generations/0000000000000000/wal/00000002/00000000.wal.lz4 b/testdata/restore/ok/generations/0000000000000000/wal/0000000000000002/0000000000000000.wal.lz4
similarity index 100%
rename from testdata/restore/ok/generations/0000000000000000/wal/00000002/00000000.wal.lz4
rename to testdata/restore/ok/generations/0000000000000000/wal/0000000000000002/0000000000000000.wal.lz4
diff --git a/testdata/restore/ok/generations/0000000000000000/wal/00000002/00001038.wal.lz4 b/testdata/restore/ok/generations/0000000000000000/wal/0000000000000002/0000000000001038.wal.lz4
similarity index 100%
rename from testdata/restore/ok/generations/0000000000000000/wal/00000002/00001038.wal.lz4
rename to testdata/restore/ok/generations/0000000000000000/wal/0000000000000002/0000000000001038.wal.lz4
diff --git a/testdata/restore/snapshot-only/00000000.db b/testdata/restore/snapshot-only/0000000000000000.db
similarity index 100%
rename from testdata/restore/snapshot-only/00000000.db
rename to testdata/restore/snapshot-only/0000000000000000.db
diff --git a/testdata/restore/snapshot-only/generations/0000000000000000/snapshots/00000000.snapshot.lz4 b/testdata/restore/snapshot-only/generations/0000000000000000/snapshots/0000000000000000.snapshot.lz4
similarity index 100%
rename from testdata/restore/snapshot-only/generations/0000000000000000/snapshots/00000000.snapshot.lz4
rename to testdata/restore/snapshot-only/generations/0000000000000000/snapshots/0000000000000000.snapshot.lz4
diff --git a/testdata/snapshot-time-bounds/ok/Makefile b/testdata/snapshot-time-bounds/ok/Makefile
index 6c7e69a..8775133 100644
--- a/testdata/snapshot-time-bounds/ok/Makefile
+++ b/testdata/snapshot-time-bounds/ok/Makefile
@@ -1,6 +1,6 @@
 .PHONY: default
 default:
-	TZ=UTC touch -ct 200001010000 generations/0000000000000000/snapshots/00000000.snapshot.lz4
-	TZ=UTC touch -ct 200001020000 generations/0000000000000000/snapshots/00000001.snapshot.lz4
-	TZ=UTC touch -ct 200001030000 generations/0000000000000000/snapshots/00000002.snapshot.lz4
+	TZ=UTC touch -ct 200001010000 generations/0000000000000000/snapshots/0000000000000000.snapshot.lz4
+	TZ=UTC touch -ct 200001020000 generations/0000000000000000/snapshots/0000000000000001.snapshot.lz4
+	TZ=UTC touch -ct 200001030000 generations/0000000000000000/snapshots/0000000000000002.snapshot.lz4
 
diff --git a/testdata/snapshot-time-bounds/ok/generations/0000000000000000/snapshots/00000000.snapshot.lz4 b/testdata/snapshot-time-bounds/ok/generations/0000000000000000/snapshots/0000000000000000.snapshot.lz4
similarity index 100%
rename from testdata/snapshot-time-bounds/ok/generations/0000000000000000/snapshots/00000000.snapshot.lz4
rename to testdata/snapshot-time-bounds/ok/generations/0000000000000000/snapshots/0000000000000000.snapshot.lz4
diff --git a/testdata/snapshot-time-bounds/ok/generations/0000000000000000/snapshots/00000001.snapshot.lz4 b/testdata/snapshot-time-bounds/ok/generations/0000000000000000/snapshots/0000000000000001.snapshot.lz4
similarity index 100%
rename from testdata/snapshot-time-bounds/ok/generations/0000000000000000/snapshots/00000001.snapshot.lz4
rename to testdata/snapshot-time-bounds/ok/generations/0000000000000000/snapshots/0000000000000001.snapshot.lz4
diff --git a/testdata/snapshot-time-bounds/ok/generations/0000000000000000/snapshots/00000002.snapshot.lz4 b/testdata/snapshot-time-bounds/ok/generations/0000000000000000/snapshots/0000000000000002.snapshot.lz4
similarity index 100%
rename from testdata/snapshot-time-bounds/ok/generations/0000000000000000/snapshots/00000002.snapshot.lz4
rename to testdata/snapshot-time-bounds/ok/generations/0000000000000000/snapshots/0000000000000002.snapshot.lz4
diff --git a/testdata/wal-downloader/err-download-wal/generations/0000000000000000/wal/00000000/00000000.wal.lz4 b/testdata/wal-downloader/err-download-wal/generations/0000000000000000/wal/0000000000000000/0000000000000000.wal.lz4
similarity index 100%
rename from testdata/wal-downloader/err-download-wal/generations/0000000000000000/wal/00000000/00000000.wal.lz4
rename to testdata/wal-downloader/err-download-wal/generations/0000000000000000/wal/0000000000000000/0000000000000000.wal.lz4
diff --git a/testdata/wal-downloader/err-read-wal-segment/generations/0000000000000000/wal/00000000/00000000.wal.lz4 b/testdata/wal-downloader/err-read-wal-segment/generations/0000000000000000/wal/0000000000000000/0000000000000000.wal.lz4
similarity index 100%
rename from testdata/wal-downloader/err-read-wal-segment/generations/0000000000000000/wal/00000000/00000000.wal.lz4
rename to testdata/wal-downloader/err-read-wal-segment/generations/0000000000000000/wal/0000000000000000/0000000000000000.wal.lz4
diff --git a/testdata/wal-downloader/err-write-wal/generations/0000000000000000/wal/00000000/00000000.wal.lz4 b/testdata/wal-downloader/err-write-wal/generations/0000000000000000/wal/0000000000000000/0000000000000000.wal.lz4
similarity index 100%
rename from testdata/wal-downloader/err-write-wal/generations/0000000000000000/wal/00000000/00000000.wal.lz4
rename to testdata/wal-downloader/err-write-wal/generations/0000000000000000/wal/0000000000000000/0000000000000000.wal.lz4
diff --git a/testdata/wal-downloader/missing-ending-index/generations/0000000000000000/wal/00000000/00000000.wal.lz4 b/testdata/wal-downloader/missing-ending-index/generations/0000000000000000/wal/0000000000000000/0000000000000000.wal.lz4
similarity index 100%
rename from testdata/wal-downloader/missing-ending-index/generations/0000000000000000/wal/00000000/00000000.wal.lz4
rename to testdata/wal-downloader/missing-ending-index/generations/0000000000000000/wal/0000000000000000/0000000000000000.wal.lz4
diff --git a/testdata/wal-downloader/missing-ending-index/generations/0000000000000000/wal/00000000/00002050.wal.lz4 b/testdata/wal-downloader/missing-ending-index/generations/0000000000000000/wal/0000000000000000/0000000000002050.wal.lz4
similarity index 100%
rename from testdata/wal-downloader/missing-ending-index/generations/0000000000000000/wal/00000000/00002050.wal.lz4
rename to testdata/wal-downloader/missing-ending-index/generations/0000000000000000/wal/0000000000000000/0000000000002050.wal.lz4
diff --git a/testdata/wal-downloader/missing-ending-index/generations/0000000000000000/wal/00000000/00003068.wal.lz4 b/testdata/wal-downloader/missing-ending-index/generations/0000000000000000/wal/0000000000000000/0000000000003068.wal.lz4
similarity index 100%
rename from testdata/wal-downloader/missing-ending-index/generations/0000000000000000/wal/00000000/00003068.wal.lz4
rename to testdata/wal-downloader/missing-ending-index/generations/0000000000000000/wal/0000000000000000/0000000000003068.wal.lz4
diff --git a/testdata/wal-downloader/missing-ending-index/generations/0000000000000000/wal/00000001/00000000.wal.lz4 b/testdata/wal-downloader/missing-ending-index/generations/0000000000000000/wal/0000000000000001/0000000000000000.wal.lz4
similarity index 100%
rename from testdata/wal-downloader/missing-ending-index/generations/0000000000000000/wal/00000001/00000000.wal.lz4
rename to testdata/wal-downloader/missing-ending-index/generations/0000000000000000/wal/0000000000000001/0000000000000000.wal.lz4
diff --git a/testdata/wal-downloader/missing-initial-index/generations/0000000000000000/wal/00000001/00000000.wal.lz4 b/testdata/wal-downloader/missing-initial-index/generations/0000000000000000/wal/0000000000000001/0000000000000000.wal.lz4
similarity index 100%
rename from testdata/wal-downloader/missing-initial-index/generations/0000000000000000/wal/00000001/00000000.wal.lz4
rename to testdata/wal-downloader/missing-initial-index/generations/0000000000000000/wal/0000000000000001/0000000000000000.wal.lz4
diff --git a/testdata/wal-downloader/missing-initial-index/generations/0000000000000000/wal/00000002/00000000.wal.lz4 b/testdata/wal-downloader/missing-initial-index/generations/0000000000000000/wal/0000000000000002/0000000000000000.wal.lz4
similarity index 100%
rename from testdata/wal-downloader/missing-initial-index/generations/0000000000000000/wal/00000002/00000000.wal.lz4
rename to testdata/wal-downloader/missing-initial-index/generations/0000000000000000/wal/0000000000000002/0000000000000000.wal.lz4
diff --git a/testdata/wal-downloader/missing-initial-index/generations/0000000000000000/wal/00000002/00001038.wal.lz4 b/testdata/wal-downloader/missing-initial-index/generations/0000000000000000/wal/0000000000000002/0000000000001038.wal.lz4
similarity index 100%
rename from testdata/wal-downloader/missing-initial-index/generations/0000000000000000/wal/00000002/00001038.wal.lz4
rename to testdata/wal-downloader/missing-initial-index/generations/0000000000000000/wal/0000000000000002/0000000000001038.wal.lz4
diff --git a/testdata/wal-downloader/missing-middle-index/generations/0000000000000000/wal/00000000/00000000.wal.lz4 b/testdata/wal-downloader/missing-middle-index/generations/0000000000000000/wal/0000000000000000/0000000000000000.wal.lz4
similarity index 100%
rename from testdata/wal-downloader/missing-middle-index/generations/0000000000000000/wal/00000000/00000000.wal.lz4
rename to testdata/wal-downloader/missing-middle-index/generations/0000000000000000/wal/0000000000000000/0000000000000000.wal.lz4
diff --git a/testdata/wal-downloader/missing-middle-index/generations/0000000000000000/wal/00000000/00002050.wal.lz4 b/testdata/wal-downloader/missing-middle-index/generations/0000000000000000/wal/0000000000000000/0000000000002050.wal.lz4
similarity index 100%
rename from testdata/wal-downloader/missing-middle-index/generations/0000000000000000/wal/00000000/00002050.wal.lz4
rename to testdata/wal-downloader/missing-middle-index/generations/0000000000000000/wal/0000000000000000/0000000000002050.wal.lz4
diff --git a/testdata/wal-downloader/missing-middle-index/generations/0000000000000000/wal/00000000/00003068.wal.lz4 b/testdata/wal-downloader/missing-middle-index/generations/0000000000000000/wal/0000000000000000/0000000000003068.wal.lz4
similarity index 100%
rename from testdata/wal-downloader/missing-middle-index/generations/0000000000000000/wal/00000000/00003068.wal.lz4
rename to testdata/wal-downloader/missing-middle-index/generations/0000000000000000/wal/0000000000000000/0000000000003068.wal.lz4
diff --git a/testdata/wal-downloader/missing-middle-index/generations/0000000000000000/wal/00000002/00000000.wal.lz4 b/testdata/wal-downloader/missing-middle-index/generations/0000000000000000/wal/0000000000000002/0000000000000000.wal.lz4
similarity index 100%
rename from testdata/wal-downloader/missing-middle-index/generations/0000000000000000/wal/00000002/00000000.wal.lz4
rename to testdata/wal-downloader/missing-middle-index/generations/0000000000000000/wal/0000000000000002/0000000000000000.wal.lz4
diff --git a/testdata/wal-downloader/missing-middle-index/generations/0000000000000000/wal/00000002/00001038.wal.lz4 b/testdata/wal-downloader/missing-middle-index/generations/0000000000000000/wal/0000000000000002/0000000000001038.wal.lz4
similarity index 100%
rename from testdata/wal-downloader/missing-middle-index/generations/0000000000000000/wal/00000002/00001038.wal.lz4
rename to testdata/wal-downloader/missing-middle-index/generations/0000000000000000/wal/0000000000000002/0000000000001038.wal.lz4
diff --git a/testdata/wal-downloader/missing-offset/generations/0000000000000000/wal/00000000/00000000.wal.lz4 b/testdata/wal-downloader/missing-offset/generations/0000000000000000/wal/0000000000000000/0000000000000000.wal.lz4
similarity index 100%
rename from testdata/wal-downloader/missing-offset/generations/0000000000000000/wal/00000000/00000000.wal.lz4
rename to testdata/wal-downloader/missing-offset/generations/0000000000000000/wal/0000000000000000/0000000000000000.wal.lz4
diff --git a/testdata/wal-downloader/missing-offset/generations/0000000000000000/wal/00000000/00003068.wal.lz4 b/testdata/wal-downloader/missing-offset/generations/0000000000000000/wal/0000000000000000/0000000000003068.wal.lz4
similarity index 100%
rename from testdata/wal-downloader/missing-offset/generations/0000000000000000/wal/00000000/00003068.wal.lz4
rename to testdata/wal-downloader/missing-offset/generations/0000000000000000/wal/0000000000000000/0000000000003068.wal.lz4
diff --git a/testdata/wal-downloader/ok/00000000.wal b/testdata/wal-downloader/ok/0000000000000000.wal
similarity index 100%
rename from testdata/wal-downloader/ok/00000000.wal
rename to testdata/wal-downloader/ok/0000000000000000.wal
diff --git a/testdata/wal-downloader/ok/00000001.wal b/testdata/wal-downloader/ok/0000000000000001.wal
similarity index 100%
rename from testdata/wal-downloader/ok/00000001.wal
rename to testdata/wal-downloader/ok/0000000000000001.wal
diff --git a/testdata/wal-downloader/ok/00000002.wal b/testdata/wal-downloader/ok/0000000000000002.wal
similarity index 100%
rename from testdata/wal-downloader/ok/00000002.wal
rename to testdata/wal-downloader/ok/0000000000000002.wal
diff --git a/testdata/wal-downloader/ok/README b/testdata/wal-downloader/ok/README
index 63eda96..c4e68f6 100644
--- a/testdata/wal-downloader/ok/README
+++ b/testdata/wal-downloader/ok/README
@@ -7,8 +7,8 @@ To reproduce this testdata, run sqlite3 and execute:
 
 And copy & split the WAL into segments:
 
-	sl3 split -o generations/0000000000000000/wal/00000000 db-wal
-	cp db-wal 00000000.wal
+	sl3 split -o generations/0000000000000000/wal/0000000000000000 db-wal
+	cp db-wal 0000000000000000.wal
 
 
 Then execute:
@@ -18,8 +18,8 @@ Then execute:
 
 And split again:
 
-	sl3 split -o generations/0000000000000000/wal/00000001 db-wal
-	cp db-wal 00000001.wal
+	sl3 split -o generations/0000000000000000/wal/0000000000000001 db-wal
+	cp db-wal 0000000000000001.wal
 
 
 Then execute:
@@ -30,8 +30,8 @@ Then execute:
 
 And split again:
 
-	sl3 split -o generations/0000000000000000/wal/00000002 db-wal
-	cp db-wal 00000002.wal
+	sl3 split -o generations/0000000000000000/wal/0000000000000002 db-wal
+	cp db-wal 0000000000000002.wal
 
 
 Finally, remove the original database files:
diff --git a/testdata/wal-downloader/ok/generations/0000000000000000/wal/00000000/00000000.wal.lz4 b/testdata/wal-downloader/ok/generations/0000000000000000/wal/0000000000000000/0000000000000000.wal.lz4
similarity index 100%
rename from testdata/wal-downloader/ok/generations/0000000000000000/wal/00000000/00000000.wal.lz4
rename to testdata/wal-downloader/ok/generations/0000000000000000/wal/0000000000000000/0000000000000000.wal.lz4
diff --git a/testdata/wal-downloader/ok/generations/0000000000000000/wal/00000000/00002050.wal.lz4 b/testdata/wal-downloader/ok/generations/0000000000000000/wal/0000000000000000/0000000000002050.wal.lz4
similarity index 100%
rename from testdata/wal-downloader/ok/generations/0000000000000000/wal/00000000/00002050.wal.lz4
rename to testdata/wal-downloader/ok/generations/0000000000000000/wal/0000000000000000/0000000000002050.wal.lz4
diff --git a/testdata/wal-downloader/ok/generations/0000000000000000/wal/00000000/00003068.wal.lz4 b/testdata/wal-downloader/ok/generations/0000000000000000/wal/0000000000000000/0000000000003068.wal.lz4
similarity index 100%
rename from testdata/wal-downloader/ok/generations/0000000000000000/wal/00000000/00003068.wal.lz4
rename to testdata/wal-downloader/ok/generations/0000000000000000/wal/0000000000000000/0000000000003068.wal.lz4
diff --git a/testdata/wal-downloader/ok/generations/0000000000000000/wal/00000001/00000000.wal.lz4 b/testdata/wal-downloader/ok/generations/0000000000000000/wal/0000000000000001/0000000000000000.wal.lz4
similarity index 100%
rename from testdata/wal-downloader/ok/generations/0000000000000000/wal/00000001/00000000.wal.lz4
rename to testdata/wal-downloader/ok/generations/0000000000000000/wal/0000000000000001/0000000000000000.wal.lz4
diff --git a/testdata/wal-downloader/ok/generations/0000000000000000/wal/00000002/00000000.wal.lz4 b/testdata/wal-downloader/ok/generations/0000000000000000/wal/0000000000000002/0000000000000000.wal.lz4
similarity index 100%
rename from testdata/wal-downloader/ok/generations/0000000000000000/wal/00000002/00000000.wal.lz4
rename to testdata/wal-downloader/ok/generations/0000000000000000/wal/0000000000000002/0000000000000000.wal.lz4
diff --git a/testdata/wal-downloader/ok/generations/0000000000000000/wal/00000002/00001038.wal.lz4 b/testdata/wal-downloader/ok/generations/0000000000000000/wal/0000000000000002/0000000000001038.wal.lz4
similarity index 100%
rename from testdata/wal-downloader/ok/generations/0000000000000000/wal/00000002/00001038.wal.lz4
rename to testdata/wal-downloader/ok/generations/0000000000000000/wal/0000000000000002/0000000000001038.wal.lz4
diff --git a/testdata/wal-downloader/one/00000000.wal b/testdata/wal-downloader/one/0000000000000000.wal
similarity index 100%
rename from testdata/wal-downloader/one/00000000.wal
rename to testdata/wal-downloader/one/0000000000000000.wal
diff --git a/testdata/wal-downloader/one/README b/testdata/wal-downloader/one/README
index afe550f..bcad8cf 100644
--- a/testdata/wal-downloader/one/README
+++ b/testdata/wal-downloader/one/README
@@ -7,8 +7,8 @@ To reproduce this testdata, run sqlite3 and execute:
 
 And copy & split the WAL into segments:
 
-	sl3 split -o generations/0000000000000000/wal/00000000 db-wal
-	cp db-wal 00000000.wal
+	sl3 split -o generations/0000000000000000/wal/0000000000000000 db-wal
+	cp db-wal 0000000000000000.wal
 
 
 Finally, remove the original database files:
diff --git a/testdata/wal-downloader/one/generations/0000000000000000/wal/00000000/00000000.wal.lz4 b/testdata/wal-downloader/one/generations/0000000000000000/wal/0000000000000000/0000000000000000.wal.lz4
similarity index 100%
rename from testdata/wal-downloader/one/generations/0000000000000000/wal/00000000/00000000.wal.lz4
rename to testdata/wal-downloader/one/generations/0000000000000000/wal/0000000000000000/0000000000000000.wal.lz4
diff --git a/testdata/wal-downloader/one/generations/0000000000000000/wal/00000000/00002050.wal.lz4 b/testdata/wal-downloader/one/generations/0000000000000000/wal/0000000000000000/0000000000002050.wal.lz4
similarity index 100%
rename from testdata/wal-downloader/one/generations/0000000000000000/wal/00000000/00002050.wal.lz4
rename to testdata/wal-downloader/one/generations/0000000000000000/wal/0000000000000000/0000000000002050.wal.lz4
diff --git a/testdata/wal-downloader/one/generations/0000000000000000/wal/00000000/00003068.wal.lz4 b/testdata/wal-downloader/one/generations/0000000000000000/wal/0000000000000000/0000000000003068.wal.lz4
similarity index 100%
rename from testdata/wal-downloader/one/generations/0000000000000000/wal/00000000/00003068.wal.lz4
rename to testdata/wal-downloader/one/generations/0000000000000000/wal/0000000000000000/0000000000003068.wal.lz4
diff --git a/testdata/wal-time-bounds/ok/Makefile b/testdata/wal-time-bounds/ok/Makefile
index fa7ab33..155d281 100644
--- a/testdata/wal-time-bounds/ok/Makefile
+++ b/testdata/wal-time-bounds/ok/Makefile
@@ -1,6 +1,6 @@
 .PHONY: default
 default:
-	TZ=UTC touch -ct 200001010000 generations/0000000000000000/wal/00000000/00000000.wal.lz4
-	TZ=UTC touch -ct 200001020000 generations/0000000000000000/wal/00000000/00000001.wal.lz4
-	TZ=UTC touch -ct 200001030000 generations/0000000000000000/wal/00000001/00000000.wal.lz4
+	TZ=UTC touch -ct 200001010000 generations/0000000000000000/wal/0000000000000000/0000000000000000.wal.lz4
+	TZ=UTC touch -ct 200001020000 generations/0000000000000000/wal/0000000000000000/0000000000000001.wal.lz4
+	TZ=UTC touch -ct 200001030000 generations/0000000000000000/wal/0000000000000001/0000000000000000.wal.lz4
 
diff --git a/wal_downloader_test.go b/wal_downloader_test.go
index fd1817f..f43ff17 100644
--- a/wal_downloader_test.go
+++ b/wal_downloader_test.go
@@ -38,7 +38,7 @@ func testWALDownloader(t *testing.T, parallelism int) {
 			t.Fatal(err)
 		} else if got, want := index, 0; got != want {
 			t.Fatalf("index=%d, want %d", got, want)
-		} else if !fileEqual(t, filepath.Join(testDir, "00000000.wal"), filename) {
+		} else if !fileEqual(t, filepath.Join(testDir, "0000000000000000.wal"), filename) {
 			t.Fatalf("output file mismatch: %s", filename)
 		}
 
@@ -46,7 +46,7 @@ func testWALDownloader(t *testing.T, parallelism int) {
 			t.Fatal(err)
 		} else if got, want := index, 1; got != want {
 			t.Fatalf("index=%d, want %d", got, want)
-		} else if !fileEqual(t, filepath.Join(testDir, "00000001.wal"), filename) {
+		} else if !fileEqual(t, filepath.Join(testDir, "0000000000000001.wal"), filename) {
 			t.Fatalf("output file mismatch: %s", filename)
 		}
 
@@ -54,7 +54,7 @@ func testWALDownloader(t *testing.T, parallelism int) {
 			t.Fatal(err)
 		} else if got, want := index, 2; got != want {
 			t.Fatalf("index=%d, want %d", got, want)
-		} else if !fileEqual(t, filepath.Join(testDir, "00000002.wal"), filename) {
+		} else if !fileEqual(t, filepath.Join(testDir, "0000000000000002.wal"), filename) {
 			t.Fatalf("output file mismatch: %s", filename)
 		}
 
@@ -82,7 +82,7 @@ func testWALDownloader(t *testing.T, parallelism int) {
 			t.Fatal(err)
 		} else if got, want := index, 0; got != want {
 			t.Fatalf("index=%d, want %d", got, want)
-		} else if !fileEqual(t, filepath.Join(testDir, "00000000.wal"), filename) {
+		} else if !fileEqual(t, filepath.Join(testDir, "0000000000000000.wal"), filename) {
 			t.Fatalf("output file mismatch: %s", filename)
 		}
 
@@ -106,7 +106,7 @@ func testWALDownloader(t *testing.T, parallelism int) {
 			t.Fatal(err)
 		} else if got, want := index, 1; got != want {
 			t.Fatalf("index=%d, want %d", got, want)
-		} else if !fileEqual(t, filepath.Join(testDir, "00000001.wal"), filename) {
+		} else if !fileEqual(t, filepath.Join(testDir, "0000000000000001.wal"), filename) {
 			t.Fatalf("output file mismatch: %s", filename)
 		}
 
@@ -130,7 +130,7 @@ func testWALDownloader(t *testing.T, parallelism int) {
 			t.Fatal(err)
 		} else if got, want := index, 0; got != want {
 			t.Fatalf("index=%d, want %d", got, want)
-		} else if !fileEqual(t, filepath.Join(testDir, "00000000.wal"), filename) {
+		} else if !fileEqual(t, filepath.Join(testDir, "0000000000000000.wal"), filename) {
 			t.Fatalf("output file mismatch: %s", filename)
 		}
 
@@ -138,7 +138,7 @@ func testWALDownloader(t *testing.T, parallelism int) {
 			t.Fatal(err)
 		} else if got, want := index, 1; got != want {
 			t.Fatalf("index=%d, want %d", got, want)
-		} else if !fileEqual(t, filepath.Join(testDir, "00000001.wal"), filename) {
+		} else if !fileEqual(t, filepath.Join(testDir, "0000000000000001.wal"), filename) {
 			t.Fatalf("output file mismatch: %s", filename)
 		}
 
@@ -162,7 +162,7 @@ func testWALDownloader(t *testing.T, parallelism int) {
 			t.Fatal(err)
 		} else if got, want := index, 1; got != want {
 			t.Fatalf("index=%d, want %d", got, want)
-		} else if !fileEqual(t, filepath.Join(testDir, "00000001.wal"), filename) {
+		} else if !fileEqual(t, filepath.Join(testDir, "0000000000000001.wal"), filename) {
 			t.Fatalf("output file mismatch: %s", filename)
 		}
 
@@ -170,7 +170,7 @@ func testWALDownloader(t *testing.T, parallelism int) {
 			t.Fatal(err)
 		} else if got, want := index, 2; got != want {
 			t.Fatalf("index=%d, want %d", got, want)
-		} else if !fileEqual(t, filepath.Join(testDir, "00000002.wal"), filename) {
+		} else if !fileEqual(t, filepath.Join(testDir, "0000000000000002.wal"), filename) {
 			t.Fatalf("output file mismatch: %s", filename)
 		}
 
@@ -191,7 +191,7 @@ func testWALDownloader(t *testing.T, parallelism int) {
 		const n = 1000
 		tempDir := t.TempDir()
 		for i := 0; i < n; i++ {
-			filename := filepath.Join(tempDir, "generations", "0000000000000000", "wal", fmt.Sprintf("%08x", i), "00000000.wal.lz4")
+			filename := filepath.Join(tempDir, "generations", "0000000000000000", "wal", litestream.FormatIndex(i), "0000000000000000.wal.lz4")
 			if err := os.MkdirAll(filepath.Dir(filename), 0777); err != nil {
 				t.Fatal(err)
 			} else if err := os.WriteFile(filename, testingutil.CompressLZ4(t, []byte(fmt.Sprint(i))), 0666); err != nil {
@@ -316,7 +316,7 @@ func testWALDownloader(t *testing.T, parallelism int) {
 			t.Fatal(err)
 		} else if got, want := index, 0; got != want {
 			t.Fatalf("index=%d, want %d", got, want)
-		} else if !fileEqual(t, filepath.Join(testDir, "00000000.wal"), filename) {
+		} else if !fileEqual(t, filepath.Join(testDir, "0000000000000000.wal"), filename) {
 			t.Fatalf("output file mismatch: %s", filename)
 		}
 
@@ -441,7 +441,7 @@ func testWALDownloader(t *testing.T, parallelism int) {
 		d := litestream.NewWALDownloader(client, filepath.Join(tempDir, "wal"), "0000000000000000", 0, 0)
 		defer d.Close()
 
-		if _, _, err := d.Next(context.Background()); err == nil || err.Error() != `missing WAL offset: generation=0000000000000000 index=00000000 offset=00002050` {
+		if _, _, err := d.Next(context.Background()); err == nil || err.Error() != `missing WAL offset: generation=0000000000000000 index=0000000000000000 offset=0000000000002050` {
 			t.Fatal(err)
 		} else if err := d.Close(); err != nil {
 			t.Fatal(err)
@@ -529,7 +529,7 @@ func testWALDownloader(t *testing.T, parallelism int) {
 
 func TestWALNotFoundError(t *testing.T) {
 	err := &litestream.WALNotFoundError{Generation: "0123456789abcdef", Index: 1000}
-	if got, want := err.Error(), `wal not found: generation=0123456789abcdef index=000003e8`; got != want {
+	if got, want := err.Error(), `wal not found: generation=0123456789abcdef index=00000000000003e8`; got != want {
 		t.Fatalf("Error()=%q, want %q", got, want)
 	}
 }