Documentation
¶
Overview ¶
Package waltz puts a write-ahead log in front of a Temporal history shard's cold store, so that many mutations are acked into the log and folded into one cold-store transaction. It implements no persistence itself: the log is a wal.Log and the cold store is a cold.Store, both the caller's. One implementation of each ships here, wal/memwal and cold/memcold, and both die with the process.
waltz is developed against go.temporal.io/server v1.29.6 and needs Go 1.26 or newer. The requirement on the server is a floor under minimal version selection and not a pin, so a consumer already on a newer one builds against it with no diagnostic: the WAL record format mirrors v1.29.6's request structs field-for-field, and the mirror's completeness is checked against that version here, never in a consumer's build. A field a newer server adds is a field this codec drops from a write it has already acked.
Compose is the only composition; a new caller's need belongs there as a parameter. Layer.AbstractFactory is the door out: the value a custom main hands to temporal.WithCustomDataStoreFactory, which is the whole of how a server is built over this layer.
The configuration is a `wal` section inside the custom datastore's own options. An absent section is passthrough; a malformed one is a refusal to start ([ADR 0006]).
The layer's lifecycle brackets the server's: it is composed before the server is built, so a failed budget assertion stops the binary, and Layer.Shutdown runs after it has stopped, so the shutdown drain still has a store to write to.
[ADR 0006]: docs/adr/0006-the-wal-configuration-is-a-section-of-the-datastore-options.md
Example ¶
README's "The composition" section carries this function's body as its sample, so an edit to either belongs in both. It has no `// Output:` comment and is therefore compiled and never run, which is the point: it starts a server, and what it is here to catch is a rename that leaves the README's code not compiling.
package main
import (
"context"
"os"
"os/signal"
"syscall"
"time"
"go.temporal.io/server/common/config"
"go.temporal.io/server/common/log"
"go.temporal.io/server/common/persistence/client"
"go.temporal.io/server/temporal"
"github.com/aromanovich/waltz"
"github.com/aromanovich/waltz/cold/memcold"
"github.com/aromanovich/waltz/cycle"
"github.com/aromanovich/waltz/wal/memwal"
)
func main() {
// The cold store. memcold is the one shipped here — Temporal's own SQL
// persistence over a database in this process — and a deployment puts its
// own cold.Store here instead.
store, release, err := memcold.New("active")
if err != nil {
panic(err)
}
defer release()
layer, err := waltz.Compose(
waltz.Backends{
Log: memwal.New(), // your wal.Log; memwal is the one shipped here
Cold: store, // your cold.Store
},
cycle.Fixed(cycle.Defaults()),
waltz.DefaultTaskCategories(),
log.NewCLILogger(),
nil, // the server's metrics handler arrives later, through the factory
)
if err != nil {
panic(err)
}
// base is the persistence factory that owns the cold data — memcold's here,
// your plugin's otherwise.
var base client.AbstractDataStoreFactory = memcold.NewAbstractDataStoreFactory(store)
// The server's own configuration, read the way a stock server reads it. It
// must name a custom datastore in Persistence.DataStores; that naming is the
// whole of how the factory below enters its persistence graph.
cfg, err := config.LoadConfig("development", "config", "")
if err != nil {
panic(err)
}
s, err := temporal.NewServer(
temporal.WithConfig(cfg),
temporal.WithCustomDataStoreFactory(layer.AbstractFactory(base)),
)
if err != nil {
panic(err)
}
if err := s.Start(); err != nil {
panic(err)
}
defer func() {
_ = s.Stop()
// After the server has stopped: the shutdown drain still needs a store
// to write to. `defer release()` above runs after this one, which is the
// order that leaves the drain a database.
// A main that finds entries here logs them before it exits: they are in
// the log for the next owner, and nothing replays them if this node comes
// back without the wal section.
if err := layer.Shutdown(context.Background(), 30*time.Second); err != nil {
panic(err)
}
}()
// Start returns once the services are up, so a main that did not wait here
// would run both deferred shutdowns immediately — and the ordering they are
// written for is the whole point of them.
stop := make(chan os.Signal, 1)
signal.Notify(stop, os.Interrupt, syscall.SIGTERM)
<-stop
}
Output:
Index ¶
- Constants
- Variables
- func AbstractFactory(base client.AbstractDataStoreFactory, opts wrapper.Options) client.AbstractDataStoreFactory
- func NewPolicy(dc *dynamicconfig.Collection, w WAL) cycle.Policy
- type Backends
- type Config
- type Layer
- func (l *Layer) AbstractFactory(base client.AbstractDataStoreFactory) client.AbstractDataStoreFactory
- func (l *Layer) Options() wrapper.Options
- func (l *Layer) Policy() cycle.Policy
- func (l *Layer) RetireShard(shard wal.ShardID, epoch wal.Epoch) bool
- func (l *Layer) ShardStats(shard wal.ShardID) (cycle.Stats, bool)
- func (l *Layer) Shutdown(ctx context.Context, budget time.Duration) error
- func (l *Layer) Totals() cycle.Totals
- type Registry
- type UndrainedError
- type WAL
Examples ¶
Constants ¶
const SectionKey = "wal"
SectionKey is the key the WAL layer's configuration lives under, inside the custom datastore's options. A deployment or a test writing a config writes this key.
Variables ¶
var ( WindowMutations = dynamicconfig.NewGlobalIntSetting( "wal.windowMutations", measured.Mutations, `WindowMutations is the drain trigger in mutations: the window is applied as one transaction when it holds this many. It sits at the measured collapse knee, so lowering it gives collapse away and raising it holds more unapplied work per shard.`) WindowBytes = dynamicconfig.NewGlobalIntSetting( "wal.windowBytes", measured.Bytes, `WindowBytes is the same trigger in encoded bytes, whichever trips first.`) WindowAge = dynamicconfig.NewGlobalDurationSetting( "wal.windowAge", measured.Age, `WindowAge drains a tail nothing is pushing on. It is a recovery-budget choice rather than a measured one: it bounds how long an idle shard's acked-but-unapplied work waits, and therefore what the next owner would replay. A change takes effect within one window, since the age timer is re-armed at every tick — which is also why zero or a negative value is read as the default rather than obeyed: it is the interval that timer is re-armed at.`) TrimEvery = dynamicconfig.NewGlobalIntSetting( "wal.trimEvery", measured.TrimEvery, `TrimEvery is the trim cadence in drains: the log below the applied watermark is deleted every this many drains. A DeleteRange per drain is a transaction per drain for no gain; raising it leaves more of the log behind, which is what a post-mortem reads.`) TrimAfter = dynamicconfig.NewGlobalDurationSetting( "wal.trimAfter", measured.TrimAfter, `TrimAfter is the same cadence in time, whichever trips first. Raising TrimEvery alone does not keep a log: this one fires anyway.`) HardMaxEntries = dynamicconfig.NewGlobalIntSetting( "wal.hardMaxEntries", measured.HardMaxEntries, `HardMaxEntries is invariant I10's bound on one shard's tail in entries: what has been acked and not yet applied. A shard at the bound refuses its writers with ResourceExhausted rather than parking them behind the apply. READ AT START-UP: a change needs the history services restarted. It is read once because it is one half of a bound whose other half is wal.hardMaxBytes — neither unit works alone, and a node honouring one of the two from a different edit than the other is a bound nobody wrote.`) HardMaxBytes = dynamicconfig.NewGlobalIntSetting( "wal.hardMaxBytes", measured.HardMaxBytes, `HardMaxBytes is the same bound in encoded bytes — two units because neither works alone: one workflow near the server's own 8 MB mutable-state limit turns an entries-only bound into a byte budget with no ceiling. It is arithmetic and not taste: the node's tail budget divided by the shards it may own. READ AT START-UP: it is a factor of the product cycle.Config.CheckBudget asserts before the node boots, and a factor that moved afterwards would be that refusal with nothing behind it.`) MaxShards = dynamicconfig.NewGlobalIntSetting( "wal.maxShards", measured.MaxShards, `MaxShards is what one node may own at once — not the cluster's shard count: the default 256 is 128 in steady state, doubled for a failover. It is a premise of the budget assertion and not a limit anything enforces: no acquire is refused past it, deliberately, since a node over its share is a cluster that has just lost hosts. Size it for the worst assignment you intend to survive, not for the steady one. READ AT START-UP.`) TailBudgetBytes = dynamicconfig.NewGlobalIntSetting( "wal.tailBudgetBytes", measured.TailBudgetBytes, `TailBudgetBytes is the RAM one node may hold as unapplied tail. hardMaxBytes × maxShards must fit in it or the node refuses to start — cycle.Config.CheckBudget is run rather than written down, because a doc line does not survive a config edit. READ AT START-UP.`) )
The policy's dynamic half as settings of the server's dynamic config, under the same `wal.` name the section has. Five are read at the decision that consults them; the other four are read once, when the policy is built ([atStart]), and their descriptions say so.
Exported so a process configuring the layer from flags or an environment variable can build a dynamicconfig.StaticClient over these keys rather than a second spelling of them.
Functions ¶
func AbstractFactory ¶
func AbstractFactory(base client.AbstractDataStoreFactory, opts wrapper.Options) client.AbstractDataStoreFactory
AbstractFactory is the value a custom main hands to temporal.WithCustomDataStoreFactory: base — the persistence plugin that owns the cold store — decorated with opts. opts is the whole of the mode, the zero value being passthrough, so this is the door a binary with no `wal` section takes as well.
Metrics are not an argument: the server's handler does not exist yet and arrives at wrapper.AbstractDataStoreFactory.NewFactory, which fills it into the stores and hands it to the layer through wrapper.MetricsSink.
func NewPolicy ¶
func NewPolicy(dc *dynamicconfig.Collection, w WAL) cycle.Policy
NewPolicy is the whole policy a node runs: the section's fields and the start-only settings read once, the live settings read at every decision that consults one.
A nil collection means the settings' own defaults, not a refusal — it is what a process with no dynamic config has. A caller that holds a cycle.Config already passes cycle.Fixed straight to Compose instead.
Types ¶
type Backends ¶
Backends is where a composed layer's bytes go: the log its appends are ordered in, and the cold store one drain becomes a transaction on and an unknown outcome is read back from.
They are Compose's parameter rather than something it builds, and that is the point of the type: this library implements neither. Both are seams the layer is meant to be answerable at without a cluster — wal/memwal is a whole implementation of the WAL contract (ADR 0002), and cold.Store exists so a drain's outcome can be varied without one. A caller that wants the intercept path in process reaches it here rather than by building a second registry, which is the one thing this package asks callers not to do.
type Config ¶
Config is one node's WAL configuration as this layer reads it: whether the layer is on at all, and the section saying how it behaves. The rest of the custom datastore's options belong to the plugin holding the cold store, and this package neither reads nor validates them.
Enabled false is passthrough and not an error; the zero Config is that.
func Parse ¶
Parse reads the `wal` section out of a custom datastore's options. An absent section is passthrough with no error, and naming the section is what turns intercept mode on.
An unknown key inside the section is an error, because a misspelt `snyc` would otherwise be a server silently running the other mode. An unknown key outside the section stays the cold store plugin's business, except one spelt like the section itself ([checkSectionMiscased]).
type Layer ¶
type Layer struct {
// contains filtered or unexported fields
}
Layer is the WAL layer this process owns: the registry the wrapper talks to, over the backends it was composed with.
Exactly one per process, not one per data store factory: a shard's cycle carries its epoch from the acquire through the writes that follow, so two registries would be two windows for one shard, each unaware of the other.
func Compose ¶
func Compose( backends Backends, policy cycle.Policy, categories Registry, logger log.Logger, handler metrics.Handler, ) (*Layer, error)
Compose is the one graph every process running intercept mode builds. Its five varying inputs:
- backends is where the bytes go, and the reason it is a parameter is on Backends;
- policy is required, and is a cycle.Policy: a source read at each decision, so a caller holding one that moves (NewPolicy) and one holding fixed numbers (cycle.Fixed) reach the same composition;
- categories is required: it is what replay decodes a tail with, and cycle.NewManager refuses nil rather than starting a node whose recovery is silently off;
- logger is optional, and nil is a noop: the layer's own warnings — a shutdown drain that did not commit, a trim retried at the next cadence — then have nowhere to go;
- handler is optional, and nil is the production value: the server hands one down through wrapper.MetricsSink after this runs.
It opens nothing, reaches nothing and takes no context: everything that talks to a cluster happens while the backends are built. So whatever they hold stays the caller's, and must outlive the layer, since Layer.Shutdown drains through it.
func (*Layer) AbstractFactory ¶
func (l *Layer) AbstractFactory(base client.AbstractDataStoreFactory) client.AbstractDataStoreFactory
AbstractFactory is AbstractFactory carrying this layer's own options, and the reason it is a method: the pairing of a composition with the factory that carries it was written out at every call site, and a layer composed but never handed to one is a node running passthrough with a `wal` section that says otherwise — which nothing reports, since that is what an empty layer looks like from outside. So the whole of building a server over this library is
temporal.WithCustomDataStoreFactory(layer.AbstractFactory(base))
where base is the plugin whose stores hold the cold data.
func (*Layer) Options ¶
Options is the whole of what the wrapper is composed with: the registry is the shard observer, the write path and the read path at once, and the emitter is the one this layer's cycles record through, so a store built over these options and the cycle behind it report to the same handler — the one wrapper.MetricsSink hands over.
func (*Layer) Policy ¶
Policy is what this layer's cycles read: the section's half and the dynamic config's, as one source. Calling it answers the policy now, which for the live settings need not be what it answered a minute ago.
func (*Layer) RetireShard ¶
RetireShard stops one shard's cycle without draining it, and reports whether the epoch named is the one this node still holds. It is what a process that died leaves behind, which is why it is named apart from Layer.Shutdown: a drain writes, and a kill does not.
epoch is which acquisition is being retired, and a mismatch retires nothing. Without it a late unload — a shard context cleaned up after the shard was reacquired above it — stops the owner that superseded it, since the caller has no other way to say which of the two it means. It is the check cycle.Manager.Write makes for the same reason, in the one other door that names an epoch.
The stopped cycle stays the shard's, and that is not an omission: its tail is acked entries still in the log, so Layer.ShardStats and Layer.Totals go on answering for it off the mirror. Removing it here would answer a shard nobody holds, which is the zero a caller reads as "nothing stranded".
func (*Layer) ShardStats ¶
ShardStats is what one shard's cycle knows about itself, and false if this node holds none. These and not the registry: the registry is also a write path, and wrapper.Options is the one that has an epoch check in front of it.
A value and not the cycle: callers may observe its counters and epoch, but may not gain its Retire and Close controls.
func (*Layer) Shutdown ¶
Shutdown stops the layer: every shard that still holds a window is drained into the cold store, and the log is released.
It must run after the server has stopped: the drain writes to the cold store the mutations of writers the server is shutting down. budget bounds the apply transactions — one per shard, in sequence — and not a trim already in flight, which is waited out on the minute of its own detached context; a drain the budget cuts short leaves a tail, not lost data (invariant I2: it is in the log, acked), which the next owner's replay picks up.
The budget is put on a context detached from the caller's cancellation, and that detach is why this is a door rather than an idiom every caller writes. A shutdown drain runs where a context has just been cancelled — that is what "shutdown" means — and one inheriting that cancellation returns at once, leaving a tail behind and nothing in the log that says so. Given a budget, the error is an *UndrainedError and nothing else: every tail emptied, or these did not. A budget that is not one is refused instead of obeyed: context.WithTimeout reads zero as a deadline already past, where much of Go reads it as no limit, so obeying it would drain nothing and report every shard as holding a tail — the answer a caller passing zero meant least.
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry is the answer to "which registry does this node decode a tail with", and it is constructible only by TaskCategories and DefaultTaskCategories. Upstream's interface is what cycle.Deps takes and must be — mutation.Decode needs it — but a composition that accepted it directly would accept tasks.NewDefaultTaskCategoryRegistry too, which is a second answer: the same set today, and a different one the day archival is configured.
The zero value is refused by cycle.NewManager rather than treated as the default, since a node that decodes with no registry recovers nothing until the first failover.
func DefaultTaskCategories ¶
func DefaultTaskCategories() Registry
DefaultTaskCategories is TaskCategories for a cluster that configures no archival. It answers the same set as tasks.NewDefaultTaskCategoryRegistry and is not that call: a registry a node decodes with is built the way a node builds one, so a caller reaching for the plain default is a second answer to which registry a node has.
func TaskCategories ¶
func TaskCategories(dc *dynamicconfig.Collection, cfg *config.Config) Registry
TaskCategories builds the task category registry the layer decodes an inherited tail with, here rather than from fx because the layer is composed first. It depends on cfg: the archival category exists exactly when archival is enabled, and a default registry would refuse to replay entries carrying archival tasks.
func (Registry) Categories ¶
func (reg Registry) Categories() tasks.TaskCategoryRegistry
Categories is the registry as the layer's own components take it.
type UndrainedError ¶ added in v0.3.0
type UndrainedError struct {
// Shards is every shard whose tail outlived the shutdown.
Shards []cycle.Residue
}
UndrainedError is what Layer.Shutdown answers when a tail outlived it: the shards still holding acked entries no drain applied, and how many each holds.
It reports neither a lost write nor a failed shutdown. Those entries are in the log and a successor's replay is what they are there for, so a node restarting into the same configuration needs nothing from this value. One caller does: whoever is taking the layer *out*. Removing the `wal` section strands exactly these entries and says nothing, because passthrough composes no log and so cannot see that they exist — which makes a shutdown that answered nil the only evidence that removing it is safe.
func (*UndrainedError) Error ¶ added in v0.3.0
func (e *UndrainedError) Error() string
type WAL ¶
type WAL struct {
// Sync drains inside every write and reports the drain's outcome to the
// caller. Off by default, so the shipped configuration is the windowed one
// and a hard restart leaves a tail for the next owner to replay.
Sync bool `mapstructure:"sync"`
// DrainOnRead answers every read from the cold store, draining the window
// first — see [cycle.Config.DrainOnRead] for what it costs.
DrainOnRead bool `mapstructure:"drain_on_read"`
}
WAL is the `wal` section verbatim. The yaml key names are a promise to whoever wrote the file, so they are this package's struct and not cycle.Config with tags on it.
The section is what the layer is; [settings] is how it behaves. A misspelt key here is a refusal to start; a misspelt dynamic-config key is a warning and the default standing silently.
func (WAL) StaticConfig ¶
StaticConfig is cycle.Defaults with each [knobs] row the section set overlaid; an empty section is exactly cycle.Defaults.
It is only the static half of what a node runs — NewPolicy is the whole — so every field [settings] carries is at its default here. Assert a section against this; read a live watermark off NewPolicy.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package apply is what a drain's outcome means: the class the cycle branches on, the errors that carry it, and the readback that names which row diverged.
|
Package apply is what a drain's outcome means: the class the cycle branches on, the errors that carry it, and the readback that names which row diverged. |
|
Package baserow is the cold store's two mutable-state reads, as the write path needs them: one run's row, and a workflow's current-execution row with the last_write_version beside it.
|
Package baserow is the cold store's two mutable-state reads, as the write path needs them: one run's row, and a workflow's current-execution row with the last_write_version beside it. |
|
Package cold is the cold store contract: the seam a drain lands on, as wal is the seam an ack lands on.
|
Package cold is the cold store contract: the seam a drain lands on, as wal is the seam an ack lands on. |
|
memcold
Package memcold is the one cold store this repository ships: Temporal's own SQL persistence, running over a SQLite database that lives in this process and dies with it.
|
Package memcold is the one cold store this repository ships: Temporal's own SQL persistence, running over a SQLite database that lives in this process and dies with it. |
|
Package cycle is the layer's state machine: one goroutine per (shard, epoch) owns the accumulator, the drain, the apply transaction, the trim cadence and the four reads, so that "who is touching this shard" has one answer.
|
Package cycle is the layer's state machine: one goroutine per (shard, epoch) owns the accumulator, the drain, the apply transaction, the trim cadence and the four reads, so that "who is touching this shard" has one answer. |
|
tailstate
Package tailstate is the tail's arithmetic, in one place: everything I10 bounds — unsettled acked entries, the bytes they hold, how far behind the cold store's watermark is — is counted here and nowhere else.
|
Package tailstate is the tail's arithmetic, in one place: everything I10 bounds — unsettled acked entries, the bytes they hold, how far behind the cold store's watermark is — is counted here and nowhere else. |
|
trim
Package trim is the lazy deletion of WAL entries the cold store already holds: the cadence that decides when, the one trim in flight, and the two counters that tell a cadence that fired from a trim that reached the log.
|
Package trim is the lazy deletion of WAL entries the cold store already holds: the cadence that decides when, the one trim in flight, and the two counters that tell a cadence that fired from a trim that reached the log. |
|
window
Package window is the size and age of what a cycle has folded since its last drain.
|
Package window is the size and age of what a cycle has folded since its last drain. |
|
Package fold is the tail's compaction: it accumulates the mutations of a window and emits, per dirty workflow, one merged request plus the assertions that request stands on.
|
Package fold is the tail's compaction: it accumulates the mutations of a window and emits, per dirty workflow, one merged request plus the assertions that request stands on. |
|
internal
|
|
|
verify/acceptance
Package acceptance is the volume half of the evidence: a generated stream long enough that what the layer did to it is a measurement rather than an anecdote, driven two ways.
|
Package acceptance is the volume half of the evidence: a generated stream long enough that what the layer did to it is a measurement rather than an anecdote, driven two ways. |
|
verify/basetest
Package basetest is the pre-window rows in memory.
|
Package basetest is the pre-window rows in memory. |
|
verify/checker
Package checker is what a driver writes down about the calls it made, and the vocabulary for reading that record back.
|
Package checker is what a driver writes down about the calls it made, and the vocabulary for reading that record back. |
|
verify/coldtasks
Package coldtasks is a model of the store below, for the merged task read's tests.
|
Package coldtasks is a model of the store below, for the merged task read's tests. |
|
verify/coldtest
Package coldtest is the cold store a drain lands in, in memory.
|
Package coldtest is the cold store a drain lands in, in memory. |
|
verify/drive
Package drive is the half of a run that *writes*: one mutation into one ExecutionStore call, a shard taken so those calls have an epoch to carry, and Stream — a generated stream behind the codec, for the callers that want what came back out of a log.
|
Package drive is the half of a run that *writes*: one mutation into one ExecutionStore call, a shard taken so those calls have an epoch to carry, and Stream — a generated stream behind the codec, for the callers that want what came back out of a log. |
|
verify/e2e
Package e2e is the whole of waltz under a running Temporal server: the four services in this process, over the cold store this repository ships, with a real workflow driven through the frontend by the SDK.
|
Package e2e is the whole of waltz under a running Temporal server: the four services in this process, over the cold store this repository ships, with a real workflow driven through the frontend by the SDK. |
|
verify/foldrun
Package foldrun drives mutations through one accumulator the way a cycle does — window by window, with fold's refusal recovery — and counts what the folding did.
|
Package foldrun drives mutations through one accumulator the way a cycle does — window by window, with fold's refusal recovery — and counts what the folding did. |
|
verify/guard
Package guard holds the tests whose job is to fail when a decision is reverted, rather than when the code is wrong.
|
Package guard holds the tests whose job is to fail when a decision is reverted, rather than when the code is wrong. |
|
verify/mutbuild
Package mutbuild builds one mutation of a given shape, well-formed, with no cluster: the request Temporal's ExecutionManager hands the store, as a unit test needs it — one at a time, with the ids named rather than drawn.
|
Package mutbuild builds one mutation of a given shape, well-formed, with no cluster: the request Temporal's ExecutionManager hands the store, as a unit test needs it — one at a time, with the ids named rather than drawn. |
|
verify/mutgen
Package mutgen generates valid, deterministic mutation streams.
|
Package mutgen generates valid, deterministic mutation streams. |
|
verify/witness
Package witness is the WAL layer's witness as one judged module: the claims a run over the layer must be able to make about the layer's own counters, stated once and checked the same way by every run that composes one.
|
Package witness is the WAL layer's witness as one judged module: the claims a run over the layer must be able to make about the layer's own counters, stated once and checked the same way by every run that composes one. |
|
Package mutation is the WAL's record format: it turns one ExecutionStore write request into the opaque bytes a [wal.Entry] carries, and back.
|
Package mutation is the WAL's record format: it turns one ExecutionStore write request into the opaque bytes a [wal.Entry] carries, and back. |
|
Package wal is the write-ahead log contract the layer in front of a Temporal history shard's cold store is built on.
|
Package wal is the write-ahead log contract the layer in front of a Temporal history shard's cold store is built on. |
|
memwal
Package memwal is the wal contract ([ADR 0002]) in process memory, and the only log shipped here.
|
Package memwal is the wal contract ([ADR 0002]) in process memory, and the only log shipped here. |
|
waltest
Package waltest is the conformance suite for the wal contract, and beside it Faulty: a backend that keeps the contract, wrapped so that a chosen call fails.
|
Package waltest is the conformance suite for the wal contract, and beside it Faulty: a backend that keeps the contract, wrapped so that a chosen call fails. |
|
Package walmetrics emits the WAL layer's numbers through the server's own metrics.Handler, which arrives after the layer is built; Emitter.Use is that handover, late because the handler is taken by the first service to build persistence and the layer is composed before the server is.
|
Package walmetrics emits the WAL layer's numbers through the server's own metrics.Handler, which arrives after the layer is built; Emitter.Use is that handover, late because the handler is taken by the first service to build persistence and the layer is composed before the server is. |
|
Package wrapper is the WAL layer's seam into a running temporal-server: a decorator over the base plugin's DataStoreFactory whose ExecutionStore and ShardStore the history service talks to in place of the plugin's own.
|
Package wrapper is the WAL layer's seam into a running temporal-server: a decorator over the base plugin's DataStoreFactory whose ExecutionStore and ShardStore the history service talks to in place of the plugin's own. |