# Concepts (/integrations/keda/concepts)



This page explains the model behind the KubeMQ KEDA external scaler: the gRPC protocol KEDA
speaks to it, the single metric it exposes, and the two trigger types that determine how KEDA
drives it. For the metric's underlying meaning — the queue `Waiting` count — see the
[Queues](/learn/queues) concept; the scaler does not redefine it.

## The external-scaler protocol [#the-external-scaler-protocol]

KEDA scales workloads from external signals through *scalers*: small gRPC services that
implement KEDA's `ExternalScaler` interface. KEDA does not know anything about KubeMQ — it only
knows how to call these four RPCs and act on the answers. The KubeMQ scaler implements all four,
translating each call into a `ListQueuesChannels` read against the broker.

| RPC              | What KEDA asks                                 | What the scaler does                                                                         |
| ---------------- | ---------------------------------------------- | -------------------------------------------------------------------------------------------- |
| `IsActive`       | "Should this workload be running at all?"      | Reads the live `Waiting` count, returns `true` when `Waiting > activationTargetWaiting`.     |
| `StreamIsActive` | (push mode) "Stream me active-status changes." | Sends the active flag once, then re-evaluates on a fixed `5s` ticker and pushes each update. |
| `GetMetricSpec`  | "What is the target value for your metric?"    | Returns `targetWaiting` as the target for `kubemq-queue-waiting`. No broker call.            |
| `GetMetrics`     | "What is the current value?"                   | Reads the live `Waiting` count and returns it as the metric value.                           |

Every RPC parses the `ScaledObject` trigger metadata first and returns gRPC `InvalidArgument`
on a bad spec **before** touching KubeMQ, so a misconfigured trigger fails fast without loading
the broker.

<Callout type="info">
  The scaler is a *service*, not a client library. You do not write Go against it — you write a
  KEDA `ScaledObject` (YAML) whose `external` trigger metadata configures the scaler. The full
  metadata schema lives in the [ScaledObject metadata reference](/integrations/keda/reference/scaled-object-metadata).
</Callout>

## The single metric: `kubemq-queue-waiting` [#the-single-metric-kubemq-queue-waiting]

The scaler exposes exactly one metric. Its value is the queue channel's `Outgoing.Waiting`
count, obtained by calling `client.ListQueuesChannels(queueName)` and matching the channel whose
`Name` equals `queueName`.

| Metric                 | Source                                                            | Used by                                                                      |
| ---------------------- | ----------------------------------------------------------------- | ---------------------------------------------------------------------------- |
| `kubemq-queue-waiting` | The channel's `Outgoing.Waiting` count from `ListQueuesChannels`. | `GetMetricSpec` (target = `targetWaiting`) and `GetMetrics` (current value). |

KEDA divides the current `Waiting` count by `targetWaiting` and rounds up to compute the desired
replica count, then clamps it to your `minReplicaCount` / `maxReplicaCount` bounds. This is the
standard KEDA / HPA math — the scaler only supplies the numerator (the live backlog) and the
target.

If the broker responds successfully but the named channel is **absent** from the returned list,
the scaler reports `Waiting = 0` (an empty queue is a legitimate signal, scaling the workload to
its minimum). On any *KubeMQ failure* it instead returns a gRPC error — see
[Never-fake-zero](#never-fake-zero) below.

## Trigger types: `external` vs `external-push` [#trigger-types-external-vs-external-push]

KEDA supports two external trigger types against the same scaler. They differ only in **how KEDA
drives the scaler**, not in what the scaler measures.

| Type            | Mode           | How KEDA drives it                                                                                                                            | Best for                                      |
| --------------- | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- |
| `external`      | Poll (default) | Calls `IsActive` and `GetMetrics` every `pollingInterval` seconds.                                                                            | Steady workloads with `minReplicaCount >= 1`. |
| `external-push` | Push           | Opens a long-lived `StreamIsActive` stream; the scaler pushes active-status updates on its own `5s` ticker, independent of `pollingInterval`. | Fast scale-from-zero.                         |

In poll mode the soonest KEDA can notice the first message is the next `pollingInterval` tick. In
push mode the scaler holds the stream open and pushes an active signal as soon as it detects
waiting work — so a workload at zero replicas wakes up sooner without forcing a tiny
`pollingInterval` across the whole metric pipeline. Even in push mode, KEDA still reads the metric
via `GetMetrics` on the `pollingInterval` cadence to do the replica math.

## The activation gate [#the-activation-gate]

Computing a replica count and deciding whether the workload should be active at all are two
separate decisions. Activation is governed by `activationTargetWaiting`: the scaler reports active
only when `Waiting > activationTargetWaiting` (strictly greater-than).

* `activationTargetWaiting` defaults to `0`, so the very first waiting message (`Waiting == 1`) activates the workload.
* Setting it to `"1"` requires `Waiting >= 2` before waking up — useful to keep a single stray message from paying the cost of a cold start.

The activation gate only matters when you allow the workload to reach zero (`minReplicaCount: 0`).
With a `minReplicaCount` of `1` or more, the floor is held by `minReplicaCount` regardless of
activation. See [Scale to Zero](/integrations/keda/how-to/scale-to-zero) for the end-to-end
flow.

## Never-fake-zero [#never-fake-zero]

A queue that is genuinely empty and a broker that is unreachable both *could* look like "zero
work" — but they demand opposite responses. Scaling a healthy workload to zero on a transient
outage would strand the queue with no consumer.

The scaler resolves this by **never returning `OK` with a fabricated `Waiting = 0` on a KubeMQ
failure**. It maps each KubeMQ error class to a gRPC status code and returns it, so KEDA can apply
its `fallback` replica strategy instead of scaling down. A genuine empty queue (channel present,
`Waiting = 0`, or channel absent from the list) is the only path that yields `0`. The full mapping
lives in the [error-codes reference](/integrations/keda/reference/error-codes).

## Next steps [#next-steps]

<Cards>
  <Card title="Getting Started" href="/integrations/keda/tutorials/getting-started" description="Install the scaler and create your first ScaledObject." />

  <Card title="Scale to Zero" href="/integrations/keda/how-to/scale-to-zero" description="Use external-push and activationTargetWaiting to idle at zero replicas." />

  <Card title="gRPC RPCs" href="/integrations/keda/reference/grpc-rpcs" description="The ExternalScaler RPC surface, retry behavior, and health checks." />
</Cards>
