|
1 | 1 | // Provides AtomicCounter - a high-performance atomic counter implementation |
2 | 2 | // for distributed systems with CRDT semantics. |
3 | | -// |
4 | | -// # AtomicCounter Architecture |
5 | | -// |
6 | | -// AtomicCounter provides atomic increment operations in distributed environments while |
7 | | -// optimizing performance through intelligent caching. It supports Natural (increment-only) |
8 | | -// and ZCounter (two-way) types with CRDT merge semantics. |
9 | | -// |
10 | | -// ## Core Design Principle |
11 | | -// |
12 | | -// The counter trades CPU usage for data freshness, but **only for data from other replicas**. |
13 | | -// All writes to the current replica are immediately reflected in the counter value. |
14 | | -// Caching only affects how frequently we read data that arrived via synchronization. |
15 | | -// |
16 | | -// ## How It Works |
17 | | -// |
18 | | -// The counter uses a lazy loading pattern with time-based caching. When data is requested: |
19 | | -// |
20 | | -// 1. **Cache Check**: If cached data hasn't expired, return it immediately |
21 | | -// 2. **Database Load**: Otherwise, load fresh data from the LSM database |
22 | | -// 3. **Parse & Cache**: Parse TLV data into internal structures and cache with expiration |
23 | | -// |
24 | | -// For increments, the process is: |
25 | | -// |
26 | | -// 1. **Load Data**: Get current counter state (cached or from DB) |
27 | | -// 2. **Atomic Update**: Use Go's atomic primitives to update the value |
28 | | -// 3. **Generate TLV**: Create TLV records for persistence |
29 | | -// 4. **Commit**: Write changes to database with CRDT merge semantics |
30 | | -// |
31 | | -// ## Internal Structure |
32 | | -// |
33 | | -// The counter maintains two internal representations: |
34 | | -// |
35 | | -// - **atomicNcounter**: For Natural counters, uses atomic.Uint64 for thread-safe increments |
36 | | -// - **atomicZCounter**: For ZCounter, uses atomic.Pointer with revision tracking for conflict resolution |
37 | | -// |
38 | | -// ## Performance Trade-offs |
39 | | -// |
40 | | -// The design trades CPU usage for freshness of **synchronized data from other replicas**. |
41 | | -// With updatePeriod > 0, the counter caches data to avoid expensive database reads, |
42 | | -// but may return slightly stale values from other replicas. Local writes are always |
43 | | -// immediately visible. With updatePeriod = 0, it always reads fresh synchronized data. |
44 | | -// |
45 | | -// ## Thread Safety |
46 | | -// |
47 | | -// Operations are atomic when using a single instance. Multiple instances may have |
48 | | -// race conditions due to the distributed nature of the system. |
49 | | -// |
50 | | -// ## Example: Cache vs Local Writes |
51 | | -// |
52 | | -// ```go |
53 | | -// counter := NewAtomicCounter(db, objectID, fieldOffset, 1*time.Second) |
54 | | -// |
55 | | -// // Local write - immediately visible |
56 | | -// counter.Increment(ctx, 5) // Value: 5 |
57 | | -// value, _ := counter.Get(ctx) // Returns 5 immediately |
58 | | -// |
59 | | -// // After sync from other replica (value: 10) |
60 | | -// // With cache: may still return 5 until cache expires |
61 | | -// // Without cache: immediately returns 15 (5 + 10) |
62 | | -// ``` |
| 3 | + |
63 | 4 | package counters |
64 | 5 |
|
65 | 6 | import ( |
|
0 commit comments