tales

package module
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Jul 19, 2026 License: MIT Imports: 21 Imported by: 0

README

kelindar/tales
Go Version PkgGoDev Go Report Card License

Multi-Actor Event Logging in S3

Tales is an append-only event log for Go. It buffers events in memory, stores them as compressed files in S3, and finds them by actor ID. An actor can be a user, account, device, conversation, game object, or anything else represented by a uint32.

  • Actor Queries: Find events for one actor or events shared by several actors.
  • Multiple Writers: Each application instance writes its own files under the same prefix.
  • Explicit Durability: Sync confirms that previously accepted events are stored in S3.
  • Historical Compaction: Compact reduces the number of files needed to query older days.

Use When

  • Events belong to one or more numeric actors.
  • Workloads mostly append events and read actor history.
  • S3 should be the persistent store.
  • Several application instances need to write under the same prefix.

Not For

  • Updating or deleting individual events.
  • Full-text search, joins, arbitrary JSON filters, or transactions.
  • Sharing one writer ID between live processes.
  • Workflows where every Log call must immediately write to S3; use Sync at the required durability boundary.

Quick Start

package main

import (
	"context"
	"log"
	"time"

	"github.com/kelindar/tales"
)

func main() {
	ctx := context.Background()
	logger, err := tales.New("my-bucket", "us-east-1",
		tales.WithPrefix("events"),
	)
	if err != nil {
		log.Fatal(err)
	}

	if err := logger.Log("Player joined", 12345); err != nil {
		log.Fatal(err)
	}
	if err := logger.Log("Player attacked monster", 12345, 67890); err != nil {
		log.Fatal(err)
	}
	if err := logger.Sync(ctx); err != nil {
		log.Fatal(err)
	}

	from := time.Now().Add(-time.Hour)
	to := time.Now()
	for event, err := range logger.Query(ctx, from, to, 12345) {
		if err != nil {
			log.Fatal(err)
		}
		log.Println(event.Text())
	}

	if err := logger.Close(); err != nil {
		log.Fatal(err)
	}
}

Log returns after the event has been accepted into memory. Call Sync when it must be committed to S3. A failed Close leaves the service open so Sync or Close can be retried.

Actor arguments to Query are combined with AND. Both time bounds are inclusive, and results are returned in a stable order. Event views are read-only and may refer to downloaded data; call Clone before retaining or modifying one independently.

Each service owns one writer ID. Tales creates one automatically, or WithWriterID("game-server-1") can derive a stable ID from a deployment name. Only one live process may use a given writer ID.

Historical compaction

Compaction is optional maintenance for older data. Call it from an external scheduled job, with one compactor running per prefix. The earliest eligible day is two UTC days ago.

day := time.Now().UTC().AddDate(0, 0, -2)
if err := logger.Compact(context.Background(), day); err != nil {
	log.Fatal(err)
}

Compaction is safe to retry. Queries continue using writer files until compacted metadata is committed.

Storage layout

Tales creates these keys below the configured prefix:

YYYY-MM-DD/writers/<writer>/manifest.json
YYYY-MM-DD/writers/<writer>/<sequence>.log
YYYY-MM-DD/compact/index.bin
YYYY-MM-DD/compact/data.log
YYYY-MM-DD/compact/metadata.json

Chunk sequences are zero-based and use 20 decimal digits. This format has no legacy decoder or migration path.

Installation

go get github.com/kelindar/tales

License

Tales is released under the MIT License.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Compactor added in v0.2.0

type Compactor interface {
	Compact(context.Context, time.Time) error
}

Compactor compacts an eligible historical UTC day.

type Event added in v0.2.0

type Event = codec.Event

Event is a validated log event. Its projections are zero-copy and read-only; use Clone before retaining or modifying their backing data.

type Logger added in v0.0.2

type Logger interface {
	Log(text string, actors ...uint32) error
}

Logger accepts events into the service's in-memory writer state.

type Manager added in v0.0.2

type Manager interface {
	Logger
	Querier
	Syncer
	Close() error
}

Manager is the primary service contract.

type Option

type Option func(*config)

Option configures the Service. All options are optional and may be provided to New when constructing a logger.

func WithBackblaze

func WithBackblaze() Option

WithBackblaze sets the S3 service to Backblaze B2.

func WithBuffer

func WithBuffer(size int) Option

WithBuffer sets the maximum number of entries kept in memory.

func WithClient

func WithClient(fn func(s3.Config) (s3.Client, error)) Option

WithClient allows overriding the S3 client creation function.

func WithInterval

func WithInterval(d time.Duration) Option

WithInterval sets the interval at which in-memory chunks are flushed.

func WithKey

func WithKey(key *aws.SigningKey) Option

WithKey sets the signing key to use for the S3 client.

func WithPrefix

func WithPrefix(prefix string) Option

WithPrefix sets the S3 key prefix to use when storing objects.

func WithWriterID added in v0.2.0

func WithWriterID(id string) Option

WithWriterID hashes a stable name into the service's 16-character writer ID.

type Querier added in v0.0.2

type Querier interface {
	Query(context.Context, time.Time, time.Time, ...uint32) iter.Seq2[Event, error]
}

Querier reads events in deterministic order within inclusive time bounds.

type Service

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

Service is a distributed S3-backed event log owned by one writer ID.

func New

func New(bucket, region string, opts ...Option) (*Service, error)

New opens a service for the given S3 bucket and region.

func (*Service) Close

func (l *Service) Close() error

Close syncs pending events and releases the service after a successful flush.

func (*Service) Compact added in v0.2.0

func (l *Service) Compact(ctx context.Context, value time.Time) error

Compact commits an immutable merged index for an eligible historical UTC day.

func (*Service) Log

func (l *Service) Log(text string, actors ...uint32) error

Log accepts an event into local memory for every supplied actor.

func (*Service) Query

func (l *Service) Query(ctx context.Context, from, to time.Time, actors ...uint32) iter.Seq2[Event, error]

Query yields events containing every actor within inclusive time bounds.

func (*Service) Sync added in v0.2.0

func (l *Service) Sync(ctx context.Context) error

Sync makes every previously accepted event durable.

type Syncer added in v0.2.0

type Syncer interface {
	Sync(context.Context) error
}

Syncer makes all previously accepted events durable.

Directories

Path Synopsis
cmd
bench command
example command
internal
s3

Jump to

Keyboard shortcuts

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