This document provides a detailed technical explanation of topics and partitions in cursus, including their internal structure, partition selection strategies, and ordering guarantees. Topics serve as logical message streams that are horizontally scaled through partitioning, enabling both parallelism and ordering semantics.
For information about consumer groups and how they interact with partitions, see Consumer Groups. For details on how messages are persisted within partitions, see Disk Persistence System.
A Topic is a named message stream that is divided into one or more Partitions. Each partition operates independently with its own message queue and disk storage, allowing cursus to parallelize message processing and storage operations. The system supports two partition selection strategies: key-based routing for ordering guarantees and round-robin for load balancing.
The Topic struct represents a logical message stream and manages partition assignment and consumer group registration.
| Field | Type | Purpose |
|---|---|---|
| Name | string | Unique identifier for the topic |
| Partitions | []*Partition | Array of partition instances |
| counter | uint64 | Round-robin counter for partition selection. Incremented on each publish without a key to implement simple round-robin distribution. |
| consumerGroups | map[string]*ConsumerGroup | Registered consumer groups for this topic |
| mu | sync.RWMutex | Protects topic-level state |
Each Partition is an independent message processing unit with its own buffered channel, subscription map, and disk handler.
| Field | Type | Capacity/Purpose |
|---|---|---|
| id | int | Zero-based partition identifier |
| topic | string | Parent topic name |
| ch | chan types.Message | 10,000 message buffer |
| subs | map[string]chan types.Message | Consumer group subscription channels |
| mu | sync.RWMutex | Protects partition state |
| dh | interface{} | DiskHandler for persistence |
| closed | bool | Shutdown flag |
Key Design Decisions:
The Topic.Publish() method implements two distinct partition selection strategies based on the presence of a message key.
When a message contains a Key field, the partition is selected using a hash function to ensure all messages with the same key go to the same partition.
Algorithm:
keyID := util.GenerateID(msg.Key)idx = int(keyID % uint64(len(t.Partitions)))Hash Function Implementation:
GenerateID() function uses FNV-1a (Fowler-Noll-Vo) hashing, a fast non-cryptographic hash function:FNV-1a 64-bit:
When a message has no key (msg.Key == ""), partitions are selected in round-robin fashion using an atomic counter.
Algorithm:
idx = int(t.counter % uint64(len(t.Partitions))); t.counter++| Aspect | Key-Based Routing | Round-Robin Routing |
|---|---|---|
| Trigger | msg.Key != “” | msg.Key == “” |
| Algorithm | Hash(key) % partitionCount | counter++ % partitionCount |
| Ordering | Guaranteed per key | No ordering guarantee |
| Distribution | Depends on key distribution | Uniform across partitions |
| Use Case | User sessions, entity updates | Load distribution, batch jobs |
| Thread Safety | Hash is pure function | Counter protected by t.mu lock |
cursus provides conditional ordering guarantees based on the partition selection strategy and consumer group configuration.
Guarantee: All messages with the same key are delivered to the same partition in the order they were published.
Why it works:
Example Scenario:
Messages:
- {Key: "user-123", Payload: "login"} → Hash → Partition 2
- {Key: "user-456", Payload: "purchase"} → Hash → Partition 0
- {Key: "user-123", Payload: "update"} → Hash → Partition 2
- {Key: "user-123", Payload: "logout"} → Hash → Partition 2
Result:
Partition 0: ["purchase"]
Partition 2: ["login", "update", "logout"] ← Ordered sequence
Important: There is no ordering guarantee between messages in different partitions, even with round-robin distribution.
Each message follows two parallel paths for durability and low-latency delivery:
DiskHandler.AppendMessage() writes to buffered channelrun() goroutine immediately distributes to consumer groupsThe system uses fixed-size buffered channels at multiple levels:
| Channel | Capacity | Purpose |
|---|---|---|
| Partition ch | 10,000 | Partition-level message buffer |
| Consumer group subscription | 10,000 | Per-group distribution buffer |
| Consumer MsgCh | 1,000 | Per-consumer delivery buffer |
When channels reach capacity, the system exhibits blocking behavior:
Enqueue() blocks until space availablerun() goroutine blocks on sendTopics support dynamic partition expansion through the AddPartitions() method. Partitions cannot be reduced.
Key Points:
| Component | Key Functions |
|---|---|
| Topic struct | NewTopic(), Publish(), RegisterConsumerGroup() |
| Partition struct | NewPartition(), Enqueue(), run() |
| Partition selection | Key-based and round-robin logic |
| Hash functions | GenerateID(), Hash() |
| Topic manager | CreateTopic(), Publish() |
This completes the technical documentation for topics and partitions in cursus.
The system provides a flexible partitioning model with deterministic key-based routing for ordering guarantees and round-robin distribution for load balancing, all backed by buffered channels and independent disk persistence per partition.