Skip to content

Commit 98d0277

Browse files
committed
more docs
1 parent 9e0b003 commit 98d0277

File tree

2 files changed

+60
-60
lines changed

2 files changed

+60
-60
lines changed

counters/README.md

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

counters/atomic_counter.go

Lines changed: 1 addition & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,6 @@
11
// Provides AtomicCounter - a high-performance atomic counter implementation
22
// 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+
634
package counters
645

656
import (

0 commit comments

Comments
 (0)