journal

package module
v2.0.0-...-7662268 Latest Latest
Warning

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

Go to latest
Published: Oct 24, 2026 License: MIT Imports: 29 Imported by: 0

README

Go-Journal

Go checks Behavior and crash recovery

An embedded, disk-backed journal for Go message pipelines. Store records and acknowledgements locally, recover pending work after a restart, and reclaim old segments after ownership has been transferred safely.

Go-Journal was extracted from go-utils/journal and is used by go-fluentd. It is a library, not a running service: your application supplies record IDs, delivers messages, and decides when processing is complete.

Durability is explicit. WriteData is not a durable receipt. Promise local persistence only after Sync() succeeds. Record an acknowledgement only after the required downstream work succeeds. Recovery can redeliver identical records; design consumers to be idempotent.

Install · Quick start · Delivery and replay · Configuration · Operations · Testing · Contributing

When to use it

Use Go-Journal for local retry buffers, message-forwarding pipelines, and restartable workers that need a disk-backed record of unfinished work. Records use MessagePack; data and acknowledged IDs are written to separate rotating segments, optionally compressed with gzip.

It is not a replicated message broker, database transaction manager, or multi-consumer queue. There is no network delivery adapter, distributed consensus, exactly-once guarantee, or built-in total-backlog quota. Losing the underlying volume is outside its recovery guarantee. Your application owns retry policy, backpressure, downstream deduplication, and multi-destination completion rules.

Install

The current source requires Go 1.27.0 or newer; CI targets Go 1.27.x on Linux. The process-level fault tests use Linux signals and resource limits. Other platforms and remote filesystems are not qualified by those tests.

From your application's Go module, install the revision documented here:

go get github.com/Laisky/go-journal/v2@b0c35437293efa4319db1fc588ce18edcf6cdfd3

This commit includes the recovery and rejected-payload fixes described below. Do not assume an older tag or @latest includes the same changes. For a newer revision, review its changes and rerun your application's acceptance tests. Let Go resolve the commit to a pseudo-version, and commit your application's go.mod and go.sum. See the Go dependency guide.

To try the example in a separate module:

mkdir journal-demo
cd journal-demo
go mod init example.com/journal-demo
go get github.com/Laisky/go-journal/v2@b0c35437293efa4319db1fc588ce18edcf6cdfd3

Quick start

Save the following as main.go in that module, then run go run .. Use go run . -gzip to exercise the same lifecycle with compression.

The example writes two records, marks one complete, closes and reopens the journal, and preserves the remaining record in the new active segment. It uses a disposable temporary directory, which it deletes on exit. In production, use a persistent application-owned directory and do not delete it during shutdown. The acknowledgement simulates completed processing; this example does not send to or verify a downstream service.

package main

import (
	"context"
	"flag"
	"fmt"
	"io"
	"log"
	"os"

	journal "github.com/Laisky/go-journal/v2"
)

func main() {
	compressed := flag.Bool("gzip", false, "compress journal segments")
	flag.Parse()
	if err := run(*compressed); err != nil {
		log.Fatal(err)
	}
}

func run(compressed bool) error {
	dir, err := os.MkdirTemp("", "go-journal-example-")
	if err != nil {
		return err
	}
	defer os.RemoveAll(dir) // Disposable example only, never a production WAL.

	open := func() (*journal.Journal, error) {
		j, err := journal.NewJournal(
			journal.WithBufDirPath(dir),
			journal.WithBufSizeByte(1<<20), // Small segments for this example.
			journal.WithIsCompress(compressed),
			journal.WithIsAggresiveGC(false),
		)
		if err != nil {
			return nil, err
		}
		if err := j.Start(context.Background()); err != nil {
			j.Close()
			return nil, err
		}
		return j, nil
	}

	j, err := open()
	if err != nil {
		return err
	}
	defer j.Close()
	for _, id := range []int64{1, 2} {
		if err := j.WriteData(&journal.Data{
			ID:   id,
			Data: map[string]interface{}{"message": fmt.Sprintf("task-%d", id)},
		}); err != nil {
			return err
		}
	}
	if err := j.Sync(); err != nil { // Both writes precede this shared barrier.
		return err
	}
	// A real application must complete the required downstream work FIRST.
	if err := j.WriteId(1); err != nil {
		return err
	}
	if err := j.Sync(); err != nil { // Persist the acknowledgement as well.
		return err
	}
	j.Close()

	recovered, err := open()
	if err != nil {
		return err
	}
	defer recovered.Close()
	high, err := recovered.LoadMaxId() // Recover IDs before accepting new work.
	if err != nil {
		return err
	}
	fmt.Printf("example: recovered maximum ID = %d\n", high)
	if !recovered.LockLegacy() {
		return fmt.Errorf("replay lease unavailable")
	}
	// This example has one replay owner. Close also cleans up on early failure.
	pending := 0
	for {
		d := new(journal.Data)
		err := recovered.LoadLegacyBuf(d)
		if err == io.EOF { // Replacements are synchronized before old-file cleanup.
			break
		}
		if err != nil {
			return err // Replay errors release the lease; do not advance blindly.
		}
		if d.ID != 2 || d.Data["message"] != "task-2" || pending != 0 {
			return fmt.Errorf("unexpected recovered record: %+v", d)
		}
		// Transfer ownership BEFORE requesting the next record or EOF.
		// Rewriting is not an ACK: the record must remain pending for delivery.
		if err := recovered.WriteData(d); err != nil {
			return err // Close and recover again instead of skipping this record.
		}
		pending++
		fmt.Printf("example: pending record = %d (%s)\n", d.ID, d.Data["message"])
	}
	if high != 2 || pending != 1 {
		return fmt.Errorf("unexpected recovery: maximum=%d pending=%d", high, pending)
	}
	return nil
}

Among the library's logs, expect:

example: recovered maximum ID = 2
example: pending record = 2 (task-2)

The exact Go block is compiled and executed in both modes by TestReadmeQuickstart. These are lifecycle checks, not a claim that printing a message constitutes durable downstream delivery.

Delivery and replay

Durability boundaries
Operation Meaning of success
WriteData(record) The record was accepted by the writer, or its ID was already acknowledged in the current index. Not a durable receipt by itself.
WriteId(id) The completion ID was written and added to the acknowledgement index. It must describe work that really completed; call Sync() to persist it.
Flush() Serializer/compression buffers were flushed. This is not a file/directory synchronization barrier.
Sync() Completed writes were flushed and the journal files and directory synchronized. It does not wait for another goroutine's queued work or synchronize your downstream.
Close() Idempotent resource cleanup. It has no error return and is not a substitute for an error-checked Sync(). The object cannot be restarted after closing.

The application-level sequence is:

assign immutable ID -> WriteData -> Sync -> report locally accepted
                                             |
                                             v
                              deliver to every required destination
                                             |
                                             v
                                        WriteId -> Sync

A crash between downstream completion and the persisted ACK can cause redelivery. Use the same stable ID for retries and make downstream processing idempotent. Never use one ID for different content, and never acknowledge a multi-destination record merely because one destination succeeded.

IDs are caller-assigned, nonnegative int64 values. After Start, call LoadMaxId() before new writes, then allocate above the recovered maximum with overflow protection. It scans retained sealed data and ACK segments, not the active writer, so it is not a live ID allocator or backlog counter. A return value of zero also covers an empty journal; starting fresh IDs at one is convenient.

Replay is an ownership transfer

Only one application worker should own a replay pass. Acquire LockLegacy() and call LoadLegacyBuf serially. Before advancing past each returned record, either complete WriteData(record) to preserve a replacement, or successfully deliver it and write its ACK. Completing a send into an in-memory channel is not a completed replacement write.

LoadLegacyBuf synchronizes replacement writes and ACKs before reclaiming the exhausted old snapshot. It releases the lease on EOF or a replay error; acquire it again before retrying. If your own delivery or rewrite fails after a record was returned, retain that record and retry its transfer, or close and recover again. Do not skip to the next record. Release the lease explicitly with UnLockLegacy() when voluntarily stopping a pass you still own.

Replay covers sealed predecessor files; it excludes the active writer. Rotate can make new files eligible for a later pass, but rotation alone does not drain pending work or enforce a disk quota. Pausing across a snapshot refresh may repeat identical records. Cleanup retry may reach EOF without replaying a record that was already safely transferred.

See BEHAVIOR.md for the full public contract and RECOVERY.md for reclamation and incomplete-tail handling.

Configuration

Pass functional options to NewJournal; no configuration file or background service is required. Prefer an explicit directory rather than relying on the default. Option names below match the public API, including WithIsAggresiveGC.

Option Default Purpose
WithBufDirPath(path) /var/go-fluentd Select a writable journal directory. An explicit nonempty path is created/checked while applying the option.
WithBufSizeByte(bytes) 200 MiB Active data-file rotation threshold, not a record-size limit or total disk cap.
WithRotateDuration(duration) 1 minute Age threshold for rotation.
WithRotateCheckInterval(duration) 1 second Frequency of automatic rotation checks.
WithFlushInterval(duration) 5 seconds Serializer flush interval; not a periodic durability receipt.
WithCommitIDTTL(duration) 5 minutes Retention window for the in-memory ACK index, not a message deletion deadline.
WithIsCompress(enabled) false Write gzip segments; historical files are decoded using their own suffix/format.
WithIsAggresiveGC(enabled) true Request GC when resetting the legacy loader. Measure its cost for your workload.
WithLogger(logger) Package console logger Supply a non-nil application logger.
WithName(name) journal Set the option's name value; it does not create an independent storage namespace. Use separate directories for separate journals.

Negative sizes/durations are rejected. Zero sizes/durations and empty path/name values leave the existing option value unchanged; zero does not disable a worker. The quick start disables aggressive GC explicitly; that is an example choice, not a benchmark-backed universal tuning recommendation.

Operations

Ownership and shutdown. One live journal owns one directory through an OS lock. Never delete .journal.lock to bypass it. Stop new work, wait for the application's in-flight writes, check Sync(), then Close(). Canceling the startup context stops maintenance workers; it does not drain your application queues or replace explicit synchronization and cleanup.

Failure handling. A serialization rejection leaves the live data stream unchanged, so a corrected record can be retried. A storage error may have partial or unknown persistence effects; it is not proof of rollback. After a data append/flush/compression-finalization error, the data encoder fails subsequent appends and barriers closed. Stop using that instance and reopen through normal recovery. A failed file Sync also has an unknown outcome: do not return a durable receipt or invent a rollback guarantee.

Storage and security. Keep the WAL on persistent local storage with reliable file locking and synchronization. Restrict directory access and encrypt the volume where required: MessagePack and gzip are not encryption. Apply disk-space and backlog limits in the application; preallocation, compression and rotation do not bound total retained data. Do not edit/delete live segments. For a portable backup, quiesce the application, successfully synchronize and close the journal, then copy the complete directory rather than isolated data or ACK files.

Recovery evidence. The narrowly supported incomplete newest-tail case retains the original file through an .incomplete hard link before recovering its valid prefix. Those evidence files are not replayed or automatically deleted; inspect and archive them. Corrupt ACKs, invalid MessagePack, gzip checksum errors, and older-segment corruption must not be treated as successful EOF. There is no checksum-based arbitrary-corruption repair for plain segments.

Observability. GetMetric()["idsSetLen"] measures the in-memory ACK index, not pending messages or verified downstream delivery. Track disk usage, oldest pending work, append/Sync/replay errors, recovery time, duplicate deliveries, and application acceptance-to-delivery latency separately.

Sync depends on the storage stack honoring it. The existing SIGKILL tests leave the OS page cache intact; they do not qualify physical power loss, lost volumes, all filesystems, or all possible concurrent interleavings.

Performance

PERFORMANCE.md contains reproducible before/after workloads, raw measurements, rejected optimizations, and the cost of safer serialization. The encoder stages a whole record before appending: large records need temporary memory proportional to their encoded size. Its 128 KiB idle scratch-retention threshold is neither a record-size limit nor a peak-memory bound.

Separate buffered append, append plus file synchronization, full Journal.Sync, and end-to-end delivery measurements. They are not interchangeable. Applications may share a Sync across completed writes, as in the example, but must not acknowledge any member before that barrier succeeds. Automatic timed group commit is not a configuration option in this library.

For diagnostic serializer/file benchmarks:

go test -mod=readonly -run '^$' -bench '^BenchmarkUserJournalAppend$' \
  -benchmem -benchtime=1s -count=6 .

Measure your payloads, concurrency, compression, storage, throughput and tail latency before choosing production settings. Hosted or cache-hot benchmark numbers are not a production disk-capacity estimate.

Testing

From a repository checkout, use its native module graph. Go race builds require a supported C toolchain; Python 3 is needed only for the additional verification scripts.

go mod download
go mod verify
go build -mod=readonly ./...
go vet -mod=readonly ./...
go test -mod=readonly -count=1 -timeout=180s ./...
go test -mod=readonly -race -count=5 -shuffle=on -timeout=180s ./...
go test -mod=readonly -run '^TestReadme' -count=1 .

Public-API tests cover exact payloads and IDs, ACK suppression, codec changes, rotation, lifecycle, ownership, rejected encodings and cleanup retries. Linux subprocess tests add actual SIGKILL/restart, kernel file/descriptor limits and independently reconciled delivery ledgers. Test counts and coverage are evidence, not a proof of exactly-once delivery or universal correctness.

For retained failure-versus-control comparisons and bounded fuzzing:

python3 .scripts/verify_behavior_regressions.py --artifacts /tmp/journal-red-green
python3 .scripts/verify_pr5_regressions.py --artifacts /tmp/journal-append-safety --benchmark-pairs 6
go test -mod=readonly -run '^$' -fuzz '^FuzzBehaviorFileNames$' -fuzztime=15s -parallel=2 .

The comparison scripts need the historical commits named in their source; use a full Git clone or fetch that history. BEHAVIOR.md describes the oracles, evidence directories and validation boundaries. The README tests also check local links and execute the displayed example without a second copy of its source.

Contributing and support

Open an issue with the exact revision, Go version, OS/filesystem, relevant options, a minimal reproducer, and expected versus observed behavior. Remove secrets and production payloads from reports. For a sensitive security finding, arrange a private channel with the maintainer before sharing details; do not publish raw journals or credentials.

Keep PRs focused. Reproduce defects with application-visible behavior and independent assertions, retain passing controls, and run the native-module and race suites. For performance changes, use identical workloads, repeated paired measurements and unchanged durability semantics; report regressions as well as improvements. Update the relevant contract and this README when behavior changes. Do not weaken an oracle or count a compilation failure as a reproduced defect.

Maintained by Laisky, with contributions through pull requests.

License

MIT. Copyright and license terms are preserved in the license file.

Version 2 source migration

This candidate changes the module path and logging contract. Read the v2 migration notes before adopting it. Published v1 versions and stored records remain unchanged.

Documentation

Index

Examples

Constants

View Source
const (
	// FileMode requests private creation; the process umask can further restrict it.
	FileMode os.FileMode = 0600
	// DirMode requests private directory creation without changing the umask.
	DirMode = os.FileMode(0700) | os.ModeDir

	// BufSize default buf file size
	BufSize = 1024 * 1024 * 4 // 4 MB
)

Variables

View Source
var (
	// ErrNotStarted means Start has not initialized a usable journal.
	ErrNotStarted = fmt.Errorf("journal not started")
	// ErrDuringRotate rotate error
	ErrDuringRotate = fmt.Errorf("during rotating")
)

Functions

func GenerateNewBufFName

func GenerateNewBufFName(now time.Time, oldFName string) (string, error)

GenerateNewBufFName return new buf file name depends on current time file name looks like `yyyymmddnnnn.ids`, nnnn begin from 0001 for each day

func OpenBufFile

func OpenBufFile(filepath string, preallocateBytes int64) (fp *os.File, err error)

OpenBufFile create and open file

func PrepareDir

func PrepareDir(path string) error

PrepareDir `mkdir -p`

func PrepareNewBufFile

func PrepareNewBufFile(dirPath string, oldFsStat *bufFileStat, isScan, isGz bool, sizeBytes int64) (fsStat *bufFileStat, err error)

PrepareNewBufFile create new data & id files, and update bufFileStat.

  • if `isScan=true`, will scan directory to find existing buf files, then generate new buf files.

* if `isScan=false`, keep old buf files, directly generate new file without scan directory.

Types

type BaseSerializer

type BaseSerializer struct {
	sync.Mutex
	// contains filtered or unexported fields
}

BaseSerializer base serializer

type ClosedRecoveryArchive

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

ClosedRecoveryArchive owns an existing stopped journal's lock and retained directory capability. It creates, synchronizes, acknowledges and deletes nothing. Callers may validate then replay the same captured file prefixes.

func OpenClosedRecoveryArchive

func OpenClosedRecoveryArchive(root *os.Root, maxRecordBytes int64) (a *ClosedRecoveryArchive, err error)

func (*ClosedRecoveryArchive) Close

func (a *ClosedRecoveryArchive) Close() error

func (*ClosedRecoveryArchive) WalkRaw

func (a *ClosedRecoveryArchive) WalkRaw(ctx context.Context, visit func(*Data, []byte) error) (int, error)

WalkRaw returns exact MessagePack record bytes alongside the decoded identity. A successful callback transfers no ownership of persistent state. It must not reenter this reader or mutate the supplied bytes while another call uses them.

type Data

type Data struct {
	Data map[string]interface{}
	ID   int64
}

Data msgp data schema

func (*Data) DecodeMsg

func (z *Data) DecodeMsg(dc *msgp.Reader) (err error)

DecodeMsg implements msgp.Decodable

func (*Data) EncodeMsg

func (z *Data) EncodeMsg(en *msgp.Writer) (err error)

EncodeMsg implements msgp.Encodable

func (*Data) MarshalMsg

func (z *Data) MarshalMsg(b []byte) (o []byte, err error)

MarshalMsg implements msgp.Marshaler

func (*Data) Msgsize

func (z *Data) Msgsize() (s int)

Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message

func (*Data) UnmarshalMsg

func (z *Data) UnmarshalMsg(bts []byte) (o []byte, err error)

UnmarshalMsg implements msgp.Unmarshaler

type DataDecoder

type DataDecoder struct {
	BaseSerializer
	// contains filtered or unexported fields
}

DataDecoder data deserializer

func NewDataDecoder

func NewDataDecoder(fp *os.File, isCompress bool) (decoder *DataDecoder, err error)

NewDataDecoder create new DataDecoder

func (*DataDecoder) Read

func (dec *DataDecoder) Read(data *Data) (err error)

Read deserialize data from fp

type DataEncoder

type DataEncoder struct {
	BaseSerializer
	// contains filtered or unexported fields
}

DataEncoder data serializer

func NewDataEncoder

func NewDataEncoder(fp *os.File, isCompress bool) (enc *DataEncoder, err error)

NewDataEncoder create new DataEncoder

func (*DataEncoder) Close

func (enc *DataEncoder) Close() error

Close releases scratch state and never flushes a known incomplete append.

func (*DataEncoder) Flush

func (enc *DataEncoder) Flush() error

Flush flushes encoded bytes, but cannot certify an already damaged stream.

func (*DataEncoder) Write

func (enc *DataEncoder) Write(msg *Data) error

Write serializes a complete record before touching the live stream. Encoding rejection is retryable; an I/O failure after append begins poisons this encoder.

type IdsDecoder

type IdsDecoder struct {
	BaseSerializer
	// contains filtered or unexported fields
}

IdsDecoder ids deserializer

func NewIdsDecoder

func NewIdsDecoder(fp *os.File, isCompress bool) (decoder *IdsDecoder, err error)

NewIdsDecoder create new IdsDecoder

func (*IdsDecoder) LoadMaxID

func (dec *IdsDecoder) LoadMaxID() (int64, error)

LoadMaxID is the idiomatic spelling of LoadMaxId.

func (*IdsDecoder) LoadMaxId

func (dec *IdsDecoder) LoadMaxId() (maxId int64, err error)

LoadMaxId load the maxium id in all files

func (*IdsDecoder) ReadAllToBmap

func (dec *IdsDecoder) ReadAllToBmap() (ids *roaring.Bitmap, err error)

ReadAllToBmap read all ids in all files into bmap

func (*IdsDecoder) ReadAllToInt64Set

func (dec *IdsDecoder) ReadAllToInt64Set(ids Int64SetItf) (err error)

ReadAllToBmap read all ids in all files into set

type IdsEncoder

type IdsEncoder struct {
	BaseSerializer
	// contains filtered or unexported fields
}

IdsEncoder ids serializer

func NewIdsEncoder

func NewIdsEncoder(fp *os.File, isCompress bool) (enc *IdsEncoder, err error)

NewIdsEncoder create new IdsEncoder

func (*IdsEncoder) Close

func (enc *IdsEncoder) Close() (err error)

Close close ids gzip writer

func (*IdsEncoder) Flush

func (enc *IdsEncoder) Flush() (err error)

Flush flush buf to fp

func (*IdsEncoder) Write

func (enc *IdsEncoder) Write(id int64) (err error)

Write serialize id info fp

type Int64Set

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

Int64Set set depends on sync.Map. cost much more memory than bitmap

Example
s := NewInt64Set()
s.Add(5)

s.CheckAndRemove(5) // true
s.CheckAndRemove(3) // false

func NewInt64Set

func NewInt64Set() *Int64Set

NewInt64Set create new Int64Set

func (*Int64Set) Add

func (s *Int64Set) Add(i int)

Add add new number

func (*Int64Set) AddInt64

func (s *Int64Set) AddInt64(i int64)

AddInt64 add int64

func (*Int64Set) CheckAndRemove

func (s *Int64Set) CheckAndRemove(i int64) (ok bool)

CheckAndRemove return true if exists

func (*Int64Set) GetLen

func (s *Int64Set) GetLen() int

GetLen return length

type Int64SetItf

type Int64SetItf interface {
	Add(int)
	AddInt64(int64)
	CheckAndRemove(int64) bool
	GetLen() int
}

Int64SetItf set for int64

type Int64SetWithTTL

type Int64SetWithTTL struct {
	sync.RWMutex
	// contains filtered or unexported fields
}

Int64SetWithTTL int64 set with TTL

func NewInt64SetWithTTL

func NewInt64SetWithTTL(ctx context.Context, ttl time.Duration) *Int64SetWithTTL

NewInt64SetWithTTL create new int64 set with ttl

func (*Int64SetWithTTL) Add

func (s *Int64SetWithTTL) Add(id int)

Add add int

func (*Int64SetWithTTL) AddInt64

func (s *Int64SetWithTTL) AddInt64(id int64)

AddInt64 add int64

func (*Int64SetWithTTL) CheckAndRemove

func (s *Int64SetWithTTL) CheckAndRemove(id int64) (ok bool)

CheckAndRemove return true if id committed

func (*Int64SetWithTTL) Close

func (s *Int64SetWithTTL) Close()

Close close set, stop rotate

func (*Int64SetWithTTL) GetLen

func (s *Int64SetWithTTL) GetLen() (r int)

GetLen get items number of set

func (*Int64SetWithTTL) StartRotate

func (s *Int64SetWithTTL) StartRotate(ctx context.Context)

StartRotate start counter rotate

type Journal

type Journal struct {

	// RWMutex journal rwlock.
	// acquire write lock when flush/rotate journal legacy.
	// acquire read lock when read/write journal legacy.
	sync.RWMutex
	// contains filtered or unexported fields
}

Journal redo log consist by msgs and committed ids

func NewJournal

func NewJournal(opts ...OptionFunc) (j *Journal, err error)

NewJournal create new Journal

func (*Journal) CleanLegacyBuf

func (j *Journal) CleanLegacyBuf() error

CleanLegacyBuf synchronizes current ACKs before retiring the caller-proven resolved frozen snapshot. It never unlocks the caller's legacy ownership.

func (*Journal) Close

func (j *Journal) Close()

Close broadcasts shutdown, waits for both workers, and is idempotent.

func (*Journal) Flush

func (j *Journal) Flush() error

Flush flush journal files buffer to file

func (*Journal) GetMetric

func (j *Journal) GetMetric() map[string]interface{}

GetMetric monitor inteface

func (*Journal) HasRetainedData

func (j *Journal) HasRetainedData() (bool, error)

HasRetainedData is a conservative metadata observation for a quiescent owner. Nonempty sealed files remain obligations until an acknowledged cleanup pass.

func (*Journal) HasUnacknowledgedData

func (j *Journal) HasUnacknowledgedData() bool

HasUnacknowledgedData is logical transport progress, not a durable-sink proof. Atomic-only observation is safe while the owner's mutation barrier is held.

func (*Journal) IsLegacyRunning

func (j *Journal) IsLegacyRunning() bool

IsLegacyRunning check whether running legacy loading

func (*Journal) LoadLegacyBuf

func (j *Journal) LoadLegacyBuf(data *Data) (err error)

LoadLegacyBuf load legacy data one by one ⚠️Warn: should call `j.LockLegacy()` before invoke this method

func (*Journal) LoadLegacyBufRetained

func (j *Journal) LoadLegacyBufRetained(data *Data) error

LoadLegacyBufRetained reads a frozen snapshot WITHOUT retiring any segment at EOF. The caller must hold LockLegacy and may retry by rotating after EOF. It must not call CleanLegacyBuf until every unresolved record is durably acknowledged or has its complete terminal evidence retained elsewhere.

func (*Journal) LoadMaxID

func (j *Journal) LoadMaxID() (int64, error)

LoadMaxID is the idiomatic spelling of LoadMaxId.

func (*Journal) LoadMaxId

func (j *Journal) LoadMaxId() (int64, error)

LoadMaxId includes all retained data and acknowledgement records.

func (*Journal) LockLegacy

func (j *Journal) LockLegacy() bool

LockLegacy lock legacy to prevent rotate, clean

func (*Journal) PrepareRetainedReplay

func (j *Journal) PrepareRetainedReplay(ctx context.Context) (bool, error)

PrepareRetainedReplay rewinds only at caller-proven snapshot EOF. An empty active data writer is reused, so repeated sink outages do not manufacture empty segments. Current synchronized ACKs are included in the next snapshot. The caller serializes this method with its replay cursor and admission.

func (*Journal) ReadRecoveryRecords

func (j *Journal) ReadRecoveryRecords(ctx context.Context, visit func(*Data) error) (int, error)

ReadRecoveryRecords captures bounded synchronized file prefixes, then reads original records INCLUDING acknowledged ones. It does not mutate ACK state or delete any segment. Callback must not reenter journal I/O.

func (*Journal) Rotate

func (j *Journal) Rotate(ctx context.Context) error

Rotate serializes rotation with writes, Flush, Sync and Close. A failed replacement-file preparation leaves the current synchronized writer usable.

func (*Journal) RotateIfFull

func (j *Journal) RotateIfFull(ctx context.Context) error

func (*Journal) Start

func (j *Journal) Start(ctx context.Context) (err error)

func (*Journal) Sync

func (j *Journal) Sync() error

Sync makes completed writes durable. It does not wait for work queued in another goroutine: replay callers must WriteData before requesting cleanup. Overlapping callers may share a barrier; sequential calls never reuse a completed result. Each shared barrier still synchronizes both files and dir.

func (*Journal) UnLockLegacy

func (j *Journal) UnLockLegacy() bool

UnLockLegacy release legacy lock

func (*Journal) WriteData

func (j *Journal) WriteData(data *Data) (err error)

WriteData write data to journal

func (*Journal) WriteID

func (j *Journal) WriteID(id int64) error

WriteID acknowledges a record. It is the idiomatic spelling of WriteId; both methods preserve the same validation, lifecycle and durability contract.

func (*Journal) WriteId

func (j *Journal) WriteId(id int64) error

WriteId write id to journal

type LegacyLoader

type LegacyLoader struct {
	// acquire write lock during reset.
	// acquire read lock during read/write data/ids files.
	sync.RWMutex
	// contains filtered or unexported fields
}

func NewLegacyLoader

func NewLegacyLoader(ctx context.Context,
	logger Logger,
	dataFNames, idsFNames []string,
	isCompress bool,
	committedIDTTL time.Duration,
) *LegacyLoader

NewLegacyLoader creates a replay reader using the caller's logging sink.

func (*LegacyLoader) AddID

func (l *LegacyLoader) AddID(id int64)

AddID add id in ids

func (*LegacyLoader) CheckAndRemove

func (l *LegacyLoader) CheckAndRemove(id int64) bool

func (*LegacyLoader) Clean

func (l *LegacyLoader) Clean() error

Clean remove old legacy files

func (*LegacyLoader) GetIdsLen

func (l *LegacyLoader) GetIdsLen() int

GetIdsLen return length of ids

func (*LegacyLoader) Load

func (l *LegacyLoader) Load(data *Data) (err error)

Load load data from legacy

func (*LegacyLoader) LoadAllids

func (l *LegacyLoader) LoadAllids(ids Int64SetItf) error

LoadAllids read all ids from ids file into ids set

func (*LegacyLoader) LoadMaxID

func (l *LegacyLoader) LoadMaxID() (int64, error)

LoadMaxID is the idiomatic spelling of LoadMaxId.

func (*LegacyLoader) LoadMaxId

func (l *LegacyLoader) LoadMaxId() (maxId int64, err error)

LoadMaxId includes sealed data as well as ACKs, without consuming either.

func (*LegacyLoader) Reset

func (l *LegacyLoader) Reset(dataFNames, idsFNames []string)

Reset reset journal legacy link to existing files

type Logger

type Logger interface {
	Debug(string, ...zapcore.Field)
	Info(string, ...zapcore.Field)
	Warn(string, ...zapcore.Field)
	Error(string, ...zapcore.Field)
	Panic(string, ...zapcore.Field)
}

Logger is the logging contract consumed by journals and replay readers. It deliberately requires no factory, child, level, flush or Zap-core API.

type OptionFunc

type OptionFunc func(*option) error

func WithBufDirPath

func WithBufDirPath(path string) OptionFunc

func WithBufSizeByte

func WithBufSizeByte(bufSize int64) OptionFunc

func WithCommitIDTTL

func WithCommitIDTTL(d time.Duration) OptionFunc

func WithFlushInterval

func WithFlushInterval(d time.Duration) OptionFunc

func WithIsAggresiveGC

func WithIsAggresiveGC(is bool) OptionFunc

func WithIsCompress

func WithIsCompress(is bool) OptionFunc

func WithLogger

func WithLogger(logger Logger) OptionFunc

WithLogger injects the small logging contract used by the journal.

func WithName

func WithName(name string) OptionFunc

func WithRoot

func WithRoot(root *os.Root) OptionFunc

WithRoot selects an already opened journal directory. NewJournal duplicates the root only after all options validate; the caller may close its handle after NewJournal returns. The journal owns the duplicate until Close, even when Start fails. No path writability probe or /proc descriptor path is used. The directory is an operator-owned single-writer WAL domain. os.Root confines symlink resolution; it does not prevent hard links, mounts or hostile writers already authorized to mutate the directory's contents.

func WithRotateCheckInterval

func WithRotateCheckInterval(d time.Duration) OptionFunc

func WithRotateDuration

func WithRotateDuration(d time.Duration) OptionFunc

func WithStorageGuard

func WithStorageGuard(guard StorageGuard) OptionFunc

type StorageGuard

type StorageGuard func(kind string, bytes int64) (release func(), err error)

StorageGuard funds managed growth before a mutation and holds the owner's quiescence barrier until release. The journal never supplies record contents. Invoked with a journal lock held: implementations must never reenter Journal operations or take another lock that callers hold while waiting on Journal. A successful grant must return a non-nil release function.

type Uint32Set

type Uint32Set struct {
	sync.Mutex
	// contains filtered or unexported fields
}

Uint32Set set depends on bitmap. only support uint32, so cannot support more than 4294967295 numbers.

func NewUint32Set

func NewUint32Set() *Uint32Set

NewUint32Set create new Uint32Set

func (*Uint32Set) AddInt64

func (s *Uint32Set) AddInt64(i int64)

AddInt64 add new number

func (*Uint32Set) AddUint32

func (s *Uint32Set) AddUint32(i uint32)

AddUint32 add new number

func (*Uint32Set) CheckAndRemoveInt64

func (s *Uint32Set) CheckAndRemoveInt64(i int64) (ok bool)

CheckAndRemoveInt64 return true if exists

func (*Uint32Set) CheckAndRemoveUint32

func (s *Uint32Set) CheckAndRemoveUint32(i uint32) (ok bool)

CheckAndRemoveUint32 return true if exists

func (*Uint32Set) GetLen

func (s *Uint32Set) GetLen() int

GetLen return length

Directories

Path Synopsis
tests
benchgate command
Command benchgate measures fixed public-API work.
Command benchgate measures fixed public-API work.
e2eperf command
Command e2eperf is an ordinary external consumer of the exported journal API.
Command e2eperf is an ordinary external consumer of the exported journal API.

Jump to

Keyboard shortcuts

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