qumo-ledger

module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Aug 5, 2026 License: Apache-2.0

README

qumo-ledger

The temporal streaming platform. Store, replay, and stream time-oriented data.

Experimental. The storage format is not stable and there is no compatibility promise yet.

qumo-ledger stores temporal data — video, audio, logs, sensor readings — as immutable objects described by manifest objects. It takes the append-only ordered log from Kafka and the independently-decodable segment from HLS, and makes a Group both at once: the unit of independent decoding and the unit of storage.

There is no database to run. The manifest is the source of truth, so a track is complete and self-describing wherever you copy it, and a reader with nothing but object-store credentials can seek and replay it with no ledger process running anywhere.

What it is not

It is not a live path. A group can only be stored once sealed, so replay through the ledger is inherently behind a relay serving the same frames from memory. Live delivery belongs to a relay such as qumo; the ledger serves what has already happened. See docs/ARCHITECTURE.md for why that boundary is drawn where it is.

Install

go get github.com/okdaichi/qumo-ledger

Usage

objects, _ := fsstore.New("/var/lib/qumo-ledger")

// Create establishes a new track, like os.Create: it fixes the schema once and
// returns a handle. Re-creating an existing track fails rather than truncating
// it — a track is an immutable, append-only log. The empty Config is the common
// case: every setting has a documented default and belongs to a deployment, not
// a track.
track, _ := ledger.Create(ctx, objects, "live/cam1/video", ledger.TrackSchema{
    Timescale:  90000,                  // 90 kHz, the usual video timescale
    TimeSource: ledger.TimeSourceFrame, // timestamps came from the data itself
    MIME:       "video/mp4",
    Encoding:   "fmp4",
}, ledger.Config{Logger: log})

writer, _ := track.Writer(ctx)

// Append is the common case: hand the ledger a duration and it derives
// sequence, media time, and wallclock for you.
writer.Append(ctx, 180000, payload) // two seconds at 90 kHz

// AppendGroup is the escape hatch — a producer's own numbering, a dropped
// group, or a media anchor that is not simply the previous group's end. The
// writer stamps the epoch; advance it with Writer.NewEpoch on a restart.
writer.AppendGroup(ctx, ledger.GroupInfo{
    ID:        ledger.NewGroupID(0, 42), // sequence; the epoch is stamped by the writer
    MediaTime: 7560000, // media anchor, in timescale units
    Duration:  180000,  // optional: two seconds at 90 kHz
    Wallclock: w0,      // optional: wallclock anchor, Unix nanoseconds
}, payload)

// Open references an existing track, like os.Open. Unlike os.Open the handle is
// both read- and write-capable, so a writer that crashed mid-append resumes here.
opened, _ := ledger.Open(ctx, objects, "live/cam1/video", ledger.Config{Logger: log})
reader, _ := opened.Reader(ctx)

// A window, not a point. Run the same window over a video track and a sensor
// track and the two recordings line up.
for group, err := range reader.RangeWallclock(ctx, from, to) {
    if err != nil {
        return err   // the error is terminal; nothing follows it
    }
    frames, _ := reader.ReadGroup(ctx, group)
    _ = frames
}

// Following is polling, because object stores do not push — but the poll loop
// is the caller's. The Reader streams the track in commit order and reports its
// position, so a follower can persist it and resume.
reader.SeekTip(ctx)
ticker := time.NewTicker(ledger.DefaultPollInterval)
defer ticker.Stop()
for {
    group, err := reader.Next(ctx)
    if errors.Is(err, io.EOF) {
        select {
        case <-ctx.Done():
            return ctx.Err()
        case <-ticker.C:
            continue
        }
    }
    if err != nil {
        return err
    }
    save(reader.Position()) // survives a restart: it is the GroupID text
}

A group is anchored on two timelines rather than described as an interval, because groups are serial — the start of one is the end of the last. Media time (MediaTime) is exact and skew-free but relative to one track's origin; wallclock (Wallclock) is absolute and comparable across tracks, which is what makes "the video and the sensor readings at 14:32" a question with an answer.

Duration and Wallclock are optional. Duration is stored rather than derived from the next group because that derivation breaks across a dropped group and is undefined for the newest one — and it is exactly what HLS EXTINF and DASH @d need. A group that contradicts its predecessor is refused at append time.

Object layout

<track-path>/
  root.manifest                 track metadata; changes only on seal
  delta/
    head                        ← the only mutable object in the store
    open/00000042.manifest      immutable, one per commit
    sealed-000001.manifest      immutable, rotated by size
  groups/
    e000001-g00000042           immutable payload

Writing a delta manifest is the commit. head is a discovery cache that may lag or vanish without affecting correctness — a reader that finds it stale probes forward and catches up.

CLI

mage build

./bin/qumo-ledger inspect -root /var/lib/qumo-ledger -track live/cam1/video -groups
./bin/qumo-ledger follow  -root /var/lib/qumo-ledger -track live/cam1/video
./bin/qumo-stream    -root /var/lib/qumo-ledger -track live/cam1/video   # HLS (.m3u8) + DASH (.mpd)

The CLI is a convenience, not a component: it uses the same public API any reader would.

Packages

ledger The core. Depends on no transport and no cloud SDK.
ledger/store The storage contract: conditional create, compare-and-swap, optional listing. A leaf, so a backend never imports the ledger.
ledger/store/memstore In-memory backend; also the reference implementation.
ledger/store/fsstore Local filesystem backend.
ledger/store/storetest Conformance suite every backend must pass.
stream HLS and DASH renderers over a ledger track — derived views, served over HTTP.

Development

mage test     # go test -race ./...
mage cover    # coverage.out + summary
mage check    # tidy, lint, test
  • qumo — Media over QUIC relay
  • gomoqt — Media over QUIC Transport in Go

License

Apache 2.0. See LICENSE.

Directories

Path Synopsis
cmd
qumo-ledger command
Command qumo-ledger inspects tracks stored by the ledger.
Command qumo-ledger inspects tracks stored by the ledger.
qumo-stream command
Command qumo-stream serves a ledger track over HTTP as HLS and DASH.
Command qumo-stream serves a ledger track over HTTP as HLS and DASH.
Package fmp4 reads the few facts a ledger writer needs out of fragmented-MP4 bytes: how long a fragment runs, and in what timescale.
Package fmp4 reads the few facts a ledger writer needs out of fragmented-MP4 bytes: how long a fragment runs, and in what timescale.
internal
version
Package version carries build information stamped in at link time.
Package version carries build information stamped in at link time.
Package ledger stores and replays temporal data — video, audio, logs, and sensor readings — as immutable objects described by manifest objects.
Package ledger stores and replays temporal data — video, audio, logs, and sensor readings — as immutable objects described by manifest objects.
store
Package store defines the storage substrate qumo-ledger is built on.
Package store defines the storage substrate qumo-ledger is built on.
store/fsstore
Package fsstore provides a local-filesystem store.Store.
Package fsstore provides a local-filesystem store.Store.
store/memstore
Package memstore provides an in-memory store.Store.
Package memstore provides an in-memory store.Store.
store/storetest
Package storetest provides a conformance suite for store.Store implementations.
Package storetest provides a conformance suite for store.Store implementations.
Package stream renders a ledger track over HTTP as HLS and DASH.
Package stream renders a ledger track over HTTP as HLS and DASH.

Jump to

Keyboard shortcuts

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