Documentation
¶
Overview ¶
Package s3lite provides S3-backed embedded SQLite for serverless containers. It wraps litestream for continuous replication and a CGO-free SQLite driver.
Example (Basic) ¶
package main
import (
"context"
"fmt"
"log"
"os"
"path/filepath"
"github.com/atmin/s3lite"
)
func main() {
ctx := context.Background()
dir, err := os.MkdirTemp("", "s3lite-example-*")
if err != nil {
log.Fatal(err)
}
defer os.RemoveAll(dir)
dbPath := filepath.Join(dir, "app.sqlite3")
replicaURL := "file://" + filepath.Join(dir, "replica")
db, err := s3lite.Open(ctx, s3lite.Config{
LocalPath: dbPath,
RestoreFrom: replicaURL,
BackupTo: replicaURL,
Migrations: []string{
`CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, email TEXT)`,
`CREATE INDEX IF NOT EXISTS users_email ON users(email)`,
},
})
if err != nil {
log.Fatal(err)
}
defer db.Close()
if _, err := db.ExecContext(ctx, `INSERT INTO users (email) VALUES (?)`, "alice@example.com"); err != nil {
log.Fatal(err)
}
var email string
if err := db.QueryRowContext(ctx, `SELECT email FROM users LIMIT 1`).Scan(&email); err != nil {
log.Fatal(err)
}
fmt.Println(email)
}
Output: alice@example.com
Example (S3) ¶
Example_s3 shows the S3 replica URL form. The caller sources S3 settings (from env, config file, secret manager, etc.) and passes them explicitly. Empty S3Config fields fall back to the AWS SDK's default credential chain.
package main
import (
"context"
"log"
"os"
"github.com/atmin/s3lite"
)
func main() {
ctx := context.Background()
db, err := s3lite.Open(ctx, s3lite.Config{
LocalPath: "/tmp/app.sqlite3",
RestoreFrom: "s3://my-bucket/db",
BackupTo: "s3://my-bucket/db",
S3: s3lite.S3Config{
Region: os.Getenv("AWS_REGION"),
Endpoint: os.Getenv("AWS_ENDPOINT_URL"),
AccessKeyID: os.Getenv("AWS_ACCESS_KEY_ID"),
SecretAccessKey: os.Getenv("AWS_SECRET_ACCESS_KEY"),
},
Migrations: []string{
`CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, email TEXT)`,
},
})
if err != nil {
log.Fatal(err)
}
defer db.Close()
_ = db // use db as a standard *sql.DB
}
Output:
Index ¶
- Constants
- Variables
- type Config
- type DB
- func (db *DB) Close() error
- func (db *DB) CloseContext(ctx context.Context) error
- func (db *DB) Generation() int64
- func (db *DB) IsLeader() bool
- func (db *DB) LastPromoteOutcome() (PromoteOutcome, bool)
- func (db *DB) OnDemote(fn func(error))
- func (db *DB) OnPromote(fn func())
- func (db *DB) OnRefresh(fn func())
- func (db *DB) Refresh(ctx context.Context) (bool, error)
- func (db *DB) ReplicationStatus() ReplicationStatus
- func (db *DB) Sync(ctx context.Context) error
- func (db *DB) TryPromote(ctx context.Context) (bool, error)
- func (db *DB) YieldLease(ctx context.Context) error
- type PromoteOutcome
- type ReplicationStatus
- type Role
- type S3Config
Examples ¶
Constants ¶
const DefaultLeaseTTL = 30 * time.Second
DefaultLeaseTTL is the lease duration used when Config.LeaseTTL is zero. It matches litestream's s3.DefaultLeaseTTL.
const DefaultShutdownSyncTimeout = 30 * time.Second
DefaultShutdownSyncTimeout is the flush deadline Close uses when Config.ShutdownSyncTimeout is zero.
Variables ¶
var ErrClosed = errors.New("s3lite: instance is closed")
ErrClosed is returned by TryPromote when the instance is closing, so a promotion racing Close cannot resurrect a torn-down instance.
var ErrKeyMismatch = errors.New("s3lite: replica object does not authenticate under the configured EncryptionKey")
ErrKeyMismatch reports that an encrypted object would not authenticate under the configured key — "this bucket is not yours (or its bytes were tampered with)". A wrong key and a modified object are cryptographically indistinguishable, so both report this; either way no plaintext is ever returned.
var ErrNotLeader = errors.New("s3lite: not the lease holder")
ErrNotLeader is returned by YieldLease when the instance is not the lease holder — a follower, an already-yielded instance (double yield), or an unleased sole writer (which has no lease to yield). Only a live leader can voluntarily hand off.
var ErrObjectNotEncrypted = errors.New("s3lite: replica holds a plaintext object and RequireEncrypted is set")
ErrObjectNotEncrypted reports that a plaintext object was found on a replica opened with Config.RequireEncrypted. Leave that flag false while a previously-plaintext replica still holds pre-key objects.
var ErrReplicaEncrypted = errors.New("s3lite: replica is encrypted but no EncryptionKey is configured")
ErrReplicaEncrypted reports that the replica holds s3lite-encrypted objects but this instance has no Config.EncryptionKey — "you forgot the key", not a corrupt database. Restore, follower refresh and Open surface it wrapped.
var ErrReplicaFormatNewer = errors.New("s3lite: the replica was written by a newer s3lite than this build can read")
ErrReplicaFormatNewer reports a replica object that announces itself as an LTX file and then does not parse as one — "this replica was written by a newer s3lite", not "your database is corrupt". Consumers render it as an instruction to upgrade.
It is a reading of the evidence rather than a guess. The object's frames authenticated under the configured key (or the replica is plaintext and there is nothing to authenticate), so its bytes are exactly the bytes some writer wrote — damage in transit or at rest would have failed the tag first. Bytes that are authentic, say LTX, and still do not parse can only have come from a format this build predates.
Functions ¶
This section is empty.
Types ¶
type Config ¶
type Config struct {
// LocalPath is the on-disk path for the SQLite file. Required. It may not
// contain '#', '?' or '%': SQLite reads all three as URI syntax inside a
// filename, so such a path would not open the file it names, and Open refuses
// it rather than escaping it (INVARIANTS.md #13).
LocalPath string
// RestoreFrom is a replica URL (file:// or s3://) to restore from on startup
// when LocalPath does not yet exist. Ignored if LocalPath already exists.
// Safe to set on first deploy — an empty replica is not an error.
RestoreFrom string
// BackupTo is a replica URL (file:// or s3://) to replicate to continuously.
// Omit to run without replication.
BackupTo string
// Migrations are SQL strings executed in order on every Open. Each must be
// idempotent (e.g. CREATE TABLE IF NOT EXISTS) — there is no version table.
Migrations []string
// Synchronous sets PRAGMA synchronous on every writer connection: "OFF",
// "NORMAL", "FULL", or "EXTRA" (case-insensitive). Empty means "NORMAL",
// the default documented in the README's durability note: WAL keeps the
// local file crash-consistent either way, and the un-fsynced WAL tail
// NORMAL can lose on a hard crash is already within the replication
// window. Set "FULL" when the application's own contract makes a commit
// mean fsynced-to-local-disk (e.g. a server whose reply promises stable
// storage) — that closes the local-crash window; the replication window
// to the bucket remains. Followers are unaffected: their connections are
// query_only and never write.
Synchronous string
// TxLock sets how transactions opened through database/sql (Begin,
// BeginTx) start on writer connections: "deferred" (SQLite's default),
// "immediate", or "exclusive" (case-insensitive) — the driver's _txlock
// DSN parameter. A writer whose every transaction mutates wants
// "immediate": the write lock is taken at BEGIN, so contention surfaces
// as a busy_timeout wait up front instead of SQLITE_BUSY at the first
// write inside the transaction. Followers are unaffected.
TxLock string
// S3 configures s3:// replicas. Applies to both RestoreFrom and BackupTo.
// Empty fields fall back to the AWS SDK's default credential and region chain
// (env vars, ~/.aws/config, IAM roles). Set Endpoint for MinIO or other
// S3-compatible providers — path-style addressing is enabled automatically.
S3 S3Config
// Logger receives s3lite's own lifecycle events (promote/demote/restore) at
// their natural levels — including the start and completion of every restore,
// with its elapsed time, so the cold restore inside Open is observable rather
// than an unexplained pause. litestream's internal logger is derived from this but
// gated to WARN+, so its per-interval "replica sync" INFO chatter is dropped
// while real replication problems still surface. When nil, a WARN+ stderr
// logger is used. Set to slog.Default() to mirror the host application.
Logger *slog.Logger
// OnRestoreProgress, when non-nil, is called while a whole-database restore
// downloads, with the bytes fetched so far and the total the restore plan will
// fetch — LTX bytes on the replica, not the size of the restored database. It
// fires on every path that pulls the whole database down: Open's cold restore,
// the Open-direct fork guard, and a takeover promotion's rebuild. (A follower's
// incremental refresh applies deltas to a private follow file instead and
// reports nothing.) It is a Config field rather than a method because Open's
// restore runs before the handle exists, and that is the restore that matters
// most.
//
// It answers what the restore log lines cannot. A percentage for a human
// waiting on a cold multi-GB start — and, because a wedged range read freezes
// the byte count while a slow link keeps it moving, the difference between a
// stalled restore and a merely slow one, which no deadline can tell apart
// without also killing slow-but-healthy restores.
//
// The first call reports (0, total) as soon as the plan is known, applied never
// regresses, and the last call of a successful restore is (total, total). Calls
// are serialized, so the callback needs no locking of its own — but it runs
// inline with the download, once per read chunk, so it must be cheap: sample it,
// do not redraw or log per call. A restore with nothing to fetch (an empty
// replica) never calls it at all.
//
// Depends on the litestream fork — see docs/litestream-fork.md, row 4.
OnRestoreProgress func(applied, total int64)
// ShutdownSyncTimeout bounds the final durable flush performed by Close on a
// replicated DB. Close blocks up to this long waiting for the replica to
// catch up before it gives up (returning an error) rather than hanging on an
// unreachable replica. When zero, DefaultShutdownSyncTimeout (30s) is used.
// Ignored when BackupTo is empty. CloseContext ignores this in favour of the
// caller-supplied context deadline.
ShutdownSyncTimeout time.Duration
// Role selects how an instance coordinates single-writer access. Coordination
// needs the object store's atomic conditional write, so it applies only to an
// s3:// BackupTo — where the lease lives at "<BackupTo path>/lock.json" and is
// mandatory: an s3-replicated DB is always leased, never an uncoordinated
// writer. Without an s3:// BackupTo (empty, or a file:// replica) there is no
// shared WAL to coordinate on, so the instance is simply the sole writer and
// Role is moot.
//
// RoleAuto — try to acquire; become writer on success, follower on
// contention. The default, and the mode a serverless consumer
// wants. With no s3:// BackupTo it degrades to the sole writer.
// RoleWriter — acquire the lease or fail Open with *litestream.LeaseExistsError.
// RoleFollower — never replicate; open read-only and serve the restored
// snapshot; promote to writer if the lease becomes free.
//
// RoleWriter and RoleFollower demand a lease, so Open fails if BackupTo is not
// s3://. litestream requires exactly one writer per replica; the lease enforces
// that by protocol so N instances run safely as one writer + many followers.
Role Role
// LeaseTTL is the lease duration for leased instances. The holder renews at
// TTL/3; a holder that cannot renew stops replicating before the TTL lets anyone
// else acquire, so two writers never overlap. When zero, DefaultLeaseTTL (30s)
// is used. Ignored without an s3:// BackupTo.
LeaseTTL time.Duration
// Owner is the lease owner id recorded in the lock file for diagnostics. When
// empty, litestream generates one (hostname:pid). Ignored without an s3:// BackupTo.
Owner string
// FollowerRefreshInterval, when > 0, makes a follower periodically catch up to the
// leader's latest committed state from BackupTo and serve it read-only, giving
// near-live reads with staleness bounded by roughly this interval plus the
// leader's replication lag. The catch-up is incremental — it fetches and applies
// only the LTX committed since the follower's position, not a full snapshot each
// tick — so it stays cheap even for a large database on a short interval. When 0
// (the default) a follower serves the snapshot it restored at Open and refreshes
// only on promotion — bit-identical to prior behaviour. Ignored without an s3://
// BackupTo (nothing to follow) and while this instance is the writer. Best-effort:
// a failed refresh is logged and the follower keeps serving its current state. A
// refresh that lands new data swaps the local file underneath the stable handle, so
// an in-flight read spanning the swap may see a rare, retryable error; ticks that
// find nothing new do no swap.
FollowerRefreshInterval time.Duration
// EncryptionKey, when non-empty, encrypts every LTX object written under
// BackupTo with the caller's 32-byte key, so the bucket operator — or anyone
// who obtains read credentials — holds only ciphertext. Reads require the same
// key; a wrong or absent key is a clean typed error (ErrKeyMismatch,
// ErrReplicaEncrypted), never data and never a corrupt-looking database. Empty
// (the default) writes plaintext, byte-identical to a build without this
// feature: no wrapper is installed at all.
//
// What it does not hide: object names encode the compaction level and TXID
// range, object sizes are visible (so database size and write volume are),
// object timestamps are in the metadata litestream already writes, and
// lock.json stays plaintext — though with a key set an empty Owner defaults to
// an opaque random id instead of the hostname. Local state (LocalPath, the
// position directory, staging temp files) is out of scope and rests on the
// host's disk encryption. See the README's client-side-encryption section.
//
// Key rotation is deliberately not supported: s3lite is a single-key
// primitive. Each object is sealed under a key derived from its own random
// salt, so an in-place rotation would be a full rewrite of the replica. A
// consumer that wants cheap rotation should wrap its own data key above s3lite
// and keep the master here.
EncryptionKey []byte
// RequireEncrypted refuses to read a plaintext object from an encrypted
// replica. Leave it false while a previously-plaintext replica still holds
// pre-key objects — they keep restoring while retention ages them out. Set it
// once every live object is encrypted: that closes the downgrade path where
// anyone with bucket *write* access substitutes a crafted plaintext LTX file
// for an encrypted one. Requires EncryptionKey.
RequireEncrypted bool
// OnDemandPromotion, when true, makes a follower promote only via an explicit
// TryPromote (the consumer's write path) rather than greedily in the background. It
// exists to pair with YieldLease: a bare yield is self-defeating without it, since the
// yielder's own background loop (or an idle peer's) would re-acquire the just-freed
// lock within a tick and ping-pong restores. With it set, the lock is held only while a
// writer is actively writing; between bursts it is free and whoever writes next acquires
// instantly through TryPromote.
//
// Open's direct acquire is unchanged — bootstrap (first-writer schema creation) and an
// expired-crash re-entry still acquire at Open — at the cost of one harmless
// acquire-then-idle cycle per boot when the lock is free. The one exception to passivity
// is eager recovery: an instance that may hold an unshipped local tail (a follower that
// finds it was a crashed ex-leader here, or one that was just demoted) keeps
// background-promoting until a writer entry ships or discards that tail, so on-demand
// mode never turns a crash into silent data loss. A cleanly yielded/closed instance has
// its tail provably shipped, so it stays passive. Ignored without an s3:// BackupTo.
OnDemandPromotion bool
}
Config holds the options for Open.
type DB ¶
DB wraps *sql.DB with optional litestream replication. All *sql.DB methods are available directly via embedding.
The embedded *sql.DB is stable for the whole life of the instance: it is created once and never reassigned, even across lease promote/demote. Callers may take it once (database := db.DB), hand it to repositories, and keep using it — connections are transparently re-dialed against the current local file in the current mode (writable as leader, read-only as follower). Use IsLeader or OnPromote/OnDemote only to make decisions about role; you never need them to keep a handle valid. A follower's handle rejects writes (query_only), so gate write paths on IsLeader if you serve traffic before promotion.
Fencing caveats for a misbehaving consumer:
- A follower can escape read-only mode with PRAGMA query_only=0 and write to its local file. Such writes never replicate and are destroyed by the next restore/refresh — do not do this.
- Demote fences the cached handle, including in-flight transactions and writes on a checked-out *sql.Conn. One residual hole is not closed: a *sql.Stmt prepared and then executed on a connection that was already checked out before the demote bypasses the driver-level write fence. Prepared-statement writes should still be gated on IsLeader.
func Open ¶
Open opens or creates a SQLite database at cfg.LocalPath.
Without an s3:// BackupTo the instance is the sole writer:
- If RestoreFrom is set and LocalPath does not exist, restore from replica.
- Start litestream replication if BackupTo is set (a file:// replica).
- Open the SQLite connection and apply WAL pragmas.
- Run Migrations in order.
With an s3:// BackupTo the instance first coordinates via the lease (see Config.Role): a writer replicates and runs Migrations as above; a follower opens read-only, skips replication and Migrations, and serves the restored snapshot. Open returns *litestream.LeaseExistsError for RoleWriter when the lease is held.
Call Close when done to flush replication, release the lease, and free resources.
func (*DB) Close ¶
Close durably flushes pending replication, stops litestream, and closes the database. On a replicated DB (BackupTo set) it guarantees the replica reflects all committed writes before returning — callers do not need a separate Sync. The flush is bounded by Config.ShutdownSyncTimeout (default 30s): if the replica cannot be reached in time, Close returns an error rather than hanging. With no replica configured it is a fast no-op beyond closing the DB. Returns the first non-nil error encountered.
func (*DB) CloseContext ¶
CloseContext is like Close but bounds the final durable flush by ctx instead of Config.ShutdownSyncTimeout. Use it to wire Close into an existing graceful-shutdown deadline. The database handle is always closed even if the flush errors or ctx expires. For a leader it also releases the lease so a successor can take over immediately rather than waiting out the TTL.
It is safe to call Close/CloseContext more than once sequentially: after the first call every later call is a nil no-op. (Concurrent calls from multiple goroutines are not made safe — serialise your shutdown.)
func (*DB) Generation ¶
Generation returns the lease generation, or 0 when not holding a lease (including an unleased sole writer). It is unique among concurrent contenders and increases across takeovers only while the lock object survives: an expiry or a forced steal bumps it (1 → 2 → 3 …), but a clean release (Close) deletes the lock, so the next acquirer starts again at 1.
Because of that reset, do not use Generation as a cross-handoff fencing token for external systems: after a clean handoff a consumer sees the sequence go 1 → 2 → 1, which is exactly when a stale-writer fence would need it to keep increasing. What it is good for: telling apart two promotions within one instance's lifetime, and logging/diagnostics.
A durable fencing token would require persisting an epoch next to the replica data (outside lock.json, which litestream's s3.Leaser owns); that is intentionally not built here.
func (*DB) IsLeader ¶
IsLeader reports whether this instance currently holds write access: it is the lease holder, or the sole writer when unleased (no s3:// BackupTo). Followers return false until they promote. Use it to gate whether the process should accept writes.
func (*DB) LastPromoteOutcome ¶ added in v0.7.0
func (db *DB) LastPromoteOutcome() (PromoteOutcome, bool)
LastPromoteOutcome reports how this instance most recently became the writer (see PromoteOutcome). ok is true once this instance has entered the writer role — after a writer Open or a later promotion — and false on a follower that has never promoted. The value is stable until the next writer entry; a demotion leaves it reporting the last one. Read it inside OnPromote, or after Open/TryPromote reports this instance is the writer, to decide whether derived state may have been rewound.
func (*DB) OnDemote ¶
OnDemote registers a callback fired after this instance loses the lease and stops replicating (the err explains why). The consumer must stop accepting writes on demotion. It must not call Close — the callback runs on the lease-loop goroutine and Close blocks waiting for that goroutine to exit, so calling Close from here deadlocks — and it should return quickly.
func (*DB) OnPromote ¶
func (db *DB) OnPromote(fn func())
OnPromote registers a callback fired after this instance becomes the writer (lease acquired and replication started). It must not call Close — the callback runs on the lease-loop goroutine and Close blocks waiting for that goroutine to exit, so calling Close from here deadlocks — and it should return quickly. Set it before Open returns control to other goroutines; it is not safe to change concurrently with a role transition.
func (*DB) OnRefresh ¶ added in v0.4.0
func (db *DB) OnRefresh(fn func())
OnRefresh registers a callback fired after a follower refresh swaps in newer state from the replica (see Config.FollowerRefreshInterval and Refresh), e.g. to bust caches built on the previous snapshot. It never fires for a writer or when a refresh finds nothing new. It runs on whichever goroutine drove the refresh: the internal lease loop for an interval tick, or the caller's for Refresh. It must not call Close — Close blocks waiting for the lease-loop goroutine to exit, so calling it from a callback that loop is running deadlocks — and it should return quickly. Set it before Open returns control to other goroutines.
func (*DB) Refresh ¶ added in v0.13.0
Refresh brings a follower up to the replica's latest committed state now, instead of waiting for the next Config.FollowerRefreshInterval tick, and reports whether the published state actually advanced. It is the synchronous "force freshness before this read" entry point: an interactive consumer can call it per statement and a request handler per request, paying the freshness cost only where it is needed. FollowerRefreshInterval is the background cadence, not a prerequisite — Refresh works with it unset.
It never takes the writer pen: a follower stays a follower (TryPromote promotes). It returns (false, nil) without any work on the writer — including an unleased sole writer, which already holds the latest state — and on a probe that finds the replica unchanged: no fetch, no swap, no OnRefresh. Every refresh that does advance fires OnRefresh and does exactly what an interval tick does, incremental apply included.
Safe from any goroutine and safe to call concurrently with itself, TryPromote and the background loop: they serialise on one lock, so at most one rebuild runs at a time and a promotion winning the race degrades this to the writer no-op. Bound ctx to cap how long the fetch may take; a cancelled or failed Refresh leaves the follower serving its current state. Once Close has begun it returns ErrClosed.
func (*DB) ReplicationStatus ¶ added in v0.8.0
func (db *DB) ReplicationStatus() ReplicationStatus
ReplicationStatus reports the metadata replica's health without any network I/O (see ReplicationStatus). Safe to call concurrently and at any role: a follower or an unreplicated sole writer reports Replicating=false with the remaining fields zero.
func (*DB) Sync ¶
Sync blocks until the replica is caught up with the current database state. It is a no-op when there is no replication store (BackupTo unset, or a follower that does not replicate). Calling it concurrently with a demotion may return an error: a role transition can close the store between the snapshot below and SyncAndWait, which correctly reports that the sync did not happen.
func (*DB) TryPromote ¶ added in v0.4.0
TryPromote attempts to acquire the writer lease immediately rather than waiting for the next background poll. It returns true if this instance is (or, after restoring the latest replica, has just become) the writer, and false if the lease is still held by a live writer elsewhere. On a follower it blocks for the restore while promoting; bound ctx to cap that wait.
It never promotes two writers — acquisition is the same lease CAS the background loop uses, so this makes promotion happen sooner, never more often. An unleased sole writer (no s3:// BackupTo) is always the writer, so this returns true without any I/O. Safe to call concurrently. Once Close has begun it returns ErrClosed; callers should stop calling TryPromote when they start shutting down.
func (*DB) YieldLease ¶ added in v0.9.0
YieldLease voluntarily releases the lease while keeping this instance alive as a read-only follower, so a peer's next write can acquire immediately instead of waiting out the TTL or being refused while a live-but-idle holder pins the lock. It is the release-on-idle handoff: the consumer, which knows its own activity, calls it when it goes quiet; s3lite performs a safe hand-back — fence, final durable sync, stop replication, record a clean-shutdown marker, then delete the lock last — so a successor acquiring right after sees every acked write (INVARIANTS.md #10).
Pair it with Config.OnDemandPromotion: without that, this instance's own background loop (or an idle peer's) re-acquires the just-freed lock within a tick, making the yield self-defeating. With it, the yielded instance stays passive and whoever writes next promotes via TryPromote.
It returns ErrNotLeader if the instance is not the current lease holder (a follower, an already-yielded instance, or an unleased sole writer). If the final sync cannot complete before the lease deadline the yield aborts atomically: the instance stays the leader with renewals running, or — if it can no longer hold the lease — takes the standard demote path (the only case that fires OnDemote). A successful yield never fires OnDemote. Bound ctx to cap how long the final sync may take. Do not call it concurrently with Close.
type PromoteOutcome ¶ added in v0.7.0
type PromoteOutcome struct {
// Restored is true when writer entry replaced the local database with the replica's
// committed state: a takeover (a successor acquired the lease and could have forked
// the lineage since this instance's tenure — anything the previous holder acked but
// had not synced is gone), or a first-ever writer entry with no prior local file.
// Treat it as a possible rewind: pause destructive maintenance (e.g. garbage
// collection) and reconcile derived state before trusting the metadata again.
//
// It is false only for a provable resume-in-place — self-succession (a crash and
// restart on this machine with no takeover between) or a clean restart — where the
// local committed tail was kept intact and nothing was discarded, so no
// reconciliation is needed.
Restored bool
// Generation is the lease generation at that entry, or 0 for an unleased sole writer.
Generation int64
}
PromoteOutcome reports how this instance entered the writer role, letting a consumer that keeps state derived from the database (caches, externally stored blobs, queued deletions) tell a rewind-bearing takeover from a harmless resume-in-place. Both outcomes carry the same Generation > 1 signal, so Generation alone cannot separate them — that is exactly the gap this type closes.
type ReplicationStatus ¶ added in v0.8.0
type ReplicationStatus struct {
// Replicating is true only for a writer that runs a replication store
// (BackupTo set). An unreplicated sole writer and every follower report
// false with the remaining fields zero — neither drives replication.
Replicating bool
// LastSyncAt is when the replica last advanced (any successful upload),
// zero if it never has. Its staleness is the primary health signal.
LastSyncAt time.Time
// LocalTXID is the local committed tip litestream replicates from;
// RemoteTXID is the last position confirmed on the replica. RemoteTXID
// below LocalTXID is the unreplicated lag.
LocalTXID uint64
RemoteTXID uint64
// InSync is true when the replica has caught up to the local tip.
InSync bool
// LastError is the most recent sync/checkpoint error, empty when the last
// attempt succeeded. It is the last op's error, not a sticky latch — a
// later success clears it, so pair it with LastSyncAt staleness to judge a
// sustained failure.
LastError string
}
ReplicationStatus is a point-in-time snapshot of the metadata replica's health. It is read from the replication engine's own cached view without any network I/O, so it stays responsive precisely when the bucket is unreachable — which is exactly when a caller asks. LastSyncAt and LastError update on every sync attempt; LocalTXID and RemoteTXID are the last-known local and replica tips, and RemoteTXID is reset to zero after a failed sync, so a stalled replica reads as a large lag rather than a misleading ~0.
type Role ¶
type Role int
Role selects how an instance coordinates single-writer access to an s3:// replica. It has no effect without one — an unreplicated or file:// DB is always the sole writer, since there is no shared WAL to coordinate on.
const ( // RoleAuto acquires the lease if free (writer) or falls back to follower. The // default; with no s3:// BackupTo it is simply the sole writer. RoleAuto Role = iota // RoleWriter requires acquiring the lease; Open fails if it is already held. RoleWriter // RoleFollower never replicates; it serves the restored snapshot read-only and // promotes itself to writer if the lease becomes available. RoleFollower )
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
s3lite
command
Command s3lite is a sqlite3-familiar shell over an s3lite database: it opens a replica URL, restores the local copy, and reads statements from a prompt or a pipe.
|
Command s3lite is a sqlite3-familiar shell over an s3lite database: it opens a replica URL, restores the local copy, and reads statements from a prompt or a pipe. |