Add AICL example: 59_streaming_pipeline.aicl
Browse files
data/aicl/examples/59_streaming_pipeline.aicl
ADDED
|
@@ -0,0 +1,402 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# AICL Example: Streaming Data Pipeline
|
| 2 |
+
# Kafka-like distributed streaming data pipeline with exactly-once semantics, schema registry,
|
| 3 |
+
# stream-table duality, watermarking, and partitioned processing for event-driven architectures.
|
| 4 |
+
|
| 5 |
+
Goal: Build a distributed streaming data pipeline that provides Kafka-like event streaming with
|
| 6 |
+
exactly-once delivery semantics, centralized schema management, stream-table duality for
|
| 7 |
+
stateful processing, and watermark-based time tracking for out-of-order event handling.
|
| 8 |
+
|
| 9 |
+
Constraint: Exactly-once semantics must be guaranteed end-to-end from producer to consumer
|
| 10 |
+
through idempotent producers, transactional publishing, and consumer deduplication.
|
| 11 |
+
|
| 12 |
+
Constraint: Schema registry must support backward, forward, and full compatibility modes
|
| 13 |
+
with zero-downtime schema evolution and automatic migration planning.
|
| 14 |
+
|
| 15 |
+
Constraint: Stream-table duality must allow seamless switching between materialized table
|
| 16 |
+
views and changelog streams with point-in-time query capability.
|
| 17 |
+
|
| 18 |
+
Constraint: Watermarking must track event time progress across all partitions with configurable
|
| 19 |
+
allowed lateness and idle partition detection.
|
| 20 |
+
|
| 21 |
+
Risk: Producer sends duplicate messages during network retry causing duplicate processing
|
| 22 |
+
Recovery: Enable idempotent producer with sequence number tracking per partition; consumer
|
| 23 |
+
deduplication using offset and message key tracking; compaction removes duplicates
|
| 24 |
+
|
| 25 |
+
Risk: Schema evolution breaks deserialization for existing consumers
|
| 26 |
+
Recovery: Schema registry rejects incompatible changes; consumers fall back to last compatible
|
| 27 |
+
schema version; dead-letter queue captures undecodable messages for manual resolution
|
| 28 |
+
|
| 29 |
+
Risk: Partition rebalancing during consumer group changes causes processing gaps
|
| 30 |
+
Recovery: Implement cooperative rebalancing with incremental partition assignment; consumers
|
| 31 |
+
complete in-flight processing before releasing partitions; offset commit on rebalance
|
| 32 |
+
|
| 33 |
+
Risk: Watermark stalls on idle partitions blocking downstream window processing
|
| 34 |
+
Recovery: Detect idle partitions via configurable timeout; advance watermark using active
|
| 35 |
+
partition progress; emit watermark advancement events to unblock downstream operators
|
| 36 |
+
|
| 37 |
+
Risk: State store corruption during stream-table duality materialization
|
| 38 |
+
Recovery: Maintain replicated state store with write-ahead log; recover from replica on
|
| 39 |
+
corruption; fallback to changelog topic replay for full state reconstruction
|
| 40 |
+
|
| 41 |
+
Risk: Broker failure causes data unavailability for critical partitions
|
| 42 |
+
Recovery: Replicate partitions across multiple brokers with ISR quorum; promote in-sync
|
| 43 |
+
replica to leader; under-replicated partition alert triggers automatic re-replication
|
| 44 |
+
|
| 45 |
+
Layer: StreamingPipeline
|
| 46 |
+
SubLayer: EventBroker
|
| 47 |
+
SubLayer: SchemaRegistry
|
| 48 |
+
SubLayer: StreamProcessor
|
| 49 |
+
SubLayer: StateStore
|
| 50 |
+
SubLayer: WatermarkTracker
|
| 51 |
+
|
| 52 |
+
Validation: Exactly-once delivery verified through end-to-end idempotency checks
|
| 53 |
+
Validation: Schema compatibility must pass automated validation before registration
|
| 54 |
+
Validation: Watermark must advance monotonically and never regress
|
| 55 |
+
Validation: State store must be recoverable from changelog within 5 minutes
|
| 56 |
+
Validation: Partition rebalancing must complete within 30 seconds without data loss
|
| 57 |
+
Validation: Consumer lag must not exceed 1000 messages under normal operating conditions
|
| 58 |
+
Validation: All produced messages must be readable by at least one in-sync replica
|
| 59 |
+
|
| 60 |
+
# Level 2: Entities
|
| 61 |
+
|
| 62 |
+
Entity Topic
|
| 63 |
+
topic_id: string
|
| 64 |
+
topic_name: string
|
| 65 |
+
partition_count: integer
|
| 66 |
+
replication_factor: integer
|
| 67 |
+
retention_ms: integer
|
| 68 |
+
cleanup_policy: string
|
| 69 |
+
schema_subject: string
|
| 70 |
+
is_compacted: boolean
|
| 71 |
+
|
| 72 |
+
Entity Producer
|
| 73 |
+
producer_id: string
|
| 74 |
+
client_id: string
|
| 75 |
+
transactional_id: string
|
| 76 |
+
acks_mode: string
|
| 77 |
+
enable_idempotence: boolean
|
| 78 |
+
batch_size: integer
|
| 79 |
+
linger_ms: integer
|
| 80 |
+
schema_version: string
|
| 81 |
+
|
| 82 |
+
Entity ConsumerGroup
|
| 83 |
+
group_id: string
|
| 84 |
+
members: list
|
| 85 |
+
partition_assignment: dict
|
| 86 |
+
commit_offset: dict
|
| 87 |
+
rebalance_strategy: string
|
| 88 |
+
session_timeout_ms: integer
|
| 89 |
+
is_rebalancing: boolean
|
| 90 |
+
|
| 91 |
+
Entity SchemaVersion
|
| 92 |
+
schema_id: integer
|
| 93 |
+
subject: string
|
| 94 |
+
version: integer
|
| 95 |
+
schema_definition: dict
|
| 96 |
+
compatibility_mode: string
|
| 97 |
+
fingerprint: string
|
| 98 |
+
registered_at: datetime
|
| 99 |
+
is_latest: boolean
|
| 100 |
+
|
| 101 |
+
Entity StateStore
|
| 102 |
+
store_id: string
|
| 103 |
+
store_name: string
|
| 104 |
+
store_type: string
|
| 105 |
+
changelog_topic: string
|
| 106 |
+
partition_count: integer
|
| 107 |
+
total_size_bytes: integer
|
| 108 |
+
last_checkpoint: datetime
|
| 109 |
+
is_persistent: boolean
|
| 110 |
+
|
| 111 |
+
Entity Watermark
|
| 112 |
+
partition_id: integer
|
| 113 |
+
event_time: datetime
|
| 114 |
+
watermark_time: datetime
|
| 115 |
+
idle_detected: boolean
|
| 116 |
+
last_event_seen: datetime
|
| 117 |
+
allowed_lateness_ms: integer
|
| 118 |
+
|
| 119 |
+
# Level 3: Behaviors
|
| 120 |
+
|
| 121 |
+
Behavior PublishEvent
|
| 122 |
+
Input:
|
| 123 |
+
topic: string
|
| 124 |
+
key: string
|
| 125 |
+
value: bytes
|
| 126 |
+
headers: dict
|
| 127 |
+
event_timestamp: datetime
|
| 128 |
+
Output:
|
| 129 |
+
partition: integer
|
| 130 |
+
offset: integer
|
| 131 |
+
timestamp: datetime
|
| 132 |
+
Action:
|
| 133 |
+
Validate event against registered schema subject and version
|
| 134 |
+
Assign partition based on key hash or custom partitioner
|
| 135 |
+
Begin transaction if producer is transactional
|
| 136 |
+
Append to partition log with replication to ISR
|
| 137 |
+
Commit transaction and return partition-offset pair
|
| 138 |
+
|
| 139 |
+
Behavior SubscribeToTopic
|
| 140 |
+
Input:
|
| 141 |
+
group_id: string
|
| 142 |
+
topics: list
|
| 143 |
+
rebalance_callback: string
|
| 144 |
+
Output:
|
| 145 |
+
assigned_partitions: list
|
| 146 |
+
committed_offsets: dict
|
| 147 |
+
Action:
|
| 148 |
+
Register consumer with group coordinator
|
| 149 |
+
Participate in cooperative rebalancing protocol
|
| 150 |
+
Receive partition assignments with committed offsets
|
| 151 |
+
Initialize fetch loops for assigned partitions
|
| 152 |
+
Report heartbeat and process records in poll loop
|
| 153 |
+
|
| 154 |
+
Behavior RegisterSchema
|
| 155 |
+
Input:
|
| 156 |
+
subject: string
|
| 157 |
+
schema_definition: dict
|
| 158 |
+
compatibility_mode: string
|
| 159 |
+
Output:
|
| 160 |
+
schema_id: integer
|
| 161 |
+
version: integer
|
| 162 |
+
is_compatible: boolean
|
| 163 |
+
Action:
|
| 164 |
+
Compute schema fingerprint for deduplication check
|
| 165 |
+
Validate compatibility against existing versions per mode
|
| 166 |
+
Register new version if compatible, reject otherwise
|
| 167 |
+
Broadcast schema update to subscribed producers and consumers
|
| 168 |
+
Update schema cache on all broker nodes
|
| 169 |
+
|
| 170 |
+
Behavior MaterializeTable
|
| 171 |
+
Input:
|
| 172 |
+
source_topic: string
|
| 173 |
+
state_store_name: string
|
| 174 |
+
key_extractor: string
|
| 175 |
+
value_aggregator: string
|
| 176 |
+
Output:
|
| 177 |
+
store_id: string
|
| 178 |
+
record_count: integer
|
| 179 |
+
size_bytes: integer
|
| 180 |
+
Action:
|
| 181 |
+
Subscribe to source topic changelog from beginning
|
| 182 |
+
Apply key extraction and value aggregation per record
|
| 183 |
+
Update state store with upsert or delete operations
|
| 184 |
+
Checkpoint state periodically to changelog topic
|
| 185 |
+
Expose point-in-time query interface over materialized state
|
| 186 |
+
|
| 187 |
+
Behavior AdvanceWatermark
|
| 188 |
+
Input:
|
| 189 |
+
partition_id: integer
|
| 190 |
+
event_timestamp: datetime
|
| 191 |
+
Output:
|
| 192 |
+
new_watermark: datetime
|
| 193 |
+
idle_partitions: list
|
| 194 |
+
Action:
|
| 195 |
+
Track per-partition event time progress
|
| 196 |
+
Detect idle partitions exceeding idle timeout
|
| 197 |
+
Compute global watermark as min across active partitions
|
| 198 |
+
Emit watermark advancement events downstream
|
| 199 |
+
Handle late-arriving events within allowed lateness window
|
| 200 |
+
|
| 201 |
+
# Level 4: Conditions
|
| 202 |
+
|
| 203 |
+
Condition: SchemaIncompatible
|
| 204 |
+
When new schema version fails compatibility check against latest version
|
| 205 |
+
Then reject schema registration and return detailed incompatibility report
|
| 206 |
+
with specific fields causing the violation and suggested modifications
|
| 207 |
+
|
| 208 |
+
Condition: ConsumerLagExceeded
|
| 209 |
+
When consumer group lag exceeds 10000 messages on any partition
|
| 210 |
+
Then scale up consumer instances within the group, increase fetch rate,
|
| 211 |
+
and alert operations team if lag continues to grow for 5 minutes
|
| 212 |
+
|
| 213 |
+
Condition: UnderReplicatedPartition
|
| 214 |
+
When partition ISR count drops below configured replication factor
|
| 215 |
+
Then prioritize replica recovery for under-replicated partitions, alert
|
| 216 |
+
operations team, and restrict leader election to in-sync replicas only
|
| 217 |
+
|
| 218 |
+
Condition: IdlePartitionDetected
|
| 219 |
+
When partition receives no events for longer than idle_timeout_ms
|
| 220 |
+
Then mark partition as idle, exclude from watermark computation, and
|
| 221 |
+
advance watermark using only active partitions to prevent stalling
|
| 222 |
+
|
| 223 |
+
Condition: StateStoreCorrupted
|
| 224 |
+
When state store checksum validation fails on checkpoint load
|
| 225 |
+
Then initiate recovery from changelog topic replay, redirect queries
|
| 226 |
+
to replica store during recovery, and log corruption details for analysis
|
| 227 |
+
|
| 228 |
+
# Level 5: Events
|
| 229 |
+
|
| 230 |
+
Event: SchemaRegistered
|
| 231 |
+
On new_schema_version_approved
|
| 232 |
+
Action: Broadcast schema update to all registered producers and consumers,
|
| 233 |
+
update schema cache on brokers, and validate existing data compatibility
|
| 234 |
+
|
| 235 |
+
Event: RebalanceStarted
|
| 236 |
+
On consumer_group_rebalance_triggered
|
| 237 |
+
Action: Pause record processing for partitions being revoked, commit current
|
| 238 |
+
offsets, execute cooperative rebalancing protocol, and resume with new assignments
|
| 239 |
+
|
| 240 |
+
Event: WatermarkAdvanced
|
| 241 |
+
On global_watermark_progress_updated
|
| 242 |
+
Action: Trigger downstream window evaluations, emit late-arrival corrections
|
| 243 |
+
for events between old and new watermark, and update watermark metrics
|
| 244 |
+
|
| 245 |
+
Event: StateCheckpointed
|
| 246 |
+
On state_store_checkpoint_completed
|
| 247 |
+
Action: Commit consumer offsets to match checkpoint, compact changelog topic
|
| 248 |
+
up to checkpoint offset, and log checkpoint metadata for recovery
|
| 249 |
+
|
| 250 |
+
Event: TransactionCompleted
|
| 251 |
+
On producer_transaction_commit_or_abort
|
| 252 |
+
Action: Make transactional records visible to consumers, release partition
|
| 253 |
+
locks held by transaction, and update transaction coordinator state
|
| 254 |
+
|
| 255 |
+
# Level 6: Concurrency
|
| 256 |
+
|
| 257 |
+
Parallel:
|
| 258 |
+
Event production across all topic partitions
|
| 259 |
+
Consumer group record processing per partition
|
| 260 |
+
Schema validation and registration
|
| 261 |
+
State store materialization and checkpointing
|
| 262 |
+
Watermark tracking across all partitions
|
| 263 |
+
Partition replication and ISR management
|
| 264 |
+
Changelog compaction for state stores
|
| 265 |
+
|
| 266 |
+
# Level 7: Optimization
|
| 267 |
+
|
| 268 |
+
Optimize: Throughput and latency of event streaming pipeline
|
| 269 |
+
Priority: Producer batching optimized for throughput without exceeding linger latency
|
| 270 |
+
Priority: Consumer prefetch and parallel processing within partition assignment
|
| 271 |
+
Priority: State store checkpoint frequency balanced against recovery time
|
| 272 |
+
Priority: Schema cache locality on brokers closest to producers and consumers
|
| 273 |
+
Priority: Changelog compaction scheduled during low-traffic periods
|
| 274 |
+
|
| 275 |
+
# Level 8: Learning
|
| 276 |
+
|
| 277 |
+
Learn: Optimal partition count per topic based on throughput patterns
|
| 278 |
+
Goal: Minimize consumer group rebalancing while maximizing parallelism
|
| 279 |
+
Adapt: Partition count recommendations for new topics
|
| 280 |
+
Based: Historical throughput per partition, consumer group size, and rebalance frequency
|
| 281 |
+
|
| 282 |
+
Learn: Consumer fetch parameters for optimal throughput
|
| 283 |
+
Goal: Achieve target throughput with minimum consumer CPU utilization
|
| 284 |
+
Adapt: Fetch size, max poll records, and processing concurrency
|
| 285 |
+
Based: Consumer processing latency, message size distribution, and lag patterns
|
| 286 |
+
|
| 287 |
+
Learn: Watermark idle timeout configuration per topic
|
| 288 |
+
Goal: Advance watermarks promptly without excessive late-arrival corrections
|
| 289 |
+
Adapt: Idle partition timeout and allowed lateness parameters
|
| 290 |
+
Based: Event arrival rate distribution, late arrival frequency, and window accuracy
|
| 291 |
+
|
| 292 |
+
# Level 9: Security
|
| 293 |
+
|
| 294 |
+
Security:
|
| 295 |
+
Encrypt: All inter-broker communication using TLS with mutual authentication
|
| 296 |
+
Encrypt: Data at rest on broker log segments using AES-256 with per-topic keys
|
| 297 |
+
Protect: Schema registry access with RBAC and schema-level permissions
|
| 298 |
+
Protect: Consumer group operations with ACLs per principal and topic
|
| 299 |
+
Protect: State store contents with encryption and access control
|
| 300 |
+
Encrypt: Producer-consumer communication with SASL/OAUTHBEARER and TLS
|
| 301 |
+
|
| 302 |
+
# Level 10: Native
|
| 303 |
+
|
| 304 |
+
Native: Java
|
| 305 |
+
{
|
| 306 |
+
import org.apache.kafka.clients.producer.*;
|
| 307 |
+
import org.apache.kafka.clients.consumer.*;
|
| 308 |
+
import org.apache.kafka.common.*;
|
| 309 |
+
import org.apache.kafka.streams.*;
|
| 310 |
+
import org.apache.kafka.streams.processor.*;
|
| 311 |
+
import org.apache.kafka.streams.state.*;
|
| 312 |
+
import java.time.Duration;
|
| 313 |
+
import java.util.*;
|
| 314 |
+
|
| 315 |
+
public class StreamingPipeline {
|
| 316 |
+
|
| 317 |
+
private final KafkaProducer<String, byte[]> producer;
|
| 318 |
+
private final KafkaStreams streams;
|
| 319 |
+
private final SchemaRegistryClient schemaRegistry;
|
| 320 |
+
|
| 321 |
+
public StreamingPipeline(Properties producerConfig,
|
| 322 |
+
Properties streamsConfig,
|
| 323 |
+
SchemaRegistryClient schemaRegistry) {
|
| 324 |
+
this.producer = new KafkaProducer<>(producerConfig);
|
| 325 |
+
this.schemaRegistry = schemaRegistry;
|
| 326 |
+
this.streams = null;
|
| 327 |
+
}
|
| 328 |
+
|
| 329 |
+
public RecordMetadata publishEvent(String topic, String key, byte[] value,
|
| 330 |
+
Map<String, String> headers) throws Exception {
|
| 331 |
+
String subject = topic + "-value";
|
| 332 |
+
int schemaId = schemaRegistry.getLatestVersion(subject).getId();
|
| 333 |
+
|
| 334 |
+
ProducerRecord<String, byte[]> record = new ProducerRecord<>(topic, key, value);
|
| 335 |
+
headers.forEach(record.headers()::add);
|
| 336 |
+
|
| 337 |
+
try {
|
| 338 |
+
return producer.send(record).get();
|
| 339 |
+
} catch (Exception e) {
|
| 340 |
+
producer.abortTransaction();
|
| 341 |
+
throw new PipelineException("Failed to publish event", e);
|
| 342 |
+
}
|
| 343 |
+
}
|
| 344 |
+
|
| 345 |
+
public KafkaStreams buildMaterializedView(String sourceTopic,
|
| 346 |
+
String storeName) {
|
| 347 |
+
Properties props = new Properties();
|
| 348 |
+
props.put(StreamsConfig.APPLICATION_ID_CONFIG, storeName + "-app");
|
| 349 |
+
props.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
|
| 350 |
+
|
| 351 |
+
StreamsBuilder builder = new StreamsBuilder();
|
| 352 |
+
builder.table(sourceTopic,
|
| 353 |
+
Materialized.<String, byte[], KeyValueStore<Bytes, byte[]>>as(storeName)
|
| 354 |
+
.withKeySerde(Serdes.String())
|
| 355 |
+
.withValueSerde(Serdes.ByteArray())
|
| 356 |
+
.withCachingEnabled()
|
| 357 |
+
.withLoggingEnabled(Collections.emptyMap())
|
| 358 |
+
);
|
| 359 |
+
|
| 360 |
+
KafkaStreams streams = new KafkaStreams(builder.build(), props);
|
| 361 |
+
streams.setStateListener((newState, oldState) -> {
|
| 362 |
+
if (newState == KafkaStreams.State.RUNNING) {
|
| 363 |
+
System.out.println("State store " + storeName + " materialized");
|
| 364 |
+
}
|
| 365 |
+
});
|
| 366 |
+
return streams;
|
| 367 |
+
}
|
| 368 |
+
|
| 369 |
+
public static class WatermarkTracker {
|
| 370 |
+
private final Map<TopicPartition, Long> partitionWatermarks = new HashMap<>();
|
| 371 |
+
private final Duration allowedLateness;
|
| 372 |
+
private final Duration idleTimeout;
|
| 373 |
+
private final Map<TopicPartition, Long> lastEventTime = new HashMap<>();
|
| 374 |
+
|
| 375 |
+
public WatermarkTracker(Duration allowedLateness, Duration idleTimeout) {
|
| 376 |
+
this.allowedLateness = allowedLateness;
|
| 377 |
+
this.idleTimeout = idleTimeout;
|
| 378 |
+
}
|
| 379 |
+
|
| 380 |
+
public synchronized long advanceWatermark(TopicPartition tp, long eventTime) {
|
| 381 |
+
lastEventTime.put(tp, System.currentTimeMillis());
|
| 382 |
+
partitionWatermarks.merge(tp, eventTime, Math::max);
|
| 383 |
+
return computeGlobalWatermark();
|
| 384 |
+
}
|
| 385 |
+
|
| 386 |
+
private long computeGlobalWatermark() {
|
| 387 |
+
long now = System.currentTimeMillis();
|
| 388 |
+
long globalWatermark = Long.MAX_VALUE;
|
| 389 |
+
for (Map.Entry<TopicPartition, Long> entry : partitionWatermarks.entrySet()) {
|
| 390 |
+
long lastSeen = lastEventTime.getOrDefault(entry.getKey(), now);
|
| 391 |
+
if (now - lastSeen > idleTimeout.toMillis()) {
|
| 392 |
+
continue;
|
| 393 |
+
}
|
| 394 |
+
globalWatermark = Math.min(globalWatermark, entry.getValue());
|
| 395 |
+
}
|
| 396 |
+
return globalWatermark == Long.MAX_VALUE
|
| 397 |
+
? partitionWatermarks.values().stream().max(Long::compare).orElse(0L)
|
| 398 |
+
: globalWatermark;
|
| 399 |
+
}
|
| 400 |
+
}
|
| 401 |
+
}
|
| 402 |
+
}
|