thefinalboss commited on
Commit
7fb6e8a
·
verified ·
1 Parent(s): c2a1e0d

Add AICL example: 26_distributed_consensus.aicl

Browse files
data/aicl/examples/26_distributed_consensus.aicl ADDED
@@ -0,0 +1,393 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # AICL Example: Distributed Consensus System
2
+ # Implements a Raft-based distributed consensus protocol with leader election, log replication,
3
+ # split-brain prevention, and commit index management for strongly consistent state machines.
4
+
5
+ # Level 1: Architecture
6
+ Goal: Provide a fault-tolerant distributed consensus mechanism ensuring linearizable consistency across a cluster of nodes using the Raft protocol, with automatic leader election, log replication, and split-brain prevention.
7
+
8
+ Constraint: Quorum must be maintained with (N/2 + 1) nodes for any state mutation
9
+ Constraint: Leader election timeout must be randomized between 150ms-300ms to prevent split votes
10
+ Constraint: Log entries must be committed only after replication to a majority of nodes
11
+ Constraint: Term numbers must be monotonically increasing and never reused
12
+ Constraint: Candidates must increment term before requesting votes
13
+
14
+ Risk: Network partition causing split-brain with dual leaders
15
+ Recovery: Enforce strict term-based leadership; nodes in minority partition automatically step down to follower when detecting higher term from majority partition heartbeat
16
+
17
+ Risk: Leader failure during log replication leaving followers with divergent logs
18
+ Recovery: Followers truncate conflicting log entries beyond the last agreed-upon index and re-replicate from the new leader's log using AppendEntries consistency check
19
+
20
+ Risk: Split vote during leader election with no candidate achieving majority
21
+ Recovery: Randomized election timeouts ensure candidates retry at staggered intervals; subsequent election round produces a clear winner with high probability
22
+
23
+ Risk: Stale leader continuing to serve requests after network partition heals
24
+ Recovery: Stale leader detects higher term in AppendEntries or RequestVote response and immediately steps down to follower, discarding uncommitted entries
25
+
26
+ Risk: Log divergence across nodes due to missed entries during transient failures
27
+ Recovery: Leader maintains nextIndex and matchIndex per follower; decrements nextIndex on consistency failure and re-sends entries from the point of divergence
28
+
29
+ Risk: Quorum loss causing complete system unavailability
30
+ Recovery: Remaining nodes enter pre-vote candidate state; system remains in degraded read-only mode until quorum is restored; write operations are rejected with explicit error
31
+
32
+ Layer: ConsensusCore
33
+ SubLayer: LeaderElection
34
+ SubLayer: LogReplication
35
+ SubLayer: CommitManagement
36
+ SubLayer: SafetyGuarantees
37
+ Layer: ClusterManagement
38
+ SubLayer: MembershipTracking
39
+ SubLayer: ConfigurationChange
40
+ SubLayer: SnapshotRecovery
41
+ Layer: ClientInterface
42
+ SubLayer: RequestRouting
43
+ SubLayer: ResponseCoalescing
44
+
45
+ Validation: Quorum must be achievable before accepting any write operation
46
+ Validation: Leader term must be strictly greater than all previous terms observed by a majority
47
+ Validation: Committed entries must never be overwritten or truncated across any node
48
+ Validation: Leader completeness property — any committed entry must be present in the log of any future leader
49
+ Validation: Log matching property — if two entries at the same index share the same term, all preceding entries must be identical
50
+ Validation: Election safety — at most one leader can exist per term
51
+ Validation: Snapshot last_included_index must never exceed the commit index
52
+
53
+ # Level 2: Entities
54
+ Entity ConsensusNode
55
+ nodeId: string
56
+ currentTerm: integer
57
+ votedFor: string
58
+ log: list
59
+ commitIndex: integer
60
+ lastApplied: integer
61
+ state: string
62
+ leaderId: string
63
+ electionTimeout: float
64
+ heartbeatInterval: float
65
+
66
+ Entity LogEntry
67
+ index: integer
68
+ term: integer
69
+ command: bytes
70
+ clientRequestId: string
71
+ timestamp: datetime
72
+ isCommitted: boolean
73
+ snapshotBoundary: boolean
74
+
75
+ Entity LeaderState
76
+ nextIndex: dict
77
+ matchIndex: dict
78
+ replicationInProgress: dict
79
+ lastHeartbeatSent: datetime
80
+ pendingClientRequests: list
81
+ leaseExpiry: datetime
82
+
83
+ Entity ClusterConfig
84
+ nodes: list
85
+ votingMembers: set
86
+ nonVotingMembers: set
87
+ jointConfiguration: boolean
88
+ configurationIndex: integer
89
+ version: integer
90
+ minElectionTimeout: float
91
+ maxElectionTimeout: float
92
+
93
+ Entity Snapshot
94
+ lastIncludedIndex: integer
95
+ lastIncludedTerm: integer
96
+ stateMachineData: bytes
97
+ configuration: dict
98
+ createdAt: datetime
99
+ sizeBytes: integer
100
+ checksum: string
101
+
102
+ Entity VoteRequest
103
+ candidateId: string
104
+ term: integer
105
+ lastLogIndex: integer
106
+ lastLogTerm: integer
107
+ priority: integer
108
+ preVote: boolean
109
+
110
+ Entity AppendEntriesRequest
111
+ leaderId: string
112
+ term: integer
113
+ prevLogIndex: integer
114
+ prevLogTerm: integer
115
+ entries: list
116
+ leaderCommit: integer
117
+ leaseDuration: float
118
+
119
+ # Level 3: Behaviors
120
+ Behavior RequestVote
121
+ Input: voteRequest: VoteRequest
122
+ Output: term: integer, voteGranted: boolean
123
+ Action:
124
+ If voteRequest.term < currentTerm, reject with currentTerm
125
+ If voteRequest.preVote is true, evaluate log-up-to-date without updating term
126
+ If votedFor is null or equals candidateId, and candidate log is at least as up-to-date, grant vote
127
+ Reset election timer upon granting vote
128
+ Persist votedFor to stable storage before responding
129
+
130
+ Behavior AppendEntries
131
+ Input: appendRequest: AppendEntriesRequest
132
+ Output: term: integer, success: boolean, conflictIndex: integer, conflictTerm: integer
133
+ Action:
134
+ If appendRequest.term < currentTerm, reject immediately
135
+ If prevLogIndex exceeds log length, return conflictIndex = len(log) and conflictTerm = None
136
+ If log[prevLogIndex].term != prevLogTerm, find conflictTerm and earliest conflictIndex
137
+ Truncate any conflicting entries beyond matching prefix
138
+ Append new entries not already in log
139
+ If leaderCommit > commitIndex, advance commitIndex to min(leaderCommit, index of last new entry)
140
+ Reset election timer to prevent unnecessary election
141
+
142
+ Behavior BecomeLeader
143
+ Input: term: integer, nodeId: string
144
+ Output: void
145
+ Action:
146
+ Transition state to leader
147
+ Initialize nextIndex for all peers to lastLogIndex + 1
148
+ Initialize matchIndex for all peers to 0
149
+ Append no-op entry to log to commit entries from previous terms
150
+ Begin sending periodic heartbeats with lease duration
151
+ Acquire leader lease for read requests
152
+
153
+ Behavior SubmitCommand
154
+ Input: command: bytes, clientRequestId: string
155
+ Output: index: integer, term: integer, isCommitted: boolean
156
+ Action:
157
+ Validate leader state; redirect to current leader if follower
158
+ Append entry to local log with current term
159
+ Replicate entry to all followers via AppendEntries
160
+ Wait for majority acknowledgment
161
+ Advance commit index
162
+ Apply to state machine once committed
163
+ Respond to client with commit confirmation
164
+
165
+ Behavior InstallSnapshot
166
+ Input: leaderId: string, term: integer, lastIncludedIndex: integer, lastIncludedTerm: integer, data: bytes, offset: integer, done: boolean
167
+ Output: term: integer
168
+ Action:
169
+ Validate term; reject if stale
170
+ If snapshot lastIncludedIndex <= lastApplied, discard and acknowledge
171
+ If partial snapshot, write data at offset to temporary snapshot buffer
172
+ If done is true, discard log entries covered by snapshot
173
+ Reset state machine with snapshot data
174
+ Persist snapshot to stable storage
175
+ Update lastIncludedIndex and lastIncludedTerm
176
+
177
+ Behavior TransferLeadership
178
+ Input: targetId: string, timeout: float
179
+ Output: success: boolean, newLeaderId: string
180
+ Action:
181
+ Stop accepting new client requests
182
+ If target is not up-to-date, send AppendEntries to bring it current
183
+ Send TimeoutNow request to target node
184
+ Target node starts immediate election with minimal timeout
185
+ Wait for new leader to emerge within timeout
186
+ Resume accepting requests under new leader
187
+
188
+ Behavior AdvanceCommitIndex
189
+ Input: void
190
+ Output: newCommitIndex: integer
191
+ Action:
192
+ Find highest index N such that N > commitIndex and majority of matchIndex[i] >= N
193
+ Verify log[N].term == currentTerm (only commit entries from current term)
194
+ Set commitIndex = N
195
+ Apply all entries between lastApplied and commitIndex to state machine
196
+ Return new commit index
197
+
198
+ # Level 4: Conditions
199
+ Condition: LeaderStepDown
200
+ When leader receives RequestVote or AppendEntries with term > currentTerm
201
+ Then immediately transition to follower state, update currentTerm, reset votedFor, and abort pending client requests
202
+
203
+ Condition: ElectionTimeoutExpired
204
+ When election timer fires without receiving valid heartbeat from leader
205
+ Then increment currentTerm, transition to candidate state, vote for self, and broadcast RequestVote to all peers
206
+
207
+ Condition: QuorumLost
208
+ When number of responsive nodes falls below (totalNodes / 2 + 1)
209
+ Then leader steps down to follower, all write operations are rejected, system enters read-only degraded mode, and alert is emitted to cluster operator
210
+
211
+ Condition: LogConflictDetected
212
+ When AppendEntries consistency check fails at prevLogIndex
213
+ Then decrement nextIndex for that follower and retry AppendEntries with earlier log entries until consistency point is found
214
+
215
+ Condition: ConfigChangeUnsafe
216
+ When joint consensus configuration change is in progress and a second change is attempted
217
+ Then reject the second configuration change until the first is committed; only one config change may be outstanding at a time
218
+
219
+ # Level 5: Events
220
+ Event: OnLeaderElected
221
+ On new leader confirmed by majority votes
222
+ Action: Initialize leader state, emit leadership transition metric, notify client redirect service, begin heartbeat cycle, append no-op entry to commit prior term entries
223
+
224
+ Event: OnLogCommitted
225
+ On entry committed at new commit index
226
+ Action: Apply entry to state machine, respond to waiting client, emit latency metric from submission to commit, update lastApplied index, trigger snapshot if log size exceeds threshold
227
+
228
+ Event: OnNodeJoined
229
+ On new node added to cluster configuration
230
+ Action: Add to non-voting members initially, begin log catchup via AppendEntries, promote to voting member once caught up, update cluster configuration with joint consensus, broadcast new config
231
+
232
+ Event: OnSnapshotCreated
233
+ On snapshot completed and persisted
234
+ Action: Truncate log entries covered by snapshot, emit snapshot size metric, update lastIncludedIndex, notify peers of new snapshot availability, free memory from compacted log entries
235
+
236
+ Event: OnFollowerLagDetected
237
+ On follower matchIndex falls behind commitIndex by more than 1000 entries
238
+ Action: Switch to sending InstallSnapshot instead of incremental AppendEntries, emit lag warning metric, consider throttling client submissions if replication backlog grows unbounded
239
+
240
+ # Level 6: Concurrency
241
+ Parallel:
242
+ Leader heartbeat broadcast to all followers
243
+ Log replication streams to individual followers
244
+ State machine application of committed entries
245
+ Snapshot creation on background thread
246
+ Client request acknowledgment processing
247
+
248
+ # Level 7: Optimization
249
+ Optimize: Log replication throughput
250
+ Priority: Batch multiple log entries into a single AppendEntries RPC when follower is behind, reducing round trips; pipeline AppendEntries requests to overlapping followers to maximize network utilization
251
+
252
+ Optimize: Leader election latency
253
+ Priority: Use pre-vote protocol to avoid disruptive elections from partitioned nodes; prioritize nodes with more complete logs as election candidates to reduce post-election catchup
254
+
255
+ Optimize: Read request serving
256
+ Priority: Implement leader lease-based reads without logging; serve read-only requests from followers using ReadIndex protocol for linearizable reads without write latency impact
257
+
258
+ # Level 8: Learning
259
+ Learn: Optimal election timeout for current network conditions
260
+ Goal: Minimize leader election latency while avoiding unnecessary elections from transient network delays
261
+ Adapt: electionTimeout range
262
+ Based: Observed round-trip times to peers and historical false election rates over sliding 1000-election window
263
+
264
+ Learn: Optimal batch size for AppendEntries
265
+ Goal: Maximize replication throughput without causing excessive memory pressure or latency spikes
266
+ Adapt: maxEntriesPerBatch
267
+ Based: Follower response times and network bandwidth utilization metrics over the last 60 seconds
268
+
269
+ Learn: Snapshot trigger threshold
270
+ Goal: Balance log compaction frequency against snapshot I/O overhead
271
+ Adapt: snapshotThreshold (log size in bytes before triggering snapshot)
272
+ Based: Snapshot creation duration, disk I/O load, and application read/write ratio over the last 100 snapshots
273
+
274
+ # Level 9: Security
275
+ Security:
276
+ Encrypt: All RPC communication between nodes using TLS 1.3 with mutual certificate authentication
277
+ Encrypt: Snapshot data at rest using AES-256-GCM
278
+ Encrypt: Vote persistence records on stable storage
279
+ Protect: Leader election against unauthorized nodes by validating cluster membership certificates
280
+ Protect: Log entries against tampering by computing Merkle hash chains across term boundaries
281
+ Protect: Client request integrity by requiring signed clientRequestId with replay protection nonce
282
+ Protect: Configuration change requests against unauthorized modification via multi-party authorization
283
+
284
+ # Level 10: Native
285
+ Native: Go
286
+ {
287
+ package raft
288
+
289
+ import (
290
+ "context"
291
+ "sync"
292
+ "time"
293
+ )
294
+
295
+ type State int
296
+
297
+ const (
298
+ Follower State = iota
299
+ Candidate
300
+ Leader
301
+ )
302
+
303
+ type Node struct {
304
+ mu sync.RWMutex
305
+ id string
306
+ state State
307
+ currentTerm uint64
308
+ votedFor string
309
+ log []*LogEntry
310
+ commitIndex uint64
311
+ lastApplied uint64
312
+ nextIndex map[string]uint64
313
+ matchIndex map[string]uint64
314
+ heartbeatTick *time.Ticker
315
+ electionTimer *time.Timer
316
+ config *ClusterConfig
317
+ stateMachine StateMachine
318
+ snapshotStore SnapshotStore
319
+ transport Transport
320
+ applyCh chan *LogEntry
321
+ stopCh chan struct{}
322
+ }
323
+
324
+ func (n *Node) Run(ctx context.Context) {
325
+ for {
326
+ select {
327
+ case <-n.electionTimer.C:
328
+ n.startElection()
329
+ case <-n.heartbeatTick.C:
330
+ if n.state == Leader {
331
+ n.broadcastHeartbeat()
332
+ }
333
+ case entry := <-n.applyCh:
334
+ n.stateMachine.Apply(entry.Command)
335
+ case <-ctx.Done():
336
+ n.shutdown()
337
+ return
338
+ }
339
+ }
340
+ }
341
+
342
+ func (n *Node) startElection() {
343
+ n.mu.Lock()
344
+ n.state = Candidate
345
+ n.currentTerm++
346
+ n.votedFor = n.id
347
+ term := n.currentTerm
348
+ lastIdx, lastTerm := n.lastLogInfo()
349
+ n.mu.Unlock()
350
+
351
+ votesReceived := 1
352
+ quorum := len(n.config.VotingMembers)/2 + 1
353
+
354
+ for _, peer := range n.config.VotingMembers {
355
+ if peer == n.id {
356
+ continue
357
+ }
358
+ go func(peerID string) {
359
+ resp, err := n.transport.RequestVote(peerID, &VoteRequest{
360
+ CandidateId: n.id,
361
+ Term: term,
362
+ LastLogIndex: lastIdx,
363
+ LastLogTerm: lastTerm,
364
+ })
365
+ if err != nil {
366
+ return
367
+ }
368
+ n.mu.Lock()
369
+ defer n.mu.Unlock()
370
+ if resp.Term > n.currentTerm {
371
+ n.becomeFollower(resp.Term)
372
+ return
373
+ }
374
+ if resp.VoteGranted && n.state == Candidate {
375
+ votesReceived++
376
+ if votesReceived >= quorum {
377
+ n.becomeLeader()
378
+ }
379
+ }
380
+ }(peer)
381
+ }
382
+ n.resetElectionTimer()
383
+ }
384
+
385
+ func (n *Node) becomeLeader() {
386
+ n.state = Leader
387
+ lastIdx := n.lastLogIndex()
388
+ for peer := range n.nextIndex {
389
+ n.nextIndex[peer] = lastIdx + 1
390
+ n.matchIndex[peer] = 0
391
+ }
392
+ n.appendNoOp()
393
+ }