Skip to main content

natsio extension: distributed counters (ADR-49) over JetStream counter streams

Project description

natsio-counters

Distributed counters over JetStream counter streams (ADR-49), mirroring orbit.go/counters. Distribution natsio-counters, imported as natsio.counters. Zero runtime dependencies beyond natsio. Pre-1.0, no API-stability promises.

Requires nats-server 2.12+ (the allow_msg_counter stream feature).

What it is

A counter stream is a normal JetStream stream created with allow_msg_counter. Every subject the stream captures becomes an independent, arbitrary-precision counter. Incrementing is an ordinary publish carrying a Nats-Incr header; the server folds the delta into a running total, stores it as the subject's latest message, and returns the new value in the PubAck — so a bare add needs no follow-up read.

Python int is itself unbounded, so it is used directly for both deltas and values — no big-integer wrapper.

Usage

import natsio
from natsio.counters import CounterConfig, create_counter, get_counter

nc = await natsio.connect("nats://localhost")
js = nc.jetstream()

# Create a counter stream (allow_msg_counter + allow_direct are forced on).
counter = await create_counter(js, CounterConfig(name="COUNTS", subjects=["events.>"]))
# ...or bind an existing one:  counter = await get_counter(js, "COUNTS")

# Increment / decrement — returns the new running total, straight from the PubAck.
await counter.add("events.orders", 1)      # -> 1
await counter.add("events.orders", 10)     # -> 11
await counter.add("events.orders", -4)     # negative deltas are fine  -> 7
await counter.add("events.orders", 0)      # zero is a valid no-op increment

# Read a single counter's current value.
value = await counter.load("events.orders")          # -> 7

# Full entry: value + most-recent increment + source history (aggregation only).
entry = await counter.get("events.orders")
print(entry.value, entry.incr, entry.sources)

# Enumerate many counters in one batch Direct Get (wildcards allowed).
async for entry in counter.get_multiple(["events.>"]):
    print(entry.subject, entry.value)

API

Symbol Purpose
create_counter(js, config) Create a counter stream and return its Counter.
get_counter(js, name) Bind an existing counter stream by name.
counter_from_stream(js, stream) Wrap an already-fetched counter Stream (no round-trip).
Counter.add(subject, delta) -> int Increment/decrement; returns the new total.
Counter.load(subject) -> int Current value of one subject's counter.
Counter.get(subject) -> CounterEntry Value + last increment + sources.
Counter.get_multiple(subjects) -> AsyncIterator[CounterEntry] Batch/wildcard enumeration.
CounterConfig Counter-stream configuration.
CounterEntry subject, value, sources, incr.

Errors (all subclass natsio.jetstream.JetStreamError): CounterNotEnabledError, DirectAccessRequiredError, CounterNotFoundError, CounterSubjectNotInitializedError, InvalidCounterValueError.

ADR-49 wire contract

Pinned by the wire-contract tests and exported as constants:

  • Increment header Nats-Incr (COUNTER_INCREMENT_HEADER) — a base-10 integer of any size, optional leading sign. A counter stream rejects any publish without it (message counter increment is missing).
  • Sources header Nats-Counter-Sources (COUNTER_SOURCES_HEADER) — server-set JSON {source_stream: {subject: contribution}}; present only on aggregating counters.
  • Stored payload {"val": "<int-as-string>"} — the value is a string so it survives languages whose JSON numbers cap at 2^53.
  • PubAck carries the new total in its val field (fast feedback, no Get).

Notes: allow_msg_counter can be set only at stream creation (it is read-only afterwards) and is incompatible with per-message TTLs and message schedules, so CounterConfig intentionally does not expose those knobs. Reads use Direct Get, so allow_direct is required (and forced on by create_counter).

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

natsio_counters-0.1.0.tar.gz (7.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

natsio_counters-0.1.0-py3-none-any.whl (9.8 kB view details)

Uploaded Python 3

File details

Details for the file natsio_counters-0.1.0.tar.gz.

File metadata

  • Download URL: natsio_counters-0.1.0.tar.gz
  • Upload date:
  • Size: 7.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for natsio_counters-0.1.0.tar.gz
Algorithm Hash digest
SHA256 344ed5aaeba7de0c15e9374ba8a408ed4e018185fa7382cbfc3d3e5bd1ff6ce7
MD5 f49710219836377387d2f01457394041
BLAKE2b-256 2622c6c46d80ea8bb593174601a17c3759a1c8dde92f8c4e51d44b1ea9f2d428

See more details on using hashes here.

Provenance

The following attestation bundles were made for natsio_counters-0.1.0.tar.gz:

Publisher: release-extension.yml on corruptmane/natsio

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file natsio_counters-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for natsio_counters-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 4e4b1051bf53eb9f03f3a418b0630b6c4529441ad6aa83e339ef4cf1f6b33285
MD5 48cc9bef156042597b63af0ff4d840f6
BLAKE2b-256 b8df8ea7ceec338e06234fd82824a266d5041520fba9a760e1d1a51e8361428d

See more details on using hashes here.

Provenance

The following attestation bundles were made for natsio_counters-0.1.0-py3-none-any.whl:

Publisher: release-extension.yml on corruptmane/natsio

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page