Skip to main content

client-query-cache

Client-side caching for PyMongo, kept coherent using MongoDB change streams. For Python applications that already talk to MongoDB through PyMongo directly, it adds a coherent read cache without introducing a separate cache server or changing how you connect. The library supports synchronous and asyncio clients.

It caches reads whose results the manager can invalidate correctly when the underlying data changes, and leaves everything else — including all writes — to go straight to MongoDB. Invalidation is asynchronous: a read running concurrently with a write can still return the previous cached value until the manager processes that write's change-stream event.

Built for production: 100% covered, extensively tested from cache-core invariants through real MongoDB deployments, continuously benchmarked, and protected by an automated pull-request performance regression guard.

Cached reads are up to about 1,200 times faster than a direct read, and roughly the same speed whether the server is local or a real remote deployment. Direct local server read 120 microseconds, direct real deployment (Atlas M0 free tier) read 79.4 milliseconds, cached read about 61 microseconds either way. Bars use a logarithmic scale.

Read latency across two different deployments, so you can see the range: the local-server row is the median from one of the retained local benchmark reports; the M0-deployment row is the mean of one batch from the real-server benchmark against a free-tier Atlas (M0) cluster — plotted on a logarithmic axis given the size of the gap. Cached-read latency barely moves between the two, since a cache hit never touches the network. Neither number is a universal performance guarantee for your own workload or deployment — see Stream cost benchmarks for the full local workload matrix and how to reproduce it.


Requirements

client-query-cache requires Python 3.14.6 or newer.

Reads and writes work against any MongoDB deployment PyMongo supports. Effective caching needs two separate things: a replica set or sharded cluster, since MongoDB only provides change streams on one of those topologies, not a standalone server; and MongoDB 8.0 or newer, a floor this library enforces itself at startup rather than a limit of change streams themselves. Against a deployment that doesn't meet both, the manager doesn't raise: it logs a warning and bypasses the cache for that database, executing every read as a normal, uncached PyMongo call.

Install

uv add client-query-cache

Or with pip: pip install client-query-cache.

Usage

CacheManager wraps a pymongo.MongoClient (or pymongo.AsyncMongoClient) that you construct and own. Its database and collection facades cache a narrow set of PyMongo's own read methods — find_one, find, aggregate, count_documents, estimated_document_count, and distinct — and keep cached results coherent as the underlying data changes. Every other operation, including all writes, is called directly on the facade the same way you'd call it on the wrapped PyMongo object:

from pymongo import MongoClient

from client_query_cache import CacheManager

with (
    MongoClient("mongodb://localhost:27017") as client,
    CacheManager(client) as manager,
):
    collection = manager["my_database"]["my_collection"]
    collection.insert_one({"_id": "example", "value": 42})

    collection.find_one({"_id": "example"})  # cache miss: reads from MongoDB
    collection.find_one({"_id": "example"})  # cache hit: served from the cache

    # Bridge stats like this into OpenTelemetry:
    # docs/architecture.md#opentelemetry-metrics
    print(manager.cache_core.snapshot().hits)  # 1

    collection.create_index("email", unique=True)
    collection.insert_one({"_id": "user-1", "email": "a@example.com"})
    collection.find_one({"email": "a@example.com"})  # also cached, like an `_id` lookup

find_one caches a lookup by _id and by any other field the database enforces as unique, discovered automatically from the collection's own indexes — there's nothing to declare. Only a plain unique index qualifies: a partial, sparse, or hashed unique index, or a read whose collation doesn't match the index's collation, falls back to an uncached read instead.

CacheManager starts a background change-stream task the first time a read touches a database, so close it (or use it as a context manager, as above) alongside the client — closing only the client leaves that background task running against a closed connection.

The same facades are available for pymongo.AsyncMongoClient under client_query_cache.asynchronous, with the same methods as coroutines:

import asyncio

from pymongo import AsyncMongoClient

from client_query_cache.asynchronous import CacheManager


async def main() -> None:
    async with (
        AsyncMongoClient("mongodb://localhost:27017") as client,
        CacheManager(client) as manager,
    ):
        collection = manager["my_database"]["my_collection"]
        await collection.insert_one({"_id": "example", "value": 42})

        await collection.find_one({"_id": "example"})  # cache miss
        await collection.find_one({"_id": "example"})  # cache hit


asyncio.run(main())

A read bypasses the cache instead of using it whenever caching it safely isn't possible — for example, a caller-selected session, read preference, or read concern; a view; a nondeterministic or cross-collection aggregation pipeline; a time-series collection; or a database whose change stream isn't healthy. See Bypass conditions in the API reference for the complete list.

Scope and constraints

  • Writes aren't cached — only the six read methods listed above are. See why only reads are cached for the rationale.
  • Each CacheManager is independent — it owns its own in-process cache and its own change-stream cursors; nothing is shared between managers or processes. See capacity planning before creating one per request or one per worker process.

Documentation

  • API reference — the complete public surface: construction, configuration, limits, ownership, and raw fallback.
  • Architecture and operations — system requirements, capacity planning, retry/error handling, observability, security, and recovery behavior.
  • Stream cost benchmarks — whether caching fits your workload, and the controlled benchmark reports backing that guidance.

MongoDB is a registered trademark of MongoDB, Inc. This project is independent and is not affiliated with, sponsored by, or endorsed by MongoDB, Inc.

Metadata

Release files for client-query-cache 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for client-query-cache 0.1.0
File Size Uploaded
client_query_cache-0.1.0.tar.gz 33.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for client-query-cache 0.1.0
File Interpreter ABI Platform
client_query_cache-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 82.5 kB

Release files / client_query_cache-0.1.0.tar.gz

Download URL client_query_cache-0.1.0.tar.gz
Size 33.8 kB
Tags Source
SHA-256 checksum
How to use checksums
0530b592101688185d6d2490762faacacb1d298ab01410edff245c76d38fc58a
BLAKE2b-256 checksum
How to use checksums
1d0aa1342d2019a5d68e7209dc6238f61357af9e580442fa380a40354a28b870
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 30, 2026.

Transparency log

Release files / client_query_cache-0.1.0-py3-none-any.whl

Download URL client_query_cache-0.1.0-py3-none-any.whl
Size 48.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
28f1b5ca07d92d3b9ef45c88738007b81bef4c4f5926ee558ec419fee551451c
BLAKE2b-256 checksum
How to use checksums
57abda78028677192a21658051ca59d552161d9d4d77675ad19f33851647a8cb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 30, 2026.

Transparency log

Release history Release notifications | RSS feed

0.8.0

1 release file

0.7.0

2 release files

0.6.0

2 release files

0.5.0

2 release files

0.4.0

2 release files

0.3.0

2 release files

0.2.0

2 release files

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page