KubeMQ
Client SDKsC#How-to guidesRPC

Query Group

Load-balance KubeMQ queries across a group of handlers using the C# SDK RPC client.

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

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

Code

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

  • 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.

Was this page helpful?

On this page