blob

package
v0.30.38 Latest Latest
Warning

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

Go to latest
Published: Sep 7, 2026 License: Apache-2.0 Imports: 15 Imported by: 0

Documentation

Overview

Package blob implements a content-addressed filesystem blob store.

Layout on disk:

{root}/{tenant}/{xx}/{sha256hex}

where {xx} is the first two hex characters of the SHA-256 digest (git-style prefix sharding). This keeps directory entry counts manageable at scale.

Blobs are written atomically: content is first written to a temp file in the same directory, then renamed into place. A partial write therefore never produces a visible corrupt blob.

Deduplication is structural: two clients storing identical content produce the same SHA-256 key, and the second write is a no-op after the existence check.

The key-alias index maps caller-supplied names to SHA-256 digests. Each alias is a small text file under {root}/{tenant}/.keys/{key} containing the hex SHA. This keeps the key lookup O(1) filesystem calls with no separate index database.

Index

Constants

This section is empty.

Variables

View Source
var ErrKeyInvalid = errors.New("blob: invalid key")

ErrKeyInvalid is returned when a caller-supplied key contains disallowed characters or is otherwise malformed.

View Source
var ErrNotFound = errors.New("blob: not found")

ErrNotFound is returned when a blob or key does not exist.

View Source
var ErrSHAInvalid = errors.New("blob: invalid sha256 digest")

ErrSHAInvalid is returned when a SHA-addressed operation receives a digest that is not exactly 64 lowercase hexadecimal characters. Validating the digest before it reaches the filesystem path prevents a panic on a short digest (hexSHA[:2]) and stops non-hex characters from contributing path components to the on-disk layout (D-004).

View Source
var ErrTooLarge = errors.New("blob: content too large")

ErrTooLarge is returned when the content exceeds the configured size limit.

Functions

func ValidateKey

func ValidateKey(key string) error

ValidateKey is the exported form of the key validation check, for use by callers that want to pre-validate keys before calling Put.

Types

type GCConfig

type GCConfig struct {
	// Interval between GC sweeps. Default: 1 hour.
	Interval time.Duration
	// GracePeriod is how long a blob must sit in .gc-pending/ before it is
	// hard-deleted. Default: 10 minutes.
	GracePeriod time.Duration
}

GCConfig holds tunable parameters for the GC worker.

func DefaultGCConfig

func DefaultGCConfig() GCConfig

DefaultGCConfig returns a GCConfig with production-safe defaults.

type GCReport

type GCReport struct {
	TenantsScanned int
	Quarantined    int // blobs moved to .gc-pending this cycle
	Deleted        int // blobs hard-deleted from .gc-pending this cycle
	Errors         int
}

GCReport summarises the result of a single GC sweep.

type GCWorker

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

GCWorker runs periodic mark-and-sweep GC against a Store. It is safe to create only one GCWorker per Store.

func NewGCWorker

func NewGCWorker(s *Store, cfg GCConfig, extRefs SHARefSource) *GCWorker

NewGCWorker creates a GCWorker. extRefs may be nil when there are no external SHA reference sources.

func (*GCWorker) RunOnce

func (w *GCWorker) RunOnce() GCReport

RunOnce executes a single mark-and-sweep cycle synchronously. Useful for tests and for a one-shot manual trigger from an admin endpoint.

func (*GCWorker) Start

func (w *GCWorker) Start()

Start launches the GC goroutine.

func (*GCWorker) Stop

func (w *GCWorker) Stop()

Stop signals the GC to stop and blocks until it has exited.

func (*GCWorker) Sweep

func (w *GCWorker) Sweep(_ context.Context) (gcpkg.Report, error)

Sweep implements gc.Sweeper. It runs one full mark-and-sweep cycle and maps the blob-specific GCReport to the shared gc.Report type. The sweep logic is unchanged; this method is the adapter layer only.

type GlobalUsage

type GlobalUsage struct {
	TotalBlobCount int64
	TotalKeyCount  int64
	TotalBytes     int64
	TenantCount    int64
	SampledAt      time.Time
}

GlobalUsage is an aggregate across stores, suitable for telemetry. It is composed by the blob manager from per-store SampledUsage values.

type Meta

type Meta struct {
	Key         string    // caller-assigned name
	SHA256      string    // hex-encoded SHA-256 of the content
	MD5         string    // hex-encoded MD5 of the content (S3 ETag); "" if unknown
	Size        int64     // bytes
	ContentType string    // preserved from the original store call; may be empty
	StoredAt    time.Time // when this key was last written
}

Meta holds metadata about a stored blob.

type SHARefSource

type SHARefSource interface {
	CollectLiveSHAs() (map[string]struct{}, error)
}

SHARefSource is implemented by subsystems that hold SHA references outside the key-alias index (e.g. the timeseries history store). The GC calls CollectLiveSHAs once per sweep cycle and treats every returned SHA as live.

type SampledUsage

type SampledUsage struct {
	// BlobCount is the number of distinct blob files on disk (deduplicated).
	BlobCount int64
	// KeyCount is the number of key aliases.
	KeyCount int64
	// Bytes is the total size of all blob files in bytes, excluding sidecars.
	Bytes int64
	// SampledAt is when this entry was last refreshed.
	// Zero means no walk has completed yet.
	SampledAt time.Time
}

SampledUsage is the cached result of one store's usage walk.

type Store

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

Store is a content-addressed blob store for a single xolu instance. It is safe for concurrent use.

func NewStore

func NewStore(dir string, maxSize int64) (*Store, error)

NewStore creates a Store rooted at dir. dir is created if it does not exist. maxSize is the upper bound on accepted blob sizes in bytes; 0 means no limit.

func (*Store) Delete

func (s *Store) Delete(key string) error

Delete removes the key alias for key in tenant. The underlying blob file is not removed immediately — blob GC is a separate operation that scans for unreferenced SHAs. Returns ErrNotFound when the key does not exist.

func (*Store) Get

func (s *Store) Get(key string) (io.ReadCloser, Meta, error)

Get retrieves the content and metadata for key in tenant. The caller is responsible for closing the returned ReadCloser. Returns ErrNotFound when the key does not exist.

func (*Store) GetBySHA

func (s *Store) GetBySHA(key, hexSHA string) (io.ReadCloser, Meta, error)

GetBySHA retrieves content directly by its SHA-256 hex digest, bypassing the key alias index. Useful when the caller already holds the SHA from a prior Put response. key is used only to populate Meta.Key; pass the SHA itself if no logical key is known.

func (*Store) Head

func (s *Store) Head(key string) (Meta, error)

Head returns metadata for key without reading the blob content. Returns ErrNotFound when the key does not exist.

func (*Store) List

func (s *Store) List(prefix string) ([]Meta, error)

List returns metadata for all keys in tenant whose names start with prefix. An empty prefix returns all keys. Results are in filesystem order (not guaranteed to be sorted); callers should sort if order matters.

func (*Store) Put

func (s *Store) Put(key string, r io.Reader, contentType string) (sha string, md5hex string, created bool, err error)

Put stores content under key for tenant. Returns the SHA-256 hex digest and whether the blob was newly created (false = key existed and was overwritten with possibly different content). contentType is stored alongside the blob for retrieval; it may be empty.

The content is read once, SHA-256 is computed in a single streaming pass, and the result is written atomically. If an identical blob already exists under the computed SHA, no file write is performed.

func (*Store) PutBySHA

func (s *Store) PutBySHA(key, hexSHA, contentType string) (string, bool, error)

PutBySHA stores the blob content under an alias of key, where the blob file is already on disk under hexSHA. This is used when the SHA is computed during a prior Put and the alias needs to be re-targeted. If the blob file does not exist, ErrNotFound is returned.

func (*Store) PutRaw

func (s *Store) PutRaw(r io.Reader, contentType string) (sha string, md5hex string, created bool, err error)

PutRaw stores content and returns the SHA-256 hex digest. No key alias is written. This is the correct path when the SHA itself is the identifier (e.g. the history versioning system, or any purely content-addressed use). Retrieval goes through GetBySHA; the result will not appear in List.

If an identical blob already exists on disk the write is skipped and the existing SHA is returned — the deduplication guarantee still holds.

func (*Store) Root

func (s *Store) Root() string

Root returns the root directory of the store. Used by the GC worker.

func (*Store) Usage

func (s *Store) Usage() (Usage, error)

Usage returns disk usage for this (single-tenant) store. It walks the shard directories to count and size blob files, and the .keys directory to count key aliases. Orphaned blobs (no alias, awaiting GC) are included in BlobCount and Bytes but not in KeyCount.

type Usage

type Usage struct {
	// BlobCount is the number of distinct blob files on disk (deduplicated).
	// Two keys pointing to the same content count as one blob.
	BlobCount int64
	// KeyCount is the number of key aliases.
	KeyCount int64
	// Bytes is the total size of all blob files in bytes, excluding sidecars.
	Bytes int64
}

Usage holds disk usage statistics for a single tenant's blob namespace.

type UsageSampler

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

UsageSampler periodically walks one Store and caches its usage figures.

func NewUsageSampler

func NewUsageSampler(s *Store, interval time.Duration) *UsageSampler

NewUsageSampler creates a UsageSampler for a single-tenant store. Call Start to begin sampling.

func (*UsageSampler) Current

func (u *UsageSampler) Current() SampledUsage

Current returns the most recently cached usage for this store's tenant. SampledAt is zero if no walk has completed yet.

func (*UsageSampler) ForceResample

func (u *UsageSampler) ForceResample()

ForceResample runs a usage walk synchronously and updates the cache before returning. Intended for tests that need a deterministic cache state without waiting for the ticker. Safe to call from any goroutine.

func (*UsageSampler) Start

func (u *UsageSampler) Start()

Start launches the sampler goroutine. An initial walk runs immediately so the cache is warm before the first ticker fires.

func (*UsageSampler) Stop

func (u *UsageSampler) Stop()

Stop signals the sampler to stop and blocks until it has exited.

Jump to

Keyboard shortcuts

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