Documentation
¶
Overview ¶
Package org places each organization's data on exactly one replica of the unified cloud binary, and replicates it, WITHOUT any coordinator.
In Hanzo the ORGANIZATION is the tenant boundary — identity, billing, and per-org SQLite (HIP-0302) are all org-scoped, so "tenant" and "org" are the same thing; this package speaks only of orgs. It answers one question, identically on every replica: "which replica is the single writer for org X's databases?" — via Rendezvous (Highest-Random-Weight) hashing over the live membership set. No election, no lock service, no service discovery.
Ownership is PER-ORG: the owning replica writes ALL of an org's databases (its root db plus every per-project and per-user db), so an org's data has locality and intra-org consistency and moves as a single unit on failover. Reads are served from any replica's SeaweedFS-synced copy (see Replicator). A single mega-org that outgrows one writer is a future sub-shard concern (hash org+shard); per-org is the correct, simple default and the natural isolation boundary.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func DBPath ¶
DBPath is the canonical object-store location of one of an org's SQLite databases (HIP-0302 layout). Every replica computes the same path for the same inputs.
org root: DBPath("acme", "", "iam") -> orgs/acme/iam.db
project: DBPath("acme", "projects/site", "base") -> orgs/acme/projects/site/base.db
user: DBPath("acme", "users/dave", "kv") -> orgs/acme/users/dave/kv.db
Ownership of ALL these paths is the org's single owner (org.Owner("acme", …)).
Types ¶
type DB ¶
type DB interface {
Snapshot(ctx context.Context) ([]byte, error)
Restore(ctx context.Context, data []byte) error
}
DB is the local per-org SQLite the owner writes and readers restore into, satisfied by a hanzoai/base handle over the hanzoai/sqlite driver. Snapshot returns a consistent (WAL-checkpointed) copy; Restore replaces the local bytes atomically.
type Member ¶
Member is one replica in the live membership set. ID must be stable for the life of the replica (e.g. its pod name / a persistent node id): HRW weights derive from it, so a changing ID would reshuffle ownership needlessly. Addr is the reachable address for write-forwarding (host:port).
func Owner ¶
Owner returns the replica that owns the writer for orgID, or ok=false when members is empty (fail-closed — never a wrong writer). Deterministic: the same (orgID, members) yields the same Owner on every replica, independent of the order members are supplied in.
func Replicas ¶
Replicas returns the top-n members for orgID by descending HRW weight: the owner first, then the ordered failover successors. On owner loss the next replica in this list becomes owner with no recomputation, so read replicas can pre-warm the SeaweedFS copy for the orgs they are next-in-line to own.
type Membership ¶
type Membership struct {
// contains filtered or unexported fields
}
Membership caches the latest replica snapshot and refreshes it from a Source on an interval. Reads are lock-free via an atomic snapshot pointer, so the per-request AmOwner hot-path never blocks on the refresher.
func NewMembership ¶
func NewMembership(selfID string, src Source, interval time.Duration) *Membership
NewMembership builds a Membership over src. selfID is this replica's stable id (see Member.ID). interval<=0 defaults to 5s.
func (*Membership) AmOwner ¶
func (m *Membership) AmOwner(orgID string) bool
AmOwner reports whether THIS replica owns orgID's writer right now — the per-write hot-path check. Lock-free.
func (*Membership) Members ¶
func (m *Membership) Members() []Member
Members returns the current membership snapshot (never nil; may be empty).
func (*Membership) OwnerOf ¶
func (m *Membership) OwnerOf(orgID string) (Member, bool)
OwnerOf resolves the writer-owner replica for orgID under the live set.
func (*Membership) Start ¶
func (m *Membership) Start(ctx context.Context) error
Start does an initial synchronous refresh (so Members() is populated before the first request) then refreshes on the interval until Stop or ctx cancel. Returns the initial refresh error, if any; callers may serve with a stale or self-only set regardless.
type Replicator ¶
type Replicator struct {
// contains filtered or unexported fields
}
Replicator binds ONE per-org SQLite (at a DBPath) to its object-store slot and moves bytes for whatever role the caller is:
owner replica (org.IsOwner true): Push() on a timer / after a write burst reader replica (org.IsOwner false): Pull() before a stale-tolerant read
It does NOT decide ownership — that is org.Owner. Push/Get/Put are whole-object, so a push and a pull never observe a half-written database.
func NewReplicator ¶
func NewReplicator(key string, store Store, db DB) *Replicator
NewReplicator binds the DB at key (from DBPath) to its store slot.
func (*Replicator) Key ¶
func (r *Replicator) Key() string
Key is this replicator's object-store location.
func (*Replicator) Pull ¶
func (r *Replicator) Pull(ctx context.Context) (changed bool, err error)
Pull downloads the latest snapshot and restores it into the local DB — called by a READER before serving stale-tolerant reads, or by a NEW OWNER on handover to load the last committed state. A no-op (changed=false, no restore) when the remote is unchanged since our last push/pull.
func (*Replicator) Push ¶
func (r *Replicator) Push(ctx context.Context) error
Push snapshots the local DB (WAL-checkpointed) and writes it to the object store via hanzoai/vfs. Called by the OWNER — the single writer. HIP-0107: the durable copy lives in SeaweedFS, so a lost pod loses no committed data.
func (*Replicator) PushLoop ¶
func (r *Replicator) PushLoop(ctx context.Context, every time.Duration)
PushLoop runs Push on an interval until ctx cancel — the owner's background WAL-shipper. On error it keeps the last good remote copy and retries next tick (fire-and-forget durability; never blocks writes).
type Source ¶
Source yields the current live replica set. It is the ONE thing in the horizontally-scaled cloud that needs a peer view — everything else is a pure function of (org, members). Any discovery mechanism plugs in as a Source without touching the ownership core: a static list (dev), a K8s Endpoints poll (prod), or a zapd gossip view.
func StaticSource ¶
StaticSource yields a fixed set — single-node / local dev. With no explicit members it reads CLOUD_REPLICAS ("id@addr,id2@addr2", or bare "id" with addr==id).
type Store ¶
type Store interface {
Put(ctx context.Context, key string, data []byte) error
Get(ctx context.Context, key string) (data []byte, version string, err error)
Version(ctx context.Context, key string) (version string, err error)
}
Store is the object-store surface the Replicator needs, satisfied natively by hanzoai/vfs (deps.VFS — SeaweedFS-backed, in-process). NO minio, NO external S3 SDK. version is a content hash so readers skip redundant pulls.
func NewVFSStore ¶
func NewVFSStore(vfs vfsClient) Store
NewVFSStore builds a Store over the native vfs client (pass deps.VFS).