# Cached Query (/sdks/cpp/how-to/rpc/query-cached)



## Overview [#overview]

**Query response caching** lets the broker answer repeat requests without re-running your handler — useful when a query is expensive to compute (a database lookup, an aggregation, a downstream call) but the same input is asked for repeatedly in a short window. Only the first request pays the processing cost; every other caller gets the same answer straight from the broker.

Set `SetCacheKey(...)` and `SetCacheTTL(...)` on the query builder. The first query with a given key is a miss: it reaches the handler, and the broker stores the response under that key for the TTL. A subsequent query with the same key is a hit — the broker returns the stored response directly without invoking the handler. `cache_hit` on the response tells you which happened.

**Gotchas:** the cache is keyed by the string you choose, not by the query body — if the underlying data changes mid-TTL, callers can get a stale answer until it expires. Keys are scoped per channel, so the same key on another channel is a separate entry. Caching only helps when requests genuinely repeat with the same key.

## Prerequisites [#prerequisites]

* KubeMQ server running on `localhost:50000`
* C++ SDK installed (vcpkg or CMake FetchContent)
* C++17 compiler (GCC 9+, Clang 9+, MSVC 2019+)

## Code [#code]

```cpp title="main.cc"
// Example: queries/cached_query
//
// Demonstrates query caching with CacheKey and CacheTTL.
// The first query triggers the handler; subsequent queries with the same
// cache key are served from cache (cache_hit=true) without calling the handler.
//
// Channel: cpp-queries.cached-query
// Client ID: cpp-queries-cached-query-client
//
// Run with a KubeMQ server on localhost:50000
// (see https://docs.kubemq.io/deploy).

#include <kubemq/kubemq.h>

#include <atomic>
#include <chrono>
#include <iostream>
#include <thread>

int main() {
    std::cout << "[1] Connecting to localhost:50000" << std::endl;

    kubemq::ClientOptions options;
    options.set_address("localhost", 50000);
    options.set_client_id("cpp-queries-cached-query-client");

    auto client_result = kubemq::Client::Create(options);
    if (!client_result.ok()) {
        std::cerr << "[ERROR] Failed to create client: " << client_result.status().message()
                  << std::endl;
        return 1;
    }
    auto& client = *client_result;

    std::string channel = "cpp-queries.cached-query";
    std::atomic<int> handler_calls{0};

    // Register a query handler.
    std::cout << "[2] Subscribing to queries on channel " << channel << std::endl;
    auto sub_result = client->SubscribeToQueries(
        channel, "",
        [&client, &handler_calls](const kubemq::QueryReceive& q) {
            int call_num = handler_calls.fetch_add(1) + 1;
            std::cout << "[*] Handler called (call #" << call_num << "): body=" << q.body
                      << std::endl;

            auto now_epoch = std::chrono::duration_cast<std::chrono::seconds>(
                                 std::chrono::system_clock::now().time_since_epoch())
                                 .count();
            auto reply_result = kubemq::QueryReply::Builder()
                                    .SetRequestId(q.id)
                                    .SetResponseTo(q.response_to)
                                    .SetBody("cached-result")
                                    .SetExecuted(true)
                                    .SetExecutedAt(now_epoch)
                                    .Build();
            if (!reply_result.ok()) {
                std::cerr << "[ERROR] Build reply: " << reply_result.status().message()
                          << std::endl;
                return;
            }
            auto send_status = client->SendQueryResponse(*reply_result);
            if (!send_status.ok()) {
                std::cerr << "[ERROR] SendQueryResponse: " << send_status.message() << std::endl;
            }
        },
        [](const kubemq::Status& err) {
            std::cerr << "[ERROR] Subscription error: " << err.message() << std::endl;
        });
    if (!sub_result.ok()) {
        std::cerr << "[ERROR] SubscribeToQueries: " << sub_result.status().message() << std::endl;
        return 1;
    }
    auto& sub = *sub_result;

    // Allow subscription to fully establish before sending.
    std::this_thread::sleep_for(std::chrono::milliseconds(300));

    // First query: handler is called, result is cached for 60 seconds.
    std::cout << "[3] Sending first query (cache miss expected)" << std::endl;
    auto q1_build = kubemq::Query::Builder()
                        .SetChannel(channel)
                        .SetBody("cacheable-query")
                        .SetTimeout(std::chrono::seconds(10))
                        .SetCacheKey("my-cache-key")
                        .SetCacheTTL(std::chrono::milliseconds(60000))
                        .Build();
    if (!q1_build.ok()) {
        std::cerr << "[ERROR] Build query 1: " << q1_build.status().message() << std::endl;
        return 1;
    }
    auto q1_resp = client->SendQuery(*q1_build);
    if (!q1_resp.ok()) {
        std::cerr << "[ERROR] SendQuery 1: " << q1_resp.status().message() << std::endl;
        return 1;
    }
    std::cout << "[4] First query:  executed=" << std::boolalpha << q1_resp->executed
              << " cache_hit=" << q1_resp->cache_hit << " body=" << q1_resp->body << std::endl;

    // Wait for handler to complete.
    std::this_thread::sleep_for(std::chrono::milliseconds(500));

    // Second query with same cache key: served from cache (no handler call).
    std::cout << "[5] Sending second query (cache hit expected)" << std::endl;
    auto q2_build = kubemq::Query::Builder()
                        .SetChannel(channel)
                        .SetBody("cacheable-query")
                        .SetTimeout(std::chrono::seconds(10))
                        .SetCacheKey("my-cache-key")
                        .SetCacheTTL(std::chrono::milliseconds(60000))
                        .Build();
    if (!q2_build.ok()) {
        std::cerr << "[ERROR] Build query 2: " << q2_build.status().message() << std::endl;
        return 1;
    }
    auto q2_resp = client->SendQuery(*q2_build);
    if (!q2_resp.ok()) {
        std::cerr << "[ERROR] SendQuery 2: " << q2_resp.status().message() << std::endl;
        return 1;
    }
    std::cout << "[6] Second query: executed=" << std::boolalpha << q2_resp->executed
              << " cache_hit=" << q2_resp->cache_hit << " body=" << q2_resp->body << std::endl;

    // Cancel the subscription explicitly.
    // Note: The Subscription destructor also calls Cancel(), but explicit
    // cleanup is shown here for clarity and to match Go's defer pattern.
    sub->Cancel();
    std::cout << "[7] Subscription cancelled" << std::endl;

    auto close_status = client->Close();
    if (!close_status.ok()) {
        std::cerr << "[ERROR] Close failed: " << close_status.message() << std::endl;
        return 1;
    }
    std::cout << "[8] Client closed" << std::endl;

    return 0;
}
```

## How It Works [#how-it-works]

* Sends a query with `SetCacheKey()` and `SetCacheTTL()` to enable server-side caching.
* The first query triggers the handler; the result is cached for the specified TTL.
* Subsequent queries with the same cache key are served from cache (`cache_hit=true`).
* The response `cache_hit` field indicates whether the result came from cache.

## Related [#related]

* [Pattern overview](/learn/rpc/getting-started)
* [C++ SDK Reference](/sdks/cpp/reference/rpc)
* [Send Query](/sdks/cpp/tutorials/query-send)
* [Handle Query](/sdks/cpp/how-to/rpc/query-handle)
