Replies: 3 comments
|
@StringKe there is already a task queue built on top of GoAkt: https://github.com/conveyorq/conveyor |
PositionGoAkt should not absorb the runtime feature set proposed (public cluster KV and locks, cluster-wide rate limiting, an at-least-once jobs engine, a persistent scheduler with durable delivery, a TLS edge gateway). These belong in separate libraries built on top of GoAkt, exactly the way The short version: GoAkt is an engine. The proposal asks the engine to also be the transmission, the fuel system, and the car body. Each of those is a real part of the car. None of them is the engine, and bolting them to the engine block makes the engine heavier, harder to trust, and worse at being an engine. 1. What GoAkt actually is (the bounded context)GoAkt's domain is the actor model: typed message passing, supervision hierarchies, location transparency, and clustering of actors and grains. Its cluster layer (
Every one of these primitives exists because an actor needs it, and every one is allowed to be advisory and self-healing, because the actor runtime reconciles the truth on top of it. That property is the whole reason GoAkt can sit on an availability-favoring, tunably-consistent substrate (Olric is PA/EC; GoAkt raises the read/write quorum for the placement registry to get stronger reads) and still be correct: placement is reconciled, leadership is self-correcting, a duplicated schedule claim is deduplicated. The moment we expose those same primitives as general-purpose infrastructure with their own guarantee contracts, that safety net disappears, because there is no longer a reconciling runtime above them; the user's code sits directly on the raw guarantee. This is the crux of the whole discussion. Everything in GoAkt orbits the actor model. The goodies that already ship (leader election, cluster-wide schedule claiming, non-actor pub/sub) exist to serve actors, and the fact that they exist is not a mandate to absorb the rest of a distributed runtime. A few useful primitives falling out of the cluster layer is not a recipe for putting everything into GoAkt. 2. Steelman: why the proposal is attractiveThe proposal is not naive, and the pull is real:
These are true. They are also exactly the arguments that produce a distributed monolith. Every one of them optimizes the first hour of adoption at the expense of the guarantees, the security posture, and the maintainability of the next five years. 3. The load-bearing objection: one substrate, many incompatible guarantee contractsThe central engineering fact is this: the proposed features each publish a different correctness contract, and those contracts require guarantees the substrate does not provide. GoAkt's substrate (the Olric fork) is, by its own README, a PA/EC system under PACELC: under a network partition it favors availability over consistency, resolving conflicts by last-write-wins over gossip membership, with no consensus layer (there is no Raft, Paxos, or linearizability anywhere in the fork). GoAkt tunes the read/write quorum up for the placement registry, which buys Cassandra-class tunable consistency (raising 3.1 Consistency-model mismatchConsider what each requested feature actually promises a user:
The Olric fork's own README is explicit about the lock: "there is no guarantee about reliability in the case of network partitioning. The lock implementation is recommended for efficiency purposes in general, rather than correctness," and "if you cannot tolerate losing strong consistency under network partitioning, you need to use a different tool for locking." A distributed lock that can be held by two nodes at once is not a lock; it is a race with good intentions. The same reasoning applies to a rate limiter: the fork's Newcomers, the exact audience the proposal courts, will read "distributed lock" and "at-least-once" and trust them the way they trust Redis or etcd. They will build correctness on them. They will get bitten during a partition, in production, and the bug report lands on the GoAkt maintainer, who then owns the impossible task of making PA/EC primitives behave like CP ones. A guarantee you cannot honor is a liability you signed, not a feature you shipped. Note the asymmetry with what GoAkt already does. Its own 3.2 Public-API ossification (Hyrum's Law)"No new dependencies" is the most expensive sentence in the proposal. It is true only because it works by exposing Olric as a public API surface. Today Olric lives in Publish a 3.3 Trust boundary and security blast radiusThe gateway package (WebSocket/SSE registry, cluster-wide TLS, cert lifecycle, graceful draining) is a category error inside an actor library. It places an internet-facing edge tier inside the same process, trust boundary, and release cadence as the message-passing core. An edge proxy and an actor runtime have different threat models, different audit requirements, and different CVE surfaces. TLS termination and certificate management alone are a serious, never-ending security liability. Coupling them means every actor-core patch drags the gateway's attack surface along for review, and every gateway CVE forces a core release. A library that is embedded deep inside other people's processes must keep its blast radius small. An edge tier is the opposite of small. 3.4 Coupled release cadence and versioningA monolith forces lockstep versioning. The jobs engine's semantics will evolve (backoff policy, DLQ format, leasing model). The gateway's TLS defaults will change as the ecosystem moves. In a monolith each of those is a GoAkt (v4 -> v5) event that also drags the actor core, its users, and its API stability along with it. As independent satellites, each subsystem versions on its own clock and its churn never destabilizes the actor core. The core's job is to be the boring, stable thing everything else stands on. You cannot be the stable foundation and the fast-moving jobs engine in the same 3.5 Verification and support burdenEach of these primitives is only trustworthy if it is verified to a high bar. An at-least-once queue, a fenced lease, a distributed lock, and a rate limiter are precisely the class of system that needs Jepsen-grade partition testing before anyone should build correctness on it. The core team cannot own that depth of verification for six new subsystems on top of the actor model itself. Shipping them unverified under trustworthy names ("at-least-once," "lock") is worse than not shipping them, because it manufactures false confidence. As satellites, each library owns and states its own guarantees and its own test regime, and can be as rigorous (or as explicitly best-effort) as its authors choose without putting the core's credibility on the line. 4. The pattern is already in the codebaseGoAkt already solved this problem, twice, and the proposal would undo the solution.
The ecosystem's own precedent is unambiguous: heavyweight, opinionated, guarantee-bearing capabilities are satellites; the actor core stays small and stable. There is a crucial constraint on this pattern, and it is the whole point: 5. Decision rubric: core vs satelliteA capability belongs in the actor core only if all hold:
Everything else is a satellite, built on GoAkt's public actor and cluster API. A capability is a satellite if it does any of:
6. Feature-by-feature verdict
7. The item that looks like it needs a core hook, and does notCrash relocation (4) is the proposal that comes closest to a core concern, because placement is core. Deciding where a dead node's actors respawn is squarely the actor runtime's job, and GoAkt already does it. What tempts people toward core machinery is the durable memory of "what was running where," so placement can be rebuilt after a full-cluster restart. That durability is generic storage, not an actor concern, so it does not earn a core SPI, not even "just an interface." The resolution needs nothing new in core. GoAkt already emits the actor-model signals a satellite needs: actor and grain lifecycle, membership change events, and the placement queries. A relocation satellite subscribes to those, persists placement to whatever store it chooses (with whatever durability guarantee it is willing to stand behind), and on restart re-drives the ordinary public 8. Field precedentThis is not a GoAkt-specific opinion; it is how every serious actor and runtime ecosystem is factored.
9. Recommendation
|
|
Thank you for taking the time to write this up - the rubric and the guarantee-contract analysis are genuinely valuable, and we agree with the boundary. We will keep building on the public actor and cluster API, following the eGo pattern, and take our time to get it right before anything is published. And thanks again for landing the four PRs so quickly - the collaboration this week has been outstanding. |
Uh oh!
There was an error while loading. Please reload this page.
We run a single Go application deployed as N replicas, built on GoAkt. Today, teams in this position typically compose several external systems around the actor runtime: a task queue plus Redis for durable jobs and cron (Asynq/River), Redis or database locks for coordination, nginx/caddy plus cert tooling in front for TLS and long-lived connections, and hand-rolled registries for presence. Yet the embedded cluster already has the primitives all of these need: membership, consistent placement, put-if-absent, pub/sub. Our wish, and the point of this discussion: GoAkt (core plus its ecosystem) could cover the distributed multi-instance feature set end to end, so applications do not have to stitch together half a dozen dependencies for capabilities the runtime is one narrow API away from providing.
We implemented the full set on our fork (branch
self-parkon StringKe/goakt), each piece additive-only, zero new dependencies, with unit + multi-node tests, benchmarks, and docs. We are offering all of it upstream, in whatever form you prefer.Filed today as small, independent PRs (each with its own issue): leader status + change events (#1233/#1239), ephemeral spawn option for high-churn connection actors (#1234/#1240), scheduler introspection (#1235/#1241), cluster single-fire schedules (#1236/#1242), non-actor PubSub subscriptions (#1237/#1243), topic presence introspection (#1238/#1244).
Implemented and ready, but we would like your guidance before filing anything:
quartz.WithQueuevia an ActorSystem option, with a serializable delivery-intent envelope so schedules survive restarts (adapters would live outside core, following thediscovery.Providerpattern).jobspackage - embedded at-least-once execution: lease/ack with fencing tokens, retry/backoff, dead-lettering, first-class fan-out/fan-in (map/reduce over actors), and an inspector API. We know conveyor covers app-level task queueing as a standalone server, and see this as the complementary embedded, actor-native primitive - happy to align naming/concepts or scope it differently.gatewaypackage - the cluster acting as its own edge tier: WS/SSE connection registry with two-tier delivery (local socket write fast path, cluster routing only cross-node), cluster-shared TLS where each domain is issued exactly once cluster-wide (pluggable issuers; Cloudflare Origin CA implemented with net/http only), and graceful connection draining for rolling deploys.These interlock: single-fire + a persistent queue give HA cron; the KV/lock surface underpins both the limiter and the gateway's certificate arbitration; ephemeral actors + presence + the subscriber bridge are what make the gateway's connection model clean; the placement journal closes the crash-recovery gap for exclusive long-lived workloads (bot connections and similar).
For items 5 and 6 especially, we are open to any structure you prefer: in-tree packages, separate repos under your org (the ego/ego-contrib pattern), or scoped down to just the core hooks. Everything is runnable on the fork today if you want to inspect behavior before deciding. Which of these would you take as core proposals, which belong in ecosystem repos, and which do you consider out of scope?
All reactions