GitXplorerGitXplorer
e

bitempura

public
10 stars
0 forks
1 issues

Commits

List of commits on branch main.
Verified
9fda2cb5bc8016439d76ee2ee698dbc1807e9d79

add bitempura-viz img to README (#32)

eelh committed 6 months ago
Verified
b9592b0adb9625ffccf4b1572df5c4bf814b9f09

Bump gopkg.in/yaml.v3 from 3.0.0-20200313102051-9f266ea9e77c to 3.0.0 (#31)

ddependabot[bot] committed 6 months ago
Unverified
b676631cf157b958c78ed0e212fdabb2dfa44855

remove brittle link in test

eelh committed 2 years ago
Unverified
5edf723fdb6af3e0a9a2cd48c0246b24f6511905

update test desc

eelh committed 2 years ago
Unverified
205fa85b6346f4206165cd8a7fa52702c9adc065

update README. abandoned SQL

eelh committed 2 years ago
Verified
8ae43def54638468e06d31e4ea04b52ea52bc400

Add test descriptions (#30)

eelh committed 2 years ago

README

The README file for this repository.

bitempura ⌛ → ⏳!

Go Reference Build Status Go Report Card

Bitempura.DB is a simple, bitemporal key-value database.

Bitempura provides an in-memory, concurrency-safe reference implementation.

  • Use a developer-friendly API that is as simple or complex as your use case. See "Design" section.
  • Visualize and debug the 2D valid time and transaction time history with bitempura-viz 🔮.
  • Run on the web with compilation to WebAssembly 🧩.

An experimental, SQL-querying implementation was promptly abandoned. Check out XTDB :)

bitempura


Bitemporality

Temporal databases model time as a core aspect of storing and querying data. A bitemporal database is one that supports these orthogonal axes.

  • Valid time: When the fact was true in the real world. This is the application domain's notion of time.
  • Transaction time: When the fact was recorded in the database. This is the system's notion of time.

Because every fact in a bitemporal database has these two dimensions, it enables use cases like this:

// We initialize a DB and start using it like an ordinary key-value store.
db, err := memory.NewDB()
err := db.Set("Bob/balance", 100)
val, err := db.Get("Bob/balance")
err := db.Delete("Alice/balance")
// and so on...

// We later learn that Bob had a temporary pending charge we missed from Dec 30 to Jan 3. (VT start = Dec 30, VT end = Jan 3)
// Retroactively record it! This does not change his balance today nor does it destroy any history we had about that period.
err := db.Set("Bob/balance", 90, WithValidTime(dec30), WithEndValidTime(jan3))

// We can at any point seamlessly ask questions about the real world past AND database record past!
// "What was Bob's balance on Jan 1 as best we knew on Jan 8?" (VT = Jan 1, TT = Jan 8)
val, err := db.Get("Bob/balance", AsOfValidTime(jan1), AsOfTransactionTime(jan8))

// More time passes and more corrections are made... When trying to make sense of what happened last month, we can ask again:
// "But what was it on Jan 1 as best we now know?" (VT = Jan 1, TT = now)
val, err := db.Get("Bob/balance", AsOfValidTime(jan1))

// And while we are at it, let's double check all of our transactions and known states for Bob's balance.
versions, err := db.History("Bob/balance")

*See full examples

Using a bitemporal database allows you to offload management of temporal application data (valid time) and data versions (transaction time) from your code and onto infrastructure. This provides a universal "time travel" capability across models in the database. Adopting these capabilities proactively is valuable because by the time you realize you need to update (or have already updated) data, it may be too late. Context may already be lost or painful to reconstruct manually.


Design

// DB for bitemporal data.
//
// Temporal control options.
// ReadOpt's: AsOfValidTime, AsOfTransactionTime.
// WriteOpt's: WithValidTime, WithEndValidTime.
type DB interface {
	// Get data by key (as of optional valid and transaction times).
	Get(key string, opts ...ReadOpt) (*VersionedKV, error)
	// List all data (as of optional valid and transaction times).
	List(opts ...ReadOpt) ([]*VersionedKV, error)
	// Set stores value (with optional start and end valid time).
	Set(key string, value Value, opts ...WriteOpt) error
	// Delete removes value (with optional start and end valid time).
	Delete(key string, opts ...WriteOpt) error

	// History returns all versioned key-values for key by descending end transaction time, descending end valid time.
	History(key string) ([]*VersionedKV, error)
}

// VersionedKV is a transaction time and valid time versioned key-value. Transaction and valid time starts are inclusive
// and ends are exclusive. No two VersionedKVs for the same key can overlap both transaction time and valid time.
type VersionedKV struct {
	Key   string
	Value Value

	TxTimeStart    time.Time  // inclusive
	TxTimeEnd      *time.Time // exclusive
	ValidTimeStart time.Time  // inclusive
	ValidTimeEnd   *time.Time // exclusive
}

// Value is the user-controlled data associated with a key (and valid and transaction time information) in the database.
type Value interface{}
  • DB interface is inspired by XTDB (and Datomic).
  • Storage model is inspired by Snodgrass' SQL implementations.

Author

I was learning about bitemporal databases and thought the best way to build intuition about their internal design was by building a simple one for myself. My goals are:

  • Sharing a viable, standalone key-value store lib
  • Creating artifacts for explaining bitemporality
  • Expanding scope in new tools for gracefully extending existing SQL databases with bitemporality

Bitempura was the name of my time traveling shrimp. RIP 2049-2022. 🦐

See TODO for more.