A high-performance, fault-tolerant, distributed publish-subscribe (pub-sub) system built from scratch in Python. DSMS continuously monitors OS processes across distributed servers and reliably streams real-time CPU and memory metrics through a clustered broker architecture coordinated by the RAFT Consensus Algorithm.
The system is designed with real-world distributed systems principles: strict leader consensus, append-only disk storage, offset-based streaming, automated leader discovery with failover redirection, persistent state rehydration across machine restarts, and RESTful query APIs.
- System Architecture
- Key Design Choices & Principles
- Component Deep-Dive
- Step-by-Step Data Flow
- Message & Wire Protocol
- RAFT Consensus Implementation
- Fault Tolerance & Crash Recovery Scenarios
- Directory Structure
- Installation & Setup
- Running the System
- HTTP REST API Reference
- Automated & Manual Testing
- Configuration Reference
┌────────────────────────────────────────────────────────┐
│ Publisher Host (DSMS_PUB) │
│ ┌──────────────┐ reads ┌───────────────────────┐ │
│ │ apps.json │ ────────► │ psutil Metric Sampler │ │
│ └──────────────┘ └──────────┬────────────┘ │
│ │ METRIC / │
│ │ CREATE_TOPIC │
└────────────────────────────────────────┼───────────────┘
│ (TCP)
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ Broker Cluster (RAFT) │
│ │
│ ┌───────────────────┐ REPLICATE / VOTE ┌────────────────────┐ │
│ │ Broker Node 0 │ ◄────────────────────► │ Broker Node 1 │ │
│ │ [LEADER] │ │ [FOLLOWER] │ │
│ │ ├── RaftNode │ │ ├── RaftNode │ │
│ │ ├── RaftPersister│ │ ├── RaftPersister │ │
│ │ └── LogStore │ │ └── LogStore │ │
│ └─────────┬─────────┘ └─────────┬──────────┘ │
│ ▲ REPLICATE / VOTE ▲ │
│ └──────────────────────┬─────────────────────┘ │
│ ▼ │
│ ┌───────────────────────┐ │
│ │ Broker Node 2 │ │
│ │ [FOLLOWER] │ │
│ │ ├── RaftNode │ │
│ │ ├── RaftPersister │ │
│ │ └── LogStore │ │
│ └───────────┬───────────┘ │
└────────────────────────────────────┼────────────────────────────────────┘
│ SUBSCRIBE <topic> <offset> /
│ LIST_TOPICS <pattern> (TCP)
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ Subscriber Host (DSMS_SUB) │
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ Worker Pool: One Subscription Thread + File per Pattern │ │
│ │ ├── Thread #1: polls brave.server0 ──► subscriber_output/*.log │ │
│ │ └── Thread #2: polls server0 (all) ──► subscriber_output/*.log │ │
│ └────────────────────────────────┬────────────────────────────────┘ │
│ │ Atomic state sync │
│ ┌────────────────────────────────┴────────────────────────────────┐ │
│ │ Persistence: .state_<name>.json | PID Lock: .state_<name>.lock│ │
│ └────────────────────────────────┬────────────────────────────────┘ │
│ │ REST API (:8080) │
│ ┌────────────────────────────────▼────────────────────────────────┐ │
│ │ Endpoints: POST /subscribe | DELETE /subscribe | GET /status │ │
│ │ GET /logs?topic=<pattern> │ │
│ └─────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
▲
│ HTTP Queries (curl, browser, UI)
┌────────┴────────┐
│ External Client │
└─────────────────┘
| Concern | Architectural Choice | Rationale & Practical Benefit |
|---|---|---|
| Subscriber Model | Pull-Based Polling | Subscribers fetch data at their own pace (POLL_INTERVAL=3s), eliminating message loss from subscriber buffer saturation and keeping brokers stateless with respect to subscriber clients. |
| Log Format & Reading | Append-Only with Byte Offsets | Follows the Apache Kafka storage model. Subscribers request data starting at offset=N; the broker seeks directly without scanning, returning only new bytes and advancing the offset. |
| Cluster Consensus | Full RAFT Implementation | Guarantees strong consistency (linearizable writes), deterministic state machine replication, single-leader election, and seamless failover even under node crashes. |
| Crash Durability | Double-Layer Persistence | (1) RAFT state (current_term, voted_for, last_applied, log entries) is fsynced to raft_meta.json and raft_log.jsonl via RaftPersister.(2) Committed records are appended to per-topic logs on disk ( broker_data/node<N>/<topic>.log). |
| Subscriber Reliability | Persistent Thread-Per-Subscription | Subscriptions run on independent threads writing to dedicated files (subscriber_output/). State manifests (.state_<name>.json) rehydrate upon subscriber restart so no duplicate data is fetched. |
| Process Exclusivity | Cross-Platform PID Locks | .state_<name>.lock ensures two concurrent subscriber processes cannot corrupt identical state manifests, using active PID validation. |
| Topic Aggregation | Prefix & Suffix Pattern Matching | Topics follow <app_name>.<server_id>. Subscribers can query an exact topic (brave.server0), an app prefix (brave across all servers), or a server suffix (server0 for all apps on a host). |
Files: publisher/dsms_pub.py, publisher/metrics.py, publisher/apps.json
- Configuration: Reads
apps.jsoncontainingserver_id(e.g.,"server0") and targeted process names (apps: ["brave", "whatsapp", "msedge"]). Topics are automatically constructed as{app}.{server_id}. - Metric Collection: Utilizes
psutilto inspect active processes, extracting process-level CPU percentage and RSS memory. If an application is not currently active, it seamlessly falls back to system-wide CPU and memory metrics. - Durable Registration: Sends
CREATE_TOPIC <topic>for each topic at boot with exponential retry logic (MAX_RETRIES=10). - Leader Caching & Failover: Caches the broker leader port. If a follower responds with
NACK <leader_port>, the publisher redirects immediately to the leader. If the current connection terminates, the publisher marks the socket dead and rediscovers the newly elected leader.
File: broker/broker.py
- Multi-Threaded TCP Server: Binds to the node's configured host and port, spawning dedicated worker threads per incoming connection for client requests and inter-broker RAFT communication.
- Request Routing:
CREATE_TOPIC <topic>: Proposes topic creation to the RAFT state machine. Waits for majority commit before returningACK.METRIC <topic> <data>: Validates topic existence, proposes the metric to RAFT, waits for consensus commit, and returnsACK.SUBSCRIBE <topic> <offset>: Reads committed data from the localLogStoreat the specified byte offset. ReturnsDATA <new_offset>\nfollowed by the raw bytes.LIST_TOPICS <pattern>: Evaluates registered topic files against pattern matching rules (exact, prefix, or suffix) and returns matching concrete topics to subscribers.- RAFT RPCs (
VOTE_REQUEST,VOTE_GRANTED,VOTE_DENIED,REPLICATE,REPLICATE_ACK): Dispatched to the localRaftNode.
- Follower Redirection: Followers reject client writes with
NACK <leader_port>, informing clients of the current leader's network location.
Files: broker/raft.py, broker/raft_persister.py
- State Machine: Nodes operate as
FOLLOWER,CANDIDATE, orLEADER. - Randomized Timers: Follower election timeouts are randomized between
6.0sand15.0s(ELECTION_TIMEOUT_MIN/MAX), well above the leader heartbeat interval (HEARTBEAT_INTERVAL=0.75s) to prevent split votes. - Log Commit Semantics: An entry is committed when a quorum (majority) of nodes confirms replication. Leader commit advancement strictly follows RAFT Section 5.4.2 by appending a
NO_OPupon election victory. - Non-Blocking RPC Queues: Dedicated per-peer worker threads (
_peer_send_worker) isolate network latency or dead peers from blocking the core RAFT state machine. - State Persistence (
RaftPersister): Persistscurrent_term,voted_for,last_applied, and the RAFT log entries (raft_meta.jsonandraft_log.jsonl) with atomic file replacements and fsync, including Windows file-locking retry backoff (WinError 5).
File: broker/log_store.py
- Disk Structure: Maintains isolated append-only log files under
broker_data/node<id>/<sanitized_topic>.log. - Deterministic Timestamps: Metric timestamps are assigned by the RAFT leader when an entry is proposed and replicated across the cluster, ensuring log files across all broker nodes are byte-for-byte identical.
- Zero-Copy Seeking:
read(topic, byte_offset)seeks directly to the target byte offset and streams out content up to the current file boundary, returning the advanced byte offset.
Files: subscriber/dsms_sub.py, subscriber/log_manager.py
- Dedicated Subscription Workers: Each
/subscribeinvocation creates an independentSubscriptioninstance with its own worker thread, polling loop, and offset map. - Dynamic Topic Discovery: Uses
LIST_TOPICSagainst the broker leader to automatically detect new matching topics created on the fly. - Disk Streaming: All received entries are stamped with their concrete topic name and appended sequentially to dedicated log files:
subscriber_output/<pattern>_<timestamp>_sub<id>.log - Crash Rehydration: State manifests (
subscriber_output/.state_<name>.json) track active subscriptions and their offsets. On service restart, previous subscriptions are resumed without duplicating log data. - Lockfile Protection: Enforces single-instance safety via
.state_<name>.lock. - Dual Consumption: Provides both continuous file-based log consumption and an integrated Flask HTTP API with endpoints for subscription control and query aggregation (
GET /logs,GET /status).
Step 1: Cluster Formation & Leader Election
1. Brokers 0, 1, 2 start as FOLLOWERS with randomized election timers.
2. First timer expires (e.g., Node 0) → increments term, becomes CANDIDATE, broadcasts VOTE_REQUEST.
3. Peers grant votes (candidate log is up to date, term is valid).
4. Node 0 secures majority (2/3 votes) → becomes LEADER, appends NO_OP, broadcasts heartbeats.
Step 2: Topic Creation
1. DSMS_PUB starts, reads apps.json → topics: ["brave.server0", "whatsapp.server0"].
2. Publisher sends CREATE_TOPIC brave.server0 to broker cluster.
3. If received by a follower: follower replies NACK <leader_port>; publisher retries leader.
4. Leader proposes CREATE_TOPIC entry in RAFT log, writes to raft_log.jsonl, broadcasts REPLICATE.
5. Followers append entry and reply REPLICATE_ACK.
6. Quorum reached → Leader advances commit_index → Apply threads create brave_server0.log on disk.
7. Leader sends ACK to publisher.
Step 3: Metric Publication (Every 5 Seconds)
1. Publisher queries psutil for process metrics → "cpu=14.2 memory=3.10".
2. Publisher sends: METRIC brave.server0 cpu=14.2 memory=3.10 to leader.
3. Leader proposes entry with deterministic timestamp ts="2026-04-05 12:00:00".
4. Leader replicates to followers → receives majority REPLICATE_ACK → advances commit_index.
5. Apply threads write {"ts": "2026-04-05 12:00:00", "data": "cpu=14.2 memory=3.10"} to disk.
6. Leader returns ACK to publisher.
Step 4: Subscription & Streaming
1. Client sends POST /subscribe?topic=server0 to DSMS_SUB.
2. Subscriber queries broker leader with LIST_TOPICS server0.
3. Leader resolves pattern to ["brave.server0", "whatsapp.server0"].
4. Subscription worker initializes offsets to 0, opens subscriber_output/server0_..._sub1.log.
5. Background poller sends SUBSCRIBE brave.server0 0 to leader.
6. Leader reads brave_server0.log from offset 0, replies:
DATA 84\n{"ts":"2026-04-05 12:00:00","data":"cpu=14.2 memory=3.10"}\n
7. Subscriber tags entry with source topic, appends to subscriber output file, updates offset to 84.
8. Next poll sends SUBSCRIBE brave.server0 84 → receives only incremental new data.
All network communication uses newline-terminated (\n) UTF-8 strings over persistent TCP sockets.
| Command | Direction | Format | Description |
|---|---|---|---|
CREATE_TOPIC |
Client → Broker | CREATE_TOPIC <topic>\n |
Request creation of a new topic |
METRIC |
Client → Broker | METRIC <topic> <data>\n |
Publish metric data to a topic |
ACK |
Broker → Client | ACK\n |
Operation committed successfully |
NACK |
Broker → Client | NACK <leader_port>\n |
Node is follower; redirect to leader port |
SUBSCRIBE |
Client → Broker | SUBSCRIBE <topic> <offset>\n |
Request log bytes starting from byte offset |
DATA |
Broker → Client | DATA <new_offset>\n<raw_bytes> |
Header with advanced offset followed by payload |
LIST_TOPICS |
Client → Broker | LIST_TOPICS <pattern>\n |
Query topics matching exact, prefix, or suffix |
TOPICS |
Broker → Client | TOPICS ["topic1", "topic2"]\n |
JSON list of matching topic names |
| Message | Wire Representation | Description |
|---|---|---|
VOTE_REQUEST |
VOTE_REQUEST {"term": T, "candidate_id": ID, "last_log_index": I, "last_log_term": LT}\n |
Candidate solicits election vote |
VOTE_GRANTED |
VOTE_GRANTED {"term": T, "voter_id": ID}\n |
Peer grants vote to candidate |
VOTE_DENIED |
VOTE_DENIED {"term": T}\n |
Peer rejects vote |
REPLICATE |
REPLICATE {"term": T, "leader_id": ID, "prev_log_index": PI, "prev_log_term": PT, "entries": [...], "commit_index": CI}\n |
Log replication payload or empty heartbeat |
REPLICATE_ACK |
REPLICATE_ACK {"term": T, "follower_id": ID, "match_index": MI, "success": true/false}\n |
Replication acknowledgment |
┌──────────────────────────────────────────────┐
│ │
▼ │
┌──────────────┐ Election Timeout ┌──────────────┴─┐
│ FOLLOWER ├──────────────────────►│ CANDIDATE │
└──────┬───────┘ (No Leader Ping) └──────┬─────────┘
▲ │
│ Higher Term Seen │ Majority Votes
│ ▼
└───────────────────────────────┌────────────────┐
│ LEADER │
└────────────────┘
- Follower: Listens for
REPLICATEmessages. If_timer_deadlinepasses without a valid heartbeat, transitions toCANDIDATE. - Candidate: Increments
current_term, votes for itself, writes state toraft_meta.json, and sendsVOTE_REQUESTto peers. Upon receiving(cluster_size // 2) + 1votes, transitions toLEADER. - Leader: Immediately appends a
NO_OPlog entry to flush prior-term commits, starts the periodic heartbeat loop (HEARTBEAT_INTERVAL=0.75s), and initializesnext_indexandmatch_indexfor each peer. - Step Down: If any node receives a message with
term > current_term, it immediately reverts toFOLLOWER, clearsvoted_for, persists its updated term, and resets its election timer.
- Client requests arrive at the leader's
propose()method. - Leader appends the entry to its in-memory log, stamps it with the current timestamp, fsyncs it to
raft_log.jsonl, and broadcastsREPLICATE. - Followers verify log continuity (
prev_log_indexandprev_log_term). If mismatched, the follower rejects the replication, prompting the leader to decrementnext_indexuntil log consistency is restored. - Once a majority of followers confirm the entry, the leader advances its
commit_index. - Background
_apply_loopthreads on all nodes monitorcommit_index > last_applied, sequentially applying committed entries toLogStoretopic files and persistinglast_appliedto disk.
| Failure Scenario | System Handling & Recovery Guarantee |
|---|---|
| Broker Leader Crashes | Followers detect missing heartbeats after election timeout (6–15s). A follower initiates an election and acquires quorum. The publisher receives connection errors, queries remaining brokers, receives NACK <new_port>, and seamlessly resumes publication with zero data loss. |
| Follower Broker Crashes | The 2 remaining nodes maintain a strict majority (2 out of 3). Writes and commits proceed uninterrupted. When the follower restarts, it reloads raft_meta.json and raft_log.jsonl, rejoins the cluster, and catches up via leader replication. |
| Network Partition (2 vs 1) | The minority partition (1 node) cannot achieve quorum and rejects writes. The majority partition (2 nodes) continues electing a leader and committing entries. When the partition heals, the isolated node steps down upon seeing the higher term and updates its log. |
| Subscriber Service Restarts | dsms_sub.py loads .state_<name>.json containing previous topic offsets. Resumed worker threads reopen output files in append mode ("ab") and poll brokers from their last acknowledged byte offset, preventing duplicated entries. |
| Broker Process Crash & Reboot | RaftPersister reloads current_term, voted_for, and last_applied alongside raft_log.jsonl. Because last_applied is preserved, already-committed log entries are never re-applied to topic files on reboot. |
| Concurrent Process Conflict | .state_<name>.lock performs active PID validation (psutil/os.kill), preventing accidental duplicate subscribers from clobbering identical state files. |
Pub_Sub_Systems_DSMS/
├── common/
│ ├── __init__.py
│ ├── config.py # Shared cluster topology, timeouts, ports
│ └── protocol.py # TCP message encoding, decoding & socket helpers
├── broker/
│ ├── __init__.py
│ ├── broker.py # Broker TCP server, request routing, connection handler
│ ├── log_store.py # Per-topic append-only log store & byte-offset reader
│ ├── raft.py # RAFT consensus state machine (leader election & replication)
│ └── raft_persister.py # Atomic disk persistence for RAFT metadata & logs
├── publisher/
│ ├── __init__.py
│ ├── apps.json # Publisher application monitoring configuration
│ ├── dsms_pub.py # Main publisher process with leader caching & retry loops
│ └── metrics.py # psutil CPU/memory metrics collection & fallback
├── subscriber/
│ ├── __init__.py
│ ├── dsms_sub.py # Multi-threaded subscriber, file streaming, REST API, state manifest
│ └── log_manager.py # In-memory log cache and prefix/suffix aggregation utility
├── broker_data/ # Runtime broker data directories (per node)
│ ├── node0/ # Node 0 topic logs (.log), raft_meta.json, raft_log.jsonl
│ ├── node1/ # Node 1 topic logs (.log), raft_meta.json, raft_log.jsonl
│ └── node2/ # Node 2 topic logs (.log), raft_meta.json, raft_log.jsonl
├── subscriber_output/ # Runtime subscriber output directory
│ ├── .state_<name>.json # Subscriber persistent manifest (subscriptions & offsets)
│ ├── .state_<name>.lock # PID lockfile preventing concurrent subscriber collisions
│ └── <topic>_<ts>_sub<id>.log # Dedicated streaming log files per subscription
├── tests/
│ └── test_dsms.py # End-to-end automated validation and test suite
├── docs/ # Component architectural documentation
│ ├── part1_raft_consensus.md # Part 1 deep-dive (RAFT consensus & protocol)
│ ├── part2_broker_storage.md # Part 2 deep-dive (Broker server & log storage)
│ └── part3_pubsub_client.md # Part 3 deep-dive (Publisher & persistent subscriber)
├── reset.ps1 # Windows cleanup script (wipes state, kills stale processes)
├── reset.sh # Linux/macOS cleanup script (wipes state, kills stale processes)
├── start_cluster.py # Helper to launch all 3 broker nodes
├── RUN_COMMANDS.md # Step-by-step CLI execution cheat sheet
├── requirements.txt # Project dependencies (psutil, flask)
└── README.md # Comprehensive project documentation (this file)
- Python 3.8+ installed on all machines.
- Network connectivity between machines (for multi-host deployments).
# Clone the repository
git clone <repo-url>
cd Pub_Sub_Systems_DSMS
# Install required Python packages
python -m pip install -r requirements.txtRequirements:
psutil(process inspection and system metrics)flask(subscriber HTTP REST API)
Edit common/config.py to declare the broker nodes. For local multi-terminal testing, use 127.0.0.1. For multi-machine deployment, specify the respective LAN IP addresses:
BROKERS = [
{"id": 0, "host": "10.145.99.48", "port": 5000},
{"id": 1, "host": "10.145.99.43", "port": 5001},
{"id": 2, "host": "10.145.27.212", "port": 5002},
]Before running a fresh demo or test run, wipe prior test logs and stale processes:
Windows PowerShell:
powershell -ExecutionPolicy Bypass -File .\reset.ps1Linux / macOS:
chmod +x reset.sh
./reset.shRun each broker in its own terminal:
Terminal 1 (Broker Node 0):
python broker/broker.py --id 0Terminal 2 (Broker Node 1):
python broker/broker.py --id 1Terminal 3 (Broker Node 2):
python broker/broker.py --id 2(Alternatively, run python start_cluster.py to start all three brokers automatically).
Wait ~4 seconds for election timers to fire and a leader to be established.
Configure the applications to monitor in publisher/apps.json:
{
"server_id": "server0",
"apps": ["brave", "whatsapp", "msedge"]
}Terminal 4:
python publisher/dsms_pub.py --apps publisher/apps.jsonThe publisher registers topics and streams CPU/memory metrics every 5 seconds.
Terminal 5:
python subscriber/dsms_sub.py --port 8080 --name sub_client1Base URL: http://localhost:8080 (or the configured SUBSCRIBER_HTTP_PORT).
Endpoint: POST /subscribe?topic=<pattern>
Creates an independent background poller worker and output file for the pattern. Supports exact topics (brave.server0), app prefixes (brave), or server suffixes (server0).
curl -X POST "http://localhost:8080/subscribe?topic=brave.server0"Response (200 OK):
{
"status": "subscribed",
"subscription_id": 1,
"pattern": "brave.server0",
"file": "D:\\Pub_Sub_Systems_DSMS\\subscriber_output\\brave.server0_2026-04-05_12-00-00_sub1.log",
"created_at": "2026-04-05 12:00:00"
}Endpoint: GET /status
Returns active subscriptions, monitored topics, current byte offsets, and leader information.
curl "http://localhost:8080/status"Response (200 OK):
{
"name": "sub_client1",
"leader_port": 5000,
"state_file": "D:\\Pub_Sub_Systems_DSMS\\subscriber_output\\.state_sub_client1.json",
"subscriptions": [
{
"id": 1,
"pattern": "brave.server0",
"created_at": "2026-04-05 12:00:00",
"file": "D:\\Pub_Sub_Systems_DSMS\\subscriber_output\\brave.server0_2026-04-05_12-00-00_sub1.log",
"alive": true,
"topics": {
"brave.server0": 248
}
}
]
}Endpoint: GET /logs?topic=<pattern>
Returns all collected metric records matching an exact topic name, prefix, or suffix.
# Query exact topic:
curl "http://localhost:8080/logs?topic=brave.server0"
# Suffix aggregation (all apps on server0):
curl "http://localhost:8080/logs?topic=server0"
# Prefix aggregation (all servers running brave):
curl "http://localhost:8080/logs?topic=brave"Response (200 OK):
{
"topic": "server0",
"entry_count": 2,
"entries": [
{
"topic": "brave.server0",
"ts": "2026-04-05 12:00:01",
"data": "cpu=12.4 memory=4.20"
},
{
"topic": "msedge.server0",
"ts": "2026-04-05 12:00:01",
"data": "cpu=8.1 memory=2.90"
}
]
}Endpoint: DELETE /subscribe?id=<sub_id> or DELETE /subscribe?topic=<pattern>
Stops the subscription worker thread and flushes its output log file.
# By subscription ID:
curl -X DELETE "http://localhost:8080/subscribe?id=1"
# By pattern:
curl -X DELETE "http://localhost:8080/subscribe?topic=server0"Response (200 OK):
{
"status": "unsubscribed",
"stopped": [1]
}Because each subscription outputs directly to disk, you can monitor metrics in real time using native CLI tools:
Windows PowerShell:
Get-Content -Wait -Tail 10 subscriber_output\brave.server0_*.logLinux / macOS:
tail -f subscriber_output/brave.server0_*.logThe repository includes a comprehensive automated test suite in tests/test_dsms.py.
How to run:
- Start the 3 broker nodes.
- (Optional) Start
dsms_sub.pyon port 8080 if running the HTTP prefix aggregation test. - Run the test suite:
python tests/test_dsms.pyTest Coverage:
- Test 1 — Broker Connectivity: Verifies all 3 brokers are reachable via TCP.
- Test 2 — Leader Detection: Verifies exactly one leader is elected and followers recognize it.
- Test 3 — NACK Redirection: Validates followers reply with
NACKcontaining the leader's port. - Test 4 — Topic Creation: Validates topic creation via RAFT consensus and commit ACK.
- Test 5 — Metric Publication: Verifies publication of metric entries through the leader.
- Test 6 — Baseline Subscription: Validates
SUBSCRIBEfrom offset 0 returns initial records. - Test 7 — Offset-Based Polling: Verifies reading at
offset=Nreturns only newly published data. - Test 8 — Multi-Topic Isolation: Ensures distinct topics maintain independent offsets and logs.
- Test 9 — Prefix Aggregation: Validates multi-topic prefix and suffix queries through the subscriber HTTP API.
- Test 10 — Replication Integrity: Verifies disk logs across all broker nodes are byte-for-byte identical.
All settings reside in common/config.py:
| Setting | Default | Description |
|---|---|---|
BROKERS |
Ports 5000, 5001, 5002 | Cluster network topology (node IDs, hosts, ports) |
ELECTION_TIMEOUT_MIN |
6.0 s |
Minimum follower election timeout |
ELECTION_TIMEOUT_MAX |
15.0 s |
Maximum follower election timeout |
HEARTBEAT_INTERVAL |
0.75 s |
Leader heartbeat frequency |
PUBLISH_INTERVAL |
5 s |
Publisher metric collection and broadcast frequency |
MAX_RETRIES |
10 |
Maximum retry attempts for operations and leader discovery |
RETRY_DELAY |
1.0 s |
Delay between retry attempts |
POLL_INTERVAL |
3 s |
Subscriber broker polling frequency |
SUBSCRIBER_HTTP_PORT |
8080 |
Default HTTP REST API port |