Service Discovery
Kahuna can store service instance metadata so clients and workers can discover healthy endpoints without running a separate registry.
Use a bucket per service:
services/{service-name}/{instance-id}
Examples:
services/payments/node-a
services/payments/node-b
services/search/node-c
Register an Instance
Use an expiration so the instance disappears if the process dies or stops refreshing its heartbeat.
set `services/payments/node-a` '{"host":"10.0.1.15","port":8080,"version":"1.2.3","zone":"us-east-1a"}' ex 30000 norev
From the .NET client:
await client.SetKeyValue(
"services/payments/node-a",
"""{"host":"10.0.1.15","port":8080,"version":"1.2.3","zone":"us-east-1a"}""",
expiryTime: 30000,
flags: KeyValueFlags.SetNoRevision,
durability: KeyValueDurability.Persistent
);
Refresh the Heartbeat
Refresh the key periodically before the expiration passes:
await client.ExtendKeyValue(
"services/payments/node-a",
TimeSpan.FromSeconds(30),
KeyValueDurability.Persistent
);
If instance metadata can change on heartbeat, write the full value again with the same expiration:
await client.SetKeyValue(
"services/payments/node-a",
currentMetadataJson,
expiryTime: 30000,
flags: KeyValueFlags.SetNoRevision,
durability: KeyValueDurability.Persistent
);
Discover Instances
Read all instances in the service bucket:
get by bucket `services/payments`
From the .NET client:
List<KahunaKeyValue> instances = await client.GetByBucket(
"services/payments",
KeyValueDurability.Persistent
);
foreach (KahunaKeyValue instance in instances)
Console.WriteLine($"{instance.Key}: {instance.ValueAsString()}");
Audit Historical Membership
If you need to inspect which instances were visible at a previous point in time, use an AS OF read:
get by bucket `services/payments` as of 1718392012345
Or use the .NET client snapshot parameter:
List<KahunaKeyValue> previous = await client.GetByBucket(
"services/payments",
KeyValueDurability.Persistent,
snapshotMs: 1718392012345
);
Do not use NOREV for registrations when historical membership is required. No-revision writes keep only the latest value and skip the archived revision records needed by historical reads.
Choosing Durability
Use persistent durability when discovery state must survive node restarts and remain available during failover.
Use ephemeral durability when registrations are high-volume, short-lived, and safe to rebuild from running services.
eset `services/payments/node-a` '{"host":"10.0.1.15","port":8080}' ex 30000
eget by bucket `services/payments`
Operational Notes
- Set the expiration to several heartbeat intervals, not exactly one interval.
- Use no-revision writes for high-churn persistent registrations when only the current membership matters.
- Store enough metadata for clients to make routing decisions without another lookup.
- Keep service membership under a shared bucket so reads are consistent and partition-local.
- For large ordered registries, use key-range sharding instead of treating one bucket as permanently single-partition.