Ack & Reject
Selectively acknowledge or reject individual KubeMQ queue messages using the Go SDK stream API.
Overview
Ack and reject give you per-message control over queue delivery instead of an all-or-nothing batch outcome. When receiver.Poll fetches a batch with AutoAck: false, each message stays in an open transaction on the broker — invisible to other consumers — until the consumer explicitly settles it. That's what you need when one bad record in a batch shouldn't take the rest down with it.
Settlement happens through two calls on the DownstreamMessage: dm.Ack(), which permanently removes the message from the queue, and dm.Nack(), which returns it to the queue for redelivery. Internally the broker tracks this against a receive count, which a dead-letter policy can use to stop retrying a poison message forever.
Gotchas: an unsettled message isn't gone — it snaps back to the queue once the transaction expires, so a slow consumer looks identical to a rejecting one; settle every message before that deadline, and never assume a batch is fully processed until you've called Ack() or Nack() on each one individually.
Prerequisites
- KubeMQ server running on
localhost:50000 - Go SDK installed (
go get github.com/kubemq-io/kubemq-go/v2)
Code
// Example: queues/ack-reject
//
// Demonstrates individual message acknowledgment and rejection using
// the queue downstream receiver. Messages can be individually acked
// (confirmed) or rejected (nacked) back to the queue.
//
// Channel: go-queues.ack-reject
// Client ID: go-queues-ack-reject-client
//
// Run with a KubeMQ server on localhost:50000
// (see https://docs.kubemq.io/deploy).
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/kubemq-io/kubemq-go/v2"
)
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
client, err := kubemq.NewClient(ctx,
kubemq.WithAddress("localhost", 50000),
kubemq.WithClientId("go-queues-ack-reject-client"),
)
if err != nil {
log.Fatal(err)
}
defer client.Close()
channel := "go-queues.ack-reject"
// Send two messages.
for i := 1; i <= 2; i++ {
_, err := client.SendQueueMessage(ctx, kubemq.NewQueueMessage().
SetChannel(channel).
SetBody(fmt.Appendf(nil, "msg-%d", i)))
if err != nil {
log.Fatal(err)
}
}
fmt.Println("Sent 2 messages")
// Poll messages with manual acknowledgment (AutoAck=false).
receiver, err := client.NewQueueDownstreamReceiver(ctx)
if err != nil {
log.Fatal(err)
}
defer receiver.Close()
resp, err := receiver.Poll(ctx, &kubemq.PollRequest{
Channel: channel,
MaxItems: 10,
WaitTimeoutSeconds: 5,
AutoAck: false,
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("Polled %d messages\n", len(resp.Messages))
for _, dm := range resp.Messages {
if dm.Message != nil {
fmt.Printf(" body=%s\n", dm.Message.Body)
}
}
}
How It Works
client.NewQueueDownstreamReceiver(ctx)creates a long-lived downstream gRPC stream; unlikePollQueue, it is reusable across multiplePollcalls and kept open bydefer receiver.Close().receiver.Poll(ctx, &kubemq.PollRequest{AutoAck: false})fetches up toMaxItemsmessages and holds them in an open transaction on the broker — they remain invisible to other consumers until the transaction is settled.- With
AutoAck: false, messages must be settled by callingdm.Ack()(removes from queue),dm.Nack()(returns for redelivery), or by letting the transaction expire (also returns for redelivery). - The
DownstreamMessage.TransactionIDfield identifies the transaction; individualdm.Ack()/dm.Nack()calls settle exactly one message in that transaction.
Related
Was this page helpful?