Partition rebalancer
Use krabka-rebalancer to compute partition placement proposals, review their movements, and run throttled broker reassignments.
Install and start
Start a broker first with the quickstart. Build the service from the pinned source revision, then bind it to localhost for local use. The data directory must be writable.
git clone https://github.com/krabka-io/krabka-rebalancer.git
cd krabka-rebalancer
git checkout 9df7a58ec6f0e78b582e7d69c60b208a9c73644e
cargo build --release -p krabka-rebalancer --bin krabka-rebalancer
./target/release/krabka-rebalancer \
--bootstrap-servers 127.0.0.1:9092 \
--listen-addr 127.0.0.1:9300 \
--data-dir ./rebalancer-data
For Kubernetes, install the chart from the same checkout. fullnameOverride makes the Service name match the operator's default endpoint for cluster demo. The chart uses one replica, a Recreate update strategy, and a persistent volume for local history. Its default image tag comes from Chart.yaml; use image.tag or image.digest for your chosen service build.
# Run from the checked-out krabka-rebalancer repository.
# Replace bootstrapServers with brokers reachable from the pod.
helm upgrade --install demo ./charts/krabka-rebalancer \
--namespace kafka --create-namespace \
--set-string fullnameOverride=demo-rebalancer \
--set-string bootstrapServers=demo-broker-headless.kafka.svc:9092
kubectl -n kafka port-forward service/demo-rebalancer 9300:9300
Create and review a proposal
The service exposes Connect RPC at /krabka.rebalancer.v1.Rebalancer/<Method>. All methods use POST. Check /readyz, read the cluster snapshot, then compute a proposal. Creation stores the plan without changing broker assignments.
curl -fsS http://127.0.0.1:9300/readyz
curl -fsS http://127.0.0.1:9300/krabka.rebalancer.v1.Rebalancer/GetState \
-H 'Content-Type: application/json' \
-H 'Connect-Protocol-Version: 1' \
-d '{}'
curl -fsS http://127.0.0.1:9300/krabka.rebalancer.v1.Rebalancer/CreateProposal \
-H 'Content-Type: application/json' \
-H 'Connect-Protocol-Version: 1' \
-d '{"goals":["RackAware","ReplicaDistribution","LeaderDistribution"]}'
Review every movement's topic, partition, old and new replica lists, and leader change. A newLeader value of -1 means Kafka elects the replacement; the proposal does not enforce a particular leader. Use the returned proposal ID for later calls.
# Copy the id returned by CreateProposal after reviewing its movements.
PROPOSAL_ID='replace-with-proposal-id'
curl -fsS http://127.0.0.1:9300/krabka.rebalancer.v1.Rebalancer/DryRunProposal \
-H 'Content-Type: application/json' \
-H 'Connect-Protocol-Version: 1' \
-d "{\"id\":\"$PROPOSAL_ID\"}"
curl -fsS http://127.0.0.1:9300/krabka.rebalancer.v1.Rebalancer/GetProposal \
-H 'Content-Type: application/json' \
-H 'Connect-Protocol-Version: 1' \
-d "{\"id\":\"$PROPOSAL_ID\"}"
DryRunProposal returns the stored summary. Its estimatedBytesMoved is currently zero, so it cannot estimate transfer size. A proposal with no movements needs no execution; ordinary full/add execution rejects an empty plan.
Execute, monitor, and cancel
Execute only after checking the plan. The service permits one execution at a time. The response marks the proposal as executing; it does not mean reassignment is complete. Poll GetProposal for completion and inspect failureReason on failure. The throttle is bytes per second per broker direction.
# This request changes broker assignments. Use the reviewed proposal id.
curl -fsS http://127.0.0.1:9300/krabka.rebalancer.v1.Rebalancer/ExecuteProposal \
-H 'Content-Type: application/json' \
-H 'Connect-Protocol-Version: 1' \
-d "{\"id\":\"$PROPOSAL_ID\",\"throttleBytesPerSec\":\"50000000\"}"
# Poll GetProposal until Completed, Failed, or Cancelled.
curl -fsS http://127.0.0.1:9300/krabka.rebalancer.v1.Rebalancer/GetProposal \
-H 'Content-Type: application/json' \
-H 'Connect-Protocol-Version: 1' \
-d "{\"id\":\"$PROPOSAL_ID\"}"
To stop an active execution, call CancelExecution. Cancellation can return while the proposal is still executing if its drain timeout expires. Keep polling for Cancelled, Failed, or Completed.
curl -fsS http://127.0.0.1:9300/krabka.rebalancer.v1.Rebalancer/CancelExecution \
-H 'Content-Type: application/json' \
-H 'Connect-Protocol-Version: 1' \
-d "{\"id\":\"$PROPOSAL_ID\"}"
The executor applies the replication throttle, submits reassignments, waits for completion, then attempts to clear the throttle. Check logs after a failure: a throttle cleanup failure is logged even when the proposal reaches a terminal state.
Proposal modes and broker removal
| API mode | Inputs and behavior |
|---|---|
PROPOSAL_MODE_FULL | Optimize the current cluster with selected goals. Broker list must be empty. An omitted mode uses full. |
PROPOSAL_MODE_ADD_BROKERS | Supply a nonempty broker list. Selected goals can place replicas on current replica hosts and the listed brokers. |
PROPOSAL_MODE_REMOVE_BROKERS | Supply a nonempty broker list and empty goals. Evacuate all listed brokers. A bearer credential is required. |
Add and remove modes require unique broker IDs already present in the cluster snapshot. They reject in-flight reassignments, offline partitions, and under-replicated partitions. Removal also requires enough surviving brokers to preserve every partition's replication factor and enough movement budget to evacuate all selected brokers.
Removal is disabled until the service has a nonempty evacuation token or token file. Send Authorization: Bearer <token> when creating, executing, or cancelling a removal proposal. The token file takes precedence and is read on each request, so projected Secret rotation takes effect without a restart. The Helm chart accepts brokerEvacuationAuth.existingSecret and brokerEvacuationAuth.key.
The removal planner selects replacement brokers by replica count. It does not run the rack or capacity goals. Check that the surviving brokers have enough capacity and that the proposed replicas meet your rack placement requirements before approval.
Before executing a removal plan, the service checks the current assignments again. If they changed, create a new proposal and review it. A completed evacuation request with no movements confirms that the selected brokers hold no replicas. Review the resulting cluster state before removing broker workloads. Keep the service reachable only by callers authorized to change cluster assignments; the evacuation credential protects the removal mode.
Goals and broker capacities
An empty goals list selects all 16 registered goals. Hard goals run before soft goals. If the movement cap prevents a hard goal from being satisfied, optimization fails. Soft goals use the remaining movement budget.
Hard goals
PreferredLeaderIdempotencyRackAwareReplicaCapacityDiskCapacityNetworkInCapacityNetworkOutCapacityCpuCapacity
Soft goals
ReplicaDistributionLeaderDistributionTopicReplicaDistributionMinTopicLeadersPerBrokerDiskUsageLeaderBytesInNetworkInUsageNetworkOutUsageCpuUsage
Set --broker-capacity-file capacity.yaml to load capacity limits at startup. Missing broker entries or fields mean no limit for that broker or resource. Without a capacity file, the five capacity goals make no changes. Replace the example limits with the measured capacities of your brokers.
version: 1
brokers:
1:
max_replicas: 4096
disk_bytes: 1099511627776
network_in_bytes_per_sec: 125000000
network_out_bytes_per_sec: 125000000
cpu_cores: 8.0
2:
max_replicas: 4096
disk_bytes: 1099511627776
network_in_bytes_per_sec: 125000000
network_out_bytes_per_sec: 125000000
cpu_cores: 8.0
With Helm, use brokerCapacities for the map under brokers; the chart writes the versioned capacity file. brokerCapacityFile overrides that map and requires you to provide the file. Usage goals need broker metrics. The scraper discovers each broker host from metadata and uses port 9404, or you can set static targets such as --metrics-scrape-targets 1:broker-1:9404,2:broker-2:9404.
Configure the service
Flags take precedence over their environment variables. Time values without a -secs suffix accept units, such as 60s and 100ms. Resource counts must be positive where the parser requires them.
| Flag and environment variable | Default | Purpose |
|---|---|---|
--bootstrap-serversKRABKA_BOOTSTRAP_SERVERS | Required | Comma-separated broker host:port list. |
--listen-addrKRABKA_REBALANCER_LISTEN_ADDR | 0.0.0.0:9300 | Connect RPC, health, readiness, and metrics bind address. |
--data-dirKRABKA_DATA_DIR | /var/lib/krabka-rebalancer | Writable proposal and anomaly storage. |
--scrape-interval-secsKRABKA_SCRAPE_INTERVAL_SECS | 10 | Cluster metadata snapshot interval in seconds. |
--imbalance-threshold-pctKRABKA_IMBALANCE_THRESHOLD_PCT | 10 | Soft-goal spread threshold, calculated as (max − min) × 100 / total. |
--max-movements-per-proposalKRABKA_MAX_MOVEMENTS_PER_PROPOSAL | 256 | Maximum movements in a proposal. |
--min-topic-leaders-per-brokerKRABKA_MIN_TOPIC_LEADERS_PER_BROKER | 0 | Minimum topic leaders on each broker; zero disables this goal. |
--proposal-ring-buffer-sizeKRABKA_PROPOSAL_RING_BUFFER_SIZE | 20 | Recent proposal capacity. |
--broker-capacity-fileKRABKA_BROKER_CAPACITY_FILE | Empty | Capacity YAML loaded at startup. |
--metrics-scrape-targetsKRABKA_METRICS_SCRAPE_TARGETS | Empty | Static id:host:port targets; empty uses broker metadata discovery. |
--metrics-portKRABKA_REBALANCER_METRICS_PORT | 9404 | Discovered broker metrics port; static targets override it. |
--metrics-scrape-interval-secsKRABKA_METRICS_SCRAPE_INTERVAL_SECS | 30 | Broker metrics polling interval in seconds. |
--metrics-retention-secsKRABKA_METRICS_RETENTION_SECS | 43200 | Rolling metrics retention in seconds (12 hours). |
--default-throttle-bytes-per-secKRABKA_DEFAULT_THROTTLE_BYTES_PER_SEC | 50000000 | KIP-73 replication rate per broker direction. |
--execute-deadline-secsKRABKA_EXECUTE_DEADLINE_SECS | 1800 | Execution deadline; timeout cancels reassignments and fails the proposal. |
--reassignment-poll-interval-secsKRABKA_REASSIGNMENT_POLL_INTERVAL_SECS | 5 | Reassignment completion polling interval. |
--reassignment-batch-sizeKRABKA_REASSIGNMENT_BATCH_SIZE | 200 | Maximum movements in one reassignment request. |
--reassignment-request-timeoutKRABKA_REBALANCER_REASSIGNMENT_REQUEST_TIMEOUT | 60s | Broker timeout for submit and cancel requests. |
--broker-evacuation-tokenKRABKA_REBALANCER_BROKER_EVACUATION_TOKEN | Empty | Bearer credential for broker removal; empty disables removal. |
--broker-evacuation-token-fileKRABKA_REBALANCER_BROKER_EVACUATION_TOKEN_FILE | Unset | Credential file read on every protected request; takes precedence over the token. |
--state-topic-nameKRABKA_REBALANCER_STATE_TOPIC | __krabka_rebalancer_state | Compacted executor state topic. |
--state-topic-replicationKRABKA_REBALANCER_STATE_TOPIC_REPLICATION | 3 | Initial replication factor; invalid replication factor retries with one replica. |
--state-load-timeout-secsKRABKA_REBALANCER_STATE_LOAD_TIMEOUT_SECS | 60 | State loading warning and recovery wait deadline. |
--client-dispatch-queue-capacityKRABKA_REBALANCER_CLIENT_DISPATCH_QUEUE_CAPACITY | Client default | Positive client queue capacity. |
--client-frame-maxKRABKA_REBALANCER_CLIENT_FRAME_MAX | 100MiB | Positive client frame limit. |
Anomaly detector settings
Prefix these flags with --detector-. Their environment variables use KRABKA_DETECTOR_, uppercase names, and underscores. Automatic proposal creation defaults to disabled. Detection still records broker death, under-replication, disk pressure, and slow-broker anomalies.
| Flag suffix | Default | Purpose |
|---|---|---|
tick-interval-secs | 30 | Rule evaluation interval; zero disables detection and automatic proposals. |
broker-death-threshold-secs | 60 | Minimum broker absence before BrokerDeath. |
under-replicated-threshold-secs | 120 | Minimum time with ISR smaller than replicas. |
disk-pressure-pct | 0.85 | Disk capacity fraction for a warning. |
disk-critical-pct | 0.95 | Disk capacity fraction for critical severity. |
slow-broker-multiplier | 2.0 | CPU use multiplier against the cluster median. |
slow-broker-min-cores | 0.5 | Minimum CPU use for a slow-broker anomaly. |
mute-window-secs | 900 | Mute interval after an automatic proposal. |
auto-trigger-enabled | false | Opt in to automatic proposal creation. |
--anomaly-ring-buffer-size (KRABKA_ANOMALY_RING_BUFFER_SIZE) defaults to 200. Disk threshold flags take fractions: 0.85 means 85 percent.
Runtime and state-topic settings
Prefix the names below with --. Environment variables use KRABKA_REBALANCER_, uppercase names, and underscores. The cancellation polling interval must be smaller than its drain timeout. Protocol timeout values must be positive whole milliseconds within a signed 32-bit range. The state-topic dirty ratio must be greater than zero and smaller than one.
| Flag name | Default |
|---|---|
recovery-load-poll-interval | 100ms |
executor-drain-timeout | 10s |
ingester-join-timeout | 5s |
scraper-http-timeout | 5s |
cancel-drain-timeout | 5s |
cancel-drain-poll-interval | 25ms |
detector-history-capacity | 10 |
state-topic-create-timeout | 10s |
state-loader-poll-interval | 100ms |
state-loader-quiet-polls | 5 |
state-fetch-max | 1MiB |
state-produce-retry-attempts | 50 |
state-produce-retry-backoff | 200ms |
state-produce-timeout | 10s |
state-topic-min-cleanable-dirty-ratio | 1% |
state-topic-segment-interval | 1m |
Use KafkaRebalance with the operator
Install the operator and its CRDs, and deploy a rebalancer reachable from the operator. The KafkaRebalance field explorer lists its specification and status fields. The cluster label is required. The default endpoint is http://<cluster>-rebalancer.<namespace>.svc.cluster.local:9300; use spec.endpoint to override it.
apiVersion: krabka.io/v1alpha1
kind: KafkaRebalance
metadata:
name: demo-balance
namespace: kafka
labels:
krabka.io/cluster: demo
spec:
mode: full
goals: [RackAware, ReplicaDistribution, LeaderDistribution]
throttleBytesPerSec: 50000000
The operator computes a proposal and reports ProposalReady. Check status.optimizationResult and use status.sessionId to inspect the full proposal through GetProposal. Approve it with the annotation below. The operator enters Rebalancing, then reports Ready or NotReady.
kubectl -n kafka get kafkarebalance demo-balance -o yaml
# After checking status.optimizationResult and the proposal:
kubectl -n kafka annotate kafkarebalance demo-balance \
krabka.io/rebalance=approve --overwrite
kubectl -n kafka get kafkarebalance demo-balance -w
# Request a new proposal, or cancel an execution that is running:
# kubectl -n kafka annotate kafkarebalance demo-balance krabka.io/rebalance=refresh --overwrite
# kubectl -n kafka annotate kafkarebalance demo-balance krabka.io/rebalance=stop --overwrite
For spec.mode: addBrokers or removeBrokers, provide spec.brokers. Removal uses the same-namespace Secret <cluster>-rebalancer-auth, key token, unless spec.authorizationSecretRef supplies another name and key. The service and operator must use the same credential. Keep spec.goals absent or empty for removal.
API reference
Use the versioned protobuf schema to generate clients. JSON requests use camelCase field names; protobuf 64-bit integer values can use decimal strings.
| Method | Request | Result |
|---|---|---|
GetState | {} | Latest brokers, topics, ISR, and in-flight reassignments. |
CreateProposal | goals, mode, brokers | Compute and store a proposal without executing it. |
DryRunProposal | id | Stored proposal summary; estimatedBytesMoved currently returns zero. |
GetProposal | id | Movements, status, timestamps, and failureReason. |
ListProposals | limit | Recent proposals; zero uses the full buffer capacity. |
ExecuteProposal | id, throttleBytesPerSec? | Start asynchronous execution of a computed proposal. |
CancelExecution | id | Request cancellation; poll the returned proposal until terminal. |
GetAnomalies | limit, includeResolved? | Anomaly history; includeResolved defaults to true. |
Readiness, recovery, and troubleshooting
GET /healthzchecks the running HTTP service.GET /readyzreturns 503 until a cluster snapshot exists and the state topic has loaded.GET /metricsexposes OpenMetrics under thekrabka_rebalancerprefix.- Proposal history lives in
proposals.jsonand anomaly history inanomalies.jsonunder the data directory. Preserve this directory across restarts. The internal compacted Kafka topic stores the in-flight execution record. - If state loading exceeds its timeout, the loader keeps retrying and readiness stays false until loading succeeds. Startup recovery can stop waiting at this deadline; check logs and the proposal state before starting another operation.
Unavailablemeans a snapshot or state load is still missing.InvalidArgumentincludes unknown goal names and incompatible mode inputs.FailedPreconditionincludes an unsatisfied goal, unstable cluster state, insufficient surviving brokers, stale removal assignments, an empty plan, or another active execution.PermissionDeniedduring broker removal means evacuation is disabled or the credential does not match. Check the configured token file or Secret without writing its value to logs.
Implementation references: RPC handlers, optimizer safety checks, runtime validation, Helm values, and Rust API documentation.