Help us shape the future of Go! Take the 2026 Go Developer Survey

chunkstore

package
v0.7.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 8, 2026 License: AGPL-3.0 Imports: 19 Imported by: 0

Documentation

Overview

Package chunkstore stores file slices through casstore. File metadata maps logical chunk offsets to slices; content-addressed packs deduplicate their bytes within a domain. Copy-on-write clones reuse existing slices. A disk cache provides local reads and durable staging for write-back.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type BatchItem

type BatchItem struct {
	ID    uint64
	Data  []byte
	Class StoreClass
}

BatchItem is one slice in a WriteBatch.

type BatchReadStats

type BatchReadStats = snapshot.BatchReadStats

BatchReader is an optional Store capability: read many slices in ONE call so the backing can coalesce fetches of slices that share a casstore pack (far fewer backing/S3 GETs than one Read per slice — the cold sequential-read fix for model loading). concurrency bounds in-flight backing (pack) fetches (<=0 → backing default). It is BEST-EFFORT: a slice that can't be read is omitted from the result rather than failing the batch (the demand path re-fetches per slice and surfaces real errors). The sequential-read prefetcher uses it when the backing implements it. BatchReadStats aliases casstore's stats so cache/readahead can consume it through the chunkstore package without importing casstore directly.

type BatchReader

type BatchReader interface {
	ReadBatch(ctx context.Context, ids []uint64, concurrency int) (map[uint64][]byte, BatchReadStats, error)
}

type BatchWriter

type BatchWriter interface {
	WriteBatch(ctx context.Context, items []BatchItem) error
}

BatchWriter is an optional Store capability: write many slices in ONE packed operation so their chunks share casstore packs (far fewer object/S3 PUTs than one Write per slice). The disk-cache write-back drain uses it when the backing store implements it — the fix for the -remote per-128-KB-slice PUT storm.

type CasStore

type CasStore struct {
	// contains filtered or unexported fields
}

CasStore implements Store over a casstore ChunkedStore. The ChunkedStore should be configured with a GlobalIndex so identical content across distinct slice ids deduplicates.

func New

func New(cs *snapshot.ChunkedStore) *CasStore

New wraps a casstore ChunkedStore as a slice data store.

func (*CasStore) Exists

func (s *CasStore) Exists(ctx context.Context, id uint64) (bool, error)

func (*CasStore) Read

func (s *CasStore) Read(ctx context.Context, id uint64) ([]byte, error)

func (*CasStore) ReadAt

func (s *CasStore) ReadAt(ctx context.Context, id uint64, off int64, p []byte) (int, error)

ReadAt is NOT the production read path: mlfs always mounts the durable write-back DiskCache in front (main.go), and DiskCache.ReadAt serves from a whole-slice Read + copy — it never calls this. This direct implementation exists only for cache-less callers (offline tools / tests), so it mirrors the cache's semantics rather than carrying a separate sequential-discard path.

There is nothing finer than a whole-slice fetch to range to here: a FUSE slice is ≤1 MiB (the MaxWrite cap) and the casstore chunker targets ~1 MiB, so a slice is ~1 chunk (measured: 1 chunk ≤512 KiB, ~1.4 at 1 MiB), and a chunk is the atomic content-hash-verified unit. casstore's GetLatest reader already fetches only that chunk-run's bytes via a ranged pack GET (#18), so Read pulls just the covering chunk(s); we copy the requested window out of it.

func (*CasStore) ReadBatch

func (s *CasStore) ReadBatch(ctx context.Context, ids []uint64, concurrency int) (map[uint64][]byte, snapshot.BatchReadStats, error)

ReadBatch reads many slices via casstore's cross-key pack coalescing (GetLatestBatch): slices sharing a pack cost one pack GET, not one per slice. Returned bytes are per id; an unreadable slice is simply absent from the map. The BatchReadStats report the backing data GETs + bytes (the coalescing signal).

func (*CasStore) RegisterSlice

func (s *CasStore) RegisterSlice(ctx context.Context, id uint64, chunks []snapshot.ChunkLocation, class StoreClass) error

RegisterSlice writes slice id's manifest to reference an ORDERED list of already-existing casstore chunks, WITHOUT reading or writing any chunk/pack bytes. It is the mlfs (VFS→mlfs) half of the metadata-only bridge: a blob_ref's chunks (which already live in this tenant's pack set, same dedup domain) are laid into an mlfs slice purely by synthesising the slice's manifest over them.

class selects the physical codec exactly as Write does, but since NO pack is written here it only stamps the manifest's metadata for consistency (the packs the chunks live in were already written by whoever ingested them). The chunks must be in original-stream order; the caller (the bridge) reads them from the source ref's manifest, which is already ordered.

func (*CasStore) Remove

func (s *CasStore) Remove(ctx context.Context, id uint64) error

func (*CasStore) SliceChunks

func (s *CasStore) SliceChunks(ctx context.Context, id uint64) ([]snapshot.ChunkLocation, error)

SliceChunks returns the ordered casstore chunk list backing slice id's manifest, WITHOUT reading any chunk/pack bytes (it reads only the small manifest). It is the mlfs→VFS read half of the bridge: the coordinator gathers a file's slices' chunk lists to register a blob_ref over the SAME chunks. Returns an error (wrapping snapshot.ErrNoSnapshot) when the slice has no manifest.

func (*CasStore) WalkSliceIDs

func (s *CasStore) WalkSliceIDs(ctx context.Context, fn func(id uint64, createdAt time.Time) error) error

WalkSliceIDs calls fn for each stored slice manifest, with the slice id and the manifest's creation time. GC uses this to find orphaned slices (those not in the live set) while respecting a safety window on young manifests. A slice with multiple versions yields fn once per version.

func (*CasStore) Write

func (s *CasStore) Write(ctx context.Context, id uint64, data []byte, class StoreClass) error

func (*CasStore) WriteBatch

func (s *CasStore) WriteBatch(ctx context.Context, items []BatchItem) error

WriteBatch stores many slices in one casstore PutBatch, packing their chunks into shared packs. Idempotent for identical content (casstore dedups).

type Local

type Local struct {
	Store  *CasStore
	GC     *snapshot.ChunkedGC
	Dedup  *snapshot.MemoryDedupStore
	Chunks blobstore.Storage
	// Compactor is the underlying casstore store, exposed so operator tooling
	// (mlfs-admin compact) can consolidate under-filled/small packs.
	Compactor *snapshot.ChunkedStore
}

Local bundles a CasStore with the casstore pieces behind it, for dev, tests, and single-node deployments: the GC (caller schedules it), the dedup index, and the chunk blob store (for inspecting physical blob counts).

func NewLocal

func NewLocal(dir, domain string, packTarget int, opts ...LocalOption) (*Local, error)

NewLocal wires a CasStore over local-filesystem casstore backends with a global dedup index (so identical slice content deduplicates). domain is the dedup domain; packTarget is the casstore pack target (0 = default).

type LocalOption

type LocalOption func(*localOpts)

LocalOption tunes NewLocal.

func WithCompression

func WithCompression(p snapshot.CompressionPolicy) LocalOption

WithCompression overrides the local store's pack-blob compression policy (default: zstd via a ContentTypePolicy). Pass NewContentTypePolicy(CompressNone) — or rely on per-slice store_uncompressed tags — to store uncompressed. The per-slice StoreClass tag is always honored first regardless of this policy, so a compressing policy still keeps model/tensor slices uncompressed.

func WithMeter

func WithMeter(m metric.Meter) LocalOption

WithMeter wraps the chunk blob store with the casstore backing-store OTEL adapter (obs.Wrap), so local-backend GET/PUT latency, errors, and bytes are recorded under backend="local". A nil meter (the default) skips wrapping. The dedup domain is attached as mlfs.domain so the series joins this daemon's identity stream.

func WithPackCompression

func WithPackCompression(m snapshot.PackCompressionMode) LocalOption

WithPackCompression selects the framing used for packs that ARE compressed: per-chunk (default, keeps compressed packs range-readable) or whole-pack (the rollback, better ratio but every chunk read fetches the whole pack). It has no effect on slices stored uncompressed — the model/tensor class carrying a store_uncompressed tag is framed identically either way.

type Remote

type Remote struct {
	Store *CasStore
}

Remote bundles a CasStore wired over the converged direct-S3 node data path: a global dedup index served by blobgw over NATS, presigned-URL S3 transfers, and an injected manifest store. Unlike Local it carries NO GC, dedup, or chunk-store handles: garbage collection is blobgw-owned (it holds the tenant credentials and the authoritative index), so the node never constructs one. Downstream consumers (cache→fileio→fuse) take *CasStore through the chunkstore.Store interface and are untouched by the remote vs local choice.

func NewRemote

func NewRemote(cfg RemoteConfig) (*Remote, error)

NewRemote wires a CasStore over the remote (direct-S3) backends with a global dedup index so identical slice content deduplicates across distinct slice ids via blobgw's authoritative index. It constructs no GC: pack reclamation is a blobgw concern (see Remote).

type RemoteConfig

type RemoteConfig struct {
	Tenant          string
	PackTargetBytes int
	Requester       remoteindex.Requester
	HTTP            *s3http.Client
	Manifests       snapshot.SnapshotStore
	Logger          *slog.Logger
	PresignTTL      time.Duration

	// Compression selects the casstore pack-blob compression policy. A nil
	// policy uses the remote default: UNCOMPRESSED (see NewRemote) — the correct
	// choice for the converged design's primary workload (large, largely
	// incompressible model weights such as .pt/.safetensors), because it
	// preserves mmap/zero-copy device loads per ADR-001 §6. Set it to
	// snapshot.NewContentTypePolicy(snapshot.CompressZstd) to opt compressible
	// workloads (e.g. workclaw scratch) back into zstd.
	//
	// Compressing no longer costs ranged reads: per-chunk pack framing (the
	// PackCompression default) keeps compressed packs range-readable, so the old
	// "compressed ⇒ whole-pack fetch" tradeoff is gone. Zero-copy mmap remains the
	// reason the model/tensor class stays uncompressed.
	Compression snapshot.CompressionPolicy

	// PackCompression selects the framing for packs that ARE compressed:
	// per-chunk (zero value, range-readable) or whole-pack (rollback). It has no
	// effect under the uncompressed default above — an uncompressed pack is framed
	// identically either way.
	PackCompression snapshot.PackCompressionMode

	// Meter, when non-nil, wraps the direct-S3 blob store with the casstore
	// backing-store OTEL adapter (obs.Wrap, backend="s3"), so cold-read and
	// write-back S3 GET/PUT latency, errors, and bytes are recorded — the RED view
	// that is otherwise dark on the converged path. The tenant is attached as
	// mlfs.domain so the series joins this daemon's identity stream.
	Meter metric.Meter
}

RemoteConfig configures NewRemote.

Tenant is the casstore dedup domain (the blob-ID prefix and dedup isolation boundary) and the NATS subject tenant; it must be a valid NATS subject token (see ctlproto.ValidateTenant). Requester is the shared NATS transport that feeds BOTH the remote dedup index and the presign client. HTTP is the node-side S3 transfer client. Manifests is the snapshot manifest store, injected by the caller (cmd wires the PG manifeststore; tests may pass a local store) so this constructor stays storage-agnostic and PG-free. Logger is optional (passed to the GlobalIndex for best-effort dedup diagnostics). PresignTTL, when >0, overrides the requested presigned-URL validity.

type Store

type Store interface {
	// Write stores the bytes of slice id under compression class class.
	// Idempotent for identical content (casstore dedups the underlying chunks;
	// the class only selects the physical codec, never the chunk identity).
	Write(ctx context.Context, id uint64, data []byte, class StoreClass) error
	// Read returns the full bytes of slice id.
	Read(ctx context.Context, id uint64) ([]byte, error)
	// ReadAt reads len(p) bytes of slice id starting at off, returning the
	// number of bytes read (io.ReadFull semantics).
	ReadAt(ctx context.Context, id uint64, off int64, p []byte) (int, error)
	// Exists reports whether slice id has been written.
	Exists(ctx context.Context, id uint64) (bool, error)
	// Remove deletes slice id's manifest; its chunks are reclaimed by GC once
	// no slice references them.
	Remove(ctx context.Context, id uint64) error
}

Store reads and writes slice data. Implementations are safe for concurrent use. Slice ids are allocated by the metadata engine (NewSlice) and are opaque, globally-unique handles here.

type StoreClass

type StoreClass uint8

StoreClass is a slice's compression class: it decides whether the backing casstore compresses the slice's pack or stores it uncompressed. mlfs sets it per file (by file type, at create) so a model/tensor file stays uncompressed (range-readable + mmap/zero-copy device loads) while generic data compresses — on the SAME tenant, instead of relying on a global CompressNone switch. The class is carried with the staged slice (crash-safe) so a recovery drain packs it correctly with no inode context. The zero value is ClassDefault.

const (
	// ClassDefault leaves the codec to the backing's CompressionPolicy (generic
	// content compresses; the policy still stores recognized already-compressed /
	// tensor content types uncompressed). The unchanged pre-classification
	// behavior.
	ClassDefault StoreClass = iota
	// ClassUncompressed forces the slice's pack to be stored uncompressed
	// regardless of policy — the model/tensor class that must stay byte-for-byte
	// on disk for ranged reads and mmap/zero-copy device loads.
	ClassUncompressed
)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL