# Query Group (/sdks/csharp/how-to/rpc/query-group)



## Overview [#overview]

A **consumer group** scales query handling horizontally without touching the caller's side. Instead of one process answering every query on a channel, you run several identical handler instances under the same group name, and the broker routes each query to exactly one member — never to all of them. That turns a single responder into a pool you can grow or shrink to match load, which matters for anything RPC-shaped: a lookup service, a cache-fill handler, a synchronous read path behind an API.

It works by tying group membership to the subscription: setting `Group` on `QueriesSubscription` passed to `client.SubscribeToQueriesAsync` load-balances across every subscriber sharing that `Channel` and `Group`. The sender calls `client.SendQueryResponseAsync` and the initial `SendQuery` exactly as it would against a single handler — it never knows how many members exist or which one answered.

**Gotchas:** channel and group name must match exactly, or a typo quietly creates a second, empty group instead of erroring. Omit `Group` and every subscriber reverts to broadcast, each answering independently. A stuck group member isn't bypassed — the caller just sees a timeout.

## Prerequisites [#prerequisites]

* KubeMQ server running on `localhost:50000`
* C# SDK installed (`dotnet add package KubeMQ.SDK.CSharp`)

## Code [#code]

```csharp title="Program.cs"
// KubeMQ .NET SDK — Queries: Consumer Group Subscription
//
// This example demonstrates subscribing to queries with a consumer group.
// When multiple handlers join the same group, queries are load-balanced across them
// so that only one handler in the group processes each query.
//
// Prerequisites:
//   - KubeMQ server running on localhost:50000
//   - Run Queries.SendQuery in a separate terminal to send queries
//   - dotnet run

using KubeMQ.Sdk.Client;
using KubeMQ.Sdk.Queries;
using System.Text;

await using var client = new KubeMQClient(new KubeMQClientOptions
{
    ClientId = "csharp-queries-consumer-group-client",
});
await client.ConnectAsync();

Console.WriteLine("Subscribed to queries with consumer group 'handler-group'...");

var cts = new CancellationTokenSource();
Console.CancelKeyPress += (_, e) =>
{
    e.Cancel = true;
    cts.Cancel();
};

await foreach (var query in client.SubscribeToQueriesAsync(
    new QueriesSubscription { Channel = "csharp-queries.consumer-group", Group = "handler-group" },
    cts.Token))
{
    var body = Encoding.UTF8.GetString(query.Body.Span);
    Console.WriteLine($"Query: {query.RequestId} — {body}");

    var responseBody = Encoding.UTF8.GetBytes("{\"status\":\"ok\",\"handler\":\"group-member\"}");

    await client.SendQueryResponseAsync(new QueryResponse
    {
        RequestId = query.RequestId,
        ReplyChannel = query.ReplyChannel!,
        Body = responseBody,
        Executed = true,
    });

    Console.WriteLine("  -> Responded with data");
}

Console.WriteLine("Done.");

```

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

* `Group = "handler-group"` in `QueriesSubscription` registers this handler as a member of a named load-balancing group. Each incoming query is routed to exactly one member of the group.
* Spin up multiple instances (different `ClientId`) to distribute query load horizontally. The broker round-robins across registered group members.
* Each handler must call `SendQueryResponseAsync` with `RequestId` and `ReplyChannel` from the received query for the broker to return the response to the sender.
* Omit `Group` to switch to broadcast: every subscriber independently receives every query — useful for fan-out read models.

## Related [#related]

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