Skip to content

infra+dotnet: replace Kafka with in-process Channel<T> message bus (HOL-23) - #102

Merged
BrewingCoder merged 1 commit into
mainfrom
issue-23-drop-kafka
May 9, 2026
Merged

infra+dotnet: replace Kafka with in-process Channel<T> message bus (HOL-23)#102
BrewingCoder merged 1 commit into
mainfrom
issue-23-drop-kafka

Conversation

@BrewingCoder

Copy link
Copy Markdown
Owner

Summary

Two infra containers (kafka + zookeeper) and a JVM stack went away in service of the same producer/consumer call shape. The .NET backend already runs in all-in-one mode where producer and worker share a process; the only thing Kafka was buying us at hobby scale was a JVM broker, two extra containers, and ~750 MiB of resident memory.

RAM (idle) Containers
Before HOL-23 ~2 GiB 6
After HOL-23 ~910 MiB 4
Original (pre-HOL-17) 12+ GiB 9

New abstraction (HoldFast.Shared.Messaging)

  • IMessageBusPublishAsync<T>(topic, key, value, ct) + SubscribeAsync(topic, ct)
  • InProcessMessageBus — singleton with one Channel<(string, string)> per topic, lazily created. Unbounded (queue depth stays trivial at hobby scale; observed growth is the signal to swap in a Kafka-backed implementation).
  • MessageConsumerBase<T> — replaces KafkaConsumerService<T>; reads channel, JSON-deserializes, dispatches to ProcessAsync.

Consumer mechanical refactor (6 files)

All six consumers (SessionEvents, ErrorGrouping, FrontendErrors, Logs, Metrics, Traces) re-based from KafkaConsumerService<T> to MessageConsumerBase<T>. Only the constructor signature changed (IOptions<KafkaOptions>IMessageBus); ProcessAsync bodies unchanged.

Removed

  • HoldFast.Shared/Kafka/{KafkaConsumerService, KafkaProducerService, KafkaTopicBootstrapService}.cs
  • Confluent.Kafka package reference (HoldFast.Shared.csproj)
  • Program.cs: Kafka DI block + KafkaTopicBootstrap registration
  • compose.yml: kafka + zookeeper services + kafka-data/zoo-* volumes
  • compose.hobby-dotnet.yml: Kafka__BootstrapServers env
  • .env.example: KAFKA_*, ZOOKEEPER_IMAGE_NAME
  • env.sh: KAFKA exports
  • start-infra.sh: kafka+zookeeper from SERVICES list
  • HoldFast.Shared.Tests/Kafka/KafkaOptionsTests.cs (class deleted)

Tradeoffs

  • No durability — messages live in memory; backend restart drops the in-flight queue. Acceptable at hobby scale; pushPayload still returns 200, SDK retry covers transient loss.
  • Single-process only — producer and consumer must be in the same .NET host (matches our all-in-one runtime mode).
  • No replay / consumer groups / rebalancing.
  • If you outgrow this, swap an alternative IMessageBus implementation in (Kafka, Redis Streams, RabbitMQ) without touching consumers.

Verified

  • All 3,027 .NET tests pass (was 3,037; -10 from deleted KafkaOptionsTests for a class that no longer exists)
  • Smoke test (./infra/docker/smoke-test-ingest.sh) passes end-to-end against the lean stack
  • docker compose ps now shows 4 HoldFast containers (backend, clickhouse, frontend, postgres)

Test plan

  • docker compose down && docker compose up -d → 4 containers come up
  • Smoke test passes
  • No Connection refused localhost:9092 errors anywhere
  • Backend logs show In-process consumer started for topic ... for each of session-events, backend-errors, frontend-errors, logs, metrics, traces

Stacks on #101 (HOL-22). Subtask of HOL-17. Closes HOL-23.

🤖 Generated with Claude Code

@BrewingCoder
BrewingCoder changed the base branch from issue-22-drop-redis to main May 9, 2026 14:39
…OL-23)

Two infra containers (kafka + zookeeper) and a JVM stack went away
in service of the same producer/consumer call shape. The .NET backend
already runs in all-in-one mode where producer and worker share a
process; the only thing Kafka was buying us at hobby scale was a JVM
broker, two extra containers, and ~750 MiB of resident memory.

New abstraction (HoldFast.Shared.Messaging):
- IMessageBus — PublishAsync<T>(topic, key, value, ct) + SubscribeAsync(topic, ct)
- InProcessMessageBus — singleton with one Channel<(string, string)> per
  topic, lazily created. Unbounded (queue depth stays trivial at hobby
  scale; observed growth is the signal to swap in a Kafka-backed
  implementation).
- MessageConsumerBase<T> — replaces KafkaConsumerService<T>; reads
  channel, JSON-deserializes, dispatches to ProcessAsync.

Producer side:
- KafkaProducerAdapter — same IKafkaProducer interface, now wraps
  IMessageBus instead of KafkaProducerService. Class name kept for now
  to keep the diff narrow; rename pending follow-up.

Consumer side:
- All six consumers (SessionEvents, ErrorGrouping, FrontendErrors,
  Logs, Metrics, Traces) re-based from KafkaConsumerService<T> to
  MessageConsumerBase<T>. Only the constructor signature changed
  (IOptions<KafkaOptions> → IMessageBus); ProcessAsync bodies are
  unchanged.

Removed:
- HoldFast.Shared/Kafka/{KafkaConsumerService, KafkaProducerService,
  KafkaTopicBootstrapService}.cs
- HoldFast.Shared.Tests/Kafka/KafkaOptionsTests.cs
- Confluent.Kafka package reference
- Program.cs: Kafka DI block + KafkaTopicBootstrap registration
- compose.yml: kafka + zookeeper services and their volumes
- compose.hobby-dotnet.yml: Kafka__BootstrapServers env mapping
- .env.example: KAFKA_*, ZOOKEEPER_IMAGE_NAME
- env.sh: KAFKA_ADVERTISED_LISTENERS, KAFKA_SERVERS exports
- start-infra.sh: kafka+zookeeper from SERVICES list

Tradeoffs accepted:
- No durability — messages live in memory; backend restart drops the
  in-flight queue. Acceptable at hobby scale; pushPayload still 200s,
  SDK retry covers transient loss.
- Single-process only — producer and consumer must be in the same
  .NET host (matches our all-in-one runtime mode).
- No replay / consumer groups / rebalancing.

Verified end-to-end:
- All 3,027 .NET tests pass (was 3,037; -10 from the deleted
  KafkaOptionsTests).
- Smoke test (./infra/docker/smoke-test-ingest.sh) passes against the
  reduced stack.
- docker compose ps now shows 4 HoldFast containers (backend,
  clickhouse, frontend, postgres) — was 6 after HOL-22, was 9 originally.
- Total stack RAM at idle: ~910 MiB (was 12+ GiB before HOL-18).

Stacks on HOL-22. Subtask of HOL-17. Closes HOL-23.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@BrewingCoder
BrewingCoder force-pushed the issue-23-drop-kafka branch from be73ec7 to ee6da0b Compare May 9, 2026 14:42
@BrewingCoder
BrewingCoder merged commit e95be10 into main May 9, 2026
4 checks passed
@BrewingCoder
BrewingCoder deleted the issue-23-drop-kafka branch May 9, 2026 14:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant