Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

23 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

AnheBridgeDB

Evented KV store with version history, rollback, DSL, and rule-driven state transitions.

AnheBridgeDB

This project is licensed under the Apache License 2.0.

Timeline. Diff. Rollback. Auto-transition.

Website · Docs · Contact me with Gmail

Community Projects

Ai tourism planning

Mood Polymarket(NEW🔥)

Overview

Release Notes

v1.0.3

  • Added a separated dashboard/ frontend (React + Vite) with Docker-style layout.
  • Added pages: Connection, Workbench, Data Explorer, Metrics, Import / Export.
  • Data Explorer timeline now shows old_value for rollback inspection.
  • Metrics page now supports compact per-metric mini charts and wildcard metric selectors:
    • *cpu, *mem, *io, *lat, *heap_alloc_bytes, *gc_pause_total_ms.
  • Added backend runtime metrics for observability:
    • CPU: cpu_percent, cpu_cores_used
    • Memory: memory_alloc_mb, memory_inuse_mb, memory_sys_mb, memory_heap_usage_percent
    • IO: io_read_ops_per_sec, io_write_ops_per_sec, io_write_bytes_per_sec, io_write_mb_per_sec
    • Avg latency: get_avg_ms, set_avg_ms, append_avg_ms, timeline_avg_ms
  • Security hardening for frontend-backend connectivity:
    • Removed WS query token auth (?token=); switched to WS first-frame auth.
    • Session token storage changed from localStorage to sessionStorage with expiry cleanup.
    • CORS changed to allowlist mode (localhost:5173, 127.0.0.1:5173, plus ANHEBRIDGE_CORS_ORIGINS).
    • Import/Export page adds file type/size checks, confirmation, and duplicate-submit guard.

Contact

AnheBridgeDB v1 covers the current MVP path described in docs/:

  • append-only segmented event log with compact per-event HMAC auth tag
  • WAL + CRC-protected segment records + checkpoint-based recovery
  • current-state index + historical timeline reconstruction
  • GET key, GET key AT ts, GET key ALLTIME, GET key ALLTIME WITH DIFF
  • ROLLBACK key VERSION:n and ROLLBACK key VERSION:LAST
  • CHECK key ALLTIME tamper tracing
  • DSL parser and execution endpoint
  • rule engine with CREATE RULE ... IF UNCHANGED FOR ... THEN TRANSITION
  • bucket-style persisted task queue and scheduler-driven auto transition
  • snapshot + restart recovery for state, rules, and pending tasks

Run

go run ./cmd/server -addr :8080 -data ./data -scheduler-interval 1s

DSL Examples

SET userBalance:0xabc 100;
SET userBalance:0xabc -10 SEND i=tx-001;
SET userBalance:0xabc 10 RECEIVED i=tx-001;
ASET balance:123 -10 SEND i=tx-002, balance:456 10 RECEIVED i=tx-002;
GET userBalance:0xabc;
AGET userBalance:0xabc balance:123 balance:456;
GET userBalance:0xabc AT '2026-03-13T10:00:00Z';
GET userBalance:0xabc LAST;
GET userBalance:0xabc LAST -1 -1;
GET userBalance:0xabc ALLTIME;
GET userBalance:0xabc ALLTIME WITH DIFF;
GET userBalance:0xabc ALLTIME LIMIT 100 BEFORE VERSION:2000 AFTER VERSION:1500;
SEARCH EVENTS KEY:userBalance:0xabc NAME:SEND DESC LIMIT:20 PAGE:1;
SEARCH EVENTS I:tx-001;
SEARCH EVENTS KEY:userBalance:0xabc NAME:SEND WITH SAME I;
ROLLBACK userBalance:0xabc VERSION:5;
ROLLBACK userBalance:0xabc VERSION:LAST;
CHECK userBalance:0xabc ALLTIME;
CHECK userBalance:0xabc ALLTIME LIMIT 1000;
SNAPSHOT;
VERIFY STORAGE;
COMPACT STORAGE;

CREATE RULE auto_timeout_order
ON PATTERN "OrderStatus:*:wait_for_paid"
IF UNCHANGED FOR 15m
THEN TRANSITION TO "OrderStatus:{id}:timeout";

SHOW RULES;
SHOW RULE auto_timeout_order;
RUN SCHEDULER;

Command Catalog

Write Commands

SET key value;
SET key delta EVENT;
SET key value EVENT i=business-id;
ASET key value, key value EVENT, key value EVENT i=business-id;
DELETE key;

Read Commands

GET key;
GET key RAW;
GET key EXPAND ONLY /checkin,/owner,/rewards;
GET key EXPAND EXCEPT /chests;
AGET key1 key2 key3;
AGET key1 key2 key3 RAW;
AGET key1 key2 key3 EXPAND ONLY /checkin;
GET key AT '2026-03-15T04:00:00Z';
GET key AT '2026-03-15T04:00:00Z' EXPAND ONLY /checkin;
GET key LAST;
GET key LAST EXPAND EXCEPT /chests;
GET key LAST -1 -1;
GET key ALLTIME;
GET key ALLTIME WITH DIFF;
GET key ALLTIME LIMIT 100 BEFORE VERSION:10 AFTER VERSION:5;

Super Value (Read-Only View)

super value means a string that starts with * and points to another key.

Example:

SET uid-hksn10 {"uid":"uid-hksn10","owner":"0xabc","activated":true};
SET uid-asan10 {"uid":"uid-asan10","owner":"0xdef","activated":false};
SET usersVape ["*uid-hksn10","*uid-asan10"];
GET usersVape;
GET usersVape RAW;
GET usersVape EXPAND ONLY /0;
GET usersVape EXPAND EXCEPT /1;
AGET usersVape uid-hksn10 RAW;

Behavior:

  • GET/AGET expands super values by default.
  • GET RAW and AGET ... RAW return stored raw value without expansion.
  • EXPAND ONLY resolves only the listed JSON Pointer paths; every other *key remains unchanged and is not read.
  • EXPAND EXCEPT resolves all super values except the listed paths.
  • multiple paths are comma-separated, for example EXPAND ONLY /checkin,/owner,/rewards.
  • array indexes and wildcards are supported, for example /chests/0 and /chests/*.
  • expansion is read-only: no write-through to referenced keys.
  • circular references are rejected with super_value_cycle_detected.
  • depth/node/fanout are bounded by performance config.
  • missing referenced key is returned as found=false entry, not a server crash.

Search Commands

SEARCH EVENTS KEY:balance:1234 NAME:SEND;
SEARCH EVENTS KEY:balance:1234 NAME:SEND DESC LIMIT:20 PAGE:1;
SEARCH EVENTS I:abcd;
SEARCH EVENTS KEY:balance:1234 NAME:SEND WITH SAME I;

Search behavior:

  • NAME: filters by event name using the secondary event-name index
  • I: filters by idempotency_key
  • WITH SAME I expands the matched result set to every event that shares the same business idempotency key
  • PAGE:n performs direct page slicing on the indexed result set

Recovery And Integrity Commands

ROLLBACK key VERSION:5;
ROLLBACK key VERSION:LAST;
CHECK key ALLTIME;
CHECK key ALLTIME LIMIT 1000;
SNAPSHOT;
VERIFY STORAGE;
COMPACT STORAGE;

Rule Commands

CREATE RULE auto_timeout_order
ON PATTERN "OrderStatus:*:wait_for_paid"
IF UNCHANGED FOR 15m
THEN TRANSITION TO "OrderStatus:{id}:timeout";

SHOW RULES;
SHOW RULE auto_timeout_order;
RUN SCHEDULER;

Segment rolling is configurable in config.json:

{
  "auth": {
    "enabled": false,
    "token_ttl_minutes": 1440,
    "users": [
      {
        "username": "admin",
        "password": "change-me"
      }
    ]
  },
  "storage": {
    "strict_recovery": false,
    "segment": {
      "max_bytes": 67108864,
      "max_records": 30000
    }
  }
}

Both thresholds can be enabled together. A segment rolls when either limit is exceeded.

Automatic Backup And Health Inspection

Automatic backup is opt-in and disabled by default. When enabled, the server rotates the active segment at a durable event boundary and runs backup work in background goroutines. Normal GET/SET/ASET processing does not wait for R2 upload, Lark, or health inspection.

incremental is the recommended mode. It runs every two hours, uploads each immutable event segment only once using a content-addressed R2 object key, saves mutable rule/task/checkpoint files for the current backup, then atomically advances incremental/latest.json. A failed run never replaces the last valid restore pointer.

{
  "backup": {
    "enabled": true,
    "mode": "incremental",
    "run_on_start": true,
    "interval_seconds": 7200,
    "timeout_seconds": 7200,
    "spool_dir": "./data/backups",
    "keep_local": true,
    "local_retention_count": 3,
    "upload": {
      "enabled": true,
      "endpoint": "https://<account-id>.r2.cloudflarestorage.com",
      "bucket": "anhe-backups",
      "prefix": "production",
      "access_key_env": "ANHEBRIDGE_R2_ACCESS_KEY_ID",
      "secret_access_key_env": "ANHEBRIDGE_R2_SECRET_ACCESS_KEY",
      "max_object_bytes": 5368709120,
      "retry_count": 3,
      "retry_backoff_ms": 1000
    },
    "lark": {
      "enabled": true,
      "webhook_env": "ANHEBRIDGE_LARK_WEBHOOK",
      "secret_env": "ANHEBRIDGE_LARK_SECRET",
      "notify_success": true
    },
    "monitor": {
      "enabled": true,
      "interval_seconds": 60,
      "verify_interval_seconds": 86400,
      "consecutive_breaches": 3,
      "alert_cooldown_seconds": 1800,
      "heap_alloc_max_bytes": 8589934592,
      "pending_tasks_max": 100000,
      "overdue_tasks_max": 1000,
      "write_queue_max": 800,
      "append_p95_max_ms": 100,
      "get_p95_max_ms": 50
    }
  }
}

Secrets are read from environment variables and are never persisted in the backup:

export ANHEBRIDGE_R2_ACCESS_KEY_ID='...'
export ANHEBRIDGE_R2_SECRET_ACCESS_KEY='...'
export ANHEBRIDGE_LARK_WEBHOOK='https://open.feishu.cn/open-apis/bot/v2/hook/...'
export ANHEBRIDGE_LARK_SECRET='...'

Threshold value 0 disables that individual alert. Alerts require consecutive_breaches consecutive samples and use alert_cooldown_seconds to prevent notification storms. Full HMAC-chain verification runs separately at verify_interval_seconds; set it to 0 to disable deep verification.

Incremental R2 layout:

  • incremental/objects/<sha-prefix>/<sha256>.anhe: immutable segment objects shared by backup generations
  • incremental/backups/<backup-id>/files/...: mutable metadata captured for one generation
  • incremental/manifests/<backup-id>.json: HMAC-signed restore manifest with size and SHA-256 for every file
  • incremental/latest.json: HMAC-signed pointer updated only after all files and the manifest are durable

The local incremental-state.json avoids hashing stable segments on every run. If it is lost, content-addressed R2 HEAD checks prevent already uploaded segment bodies from being uploaded again.

Archive compaction output is capped at 4 GiB and already compacted archives are not merged again, keeping each incremental object below the default 5 GiB single-upload ceiling.

If mode is full, or R2 upload is disabled, the existing local .tar.gz full-backup path remains available.

Restore From R2

Stop the database process before restoring. Restore defaults to the latest completed incremental backup:

go run ./cmd/restore \
  -config ./config/config.json \
  -data ./data-restored \
  -manifest latest \
  -workers 4

Restore a specific generation by using the manifest object key printed in backup logs:

go run ./cmd/restore \
  -config ./config/config.json \
  -data ./data-restored \
  -manifest 'production/incremental/manifests/20260729T120000.000000000Z-e12345.json'

The restore command:

  • downloads into a sibling staging directory
  • rejects absolute or parent-traversal paths
  • verifies every object size and SHA-256
  • rebuilds position and latest-key indexes
  • opens the staged database with strict HMAC recovery by default
  • atomically renames the staged directory into place only after validation

The target must be empty unless -force is passed. With -force, the existing directory is preserved as <data>.pre-restore-<UTC> rather than deleted. Set the same ANHEBRIDGE_HMAC_KEY used by the source database before running verified restore.

The Docker image also includes /usr/local/bin/anhe-restore. Run it from a one-off container with the database service stopped and the data/config volumes mounted; the normal image entrypoint remains anhe-server.

The archive intentionally rebuilds volatile indexes during import rather than copying indexes while they are changing. An R2 or Lark configuration error does not terminate the database; local backup remains active and the failure is recorded in logs and metrics.

To enable auth for dashboard and CLI, set:

{
  "auth": {
    "enabled": true,
    "token_ttl_minutes": 1440,
    "users": [
      {
        "username": "admin",
        "password": "change-me"
      }
    ]
  }
}

You can also set ANHEBRIDGE_AUTH_SECRET in the environment to override the token signing secret.

Performance controls:

{
  "performance": {
    "event_cache_max_items": 50000,
    "event_cache_max_bytes": 134217728,
    "key_index_compact_ops": 10000,
    "scheduler_workers": 4,
    "metrics_sample_interval_seconds": 10,
    "snapshot_batch_size": 10000,
    "timeline_default_limit": 1000,
    "super_value_max_depth": 5,
    "super_value_max_fanout": 200,
    "super_value_max_nodes": 1000
  }
}

metrics_sample_interval_seconds is clamped to >= 10s so metrics history sampling stays off the hot path.

Client:

go run ./cmd/client -addr http://127.0.0.1:8080

CLI login:

anhe> login
username: admin
password: change-me

The CLI stores the bearer token in ~/.anhebridge_cli_auth.json and automatically attaches it to future requests for the same server address.

Single command:

go run ./cmd/client -addr http://127.0.0.1:8080 -e 'GET user:1 ALLTIME WITH DIFF;'

Pipe input:

printf 'SHOW RULES;\n' | go run ./cmd/client -addr http://127.0.0.1:8080

Stress test:

go run ./cmd/stress -addr http://127.0.0.1:8080 -mode rw -duration 60s -workers 16 -batch 64 -keys 20000
go run ./cmd/stress -addr http://127.0.0.1:8080 -mode orders -orders 20000 -order-batch 200 -workers 8 -rule-delay 15s
go run ./cmd/stress -addr http://127.0.0.1:8080 -mode mixed -duration 90s -workers 16 -orders 10000

For an M4 / 16GB machine, a practical starting point is:

go run ./cmd/server -addr :8080 -data ./data -scheduler-interval 500ms
go run ./cmd/stress -addr http://127.0.0.1:8080 -mode mixed -duration 120s -workers 16 -batch 128 -keys 50000 -orders 20000 -order-batch 200 -rule-delay 15s

API

When auth is enabled, all endpoints except /healthz and /auth/login require:

-H 'Authorization: Bearer <token>'

Login:

curl -X POST http://localhost:8080/auth/login \
  -H 'Content-Type: application/json' \
  -d '{"username":"admin","password":"change-me"}'

Write:

curl -X PUT http://localhost:8080/kv/user:1 \
  -H 'Content-Type: application/json' \
  -d '{"name":"tom","state":"active"}'

Read latest:

curl http://localhost:8080/kv/user:1

Read at time:

curl 'http://localhost:8080/kv/user:1?time=2026-03-14T03:00:00Z'

Timeline:

curl http://localhost:8080/kv/user:1/timeline

Timeline with diff:

curl 'http://localhost:8080/kv/user:1/timeline?diff=true'

Timeline window:

curl 'http://localhost:8080/kv/user:1/timeline?limit=100&before_version=2000&after_version=1500'

Delete:

curl -X DELETE http://localhost:8080/kv/user:1

Create snapshot:

curl -X POST http://localhost:8080/snapshot

Stats:

curl http://localhost:8080/stats

Metrics:

curl http://localhost:8080/metrics

Metrics history OHLC:

curl "http://localhost:8080/metrics/history?metrics=heap_alloc_bytes,pending_tasks&from=2026-03-15T12:00:00Z&to=2026-03-15T13:00:00Z&bucket=1m"

Debug perf:

curl http://localhost:8080/debug/perf

Verify storage:

curl -X POST http://localhost:8080/admin/verify

Compact storage:

curl -X POST http://localhost:8080/admin/compact

Export full backup tarball:

curl http://localhost:8080/admin/export?scope=full -o anhebridgedb-full.tar.gz

Export selected segments tarball:

curl "http://localhost:8080/admin/export?scope=segments&segments=segment_000001.anhe,segment_900001.anhe" -o anhebridgedb-segments.tar.gz

Import backup tarball:

curl -X POST "http://localhost:8080/admin/import?scope=full" \
  -H 'Content-Type: application/gzip' \
  --data-binary @anhebridgedb-full.tar.gz

Import selected segments tarball:

curl -X POST "http://localhost:8080/admin/import?scope=segments" \
  -H 'Content-Type: application/gzip' \
  --data-binary @anhebridgedb-segments.tar.gz

WebSocket:

ws://localhost:8080/ws?token=<bearer-token>

Example messages:

{"type":"exec","request_id":"1","query":"SHOW METRICS;"}
{"type":"subscribe","topic":"metrics","interval_seconds":10}
{"type":"subscribe","topic":"stats","interval_seconds":30}
{"type":"unsubscribe","topic":"metrics"}
{"type":"ping"}

Create rule:

curl -X POST http://localhost:8080/rules \
  -H 'Content-Type: application/json' \
  -d '{
    "id":"auto_timeout_order",
    "pattern":"OrderStatus:*:wait_for_paid",
    "delay":"15m",
    "target":"OrderStatus:{id}:timeout"
  }'

Execute DSL:

curl -X POST http://localhost:8080/dsl \
  -H 'Content-Type: application/json' \
  -d '{
    "query":"SET OrderStatus:123 {\"state\":\"wait_for_paid\",\"amount\":100}; GET OrderStatus:123 ALLTIME WITH DIFF;"
  }'

Data Layout

data/
  log/segment_000001.anhe
  log/segment_000002.anhe
  log/segment_000001.manifest.json
  log/segment_000002.manifest.json
  log/events.wal
  log/checkpoint.json
  log/archive/segment_000001.anhe
  log/archive/segment_000001.manifest.json
  log/archive/archive.manifest.json
  log/archive/segment_900001.anhe
  log/archive/segment_900001.manifest.json
  index/positions.anhe
  index/latest.snapshot.anhe
  index/latest.delta.anhe
  snapshot/latest.json
  system/rules.json
  system/task_buckets/202603140445.anhe
  system/task_buckets/202603140446.anhe

Storage Model

AnheBridgeDB stores data as an append-only event stream plus a small set of persisted indexes:

  • log/segment_xxxxxx.anhe
    • append-only event segments
    • each event record is wrapped with CRC32
    • each event stores event_id, key, operation, old_value, new_value, prev_version_offset, compact auth_tag, and metadata
  • log/segment_xxxxxx.manifest.json
    • per-segment manifest
    • stores segment-level counters, time range, event-id range, first/last auth tags, and rolling manifest auth
  • log/events.wal
    • write-ahead log for crash recovery before segment/index/manifest flush completes
  • log/checkpoint.json
    • stores next_id, current_segment, and latest auth-chain head
  • log/archive/
    • cold segments moved out of the active write path while remaining readable for history queries
    • archive compaction can merge many cold segments into a compacted archive segment
    • archive.manifest.json records archived and compacted segment lineage
  • index/positions.anhe
    • persistent position index
    • maps event_id -> segment + file offset + size
  • index/latest.snapshot.anhe + index/latest.delta.anhe
    • compact snapshot plus append-only delta log
    • maps key -> latest_event
  • snapshot/latest.json
    • full state snapshot for restart recovery
  • system/rules.json
    • rule definitions and counters
  • system/task_buckets/*.anhe
    • minute-bucket task files for delayed transitions

Write Path

flowchart LR
  A["DSL / HTTP Write"] --> B["Lock Key Shard"]
  B --> C["Build Event"]
  C --> D["Write WAL"]
  D --> E["Append To Segment"]
  E --> F["Append Position Index"]
  F --> G["Update Manifest + Checkpoint"]
  G --> H["Clear WAL"]
  H --> I["Update latest index In Memory"]
  I --> J["Apply Current State"]
  J --> K["Rule Match"]
  K --> L["Persist Task Bucket If Needed"]
Loading

Recovery Path

flowchart LR
  A["Load Snapshot"] --> B["Load Rules"]
  B --> C["Load Task Bucket Metadata"]
  C --> D["Load positions.anhe"]
  D --> E["Load latest snapshot + delta"]
  E --> F["Replay WAL If Needed"]
  F --> G["Replay Only Events After snapshot.LastEventID"]
  G --> H["Warm Bounded Event Cache On Demand"]
Loading

Archive Compaction Path

flowchart LR
  A["Snapshot Establishes Safe Cutoff"] --> B["Archive Cold Active Segments"]
  B --> C["Load Archived Segment Manifests"]
  C --> D["Merge Archived Events Into Compacted Archive Segment"]
  D --> E["Rewrite positions.anhe To New Segment Offsets"]
  E --> F["Write Compacted Archive Manifest"]
  F --> G["Update archive.manifest.json"]
  G --> H["Remove Old Archived Segment Files"]
Loading

Locking Model

The runtime uses sharded key locks instead of a single global KV lock:

  • the same key is always serialized
  • different keys can be read and written in parallel
  • batch operations lock involved key shards in sorted order to avoid deadlocks
  • event log append is serialized internally so event_id ordering stays stable
  • metadata persistence such as latest-index delta flush is serialized separately

Lock Flow

flowchart LR
  A["Request For Key"] --> B["Hash Key To Shard"]
  B --> C["Acquire Shard Lock"]
  C --> D["Read / Write State"]
  D --> E["Persist Event Or Read Result"]
  E --> F["Release Shard Lock"]
Loading

Idempotency Model

The rule engine is designed around version checks, not blind timers.

  • each scheduled task stores:
    • entity_key
    • expected_version
    • expected_state
    • cause_event_id
  • when the scheduler executes a task, it first validates that the current record still matches the captured version and state
  • if the key has changed since task creation, the task is marked skipped
  • if the key is unchanged, a new AUTO_TRANSITION event is appended

This makes delayed transitions naturally idempotent at the state-machine level.

Tamper Detection

Each event is written with:

  • auth_tag
    • compact HMAC-SHA256 tag
    • calculated from the current event payload plus the previous global event's auth_tag

This forms a chained HMAC proof with only one extra field per event. If someone edits an old event directly on disk without the signing secret, CHECK key ALLTIME will surface the mismatch.

CRC adds a second protection layer:

  • direct record corruption or partial writes are detected before event decoding
  • strict recovery mode can refuse startup on CRC / manifest / auth-chain mismatch

Segment manifests add another verification layer:

  • first / last event id
  • first / last auth_tag
  • record count
  • rolling manifest_auth

Tamper Check Flow

flowchart LR
  A["CHECK key ALLTIME"] --> B["Walk Key Version Chain"]
  B --> C["Recompute auth_tag With Previous Global auth_tag"]
  C --> D["Compare Stored auth_tag"]
  D --> E["Validate Related Segment Manifest"]
  E --> F["Return Issues Or OK"]
Loading

Idempotent Task Execution

flowchart LR
  A["Pending Task"] --> B["Load Current Record"]
  B --> C{"Version / State Match?"}
  C -- "No" --> D["Mark Skipped"]
  C -- "Yes" --> E["Append AUTO_TRANSITION Event"]
  E --> F["Mark Done"]
Loading

Read Path

Reads use different strategies depending on the query type:

  • GET key
    • returns current state directly from the in-memory state map
  • GET key LAST
    • starts from latest_event
    • follows prev_version_offset
  • GET key ALLTIME
    • walks the same version chain until the first version
  • GET key AT ts
    • walks the per-key version chain backward until it finds the first event at or before the target timestamp
  • if an event body is not already cached, it is loaded from segment + offset through positions.anhe
  • event bodies live in a bounded cache instead of unbounded in-memory growth

Read Flow

flowchart LR
  A["GET / GET LAST / GET AT / ALLTIME"] --> B["Resolve latest_event For Key"]
  B --> C{"Current Value Query?"}
  C -- "Yes" --> D["Return In-Memory State"]
  C -- "No" --> E["Follow prev_version_offset Chain"]
  E --> F["Load Event By segment + offset"]
  F --> G["Return Historical Record / Timeline"]
Loading

Rollback Model

Rollback never deletes history. It appends a new event that points back to a previous version.

  • ROLLBACK key VERSION:n
    • finds the target event
    • appends a new ROLLBACK event
    • sets new_value to the target version's value
  • ROLLBACK key VERSION:LAST
    • resolves the previous version automatically
    • then appends the same kind of rollback event

This keeps the audit trail complete and makes rollback itself queryable in timeline history.

Rollback Flow

flowchart LR
  A["ROLLBACK key VERSION:n / LAST"] --> B["Resolve Target Version"]
  B --> C["Build ROLLBACK Event"]
  C --> D["Set old_value = Current"]
  D --> E["Set new_value = Target"]
  E --> F["Append Event"]
  F --> G["Update Current State"]
Loading

Rule Engine

Rules are pattern-based delayed transitions.

Example:

CREATE RULE auto_timeout_order
ON PATTERN "OrderStatus:*:wait_for_paid"
IF UNCHANGED FOR 15m
THEN TRANSITION TO "OrderStatus:{id}:timeout";

Internal flow:

  • a write updates a key such as OrderStatus:123
  • the rule engine extracts the current state from the event value
  • if the pattern matches, it creates a delayed task in the target minute bucket
  • the scheduler later loads due buckets and validates the task
  • if still valid, worker threads append an AUTO_TRANSITION event while same-key writes remain serialized

Observability

The server now exposes:

  • /stats
    • logical state overview
  • /metrics
    • heap usage
    • GC pause totals
    • event cache size and hit ratio
    • get/get-at/timeline/check latency percentiles
    • set/aset/append/latest-index-flush latency percentiles
    • scheduler backlog and overdue task counts
    • segment/index/snapshot/manifest sizes
    • recovery timings
    • append / latest-index flush timings
    • automatic backup run/failure totals, last success time, duration, size
    • automatic monitor alert totals
  • /debug/perf
    • bucket backlog detail
    • segment manifests
    • event cache state
    • consolidated perf snapshot
  • /admin/verify
    • validates manifests
    • validates chained HMAC auth tags
    • surfaces storage integrity issues before they become silent corruption
  • /admin/compact
    • compacts latest index delta into snapshot
    • clears old fully-terminal task buckets
    • archives cold segments covered by snapshot state
  • /admin/export
    • streams a .tar.gz backup package
    • supports scope=full
    • supports scope=segments&segments=segment_000001.anhe,...
  • /admin/import
    • imports a .tar.gz backup package
    • rebuilds positions.anhe and latest key index after import
    • returns restart_required: true because in-memory state is not hot-reloaded

Rule Match And Transition Flow

flowchart LR
  A["State Write Event"] --> B["Extract state"]
  B --> C{"Rule Pattern Match?"}
  C -- "No" --> D["Stop"]
  C -- "Yes" --> E["Create Pending Task In Minute Bucket"]
  E --> F["Scheduler Loads Due Bucket"]
  F --> G{"Version / State Still Match?"}
  G -- "No" --> H["Skip Task"]
  G -- "Yes" --> I["Append AUTO_TRANSITION Event"]
Loading

About

Time-Travel Event-Sourced KV Database with Stateful Auto-Transition

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages