From 0601a36b7b96fa5c24857a9a542894ae3b6d509b Mon Sep 17 00:00:00 2001 From: "R.I.Pienaar" Date: Tue, 13 Jul 2021 10:06:47 +0200 Subject: [PATCH] remove ADR files from the server Signed-off-by: R.I.Pienaar --- doc/README.md | 20 +-- doc/adr/0001-jetstream-json-api-design.md | 155 ----------------- doc/adr/0002-nats-typed-messages.md | 183 -------------------- doc/adr/0003-distributed-tracing.md | 156 ----------------- doc/adr/0003-jaeger-trace.png | Bin 64082 -> 0 bytes doc/adr/0004-nats-headers.md | 171 ------------------ doc/adr/0005-lame-duck-notification.md | 21 --- doc/adr/0006-protocol-naming-conventions.md | 55 ------ doc/adr/0007-error-codes.md | 150 ---------------- doc/adr/0009-js-idle-heartbeat.md | 52 ------ doc/adr/0010-js-purge.md | 62 ------- 11 files changed, 1 insertion(+), 1024 deletions(-) delete mode 100644 doc/adr/0001-jetstream-json-api-design.md delete mode 100644 doc/adr/0002-nats-typed-messages.md delete mode 100644 doc/adr/0003-distributed-tracing.md delete mode 100644 doc/adr/0003-jaeger-trace.png delete mode 100644 doc/adr/0004-nats-headers.md delete mode 100644 doc/adr/0005-lame-duck-notification.md delete mode 100644 doc/adr/0006-protocol-naming-conventions.md delete mode 100644 doc/adr/0007-error-codes.md delete mode 100644 doc/adr/0009-js-idle-heartbeat.md delete mode 100644 doc/adr/0010-js-purge.md diff --git a/doc/README.md b/doc/README.md index 87e45cdc..e431f8a8 100644 --- a/doc/README.md +++ b/doc/README.md @@ -1,21 +1,3 @@ # Architecture Decision Records -The directory [adr](adr) hold Architecture Decision Records that document major decisions made -in the design of the NATS Server. - -A good intro to ADRs can be found in [Documenting Architecture Decisions](http://thinkrelevance.com/blog/2011/11/15/documenting-architecture-decisions) by Michael Nygard. - -## When to write an ADR - -Not every little decision needs an ADR, and we are not overly prescriptive about the format. -The kind of change that should have an ADR are ones likely to impact many client libraries -or those where we specifically wish to solicit wider community input. - -## Format - -The [adr-tools](https://github.com/npryce/adr-tools) utility ships with a template that's a -good starting point. We do not have a fixed format at present. - -## ADR Statuses - -Each ADR has a status, let's try to use `Proposed`, `Approved` `Partially Implemented`, `Implemented` and `Rejected`. +The NATS ADR documents have moved to their [own repository](https://github.com/nats-io/nats-architecture-and-design/) diff --git a/doc/adr/0001-jetstream-json-api-design.md b/doc/adr/0001-jetstream-json-api-design.md deleted file mode 100644 index 1a88e828..00000000 --- a/doc/adr/0001-jetstream-json-api-design.md +++ /dev/null @@ -1,155 +0,0 @@ -# 1. JetStream JSON API Design - -Date: 2020-04-30 -Author: @ripienaar - -## Status - -Partially Implemented - -## Context - -At present, the API encoding consists of mixed text and JSON, we should improve consistency and error handling. - -### Admin APIs - -#### Requests - -All Admin APIs that today accept `nil` body should also accept an empty JSON document as request body. - -Any API that responds with JSON should also accept JSON, for example to delete a message by sequence we accept -`10` as body today, this would need to become `{"seq": 10}` or similar. - -#### Responses - -All responses will be JSON objects, a few examples will describe it best. Any error that happens has to be -communicated within the originally expected message type. Even the case where JetStream is not enabled for -an account, the response has to be a valid data type with the addition of `error`. When `error` is present -empty fields may be omitted as long as the response still adheres to the schema. - -Successful Stream Info: - -```json -{ - "type": "io.nats.jetstream.api.v1.stream_info", - "time": "2020-04-23T16:51:18.516363Z", - "config": { - "name": "STREAM", - "subjects": [ - "js.in" - ], - "retention": "limits", - "max_consumers": -1, - "max_msgs": -1, - "max_bytes": -1, - "max_age": 31536000, - "max_msg_size": -1, - "storage": "file", - "num_replicas": 1 - }, - "state": { - "messages": 95563, - "bytes": 40104315, - "first_seq": 34, - "last_seq": 95596, - "consumer_count": 1 - } -} -``` - -Consumer Info Error: - -```json -{ - "type": "io.nats.jetstream.api.v1.consumer_info", - "error": { - "description": "consumer not found", - "code": 404, - "error_code": 10059 - } -} -``` - -Here we have a minimally correct response with the additional error object. - -In the `error` struct we have `description` as a short human friendly explanation that should include enough context to -identify what Stream or Consumer acted on and whatever else we feel will help the user while not sharing privileged account -information. These strings are not part of the API promises, we can update and re-word or translate them at any time. Programmatic -error handling should look at the `code` which will be HTTP like, 4xx human error, 5xx server error etc. Finally, the `error_code` -indicates the specific reason for the 404 - here `10059` means the stream did not exist, helping developers identify the -real underlying cause. - -More information about the `error_code` system can be found in [ADR-7](0007-error-codes.md). - -Ideally the error response includes a minimally valid body of what was requested but this can be very hard to implement correctly. - -Today the list API's just return `["ORDERS"]`, these will become: - -```json -{ - "type": "io.nats.jetstream.api.v1.stream_list", - "time": "2020-04-23T16:51:18.516363Z", - "streams": [ - "ORDERS" - ] -} -``` - -With the same `error` treatment when some error happens. - -## Implementation - -While implementing this in JetStream the following pattern emerged: - -```go -type JSApiResponse struct { - Type string `json:"type"` - Error *ApiError `json:"error,omitempty"` -} - -type ApiError struct { - Code int `json:"code"` - ErrCode int `json:"err_code,omitempty"` - Description string `json:"description,omitempty"` - URL string `json:"-"` - Help string `json:"-"` -} - -type JSApiConsumerCreateResponse struct { - JSApiResponse - *ConsumerInfo -} -``` - -This creates error responses without the valid `ConsumerInfo` fields but this is by far the most workable solution. - -Validating this in JSON Schema draft 7 is a bit awkward, not impossible and specifically leads to some hard to parse validation errors, but it works.: - -```json -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://nats.io/schemas/jetstream/api/v1/consumer_create_response.json", - "description": "A response from the JetStream $JS.API.CONSUMER.CREATE API", - "title": "io.nats.jetstream.api.v1.consumer_create_response", - "type": "object", - "required": ["type"], - "oneOf": [ - { - "$ref": "definitions.json#/definitions/consumer_info" - }, - { - "$ref": "definitions.json#/definitions/error_response" - } - ], - "properties": { - "type": { - "type": "string", - "const": "io.nats.jetstream.api.v1.consumer_create_response" - } - } -} -``` - -## Consequences - -URL Encoding does not carry data types, and the response fields will need documenting. diff --git a/doc/adr/0002-nats-typed-messages.md b/doc/adr/0002-nats-typed-messages.md deleted file mode 100644 index 32c6fd4e..00000000 --- a/doc/adr/0002-nats-typed-messages.md +++ /dev/null @@ -1,183 +0,0 @@ -# 2. NATS Typed Messages - -Date: 2020-05-06 -Author: @ripienaar - -## Status - -Accepted - -## Context - -NATS Server has a number of JSON based messages - monitoring, JetStream API and more. These are consumed, -and in the case of the API produced, by 3rd party systems in many languages. To assist with standardization -of data validation, variable names and more we want to create JSON Schema documents for all our outward facing -JSON based communication. Specifically this is not for server to server communication protocols. - -This effort is ultimately not for our own use - though libraries like `jsm.go` will use these to do validation -of inputs - this is about easing interoperability with other systems and to eventually create a Schema Registry. - -There are a number of emerging formats for describing message content: - - * JSON Schema - transport agnostic way of describing the shape of JSON documents - * AsyncAPI - middleware specific API description that uses JSON Schema for payload descriptions - * CloudEvents - standard for wrapping system specific events in a generic, routable, package. Supported by all - major Public Clouds and many event gateways. Can reference JSON Schema. - * Swagger / OpenAPI - standard for describing web services that uses JSON Schema for payload descriptions - -In all of these many of the actual detail like how to label types of event or how to version them are left up -to individual projects to solve. This ADR describes how we are approaching this. - -## Decision - -### Overview - -We will start by documenting our data types using JSON Schema Draft 7. AsyncAPI and Swagger can both reference -these documents using remote references so this, as a starting point, gives us most flexibility and interoperability -to later create API and Transport specific schemas that reference these. - -We define 2 major type of typed message: - - * `Message` - any message with a compatible `type` hint embedded in it - * `Event` - a specialized `message` that has timestamps and event IDs, suitable for transformation to - Cloud Events. Typically, published unsolicited. - -Today NATS Server do not support publishing Cloud Events natively however a bridge can be created to publish -those to other cloud systems using the `jsm.go` package that supports converting `events` into Cloud Event format. - -### Message Types - -There is no standard way to indicate the schema of a specific message. We looked at a lot of prior art from CNCF -projects, public clouds and more but found very little commonality. The nearest standard is the Uniform Resource Name -which still leaves most of the details up to the project and does not conventionally support versioning. - -We chose a message type like `io.nats.jetstream.api.v1.consumer_delete_response`, `io.nats.server.advisory.v1.client_connect` -or `io.nats.unknown_message`. - -`io.nats.unknown_message` is a special type returned for anything without valid type hints. In go that implies -`map[string]interface{}`. - -The structure is as follows: io.nats.``.``.v``.`` - -#### Source - -The project is the overall originator of a message and should be short but descriptive, today we have 2 - `server` and ` -jetstream` - as we continue to build systems around Stream Processing and more we'd add more of these types. I anticipate -for example adding a few to Surveyor for publishing significant lifecycle events. - -Generated Cloud Events messages has the `source` set to `urn:nats:`. - -|Project|Description| -|-------|-----------| -|`server`|The core NATS Server excluding JetStream related messages| -|`jetstream`|Any JetStream related message| - -#### Category - -The `category` groups messages by related sub-groups of the `source`, often this also appears in the subjects -these messages get published to. - -This is a bit undefined, examples in use now are `api`, `advisory`, `metric`. Where possible try to fit in with -existing chosen ones, if none suits update this table with your choice and try to pick generic category names. - -|Category|Description| -|----|-----------| -|`api`|Typically these are `messages` used in synchronous request response APIs| -|`advisory`|These are `events` that describe a significant event that happened like a client connecting or disconnecting| -|`metric`|These are `events` that relate to monitoring - how long did it take a message to be acknowledged| - -#### Versioning - -The ideal outcome is that we never need to version any message and maintain future compatibility. - -We think we can do that with the JetStream API. Monitoring, Observability and black box management is emerging, and we -know less about how that will look in the long run, so we think we will need to version those. - -The philosophy has to be that we only add fields and do not significantly change the meaning of existing ones, this -means the messages stay `v1`, but major changes will require bumps. So all message types includes a single digit version. - -#### Message Name - -Just a string identifying what this message is about - `client_connect`, `client_disconnect`, `api_audit` etc. - -## Examples - -### Messages - -At minimum a typed message must include a `type` string: - -```json -{ - "type": "io.nats.jetstream.api.v1.stream_configuration" -} -``` - -Rest of the document is up to the specific use case - -### Advisories - -Advisories must include additional fields: - -```json -{ - "type": "io.nats.jetstream.advisory.v1.api_audit", - "id": "uafvZ1UEDIW5FZV6kvLgWA", - "timestamp": "2020-04-23T16:51:18.516363Z" -} -``` - - * `timestamp` - RFC 3339 format in UTC timezone, with sub-second precision added if present - * `id` - Any sufficiently unique ID such as those produced by `nuid` - -### Errors - -Any `message` can have an optional `error` property if needed and can be specified in the JSON Schema, -they are not a key part of the type hint system which this ADR focus on. - -In JetStream [ADR 0001](0001-jetstream-json-api-design.md) we define an error message as this: - -``` -{ - "error": { - "description": "Server Error", - "code": 500 - } -} -``` - -Where error codes follow basic HTTP standards. This `error` object is not included on success and so -acceptable error codes are between `300` and `599`. - -It'll be advantageous to standardise around this structure, today only JetStream API has this and we have -not evaluated if this will suit all our needs. - -## Schema Storage - -Schemas will eventually be kept in some form of formal Schema registry. In the near future they will all be placed as -fully dereferenced JSON files at `http://nats.io/schemas`. - -The temporary source for these can be found in the `nats-io/jetstream` repository including tools to dereference the -source files. - -## Usage - -Internally the `jsm.go` package use these Schemas to validate all requests to the JetStream API. This is not required as -the server does its own validation too - but it's nice to fail fast and give extended errors like a JSON validator will -give. - -Once we add JetStream API support to other languages it would be good if those languages use the same Schemas for -validation to create a unified validation strategy. - -Eventually these Schemas could be used to generate the API structure. - -The `nats` utility has a `nats events` command that can display any `event`. It will display any it finds, special -formatting can be added using Golang templates in its source. Consider adding support to it whenever a new `event` is added. - -## Status - -While this is marked `accepted`, we're still learning and exploring their usage so changes should be anticipated. - -## Consequences - -Many more aspects of the Server move into the realm of being controlled and versioned where previously we took a much -more relaxed approach to modifications to the data produced by `/varz` and more. diff --git a/doc/adr/0003-distributed-tracing.md b/doc/adr/0003-distributed-tracing.md deleted file mode 100644 index 71414142..00000000 --- a/doc/adr/0003-distributed-tracing.md +++ /dev/null @@ -1,156 +0,0 @@ -# 3. NATS Service Latency Distributed Tracing Interoperability - -Date: 2020-05-21 -Author: @ripienaar - -## Status - -Approved - -## Context - -The goal is to enable the NATS internal latencies to be exported to distributed tracing systems, here we see a small -architecture using Traefik, a Go microservice and a NATS hosted service all being observed in Jaeger. - -![Jaeger](0003-jaeger-trace.png) - -The lowest 3 spans were created from a NATS latency Advisory. - -These traces can be ingested by many other commercial systems like Data Dog and Honeycomb where they can augment the -existing operations tooling in use by our users. Additionally Grafana 7 supports Jaeger and Zipkin today. - -Long term I think every server that handles a message should emit a unique trace so we can also get visibility into -the internal flow of the NATS system and exactly which gateway connection has a delay - see our current HM issues - but -ultimately I don't think we'll be doing that in the hot path of the server, though these traces are easy to handle async - -Meanwhile, this proposal will let us get very far with our current Latency Advisories. - -## Configuring an export - -Today there are no standards for the HTTP headers that communicate span context downstream - with Trace Context being -an emerging w3c standard. - -I suggest we support the Jaeger and Zipkin systems as well as Trace Context for long term standardisation efforts. - -Supporting these would mean we have to interpret the headers that are received in the request to determine if we should -publish a latency advisory rather than the static `50%` configuration we have today. - -Today we have: - -``` -exports: [ - { - service: weather.service - accounts: [WEB] - latency: { - sampling: 50% - subject: weather.latency - } - } -] -``` - -This enables sampling based `50%` of the service requests on this service. - -I propose we support the additional sampling value `headers` which will configure the server to -interpret the headers as below to determine if a request should be sampled. - -## Propagating headers - -The `io.nats.server.metric.v1.service_latency` advisory gets updated with an additional `headers` field. - -`headers` contains only the headers used for the sampling decision. - -```json -{ - "type": "io.nats.server.metric.v1.service_latency", - "id": "YBxAhpUFfs1rPGo323WcmQ", - "timestamp": "2020-05-21T08:06:29.4981587Z", - "status": 200, - "headers": { - "Uber-Trace-Id": ["09931e3444de7c99:50ed16db42b98999:0:1"] - }, - "requestor": { - "acc": "WEB", - "rtt": 1107500, - "start": "2020-05-21T08:06:20.2391509Z", - "user": "backend", - "lang": "go", - "ver": "1.10.0", - "ip": "172.22.0.7", - "cid": 6, - "server": "nats2" - }, - "responder": { - "acc": "WEATHER", - "rtt": 1389100, - "start": "2020-05-21T08:06:20.218714Z", - "user": "weather", - "lang": "go", - "ver": "1.10.0", - "ip": "172.22.0.6", - "cid": 6, - "server": "nats1" - }, - "start": "2020-05-21T08:06:29.4917253Z", - "service": 3363500, - "system": 551200, - "total": 6411300 -} -``` - -## Header Formats - -Numerous header formats are found in the wild, main ones are Zipkin and Jaeger and w3c `tracestate` being an emerging standard. - -Grafana supports Zipkin and Jaeger we should probably support at least those, but also Trace Context for future interop. - -### Zipkin - -``` -X-B3-TraceId: 80f198ee56343ba864fe8b2a57d3eff7 -X-B3-ParentSpanId: 05e3ac9a4f6e3b90 -X-B3-SpanId: e457b5a2e4d86bd1 -X-B3-Sampled: 1 -``` - -Also supports a single `b3` header like `b3={TraceId}-{SpanId}-{SamplingState}-{ParentSpanId}` or just `b3=0` - -[Source](https://github.com/openzipkin/b3-propagation) - -### Jaeger - -``` -uber-trace-id: {trace-id}:{span-id}:{parent-span-id}:{flags} -``` - -Where flags are: - - * One byte bitmap, as two hex digits - * Bit 1 (right-most, least significant, bit mask 0x01) is “sampled” flag - * 1 means the trace is sampled and all downstream services are advised to respect that - * 0 means the trace is not sampled and all downstream services are advised to respect that - -Also a number of keys like `uberctx-some-key: value` - -[Source](https://www.jaegertracing.io/docs/1.17/client-libraries/#tracespan-identity) - -### Trace Context - -Supported by many vendors including things like New Relic - -``` -traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01 -tracestate: rojo=00f067aa0ba902b7,congo=t61rcWkgMzE -``` - -Here the `01` of `traceparent` means its sampled. - -[Source](https://www.w3.org/TR/trace-context/) - -### OpenTelemetry - -Supports Trace Context - -[Source](https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/api.md) - diff --git a/doc/adr/0003-jaeger-trace.png b/doc/adr/0003-jaeger-trace.png deleted file mode 100644 index e1dc831cad859f3e70c0df90504012fa607eb868..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 64082 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

b*YFCys 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 Once a field has been unfolded, it may be viewed as being composed of a -> field-name followed by a colon (":"), followed by a field-body, and terminated -> by a carriage-return/line-feed. The field-name must be composed of printable -> ASCII characters (i.e., characters that have values between 33. and 126., -> decimal, except colon). The field-body may be composed of any ASCII -> characters, except CR or LF. (While CR and/or LF may be present in the actual -> text, they are removed by the action of unfolding the field.) - -The only difference between a NATS header and HTTP is the first line. Instead of -an HTTP method followed by a resource, and the HTTP version (`GET / HTTP/1.1`), -NATS will provide a string identifying the header version (`NATS/X.x`), -currently 1.0, so it is rendered as `NATS/1.0␍␊`. - -Please refer to the -[specification](https://tools.ietf.org/html/rfc7230#section-3.2) for information -on how to encode/decode HTTP headers. - -### Enabling Message Headers - -The server that is able to send and receive headers will specify so in it's -[`INFO`](https://docs.nats.io/nats-protocol/nats-protocol#info) protocol -message. The `headers` field if present, will have a boolean value. If the -client wishes to send headers, it has to enable it must add a `headers` field -with the `true` value in its -[`CONNECT` message](https://docs.nats.io/nats-protocol/nats-protocol#connect): - -``` -"lang": "node", -"version": "1.2.3", -"protocol": 1, -"headers": true, -... -``` - -### Publishing Messages With A Header - -Messages that include a header have a `HPUB` protocol: - -``` -HPUB SUBJECT REPLY 23 30␍␊NATS/1.0␍␊Header: X␍␊␍␊PAYLOAD␍␊ -HPUB SUBJECT REPLY 23 23␍␊NATS/1.0␍␊Header: X␍␊␍␊␍␊ -HPUB SUBJECT REPLY 48 55␍␊NATS/1.0␍␊Header1: X␍␊Header1: Y␍␊Header2: Z␍␊␍␊PAYLOAD␍␊ -HPUB SUBJECT REPLY 48 48␍␊NATS/1.0␍␊Header1: X␍␊Header1: Y␍␊Header2: Z␍␊␍␊␍␊ - -HPUB [REPLY] -
-``` - -#### NOTES: - -- `HDR_LEN` includes the entire serialized header, from the start of the version - string (`NATS/1.0`) up to and including the ␍␊ before the payload -- `TOT_LEN` the payload length plus the HDR_LEN - -### MSG with Headers - -Clients will see `HMSG` protocol lines for `MSG`s that contain headers - -``` -HMSG SUBJECT 1 REPLY 23 30␍␊NATS/1.0␍␊Header: X␍␊␍␊PAYLOAD␍␊ -HMSG SUBJECT 1 REPLY 23 23␍␊NATS/1.0␍␊Header: X␍␊␍␊␍␊ -HMSG SUBJECT 1 REPLY 48 55␍␊NATS/1.0␍␊Header1: X␍␊Header1: Y␍␊Header2: Z␍␊␍␊PAYLOAD␍␊ -HMSG SUBJECT 1 REPLY 48 48␍␊NATS/1.0␍␊Header1: X␍␊Header1: Y␍␊Header2: Z␍␊␍␊␍␊ - -HMSG [REPLY] - -``` - -- `HDR_LEN` includes the entire serialized header, from the start of the version - string (`NATS/1.0`) up to and including the ␍␊ before the payload -- `TOT_LEN` the payload length plus the HDR_LEN - -## Decision - -Implemented and merged to master. - -## Consequences - -Use of headers is possible. - -## Compatibility Across NATS Clients - -The following is a list of features to insure compatibility across NATS clients -that support headers. Because the feature in Go client and nats-server leverage -the Go implementation as described above, the API used will determine how header -names are serialized. - -### Case-sensitive Operations - -In order to promote compatibility across clients, this section describes how -clients should behave. All operations are _case-sensitive_. Implementations -should provide an option(s) to enable clients to work in a case-insensitive or -format header names canonically. - -#### Reading Values - -`GET` and `VALUES` are case-sensitive operations. - -- `GET` returns a `string` of the first value found matching the specified key - in a case-sensitive lookup or an empty string. -- `VALUES` returns a list of all values that case-sensitive match the specified - key or an empty/nil/null list. - -#### Setting Values - -- `APPEND` is a case-sensitive, and case-preserving operation. The header is set - exactly as specified by the user. -- `SET` and `DELETE` are case-sensitive: - - `DELETE` removes headers in case-sensitive operation - - `SET` can be considered the result of a `DELETE` followed by an `APPEND`. - This means only exact-match keys are deleted, and the specified value is - added under the specified key. - -#### Case-insensitive Option - -The operations `GET`, `VALUES`, `SET`, `DELETE`, `APPEND` in the presence of a -`case-insensitive` match requirement, will operate on equivalent matches. - -This functionality is constrained as follows: - -- `GET` returns the first matching header value in a case-insensitive match. -- `VALUES` returns the union of all headers that case-insensitive match. If the - exact key is not found, an empty/nil/null list is returned. -- `DELETE` removes the all headers that case-insensitive match the specified - key. -- `SET` is the combination of a case-insensitive `DELETE` followed by an - `APPEND`. -- `APPEND` will use the first matching key found and add values. If no key is - found, values are added to a key preserving the specified case. - -Note that case-insensitive operations are only suggested, and not required to be -implemented by clients, specially if the implementation allows the user code to -easily iterate over keys and values. - -### Multiple Header Values Serialization - -When serializing, entries that have more than one value should be serialized one -per line. While the http Header standard, prefers values to be a comma separated -list, this introduces additional parsing requirements and ambiguity from client -code. HTTP itself doesn't implement this requirement on headers such as -`Set-Cookie`. Libraries, such as Go, do not interpret comma-separated values as -lists. diff --git a/doc/adr/0005-lame-duck-notification.md b/doc/adr/0005-lame-duck-notification.md deleted file mode 100644 index d2ab11a7..00000000 --- a/doc/adr/0005-lame-duck-notification.md +++ /dev/null @@ -1,21 +0,0 @@ -# 5. lame-duck-notification - -Date: 2020-07-20 - -## Status - -Accepted - -## Context - -This document describes the _Lame Duck Mode_ server notification. When a server enters lame duck mode, it removes itself from being advertised in the cluster, and slowly starts evicting connected clients as per [`lame_duck_duration`](https://docs.nats.io/nats-server/configuration#runtime-configuration). This document describes how this information is notified -to the client, in order to allow clients to cooperate and initiate an orderly migration to a different server in the cluster. - - -## Decision - -The server notififies that it has entered _lame duck mode_ by sending an [`INFO`](https://docs.nats.io/nats-protocol/nats-protocol#info) update. If the `ldm` property is set to true, the server has entered _lame_duck_mode_ and the client should initiate an orderly self-disconnect or close. Note the `ldm` property is only available on servers that implement the notification feature. - -## Consequences - -By becoming aware of a server changing state to _lame duck mode_ clients can orderly disconnect from a server, and connect to a different server. Currently clients have no automatic support to _disconnect_ while keeping current state. Future documentation will describe strategies for initiating a new connection and exiting the old one. diff --git a/doc/adr/0006-protocol-naming-conventions.md b/doc/adr/0006-protocol-naming-conventions.md deleted file mode 100644 index 02f4f9a5..00000000 --- a/doc/adr/0006-protocol-naming-conventions.md +++ /dev/null @@ -1,55 +0,0 @@ -# 6. protocol-naming-conventions - -Date: 2021-06-28 - -## Status - -Accepted - -## Context - -This document describes naming conventions for these protocol components: - -* Subjects (including Reply Subjects) -* Stream Names -* Consumer Names -* Account Names - -## Prior Work - -Currently the NATS Docs regarding [protocol convention](https://docs.nats.io/nats-protocol/nats-protocol#protocol-conventions) says this: - -> Subject names, including reply subject (INBOX) names, are case-sensitive and must be non-empty alphanumeric strings with no embedded whitespace. All ascii alphanumeric characters except spaces/tabs and separators which are "." and ">" are allowed. Subject names can be optionally token-delimited using the dot character (.), e.g.: -A subject is comprised of 1 or more tokens. Tokens are separated by "." and can be any non space ascii alphanumeric character. The full wildcard token ">" is only valid as the last token and matches all tokens past that point. A token wildcard, "*" matches any token in the position it was listed. Wildcard tokens should only be used in a wildcard capacity and not part of a literal token. - -> Character Encoding: Subject names should be ascii characters for maximum interoperability. Due to language constraints and performance, some clients may support UTF-8 subject names, as may the server. No guarantees of non-ASCII support are provided. - -## Specification - -``` -dot = "." -asterisk = "*" -lt = "<" -gt = ">" -dollar = "$" -colon = ":" -double-quote = ["] -fwd-slash = "/" -backslash = "\" -pipe = "|" -question-mark = "?" -ampersand = "&" -printable = all printable ascii (33 to 126 inclusive) -term = (printable except dot, asterisk or gt)+ -prefix = (printable except dot, asterisk, gt or dollar)+ -filename-safe = (printable except dot, asterisk, lt, gt, colon, double-quote, fwd-slash, backslash, pipe, question-mark, ampersand) - -message-subject = term (dot term | asterisk)* (dot gt)? -reply-to = term (dot term)* -stream-name = term -queue-name = term -durable-name = term -js-internal-prefix = dollar (prefix dot)+ -js-user-prefix = (prefix dot)+ -account-name = (filename-safe)+ maximum 255 characters -``` diff --git a/doc/adr/0007-error-codes.md b/doc/adr/0007-error-codes.md deleted file mode 100644 index 1521da55..00000000 --- a/doc/adr/0007-error-codes.md +++ /dev/null @@ -1,150 +0,0 @@ -# 7. NATS Server Error Codes - -Date: 2021-05-12 -Author: @ripienaar - -## Status - -Partially Implemented in [#1811](https://github.com/nats-io/nats-server/issues/1811) - -The current focus is JetStream APIs, we will as a followup do a refactor and generalization and move onto other -areas of the server. - -## Context - -When a developer performs a Consumer Info API request she might get a 404 error, there is no way to know if this is -a 404 due to the Stream not existing or the Consumer not existing. The only way is to parse the returned error description -like `consumer not found`. Further several other error situations might arise which due to our code design would be surfaced -as a 404 when in fact it's more like a 5xx error - I/O errors and such. - -If users are parsing our error strings it means our error text form part of the Public API, we can never improve errors, -fix spelling errors or translate errors into other languages. - -This ADR describes an additional `error_code` that provides deeper context into the underlying cause of the 404. - -## Design - -We will adopt a numbering system for our errors where every error has a unique number within a range that indicates the -subsystem it belongs to. - -|Range|Description| -|-----|-----------| -|1xxxx|JetStream related errors| -|2xxxx|MQTT related errors| - -The JetStream API error will be adjusted like this initially with later work turning this into a more generic error -usable in other parts of the NATS Server code base. - -```go -// ApiError is included in all responses if there was an error. -type ApiError struct { - Code int `json:"code"` - ErrCode int `json:"err_code,omitempty"` - Description string `json:"description,omitempty"` - URL string `json:"-"` - Help string `json:"-"` -} -``` - -Here the `code` and `error_code` is what we'll consider part of the Public API with `description` being specifically -out of scope for SemVer protection and changes to these will not be considered a breaking change. - -The `ApiError` type will implement `error` and whenever it will be logged will append the code to the log line, for example: - -``` -stream not found (10059) -``` - -The `nats` CLI will have a lookup system like `nats error 10059` that will show details of this error including help, -urls and such. It will also assist in listing and searching errors. The same could be surfaced later in documentation -and other areas. - -## Using in code - -### Raising an error - -Here we raise a `stream not found` error without providing any additional context to the user, the constant is -`JSStreamNotFoundErr` from which you can guess it takes no printf style interpolations vs one that does which would -end in `...ErrF`: - -The go doc for this constant would also include the content of the error to assist via intellisense in your IDE. - -```go -err = doThing() -if err != nil { - return ApiErrors[JSStreamNotFoundErr] -} -``` - -If we have to do string interpolation of the error body, here the `JSStreamRestoreErrF` has the body -`"restore failed: {err}"`, the `NewT()` will simply use `strings.Replaces()` to create a new `ApiError` with the full string, -note this is a new instance of ApiError so normal compare of `err == ApiErrors[x]` won't match: - -```go -err = doRestore() -if err != nil { - return ApiErrors[JSStreamRestoreErrF].NewT("{err}", err) -} -``` - -If we had to handle an error that may be an `ApiError` or a traditional go error we can use the `ErrOr` function, -this will look at the result from `lookupConsumer()`, if it's an `ApiError` that error will be set else `JSConsumerNotFoundErr` be -returned. Essentially the `lookupConsumer()` would return a `JSStreamNotFoundErr` if the stream does not exist else -a `JSConsumerNotFoundErr` or go error on I/O failure for example. - -```go -var resp = JSApiConsumerCreateResponse{ApiResponse: ApiResponse{Type: JSApiStreamCreateResponseType}} - -_, err = lookupConsumer(stream, consumer) -if err != nil { - resp.Error = ApiErrors[JSConsumerNotFoundErr].ErrOr(err) -} -``` - -While the `ErrOr` function returns the `ApiErrors` pointer exactly - meaning `err == ApiErrors[x]`, the counterpart -`ErrOrNewT` will create a new instance. - -### Testing Errors - -Should you need to determine if a error is of a specific kind (error code) this can be done using the `IsNatsErr()` function: - -```go -err = doThing() -if IsNatsErr(err, JSStreamNotFoundErr, JSConsumerNotFoundErr) { - // create the stream and consumer -} else if err !=nil{ - // other critical failure -} -``` - -## Maintaining the errors - -The file `server/errors.json` holds the data used to generate the error constants, lists etc. This is JSON versions of -`server.ErrorsData`. - -```json -[ - { - "constant": "JSClusterPeerNotMemberErr", - "code": 400, - "error_code": 10040, - "description": "peer not a member" - }, - { - "constant": "JSNotEnabledErr", - "code": 503, - "error_code": 10039, - "description": "JetStream not enabled for account", - "help": "This error indicates that JetStream is not enabled either at a global level or at global and account level", - "url": "https://docs.nats.io/jetstream" - } -] -``` - -The `nats` CLI allow you to edit, add and view these files using the `nats errors` command, use the `--errors` flag to -view your local file during development. - -After editing this file run `go generate` in the top of the `nats-server` repo, and it will update the needed files. Check -in the result. - -When run this will verify that the `error_code` and `constant` is unique in each error diff --git a/doc/adr/0009-js-idle-heartbeat.md b/doc/adr/0009-js-idle-heartbeat.md deleted file mode 100644 index 28d9b9dd..00000000 --- a/doc/adr/0009-js-idle-heartbeat.md +++ /dev/null @@ -1,52 +0,0 @@ -# 9. js-idle-heartbeat - -Date: 2021-06-30 - -## Status - -Accepted - -## Context - -The JetStream ConsumerConfig option `idle_heartbeat` enables server-side -heartbeats to be sent to the client. To enable the option on the consumer simply -specify it with a value representing the number of nanoseconds that the server -should use as notification interval. - -The server will only notify after the specified interval has elapsed and no new -messages have been delivered to the consumer. Delivering a message to the -consumer resets the interval. - -The idle heartbeats notifications are sent to the consumer's subscription as a -regular NATS message. The message will have a `code` of `100` with a -`description` of `Idle Heartbeat`. The message will contain additional headers -that the client can use to re-affirm that it has not lost any messages: - -- `Nats-Last-Consumer` indicates the last consumer sequence delivered to the - client. If `0`, no messages have been delivered. -- `Nats-Last-Stream` indicates the sequence number of the newest message in the - stream. - -Here's an example of a client creating a consumer with an idle_heartbeat of 10 -seconds, followed by a server notification. - -``` -$JS.API.CONSUMER.CREATE.FRHZZ447RL7NR8TAICHCZ6 _INBOX.FRHZZ447RL7NR8TAICHCQ8.FRHZZ447RL7NR8TAICHDQ0 136␍␊ -{"config":{"ack_policy":"explicit","deliver_subject":"my.messages","idle_heartbeat":10000000000}, -"stream_name":"FRHZZ447RL7NR8TAICHCZ6"}␍␊ -... - -> HMSG my.messages 2 75 75␍␊NATS/1.0 100 Idle Heartbeat␍␊Nats-Last-Consumer: 0␍␊Nats-Last-Stream: 0␍␊␍␊␍␊ -alive - last stream seq: 0 - last consumer seq: 0 -``` - -This feature is intended as an aid to clients to detect when they have been -disconnected. Without it the consumer's subscription may sit idly waiting for -messages, without knowing that the server might have simply gone away and -recovered elsewhere. - -## Consequences - -Clients can use this information to set client-side timers that track how many -heartbeats have been missed and perhaps take some action such as re-create a -subscription to resume messages. diff --git a/doc/adr/0010-js-purge.md b/doc/adr/0010-js-purge.md deleted file mode 100644 index 797e84fe..00000000 --- a/doc/adr/0010-js-purge.md +++ /dev/null @@ -1,62 +0,0 @@ -# 10. js-purge - -Date: 2021-06-30 - -## Status - -Accepted - -## Context - -JetStream provides the ability to purge streams by sending a request message to: -`$JS.API.STREAM.PURGE.`. The request will return a new message with -the following JSON: - -```typescript -{ - type: "io.nats.jetstream.api.v1.stream_purge_response", - error?: ApiError, - success: boolean, - purged: number -} -``` - -The `error` field is an [ApiError](0007-error-codes.md). The `success` field will be set to `true` if the request -succeeded. The `purged` field will be set to the number of messages that were -purged from the stream. - -## Options - -More fine-grained control over the purge request can be achieved by specifying -additional options as JSON payload. - -```typescript -{ - seq?: number, - keep?: number, - filter?: string -} -``` - -- `seq` is the optional upper-bound sequence for messages to be deleted - (non-inclusive) -- `keep` is the maximum number of messages to be retained (might be less - depending on whether the specified count is available). -- The options `seq` and `keep` are mutually exclusive. -- `filter` is an optional subject (may include wildcards) to filter on. Only - messages matching the filter will be purged. -- `filter` and `seq` purges all messages matching filter having a sequence - number lower than the value specified. -- `filter` and `keep` purges all messages matching filter keeping at most the - specified number of messages. -- If `seq` or `keep` is specified, but `filter` is not, the stream will - remove/keep the specified number of messages. -- To `keep` _N_ number of messages for multiple subjects, invoke `purge` with - different `filter`s. -- If no options are provided, all messages are purged. - -## Consequences - -Tooling and services can use this endpoint to remove messages in creative ways. -For example, a stream may contain a number of samples, at periodic intervals a -service can sum them all and replace them with a single aggregate.