omgkit 2.13.0 → 2.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +129 -10
- package/package.json +2 -2
- package/plugin/agents/api-designer.md +5 -0
- package/plugin/agents/architect.md +8 -0
- package/plugin/agents/brainstormer.md +4 -0
- package/plugin/agents/cicd-manager.md +6 -0
- package/plugin/agents/code-reviewer.md +6 -0
- package/plugin/agents/copywriter.md +2 -0
- package/plugin/agents/data-engineer.md +255 -0
- package/plugin/agents/database-admin.md +10 -0
- package/plugin/agents/debugger.md +10 -0
- package/plugin/agents/devsecops.md +314 -0
- package/plugin/agents/docs-manager.md +4 -0
- package/plugin/agents/domain-decomposer.md +181 -0
- package/plugin/agents/embedded-systems.md +397 -0
- package/plugin/agents/fullstack-developer.md +12 -0
- package/plugin/agents/game-systems-designer.md +375 -0
- package/plugin/agents/git-manager.md +10 -0
- package/plugin/agents/journal-writer.md +2 -0
- package/plugin/agents/ml-engineer.md +284 -0
- package/plugin/agents/observability-engineer.md +353 -0
- package/plugin/agents/oracle.md +9 -0
- package/plugin/agents/performance-engineer.md +290 -0
- package/plugin/agents/pipeline-architect.md +6 -0
- package/plugin/agents/planner.md +12 -0
- package/plugin/agents/platform-engineer.md +325 -0
- package/plugin/agents/project-manager.md +3 -0
- package/plugin/agents/researcher.md +5 -0
- package/plugin/agents/scientific-computing.md +426 -0
- package/plugin/agents/scout.md +3 -0
- package/plugin/agents/security-auditor.md +7 -0
- package/plugin/agents/sprint-master.md +17 -0
- package/plugin/agents/tester.md +10 -0
- package/plugin/agents/ui-ux-designer.md +12 -0
- package/plugin/agents/vulnerability-scanner.md +6 -0
- package/plugin/commands/data/pipeline.md +47 -0
- package/plugin/commands/data/quality.md +49 -0
- package/plugin/commands/domain/analyze.md +34 -0
- package/plugin/commands/domain/map.md +41 -0
- package/plugin/commands/game/balance.md +56 -0
- package/plugin/commands/game/optimize.md +62 -0
- package/plugin/commands/iot/provision.md +58 -0
- package/plugin/commands/ml/evaluate.md +47 -0
- package/plugin/commands/ml/train.md +48 -0
- package/plugin/commands/perf/benchmark.md +54 -0
- package/plugin/commands/perf/profile.md +49 -0
- package/plugin/commands/platform/blueprint.md +56 -0
- package/plugin/commands/security/audit.md +54 -0
- package/plugin/commands/security/scan.md +55 -0
- package/plugin/commands/sre/dashboard.md +53 -0
- package/plugin/registry.yaml +787 -0
- package/plugin/skills/ai-ml/experiment-tracking/SKILL.md +338 -0
- package/plugin/skills/ai-ml/feature-stores/SKILL.md +340 -0
- package/plugin/skills/ai-ml/llm-ops/SKILL.md +454 -0
- package/plugin/skills/ai-ml/ml-pipelines/SKILL.md +390 -0
- package/plugin/skills/ai-ml/model-monitoring/SKILL.md +398 -0
- package/plugin/skills/ai-ml/model-serving/SKILL.md +386 -0
- package/plugin/skills/event-driven/cqrs-patterns/SKILL.md +348 -0
- package/plugin/skills/event-driven/event-sourcing/SKILL.md +334 -0
- package/plugin/skills/event-driven/kafka-deep/SKILL.md +252 -0
- package/plugin/skills/event-driven/saga-orchestration/SKILL.md +335 -0
- package/plugin/skills/event-driven/schema-registry/SKILL.md +328 -0
- package/plugin/skills/event-driven/stream-processing/SKILL.md +313 -0
- package/plugin/skills/game/game-audio/SKILL.md +446 -0
- package/plugin/skills/game/game-networking/SKILL.md +490 -0
- package/plugin/skills/game/godot-patterns/SKILL.md +413 -0
- package/plugin/skills/game/shader-programming/SKILL.md +492 -0
- package/plugin/skills/game/unity-patterns/SKILL.md +488 -0
- package/plugin/skills/iot/device-provisioning/SKILL.md +405 -0
- package/plugin/skills/iot/edge-computing/SKILL.md +369 -0
- package/plugin/skills/iot/industrial-protocols/SKILL.md +438 -0
- package/plugin/skills/iot/mqtt-deep/SKILL.md +418 -0
- package/plugin/skills/iot/ota-updates/SKILL.md +426 -0
- package/plugin/skills/microservices/api-gateway-patterns/SKILL.md +201 -0
- package/plugin/skills/microservices/circuit-breaker-patterns/SKILL.md +246 -0
- package/plugin/skills/microservices/contract-testing/SKILL.md +284 -0
- package/plugin/skills/microservices/distributed-tracing/SKILL.md +246 -0
- package/plugin/skills/microservices/service-discovery/SKILL.md +304 -0
- package/plugin/skills/microservices/service-mesh/SKILL.md +181 -0
- package/plugin/skills/mobile-advanced/mobile-ci-cd/SKILL.md +407 -0
- package/plugin/skills/mobile-advanced/mobile-security/SKILL.md +403 -0
- package/plugin/skills/mobile-advanced/offline-first/SKILL.md +473 -0
- package/plugin/skills/mobile-advanced/push-notifications/SKILL.md +494 -0
- package/plugin/skills/mobile-advanced/react-native-deep/SKILL.md +374 -0
- package/plugin/skills/simulation/numerical-methods/SKILL.md +434 -0
- package/plugin/skills/simulation/parallel-computing/SKILL.md +382 -0
- package/plugin/skills/simulation/physics-engines/SKILL.md +377 -0
- package/plugin/skills/simulation/validation-verification/SKILL.md +479 -0
- package/plugin/skills/simulation/visualization-scientific/SKILL.md +365 -0
- package/plugin/stdrules/ALIGNMENT_PRINCIPLE.md +240 -0
- package/plugin/workflows/ai-engineering/agent-development.md +3 -3
- package/plugin/workflows/ai-engineering/fine-tuning.md +3 -3
- package/plugin/workflows/ai-engineering/model-evaluation.md +3 -3
- package/plugin/workflows/ai-engineering/prompt-engineering.md +2 -2
- package/plugin/workflows/ai-engineering/rag-development.md +4 -4
- package/plugin/workflows/ai-ml/data-pipeline.md +188 -0
- package/plugin/workflows/ai-ml/experiment-cycle.md +203 -0
- package/plugin/workflows/ai-ml/feature-engineering.md +208 -0
- package/plugin/workflows/ai-ml/model-deployment.md +199 -0
- package/plugin/workflows/ai-ml/monitoring-setup.md +227 -0
- package/plugin/workflows/api/api-design.md +1 -1
- package/plugin/workflows/api/api-testing.md +2 -2
- package/plugin/workflows/content/technical-docs.md +1 -1
- package/plugin/workflows/database/migration.md +1 -1
- package/plugin/workflows/database/optimization.md +1 -1
- package/plugin/workflows/database/schema-design.md +3 -3
- package/plugin/workflows/development/bug-fix.md +3 -3
- package/plugin/workflows/development/code-review.md +2 -1
- package/plugin/workflows/development/feature.md +3 -3
- package/plugin/workflows/development/refactor.md +2 -2
- package/plugin/workflows/event-driven/consumer-groups.md +190 -0
- package/plugin/workflows/event-driven/event-storming.md +172 -0
- package/plugin/workflows/event-driven/replay-testing.md +186 -0
- package/plugin/workflows/event-driven/saga-implementation.md +206 -0
- package/plugin/workflows/event-driven/schema-evolution.md +173 -0
- package/plugin/workflows/fullstack/authentication.md +4 -4
- package/plugin/workflows/fullstack/full-feature.md +4 -4
- package/plugin/workflows/game-dev/content-pipeline.md +218 -0
- package/plugin/workflows/game-dev/platform-submission.md +263 -0
- package/plugin/workflows/game-dev/playtesting.md +237 -0
- package/plugin/workflows/game-dev/prototype-to-production.md +205 -0
- package/plugin/workflows/microservices/contract-first.md +151 -0
- package/plugin/workflows/microservices/distributed-tracing.md +166 -0
- package/plugin/workflows/microservices/domain-decomposition.md +123 -0
- package/plugin/workflows/microservices/integration-testing.md +149 -0
- package/plugin/workflows/microservices/service-mesh-setup.md +153 -0
- package/plugin/workflows/microservices/service-scaffolding.md +151 -0
- package/plugin/workflows/omega/1000x-innovation.md +2 -2
- package/plugin/workflows/omega/100x-architecture.md +2 -2
- package/plugin/workflows/omega/10x-improvement.md +2 -2
- package/plugin/workflows/quality/performance-optimization.md +2 -2
- package/plugin/workflows/research/best-practices.md +1 -1
- package/plugin/workflows/research/technology-research.md +1 -1
- package/plugin/workflows/security/penetration-testing.md +3 -3
- package/plugin/workflows/security/security-audit.md +3 -3
- package/plugin/workflows/sprint/sprint-execution.md +2 -2
- package/plugin/workflows/sprint/sprint-retrospective.md +1 -1
- package/plugin/workflows/sprint/sprint-setup.md +1 -1
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
# Event Sourcing
|
|
2
|
+
|
|
3
|
+
Event sourcing patterns including event stores, snapshots, projections, event replay, and temporal queries.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Event sourcing stores the state of an entity as a sequence of state-changing events rather than just current state.
|
|
8
|
+
|
|
9
|
+
## Core Concepts
|
|
10
|
+
|
|
11
|
+
### Event Store
|
|
12
|
+
- **Event**: Immutable fact that happened
|
|
13
|
+
- **Stream**: Ordered sequence of events for an entity
|
|
14
|
+
- **Version**: Position in the stream
|
|
15
|
+
- **Snapshot**: Materialized state at a point in time
|
|
16
|
+
|
|
17
|
+
### Key Benefits
|
|
18
|
+
- Complete audit trail
|
|
19
|
+
- Temporal queries (time travel)
|
|
20
|
+
- Event replay for debugging
|
|
21
|
+
- Natural fit for CQRS
|
|
22
|
+
- Rebuild state from events
|
|
23
|
+
|
|
24
|
+
## Event Design
|
|
25
|
+
|
|
26
|
+
### Event Structure
|
|
27
|
+
```typescript
|
|
28
|
+
interface DomainEvent {
|
|
29
|
+
// Identity
|
|
30
|
+
eventId: string;
|
|
31
|
+
streamId: string;
|
|
32
|
+
version: number;
|
|
33
|
+
|
|
34
|
+
// Metadata
|
|
35
|
+
eventType: string;
|
|
36
|
+
timestamp: Date;
|
|
37
|
+
correlationId?: string;
|
|
38
|
+
causationId?: string;
|
|
39
|
+
|
|
40
|
+
// Payload
|
|
41
|
+
data: Record<string, unknown>;
|
|
42
|
+
metadata?: Record<string, unknown>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Example events
|
|
46
|
+
interface OrderCreated {
|
|
47
|
+
eventType: 'OrderCreated';
|
|
48
|
+
data: {
|
|
49
|
+
orderId: string;
|
|
50
|
+
customerId: string;
|
|
51
|
+
items: Array<{ productId: string; quantity: number }>;
|
|
52
|
+
totalAmount: number;
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
interface OrderShipped {
|
|
57
|
+
eventType: 'OrderShipped';
|
|
58
|
+
data: {
|
|
59
|
+
orderId: string;
|
|
60
|
+
shippingAddress: Address;
|
|
61
|
+
carrier: string;
|
|
62
|
+
trackingNumber: string;
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Event Naming Conventions
|
|
68
|
+
```
|
|
69
|
+
Past tense (something happened):
|
|
70
|
+
- OrderCreated (not CreateOrder)
|
|
71
|
+
- PaymentReceived (not ReceivePayment)
|
|
72
|
+
- ItemAddedToCart (not AddItemToCart)
|
|
73
|
+
|
|
74
|
+
Domain language:
|
|
75
|
+
- OrderPlaced (not OrderInserted)
|
|
76
|
+
- CustomerRegistered (not UserCreated)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Aggregate Implementation
|
|
80
|
+
|
|
81
|
+
### Event-Sourced Aggregate
|
|
82
|
+
```typescript
|
|
83
|
+
abstract class AggregateRoot {
|
|
84
|
+
private uncommittedEvents: DomainEvent[] = [];
|
|
85
|
+
protected version: number = 0;
|
|
86
|
+
|
|
87
|
+
protected apply(event: DomainEvent): void {
|
|
88
|
+
this.when(event);
|
|
89
|
+
this.version++;
|
|
90
|
+
this.uncommittedEvents.push(event);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
protected abstract when(event: DomainEvent): void;
|
|
94
|
+
|
|
95
|
+
getUncommittedEvents(): DomainEvent[] {
|
|
96
|
+
return [...this.uncommittedEvents];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
clearUncommittedEvents(): void {
|
|
100
|
+
this.uncommittedEvents = [];
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
loadFromHistory(events: DomainEvent[]): void {
|
|
104
|
+
for (const event of events) {
|
|
105
|
+
this.when(event);
|
|
106
|
+
this.version++;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
class Order extends AggregateRoot {
|
|
112
|
+
private id: string;
|
|
113
|
+
private status: OrderStatus;
|
|
114
|
+
private items: OrderItem[] = [];
|
|
115
|
+
private totalAmount: number;
|
|
116
|
+
|
|
117
|
+
static create(command: CreateOrderCommand): Order {
|
|
118
|
+
const order = new Order();
|
|
119
|
+
order.apply({
|
|
120
|
+
eventType: 'OrderCreated',
|
|
121
|
+
streamId: command.orderId,
|
|
122
|
+
data: {
|
|
123
|
+
orderId: command.orderId,
|
|
124
|
+
customerId: command.customerId,
|
|
125
|
+
items: command.items,
|
|
126
|
+
totalAmount: command.totalAmount
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
return order;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
ship(address: Address, carrier: string): void {
|
|
133
|
+
if (this.status !== OrderStatus.Confirmed) {
|
|
134
|
+
throw new Error('Cannot ship unconfirmed order');
|
|
135
|
+
}
|
|
136
|
+
this.apply({
|
|
137
|
+
eventType: 'OrderShipped',
|
|
138
|
+
streamId: this.id,
|
|
139
|
+
data: { address, carrier, trackingNumber: generateTracking() }
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
protected when(event: DomainEvent): void {
|
|
144
|
+
switch (event.eventType) {
|
|
145
|
+
case 'OrderCreated':
|
|
146
|
+
this.id = event.data.orderId;
|
|
147
|
+
this.status = OrderStatus.Created;
|
|
148
|
+
this.items = event.data.items;
|
|
149
|
+
this.totalAmount = event.data.totalAmount;
|
|
150
|
+
break;
|
|
151
|
+
case 'OrderShipped':
|
|
152
|
+
this.status = OrderStatus.Shipped;
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Event Store Patterns
|
|
160
|
+
|
|
161
|
+
### PostgreSQL Event Store
|
|
162
|
+
```sql
|
|
163
|
+
CREATE TABLE events (
|
|
164
|
+
event_id UUID PRIMARY KEY,
|
|
165
|
+
stream_id VARCHAR(255) NOT NULL,
|
|
166
|
+
version INT NOT NULL,
|
|
167
|
+
event_type VARCHAR(255) NOT NULL,
|
|
168
|
+
data JSONB NOT NULL,
|
|
169
|
+
metadata JSONB,
|
|
170
|
+
timestamp TIMESTAMPTZ DEFAULT NOW(),
|
|
171
|
+
UNIQUE(stream_id, version)
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
CREATE INDEX idx_events_stream ON events(stream_id, version);
|
|
175
|
+
CREATE INDEX idx_events_type ON events(event_type);
|
|
176
|
+
CREATE INDEX idx_events_timestamp ON events(timestamp);
|
|
177
|
+
|
|
178
|
+
-- Append event with optimistic concurrency
|
|
179
|
+
INSERT INTO events (event_id, stream_id, version, event_type, data)
|
|
180
|
+
VALUES ($1, $2, $3, $4, $5)
|
|
181
|
+
ON CONFLICT (stream_id, version) DO NOTHING
|
|
182
|
+
RETURNING event_id;
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### EventStoreDB
|
|
186
|
+
```typescript
|
|
187
|
+
import { EventStoreDBClient, jsonEvent } from '@eventstore/db-client';
|
|
188
|
+
|
|
189
|
+
const client = EventStoreDBClient.connectionString(
|
|
190
|
+
'esdb://localhost:2113?tls=false'
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
// Append events
|
|
194
|
+
await client.appendToStream(
|
|
195
|
+
`order-${orderId}`,
|
|
196
|
+
[
|
|
197
|
+
jsonEvent({
|
|
198
|
+
type: 'OrderCreated',
|
|
199
|
+
data: { orderId, customerId, items }
|
|
200
|
+
})
|
|
201
|
+
],
|
|
202
|
+
{ expectedRevision: 'no_stream' }
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
// Read events
|
|
206
|
+
const events = client.readStream(`order-${orderId}`);
|
|
207
|
+
for await (const event of events) {
|
|
208
|
+
console.log(event.event?.type, event.event?.data);
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## Snapshots
|
|
213
|
+
|
|
214
|
+
### Snapshot Strategy
|
|
215
|
+
```typescript
|
|
216
|
+
class OrderRepository {
|
|
217
|
+
private readonly snapshotInterval = 100;
|
|
218
|
+
|
|
219
|
+
async save(order: Order): Promise<void> {
|
|
220
|
+
const events = order.getUncommittedEvents();
|
|
221
|
+
await this.eventStore.append(order.id, events, order.version);
|
|
222
|
+
|
|
223
|
+
if (order.version % this.snapshotInterval === 0) {
|
|
224
|
+
await this.snapshotStore.save({
|
|
225
|
+
streamId: order.id,
|
|
226
|
+
version: order.version,
|
|
227
|
+
state: order.toSnapshot(),
|
|
228
|
+
timestamp: new Date()
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
order.clearUncommittedEvents();
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async load(orderId: string): Promise<Order> {
|
|
236
|
+
// Try to load from snapshot
|
|
237
|
+
const snapshot = await this.snapshotStore.getLatest(orderId);
|
|
238
|
+
|
|
239
|
+
const fromVersion = snapshot?.version ?? 0;
|
|
240
|
+
const events = await this.eventStore.read(orderId, fromVersion);
|
|
241
|
+
|
|
242
|
+
const order = new Order();
|
|
243
|
+
if (snapshot) {
|
|
244
|
+
order.restoreFromSnapshot(snapshot.state);
|
|
245
|
+
}
|
|
246
|
+
order.loadFromHistory(events);
|
|
247
|
+
|
|
248
|
+
return order;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
## Projections
|
|
254
|
+
|
|
255
|
+
### Read Model Projection
|
|
256
|
+
```typescript
|
|
257
|
+
class OrderSummaryProjection {
|
|
258
|
+
constructor(private readonly db: Database) {}
|
|
259
|
+
|
|
260
|
+
async handle(event: DomainEvent): Promise<void> {
|
|
261
|
+
switch (event.eventType) {
|
|
262
|
+
case 'OrderCreated':
|
|
263
|
+
await this.db.query(`
|
|
264
|
+
INSERT INTO order_summaries (order_id, customer_id, status, total, created_at)
|
|
265
|
+
VALUES ($1, $2, 'created', $3, $4)
|
|
266
|
+
`, [event.data.orderId, event.data.customerId, event.data.totalAmount, event.timestamp]);
|
|
267
|
+
break;
|
|
268
|
+
|
|
269
|
+
case 'OrderShipped':
|
|
270
|
+
await this.db.query(`
|
|
271
|
+
UPDATE order_summaries
|
|
272
|
+
SET status = 'shipped', shipped_at = $2
|
|
273
|
+
WHERE order_id = $1
|
|
274
|
+
`, [event.data.orderId, event.timestamp]);
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
## Best Practices
|
|
282
|
+
|
|
283
|
+
1. **Immutable Events**: Never modify past events
|
|
284
|
+
2. **Rich Events**: Include all needed data
|
|
285
|
+
3. **Versioning**: Plan for event schema evolution
|
|
286
|
+
4. **Idempotent Projections**: Handle replays safely
|
|
287
|
+
5. **Snapshot Wisely**: Balance rebuild time vs storage
|
|
288
|
+
|
|
289
|
+
## Event Versioning
|
|
290
|
+
|
|
291
|
+
### Upcasting
|
|
292
|
+
```typescript
|
|
293
|
+
class EventUpcaster {
|
|
294
|
+
upcast(event: StoredEvent): DomainEvent {
|
|
295
|
+
switch (event.eventType) {
|
|
296
|
+
case 'OrderCreated':
|
|
297
|
+
if (event.version === 1) {
|
|
298
|
+
// v1 -> v2: Add currency field
|
|
299
|
+
return {
|
|
300
|
+
...event,
|
|
301
|
+
data: {
|
|
302
|
+
...event.data,
|
|
303
|
+
currency: 'USD' // Default for old events
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
break;
|
|
308
|
+
}
|
|
309
|
+
return event;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
## Anti-Patterns
|
|
315
|
+
|
|
316
|
+
- Storing derived data in events
|
|
317
|
+
- Large events (store references instead)
|
|
318
|
+
- Treating events like CRUD operations
|
|
319
|
+
- Skipping event versioning strategy
|
|
320
|
+
- Not planning for projection rebuilds
|
|
321
|
+
|
|
322
|
+
## When to Use
|
|
323
|
+
|
|
324
|
+
- Audit trail requirements
|
|
325
|
+
- Complex domain logic
|
|
326
|
+
- Need to understand "why" state changed
|
|
327
|
+
- Temporal query requirements
|
|
328
|
+
|
|
329
|
+
## When NOT to Use
|
|
330
|
+
|
|
331
|
+
- Simple CRUD applications
|
|
332
|
+
- No audit requirements
|
|
333
|
+
- Team unfamiliar with patterns
|
|
334
|
+
- Very high write throughput
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
# Kafka Deep Dive
|
|
2
|
+
|
|
3
|
+
Advanced Apache Kafka patterns including partitioning, consumer groups, exactly-once semantics, compaction, and operational best practices.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Apache Kafka is a distributed streaming platform for building real-time data pipelines and streaming applications.
|
|
8
|
+
|
|
9
|
+
## Core Concepts
|
|
10
|
+
|
|
11
|
+
### Topics and Partitions
|
|
12
|
+
- **Topic**: Named feed of messages
|
|
13
|
+
- **Partition**: Ordered, immutable sequence
|
|
14
|
+
- **Offset**: Position within partition
|
|
15
|
+
- **Segment**: On-disk storage unit
|
|
16
|
+
|
|
17
|
+
### Producer Patterns
|
|
18
|
+
- **Partitioning**: Key-based or round-robin
|
|
19
|
+
- **Batching**: Accumulate before send
|
|
20
|
+
- **Compression**: Reduce network/storage
|
|
21
|
+
- **Idempotence**: Exactly-once writes
|
|
22
|
+
|
|
23
|
+
### Consumer Patterns
|
|
24
|
+
- **Consumer Groups**: Parallel processing
|
|
25
|
+
- **Offset Management**: At-least-once, exactly-once
|
|
26
|
+
- **Rebalancing**: Partition reassignment
|
|
27
|
+
- **Commit Strategies**: Auto vs manual
|
|
28
|
+
|
|
29
|
+
## Producer Configuration
|
|
30
|
+
|
|
31
|
+
### High Throughput
|
|
32
|
+
```properties
|
|
33
|
+
# Maximize throughput
|
|
34
|
+
batch.size=65536
|
|
35
|
+
linger.ms=10
|
|
36
|
+
compression.type=lz4
|
|
37
|
+
buffer.memory=67108864
|
|
38
|
+
acks=1
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### High Reliability
|
|
42
|
+
```properties
|
|
43
|
+
# Maximize durability
|
|
44
|
+
acks=all
|
|
45
|
+
enable.idempotence=true
|
|
46
|
+
max.in.flight.requests.per.connection=5
|
|
47
|
+
retries=2147483647
|
|
48
|
+
delivery.timeout.ms=120000
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Exactly-Once Producer
|
|
52
|
+
```java
|
|
53
|
+
Properties props = new Properties();
|
|
54
|
+
props.put("bootstrap.servers", "localhost:9092");
|
|
55
|
+
props.put("enable.idempotence", true);
|
|
56
|
+
props.put("transactional.id", "my-transactional-id");
|
|
57
|
+
|
|
58
|
+
KafkaProducer<String, String> producer = new KafkaProducer<>(props);
|
|
59
|
+
producer.initTransactions();
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
producer.beginTransaction();
|
|
63
|
+
producer.send(new ProducerRecord<>("topic1", "key", "value1"));
|
|
64
|
+
producer.send(new ProducerRecord<>("topic2", "key", "value2"));
|
|
65
|
+
producer.commitTransaction();
|
|
66
|
+
} catch (Exception e) {
|
|
67
|
+
producer.abortTransaction();
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Consumer Configuration
|
|
72
|
+
|
|
73
|
+
### Consumer Group Setup
|
|
74
|
+
```java
|
|
75
|
+
Properties props = new Properties();
|
|
76
|
+
props.put("bootstrap.servers", "localhost:9092");
|
|
77
|
+
props.put("group.id", "my-consumer-group");
|
|
78
|
+
props.put("enable.auto.commit", false);
|
|
79
|
+
props.put("auto.offset.reset", "earliest");
|
|
80
|
+
props.put("max.poll.records", 500);
|
|
81
|
+
props.put("max.poll.interval.ms", 300000);
|
|
82
|
+
|
|
83
|
+
KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
|
|
84
|
+
consumer.subscribe(Arrays.asList("my-topic"));
|
|
85
|
+
|
|
86
|
+
while (true) {
|
|
87
|
+
ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
|
|
88
|
+
for (ConsumerRecord<String, String> record : records) {
|
|
89
|
+
processRecord(record);
|
|
90
|
+
}
|
|
91
|
+
consumer.commitSync();
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Exactly-Once Consumer
|
|
96
|
+
```java
|
|
97
|
+
// Read-process-write pattern with transactions
|
|
98
|
+
props.put("isolation.level", "read_committed");
|
|
99
|
+
|
|
100
|
+
while (true) {
|
|
101
|
+
ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
|
|
102
|
+
|
|
103
|
+
producer.beginTransaction();
|
|
104
|
+
try {
|
|
105
|
+
for (ConsumerRecord<String, String> record : records) {
|
|
106
|
+
ProducerRecord<String, String> output = process(record);
|
|
107
|
+
producer.send(output);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Commit offsets as part of transaction
|
|
111
|
+
producer.sendOffsetsToTransaction(
|
|
112
|
+
getOffsetsToCommit(records),
|
|
113
|
+
consumer.groupMetadata()
|
|
114
|
+
);
|
|
115
|
+
producer.commitTransaction();
|
|
116
|
+
} catch (Exception e) {
|
|
117
|
+
producer.abortTransaction();
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Partitioning Strategies
|
|
123
|
+
|
|
124
|
+
### Key-Based Partitioning
|
|
125
|
+
```java
|
|
126
|
+
// Default: hash(key) % numPartitions
|
|
127
|
+
producer.send(new ProducerRecord<>("topic", "userId-123", "data"));
|
|
128
|
+
|
|
129
|
+
// Custom partitioner
|
|
130
|
+
public class CustomPartitioner implements Partitioner {
|
|
131
|
+
@Override
|
|
132
|
+
public int partition(String topic, Object key, byte[] keyBytes,
|
|
133
|
+
Object value, byte[] valueBytes, Cluster cluster) {
|
|
134
|
+
List<PartitionInfo> partitions = cluster.partitionsForTopic(topic);
|
|
135
|
+
int numPartitions = partitions.size();
|
|
136
|
+
|
|
137
|
+
if (key instanceof String) {
|
|
138
|
+
String keyStr = (String) key;
|
|
139
|
+
if (keyStr.startsWith("VIP-")) {
|
|
140
|
+
return 0; // VIP customers to partition 0
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return Math.abs(Utils.murmur2(keyBytes)) % numPartitions;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Partition Count Guidelines
|
|
149
|
+
```
|
|
150
|
+
Partitions = max(T/P, T/C)
|
|
151
|
+
|
|
152
|
+
Where:
|
|
153
|
+
T = Target throughput (MB/s)
|
|
154
|
+
P = Producer throughput per partition
|
|
155
|
+
C = Consumer throughput per partition
|
|
156
|
+
|
|
157
|
+
Rules of thumb:
|
|
158
|
+
- Start with 6-12 partitions per topic
|
|
159
|
+
- More partitions = more parallelism
|
|
160
|
+
- Too many = more overhead, longer rebalances
|
|
161
|
+
- Can only increase, never decrease
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Log Compaction
|
|
165
|
+
|
|
166
|
+
### Compacted Topic Configuration
|
|
167
|
+
```properties
|
|
168
|
+
# Topic configuration
|
|
169
|
+
cleanup.policy=compact
|
|
170
|
+
min.cleanable.dirty.ratio=0.5
|
|
171
|
+
segment.ms=604800000
|
|
172
|
+
delete.retention.ms=86400000
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Use Cases
|
|
176
|
+
```
|
|
177
|
+
- Changelog topics (database CDC)
|
|
178
|
+
- State stores (Kafka Streams)
|
|
179
|
+
- Configuration distribution
|
|
180
|
+
- Latest value per key scenarios
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Operational Patterns
|
|
184
|
+
|
|
185
|
+
### Monitoring Metrics
|
|
186
|
+
```yaml
|
|
187
|
+
# Key producer metrics
|
|
188
|
+
record-send-rate # Records sent per second
|
|
189
|
+
record-error-rate # Failed sends per second
|
|
190
|
+
request-latency-avg # Average request latency
|
|
191
|
+
batch-size-avg # Average batch size
|
|
192
|
+
buffer-available-bytes # Available buffer memory
|
|
193
|
+
|
|
194
|
+
# Key consumer metrics
|
|
195
|
+
records-consumed-rate # Records consumed per second
|
|
196
|
+
records-lag # Consumer lag per partition
|
|
197
|
+
commit-latency-avg # Commit latency
|
|
198
|
+
rebalance-latency-avg # Rebalance duration
|
|
199
|
+
|
|
200
|
+
# Broker metrics
|
|
201
|
+
UnderReplicatedPartitions # Partitions with insufficient replicas
|
|
202
|
+
ActiveControllerCount # Should be 1 in cluster
|
|
203
|
+
OfflinePartitionsCount # Unavailable partitions
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Consumer Lag Management
|
|
207
|
+
```bash
|
|
208
|
+
# Check consumer lag
|
|
209
|
+
kafka-consumer-groups.sh --bootstrap-server localhost:9092 \
|
|
210
|
+
--group my-group --describe
|
|
211
|
+
|
|
212
|
+
# Reset offsets
|
|
213
|
+
kafka-consumer-groups.sh --bootstrap-server localhost:9092 \
|
|
214
|
+
--group my-group --topic my-topic \
|
|
215
|
+
--reset-offsets --to-earliest --execute
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## Best Practices
|
|
219
|
+
|
|
220
|
+
1. **Key Design**: Choose keys for even distribution
|
|
221
|
+
2. **Partition Count**: Plan for growth
|
|
222
|
+
3. **Replication Factor**: Minimum 3 for production
|
|
223
|
+
4. **Retention**: Balance storage vs replay needs
|
|
224
|
+
5. **Monitoring**: Track lag and throughput
|
|
225
|
+
|
|
226
|
+
## Common Issues
|
|
227
|
+
|
|
228
|
+
### Consumer Lag
|
|
229
|
+
- Cause: Slow processing, too few consumers
|
|
230
|
+
- Fix: Scale consumers, optimize processing
|
|
231
|
+
|
|
232
|
+
### Rebalancing Storms
|
|
233
|
+
- Cause: Frequent joins/leaves
|
|
234
|
+
- Fix: Increase session.timeout.ms, use static membership
|
|
235
|
+
|
|
236
|
+
### Data Skew
|
|
237
|
+
- Cause: Hot keys
|
|
238
|
+
- Fix: Composite keys, custom partitioner
|
|
239
|
+
|
|
240
|
+
## When to Use
|
|
241
|
+
|
|
242
|
+
- High-throughput event streaming
|
|
243
|
+
- Event sourcing and CQRS
|
|
244
|
+
- Log aggregation
|
|
245
|
+
- Stream processing
|
|
246
|
+
|
|
247
|
+
## When NOT to Use
|
|
248
|
+
|
|
249
|
+
- Simple pub/sub (use Redis/RabbitMQ)
|
|
250
|
+
- Very low latency requirements
|
|
251
|
+
- Small data volumes
|
|
252
|
+
- When ordering isn't important
|