package broker import ( "context" "fmt" "time" "github.com/nats-io/nats.go/jetstream" ) // The controller's own key-value buckets (novox/hq to-be 45 §1, §6, §7). // // **What the controller must remember across its own restart, it keeps on the bus.** A call's // outcome lived in the memory of the process that served it (novox/hq issue 265), so a controller // replaced while a push ran answered "no such call" for the one thing its caller had been told to // ask about. The bus already outlives the controller and is the shape ADR 0201 gives a module's // current state: one value per key, written by one owner, read by anybody granted it. These are the // controller's, written by it alone — the writers table of to-be 45 §1 — and asserted on every start // like the streams, so a bus raised from nothing has them before the first call is served. // CallsBucket keeps every call of the mesh's own verbs and what came of it; HandActsBucket every act // a person did by hand, with why. var ( CallsBucket = BucketName(ControllerSeat, "calls") HandActsBucket = BucketName(ControllerSeat, "hand-acts") ) // The bounds to-be 45 §6 sets for calls: the last thousand, or fourteen days, whichever is fewer. // A call is two keys — its record, and its answer apart so a listing does not read every answer — // so the stream holds twice as many messages as it keeps calls. const ( KeptCallsDurably = 1000 CallsKeptFor = 14 * 24 * time.Hour // CallAnswerBytes is the most of one answer kept: a whole declaration is far smaller, and an // answer larger is cut and says so. CallAnswerBytes = 64 << 10 // HandActsKeptFor is as long as a condition's history (to-be 45 §2): an act by hand is read // back beside what it addressed. HandActsKeptFor = 90 * 24 * time.Hour ) // IsControllerBucket says a bucket is the controller's own, not a module's state nothing declares. func IsControllerBucket(bucket string) bool { return bucket == CallsBucket || bucket == HandActsBucket } // ControllerBucketsAsserter is what raising the controller's buckets needs of a connection. type ControllerBucketsAsserter interface { EnsureControllerBuckets() error } // EnsureControllerBuckets creates the controller's buckets if absent and brings their options to // match. An update, never a delete: what they hold is the record of what the mesh was asked. func (j *JetStream) EnsureControllerBuckets() error { js, err := jetstream.New(j.conn) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() if _, err := js.CreateOrUpdateKeyValue(ctx, jetstream.KeyValueConfig{ Bucket: CallsBucket, Description: "the calls of the mesh's own verbs and what came of each (novox/hq to-be 45 §6, issue " + "265): written by the controller alone, read through `calls`; the last thousand, or fourteen days", History: 1, TTL: CallsKeptFor, MaxValueSize: CallAnswerBytes + 4<<10, MaxBytes: 2 * KeptCallsDurably * (CallAnswerBytes + 4<<10), Storage: jetstream.FileStorage, }); err != nil { return fmt.Errorf("asserting bucket %s: %w", CallsBucket, err) } // **The count, on the stream under the bucket.** A bucket has an age and a size and no count; // the stream it is made of does, and with one value per key the oldest message is the oldest // call. Asserted after the bucket, every time, because asserting the bucket writes the stream's // configuration whole and puts the count back to none. stream, err := js.Stream(ctx, "KV_"+CallsBucket) if err != nil { return fmt.Errorf("reading the stream under %s: %w", CallsBucket, err) } cfg := stream.CachedInfo().Config if cfg.MaxMsgs != 2*KeptCallsDurably { cfg.MaxMsgs = 2 * KeptCallsDurably cfg.Discard = jetstream.DiscardOld if _, err := js.UpdateStream(ctx, cfg); err != nil { return fmt.Errorf("bounding %s to the last %d calls: %w", CallsBucket, KeptCallsDurably, err) } } if _, err := js.CreateOrUpdateKeyValue(ctx, jetstream.KeyValueConfig{ Bucket: HandActsBucket, Description: "every act a person did by hand, with why (novox/hq to-be 45 §7): written by the " + "controller's repairing verbs and `hand-act record`, read through `hand-acts`", History: 1, TTL: HandActsKeptFor, MaxValueSize: 16 << 10, MaxBytes: 64 << 20, Storage: jetstream.FileStorage, }); err != nil { return fmt.Errorf("asserting bucket %s: %w", HandActsBucket, err) } return nil }