Skip to content

Comparison

How grelmicro compares to the focused libraries that cover one pattern each.

Most of these libraries are correct in one process. grelmicro is correct across all of your replicas, on the database you already run: Redis, Valkey, PostgreSQL or SQLite. A job scheduled with gate="claim" runs once per interval across replicas, not once per replica. A rate limit counts one budget for every replica. A circuit breaker shares its state, so a downstream that failed on one replica is not called again from the others.

The breadth comes second. Most Python libraries in this space are point solutions: one library for caching, another for rate limiting, another for distributed locks, another for retries, another for circuit breaking, another for health checks. Each ships its own config story, its own lifecycle, its own backend client, its own logging shape.

grelmicro ships these patterns behind one API. One config contract. One lifecycle (micro.install(app)). One Redis client shared by Cache, Lock, RateLimiter, CircuitBreaker. One Postgres client shared by Lock, LeaderElection, TaskLock. One way to reconfigure at runtime. One way to declare health checks.

If you run a single process and need a single pattern, a focused library is often the right pick and this page will say so. If you run more than one replica, or need two or more patterns, grelmicro removes the gaps between them.

Moving from APScheduler, slowapi or aioredlock? Coming from APScheduler, slowapi or aioredlock maps each one, side by side.

What should I pick?

A short decision tree before the detailed tables:

  • My service runs on more than one replica and a job, a limit or a breaker must hold across all of them: pick grelmicro. That is the case it is built for.
  • I need exactly one primitive in one process (one cache, one rate limiter, one breaker): pick the focused library for that primitive. The Quick comparison below names the right one for each Pattern.
  • I need two or more resilience or infrastructure primitives in the same async service: pick grelmicro. One toolkit replaces four or five config dialects.
  • I need to schedule background work that survives process restart, retries on failure, and runs across many workers: pick a task queue (Celery, Dramatiq, ARQ). grelmicro's Tasks runs in-process periodic work, not durable jobs.
  • I need long-running workflows with checkpoints, branches, or human-in-the-loop steps: pick a workflow engine (Temporal, Prefect, Dagster). grelmicro does not orchestrate workflows.
  • I need a web framework: pick FastAPI, Starlette, Litestar. grelmicro plugs into them, it does not replace them.

Quick comparison

If you only need this Use this Pick grelmicro when you also need
In-process job scheduler APScheduler A job that runs once per interval or cron fire across replicas (gate="claim"), not once per process
Async cache decorator over Redis aiocache or fastapi-cache Stampede protection (lock="cluster" folds misses across replicas, early= XFetch refresh), type-safe TTLCache[T], Postgres backend
FastAPI rate limiter slowapi or fastapi-limiter Sliding-window algorithm, structured RateLimitResult for retry-after headers, swap Memory and Redis with the same API
Async circuit breaker pybreaker or aiobreaker Reconfigurable thresholds, frozen Pydantic config, structured logging context, fleet-wide state on Redis or Postgres
Retry with @retry(stop=, wait=) tenacity A Retry that shares the same config + reconfigure shape as the rest of grelmicro
Redis distributed lock aioredlock Same Lock primitive across Redis, PostgreSQL, SQLite, Kubernetes, in-memory. Adapter-agnostic protocol
/healthz endpoint hand-rolled FastAPI handler Concurrent check execution, per-check TTL cache, /livez + /readyz + /healthz triple, critical vs non-critical, FastAPI router included

The pattern across every row: pick the focused library when you only need that one Pattern. Pick grelmicro when you need two or more, with a single config and lifecycle story across all of them.

One toolkit, every pattern

What you get from a single import:

Pattern grelmicro class Backends
Distributed lock Lock Redis, PostgreSQL, SQLite, Kubernetes Lease, Memory
Leader election LeaderElection Redis, PostgreSQL, Kubernetes Lease, Memory (no SQLite)
Scheduled-task lock TaskLock same as Lock
Cache decorator + TTL store Cache, TTLCache[T], @cached Redis, Memory, Postgres, SQLite
Rate limiter RateLimiter (token bucket, sliding window) Redis, Memory, Postgres, SQLite
Circuit breaker CircuitBreaker Redis, Memory, Postgres, SQLite
Idempotency Idempotency, @idempotent Redis, Memory, Postgres, SQLite
Retry Retry, @retry n/a (in-process)
In-process resilience Bulkhead, Fallback, Timeout, Shield n/a (in-process)
Health checks HealthChecks + /livez /readyz /healthz router n/a
Scheduled tasks Tasks, TaskRouter, @every, @cron n/a
Structured logging grelmicro.log (json, logfmt, pretty, auto) n/a
Tracing grelmicro.trace (@instrument, span, add_context) OpenTelemetry
Metrics Metrics, @measure, metrics_router OpenTelemetry, Prometheus
Externalized config ExternalConfig ConfigMap, Secret, .env, JSON, YAML, TOML

All of them share:

  • One config contract. Every Pattern reads GREL_{PATTERN}_{NAME}_* env vars, or accepts a kwargs constructor, or accepts a Pydantic Config. Three paths, identical shape across Patterns.
  • One lifecycle. micro.install(app) opens every backend, every component, every Pattern when the app starts and closes them when it stops. No per-library startup()/shutdown() ceremony.
  • One Redis / Postgres / SQLite client. Cache, Lock, RateLimiter on the same Redis don't open three connections. The shared client is a Provider. Patterns receive Adapters.
  • One reconfigure protocol. Every Pattern that owns runtime thresholds (CircuitBreaker, RateLimiter, Retry, Lock) supports reconfigure(new_config) without restart.
  • One logging context. Every Pattern logs through the same structured format, with the same field names.

No focused library in the Python ecosystem covers more than two of these Patterns. The closest thing in other ecosystems is the resilience-pipeline class of libraries (Java, .NET), and none of those bundle distributed locks, caches, leader election, scheduled tasks, and health checks into the same toolkit.

If your service runs on more than one replica, grelmicro keeps these patterns correct across all of them.

Cache

@cached decorator + TTLCache over Redis or in-memory.

Axis aiocache fastapi-cache grelmicro Cache
Backends Memory, Redis, Memcached Memory, Redis, Memcached, DynamoDB Memory, Redis, Postgres, SQLite
Decorator @cached @cache @cached
Type-safe Cache[T] no no yes (TTLCache[T] plus PydanticSerializer(T))
Stampede protection local lock via lock_value none lock="process" (default, folds in one process), lock="cluster" (folds across replicas through a Coordination lock backend), early= (XFetch refresh)
Serializers several built-in json, binary JsonSerializer, PydanticSerializer, PickleSerializer
FastAPI integration manual first-class works with any async framework, no FastAPI coupling

Pick aiocache if you only need the cache primitive and want Memcached. Pick fastapi-cache if you want the most FastAPI-native surface and DynamoDB. Pick grelmicro when you also need a distributed Lock, RateLimiter, or CircuitBreaker on the same Redis client and want one config story across all of them.

Rate Limiter

Cap requests per window with token bucket or sliding window, per key.

Axis slowapi fastapi-limiter aiolimiter grelmicro RateLimiter
Algorithm fixed/sliding window fixed window token bucket token bucket, sliding window
Backends Memory, Redis, Memcached, MongoDB Redis only in-process only Memory, Redis, Postgres, SQLite
Async-first partial (Flask-Limiter port) yes yes yes
Result shape for retry-after string parsing string parsing none RateLimitResult(allowed, limit, remaining, retry_after, reset_after)
Reconfigurable at runtime no no no yes (reconfigure(new_config))
FastAPI integration first-class first-class manual works in any async framework

Pick slowapi if you want fixed-window per-route limiting on FastAPI and a wider backend choice. Pick aiolimiter for the smallest in-process token bucket. Pick grelmicro when you also need a distributed Lock or Cache on the same backend, when you need precise sliding-window semantics, or when you need a structured retry-after result without parsing strings.

Circuit Breaker

Half-open / open / closed states, with thresholds and ignore lists.

Axis pybreaker aiobreaker grelmicro CircuitBreaker
Async-first sync-first, has async wrapper yes yes
Storage in-memory, Redis (via plugin) in-memory in-memory, Redis, Postgres, SQLite (fleet-wide state on Redis or Postgres)
Decorator + async CM decorator + sync CM decorator + async CM decorator + async CM
Frozen Pydantic config no no yes (CircuitBreakerConfig)
Live reconfigure no no yes (reconfigure(new_config))
Structured logging context no no yes (the breaker logs name, state, last_error)
Listener hooks yes (add_listener) basic structured logs are first-class (name, state, last_error)

Pick pybreaker if you have a sync-first codebase or want listener hooks today. Pick aiobreaker if you want a small async-only breaker with no other dependencies. Pick grelmicro when you also need a Retry or Lock that shares the same config story, or when you need to swap thresholds at runtime without restart.

Retry

Decorator and async context manager with stop / wait / retry conditions.

Axis tenacity backoff grelmicro Retry
Stop conditions rich (stop_after_attempt, stop_after_delay, ...) basic attempts= (count) and max_seconds= (time budget), whichever comes first.
Wait strategies rich (wait_exponential, wait_chain, ...) exponential, fibonacci, constant exponential, constant, linear, fibonacci, random
Retry condition retry_if_* factories, \|/& operators exception type or predicate Match.exception(...), Match.result(...), Match.exception_message(...), Match.exception_cause(...), plus not_* twins, \|/& operators
Async support yes yes yes
Sync support yes yes yes (decorator works on both)
Frozen config + reconfigure no no yes
Shares filter DSL with other resilience Patterns n/a n/a yes (Match ships with Retry, reused across the resilience module)

tenacity is the right pick for the most advanced stop and wait vocabulary. Pick grelmicro Retry when you want a smaller surface, result-based retry out of the box (Match.result(None)), and a filter DSL (Match) shared with the other resilience Patterns in the same library.

Distributed Lock

Mutex across processes, replicas, or hosts.

Axis aioredlock redis-py lock hand-rolled Postgres advisory lock grelmicro Lock
Algorithm Redlock SET NX with Lua release pg_advisory_xact_lock SET NX + Lua release in Redis, advisory locks in Postgres, coordination.k8s.io/v1 Lease with resourceVersion CAS in Kubernetes
Backends Redis only Redis only Postgres only Redis, Postgres, SQLite, Kubernetes, Memory
Async-first yes partial depends on driver yes
Token-based release yes yes n/a (transaction-scoped) yes (token derived from worker id + task or thread id, deterministic so re-acquire extends the lease)
Reentrant detection manual no n/a yes (raises LockReentrantError)
Idempotent acquire (lease extension) partial manual n/a yes (the same token re-acquire extends the lease)
from_thread adapter for blocking code no no n/a yes
Reconfigurable lease and retry no no n/a yes
Same primitive backs leader election and task lock no no n/a yes (LeaderElection and TaskLock share the protocol)

Pick aioredlock if you want the Redlock algorithm specifically. Pick a hand-rolled pg_advisory_lock if Postgres is your only backend and you only need one lock kind. Pick grelmicro when you want one Lock primitive across multiple backend choices, plus LeaderElection and TaskLock on the same protocol.

Health Checks

Liveness, readiness, and aggregate endpoints for orchestrators and load balancers.

Axis hand-rolled FastAPI handler py-healthcheck grelmicro HealthChecks
Concurrent check execution manual asyncio.gather no yes (asyncio.TaskGroup)
Per-check timeout manual no yes
Per-check TTL cache + single-flight manual no yes (default cache_ttl=1)
Critical vs non-critical manual no yes (non-critical never flips /readyz)
/livez + /readyz + /healthz triple hand-rolled no yes (FastAPI router included)
?exclude query manual no yes
Verbose details gated by Depends(...) manual no yes (show_details=Depends(fn))

Pick a hand-rolled handler if you only need one boolean endpoint. Pick grelmicro when you want the orchestrator-grade triple (/livez, /readyz, /healthz) with concurrent execution, caching, and details gated by a FastAPI dependency.

Authentication

Verify the bearer token a caller presents before any handler runs.

Axis FastAPI fastapi.security Starlette AuthenticationMiddleware Litestar JWTAuth grelmicro AuthenticatedRequests
Token verification none, the dependency hands you the raw token none, you write the backend one key or secret you hold JWKS, OpenID Connect or RFC 8414 discovery, keys rotated in the background
What is authenticated each route that declares it, or every route through the app's dependencies= with no way to open one each route that declares @requires every handler, opt out with exclude or opt={"exclude_from_auth": True} every route, opt out with exclude or Anonymous()
Unknown path without a token 404 404 404 401, so the routes cannot be listed
Refusal 401 with a bare Bearer challenge 403 from @requires, 400 in plain text from the backend 401, no challenge RFC 6750 challenge with error and scope, in the app's ErrorResponses format
Scopes Security(scopes=...), checked by your code @requires("scope") a guard you write Authenticated(scopes=...), 403 naming the missing ones
A caller that keeps forging nothing nothing nothing bans= answers 429 before verifying again
OpenAPI per route none one requirement for the whole app, excluded handlers included per operation with its scopes, public routes left open, on FastAPI and Litestar
Where to get a token none none none resource= publishes RFC 9728 protected resource metadata and points every challenge at it
Your user object a dependency you write returned by the backend you write retrieve_user_handler a Principal (issuer, subject, scopes, claims), loaded into your own model by you
Revoked tokens your code your backend revoked_token_handler check= refuses the caller or returns your user, on every request and websocket handshake
Login and cookies OAuth2PasswordRequestForm reads a login form, issuing the token is yours none JWTCookieAuth, OAuth2PasswordBearerAuth and a login helper that issues the token out of scope: it verifies the tokens an identity provider issued

Pick Litestar's JWTAuth when the service holds its own signing key and maps each token to a user object with retrieve_user_handler. Pick grelmicro when tokens come from an identity provider that rotates its keys, or when you want the challenge, the scopes and the schema to follow each operation. On Litestar, Anonymous() is the same exclude_from_auth key, so handlers move between the two unchanged.

What grelmicro is NOT

A few categories the comparison page does not cover, because grelmicro does not compete in them:

Category Use this instead
HTTP framework FastAPI, Starlette, Litestar
Embedded server Uvicorn, Hypercorn, Granian
Publish/subscribe and message brokers FastStream, aio_pika
Background workers (queues) Celery, dramatiq, taskiq
ORM SQLAlchemy, SQLModel, tortoise-orm
Login and token issuing Authlib, authx, fastapi-users
Service mesh / discovery Istio, Linkerd, Kubernetes DNS, Consul
API gateway Envoy, Nginx, Kong

grelmicro fills the gap between "I picked FastAPI for HTTP" and "I need a real distributed lock, rate limit, circuit breaker, cache, leader election, scheduled tasks, and health checks". It does not try to replace the rows above.