Stream Processing Flink Topology
blockdevopsApache Flink & RisingWave stateful streaming graph: real-time DAG topology, window throughput, checkpointing duration, RocksDB state inspector, and backpressure heat monitor.
Installation
$pnpm dlx shadcn-vue@latest add https://uipkge.dev/r/vue/stream-processing-flink-topology.json$npx shadcn-vue@latest add https://uipkge.dev/r/vue/stream-processing-flink-topology.json$yarn dlx shadcn-vue@latest add https://uipkge.dev/r/vue/stream-processing-flink-topology.json$bunx shadcn-vue@latest add https://uipkge.dev/r/vue/stream-processing-flink-topology.jsonNamed registry:
npx shadcn-vue@latest add @uipkge/stream-processing-flink-topologyInstalls to:app/components/blocks/Variants
Loading interactive previews…
Loading interactive previews…
Props
| Name | Type / Values | Default | Required |
|---|---|---|---|
operators | FlinkOperatorNode[] | () => defaultFlinkOperators, | optional |
class | HTMLAttributes['class'] | — | optional |
Schema
Type aliases exported from this item's source. Use these to shape the data you pass in.
FlinkSubtaskinterface FlinkSubtask {
id: number
host: string
status: 'RUNNING' | 'DEPLOYING' | 'RECONCILING'
inRate: string
outRate: string
cpu: string
heapMemory: string
backpressure: number
watermarkLag: string
checkpointAckMs: number
}FlinkOperatorNodeinterface FlinkOperatorNode {
id: string
name: string
shortTitle: string
stageNumber: number
category: 'source' | 'window' | 'inference' | 'sink'
operatorClass: string
parallelism: number
status: 'RUNNING' | 'CANCELED' | 'FAILING'
backpressure: number
inputRecordsRate: string
inputBytesRate: string
outputRecordsRate: string
outputBytesRate: string
totalRecordsIn: number
totalRecordsOut: number
managedStateSize: string
stateBackend: string
heapMemory: string
managedMemory: string
gcTime: string
cpuPeak: string
watermarkLag: string
currentWatermark: string
description: string
config: Record<string, string>
subtasks: FlinkSubtask[]
}Files installed (5)
app/components/blocks/StreamProcessingFlinkTopology.vue19.7 kB
<script setup lang="ts"> import { computed, ref, type HTMLAttributes } from 'vue' import { AlertTriangle, ArrowRight, Camera, Check, CheckCircle2, Clock, Copy, Cpu, Database, Loader2, Radio, Square, Workflow, } from 'lucide-vue-next' import { cn } from '@/lib/utils' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' import StreamOperatorDetail from './StreamOperatorDetail.vue' import StreamTopologyTelemetry from './StreamTopologyTelemetry.vue' import type { FlinkOperatorNode, FlinkSubtask } from './stream-topology-types' import { defaultFlinkOperators } from './stream-topology-data' export type { FlinkOperatorNode, FlinkSubtask } interface Props { operators?: FlinkOperatorNode[] class?: HTMLAttributes['class'] } const props = withDefaults(defineProps<Props>(), { operators: () => defaultFlinkOperators, }) const selectedOperatorId = ref<string>('op-ml-scoring') const isTriggeringSavepoint = ref<boolean>(false) const savepointNotice = ref<string | null>(null) const isCanceling = ref<boolean>(false) const cancelConfirmed = ref<boolean>(false) const copiedKey = ref<string | null>(null) const selectedOperator = computed(() => { return props.operators.find((op) => op.id === selectedOperatorId.value) ?? props.operators[0] }) function selectOperator(id: string) { selectedOperatorId.value = id } function handleTriggerSavepoint() { if (isTriggeringSavepoint.value) return isTriggeringSavepoint.value = true savepointNotice.value = null setTimeout(() => { isTriggeringSavepoint.value = false savepointNotice.value = 's3://flink-savepoints/prod/savepoint-fraud-90412-20260821-143245/' setTimeout(() => { if (savepointNotice.value?.includes('savepoint-fraud-90412')) { savepointNotice.value = null } }, 6500) }, 1800) } function handleCancelJob() { if (isCanceling.value) return isCanceling.value = true setTimeout(() => { isCanceling.value = false cancelConfirmed.value = true setTimeout(() => { cancelConfirmed.value = false }, 4500) }, 2000) } function copyText(key: string, value: string) { if (typeof navigator !== 'undefined' && navigator.clipboard) { navigator.clipboard.writeText(value) copiedKey.value = key setTimeout(() => { if (copiedKey.value === key) { copiedKey.value = null } }, 2000) } } </script> <template> <div data-slot="stream-processing-flink-topology" :class="cn('w-full space-y-6', props.class)"> <!-- 1. Header Section --> <div class="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between"> <div class="space-y-1.5"> <div class="flex flex-wrap items-center gap-2.5"> <div class="bg-muted text-muted-foreground border-border flex size-9 shrink-0 items-center justify-center rounded-lg border shadow-xs" aria-hidden="true" > <Workflow class="text-primary size-4.5" /> </div> <div class="flex flex-wrap items-center gap-2"> <h1 class="text-foreground font-mono text-xl font-bold tracking-tight break-all sm:text-2xl"> realtime-fraud-scoring-pipeline </h1> <Badge variant="outline" class="font-mono text-xs"> Flink 1.19 on Kubernetes </Badge> </div> <Badge variant="success" class="gap-1.5 text-xs font-medium"> <span class="relative flex size-2 shrink-0"> <span class="bg-success absolute inline-flex h-full w-full rounded-full opacity-75" /> <span class="bg-success relative inline-flex size-2 rounded-full" /> </span> <span>Job Running · Checkpointing Healthy</span> </Badge> </div> <p class="text-muted-foreground font-mono text-xs"> Checkpoint Interval: Every 10s · RocksDB StateBackend · SLA Target: < 50ms Lag · Job ID: flink_job_992014 </p> </div> <!-- Action Buttons --> <div class="flex flex-wrap items-center gap-2"> <Button variant="outline" size="sm" class="gap-1.5 text-xs font-medium" :disabled="isTriggeringSavepoint" @click="handleTriggerSavepoint" > <Loader2 v-if="isTriggeringSavepoint" class="size-3.5 animate-spin" aria-hidden="true" /> <Camera v-else class="text-info size-3.5" aria-hidden="true" /> <span>{{ isTriggeringSavepoint ? 'Creating Savepoint...' : 'Trigger Savepoint' }}</span> </Button> <Button variant="outline" size="sm" class="text-destructive hover:border-destructive/40 hover:bg-destructive/10 gap-1.5 text-xs font-medium" :disabled="isCanceling" @click="handleCancelJob" > <Loader2 v-if="isCanceling" class="size-3.5 animate-spin" aria-hidden="true" /> <Square v-else class="size-3.5 fill-current" aria-hidden="true" /> <span>{{ isCanceling ? 'Canceling with Savepoint...' : 'Cancel Job with Savepoint' }}</span> </Button> </div> </div> <!-- Active Savepoint Notice Banner --> <div v-if="savepointNotice" class="text-foreground border-info/30 bg-info/10 flex items-center justify-between rounded-lg border p-3 text-xs shadow-xs" role="status" > <div class="flex items-center gap-2.5"> <CheckCircle2 class="text-info size-4 shrink-0" aria-hidden="true" /> <div> <span class="text-info font-semibold">Savepoint completed successfully:</span> <span class="ml-1 font-mono text-xs">{{ savepointNotice }}</span> </div> </div> <Button variant="ghost" size="xs" class="h-6 gap-1 px-2 text-xs" @click="copyText('savepoint', savepointNotice)"> <Check v-if="copiedKey === 'savepoint'" class="text-success size-3" /> <Copy v-else class="size-3" /> <span>{{ copiedKey === 'savepoint' ? 'Copied' : 'Copy URI' }}</span> </Button> </div> <!-- Active Cancellation Notice Banner --> <div v-if="cancelConfirmed" class="text-foreground border-warning/30 bg-warning/10 flex items-center justify-between rounded-lg border p-3 text-xs shadow-xs" role="status" > <div class="flex items-center gap-2.5"> <AlertTriangle class="text-warning size-4 shrink-0" aria-hidden="true" /> <div> <span class="text-warning font-semibold">Graceful Job Drain Initiated:</span> <span class="ml-1">Stopping topology sources and committing final RocksDB savepoint to S3 storage.</span> </div> </div> <Badge variant="outline" class="font-mono text-xs">Drain Status: 100%</Badge> </div> <!-- 2. 4 Stream Telemetry KPI Cards --> <StreamTopologyTelemetry /> <!-- 3. Interactive 4-Operator Streaming Topology DAG --> <Card class="shadow-xs"> <CardHeader class="pb-3"> <div class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between"> <div> <div class="flex items-center gap-2"> <CardTitle class="text-base font-semibold">Streaming Execution Topology DAG</CardTitle> <Badge variant="outline" class="font-mono text-xs">4 Stateful Operators</Badge> </div> <CardDescription class="text-xs"> Live dataflow graph with event-time watermarking, state sizes, and backpressure telemetry. Click any operator to inspect metrics and subtasks. </CardDescription> </div> <div class="flex flex-wrap items-center gap-2"> <div class="border-border bg-muted/40 text-muted-foreground flex items-center gap-1.5 rounded-md border px-2.5 py-1 font-mono text-xs" > <span class="bg-success size-2 rounded-full" /> <span>Backpressure Heat: 0.0% (Green)</span> </div> <div class="border-border bg-muted/40 text-muted-foreground flex items-center gap-1.5 rounded-md border px-2.5 py-1 font-mono text-xs" > <Database class="size-3" /> <span>Total Managed State: 57.0 MB</span> </div> </div> </div> </CardHeader> <CardContent class="p-4 pt-1 sm:p-6"> <div class="border-border bg-muted/15 rounded-xl border p-4 sm:p-5"> <!-- 4 Connected Nodes Grid --> <div class="grid grid-cols-1 gap-4 lg:grid-cols-4"> <!-- Node 1: Kafka Ingestion Source --> <div role="button" tabindex="0" :class=" cn( 'group focus-visible:ring-ring relative flex cursor-pointer flex-col justify-between rounded-xl border p-4 transition-colors duration-200 focus-visible:ring-2 focus-visible:outline-none', selectedOperatorId === 'op-kafka-source' ? 'border-primary bg-primary/[0.04] ring-primary/30 shadow-xs ring-2' : 'border-border bg-card hover:border-primary/50 hover:bg-muted/30', ) " @click="selectOperator('op-kafka-source')" @keydown.enter="selectOperator('op-kafka-source')" @keydown.space.prevent="selectOperator('op-kafka-source')" > <div class="space-y-3"> <div class="flex items-start justify-between gap-2"> <div class="flex items-center gap-2"> <div class="border-info/30 bg-info/10 text-info flex size-7 items-center justify-center rounded-md border" > <Radio class="size-3.5" /> </div> <Badge variant="outline" class="border-info/30 text-info font-mono text-xs"> SOURCE </Badge> </div> <Badge variant="outline" class="font-mono text-xs">p=8</Badge> </div> <div> <div class="text-foreground font-mono text-xs leading-snug font-bold">Kafka Ingestion Source</div> <p class="text-muted-foreground mt-1 font-mono text-xs">42.5k rec/s · Backpressure: 0%</p> </div> <div class="border-border/60 space-y-1.5 border-t pt-2.5 font-mono text-xs"> <div class="text-muted-foreground flex items-center justify-between"> <span>Throughput:</span> <span class="text-foreground font-semibold tabular-nums">8.4 MB/s</span> </div> <div class="text-muted-foreground flex items-center justify-between"> <span>State Size:</span> <span class="text-foreground tabular-nums">0 MB (Stateless)</span> </div> </div> </div> <div class="border-border/60 mt-3 flex items-center justify-between border-t pt-2 text-xs"> <span class="text-muted-foreground font-mono">events.fraud.transactions</span> <ArrowRight class="text-muted-foreground group-hover:text-primary size-3.5 shrink-0 transition-colors" /> </div> </div> <!-- Node 2: Keyed Sliding Window --> <div role="button" tabindex="0" :class=" cn( 'group focus-visible:ring-ring relative flex cursor-pointer flex-col justify-between rounded-xl border p-4 transition-colors duration-200 focus-visible:ring-2 focus-visible:outline-none', selectedOperatorId === 'op-keyed-window' ? 'border-primary bg-primary/[0.04] ring-primary/30 shadow-xs ring-2' : 'border-border bg-card hover:border-primary/50 hover:bg-muted/30', ) " @click="selectOperator('op-keyed-window')" @keydown.enter="selectOperator('op-keyed-window')" @keydown.space.prevent="selectOperator('op-keyed-window')" > <div class="space-y-3"> <div class="flex items-start justify-between gap-2"> <div class="flex items-center gap-2"> <div class="border-chart-1/30 bg-chart-1/10 text-chart-1 flex size-7 items-center justify-center rounded-md border" > <Clock class="size-3.5" /> </div> <Badge variant="outline" class="border-chart-1/30 text-chart-1 font-mono text-xs"> WINDOW </Badge> </div> <Badge variant="outline" class="font-mono text-xs">p=16</Badge> </div> <div> <div class="text-foreground font-mono text-xs leading-snug font-bold">Keyed Sliding Window</div> <p class="text-muted-foreground mt-1 font-mono text-xs">5-min window, 10s slide · 12,400 keys</p> </div> <div class="border-border/60 space-y-1.5 border-t pt-2.5 font-mono text-xs"> <div class="text-muted-foreground flex items-center justify-between"> <span>Output Rate:</span> <span class="text-foreground font-semibold tabular-nums">38.2k rec/s</span> </div> <div class="text-muted-foreground flex items-center justify-between"> <span>RocksDB State:</span> <span class="text-foreground text-chart-1 font-semibold tabular-nums">18.4 MB</span> </div> </div> </div> <div class="border-border/60 mt-3 flex items-center justify-between border-t pt-2 text-xs"> <span class="text-muted-foreground font-mono">Cardholder Velocity Agg</span> <ArrowRight class="text-muted-foreground group-hover:text-primary size-3.5 shrink-0 transition-colors" /> </div> </div> <!-- Node 3: ML Fraud Scoring Pattern Matcher --> <div role="button" tabindex="0" :class=" cn( 'group focus-visible:ring-ring relative flex cursor-pointer flex-col justify-between rounded-xl border p-4 transition-colors duration-200 focus-visible:ring-2 focus-visible:outline-none', selectedOperatorId === 'op-ml-scoring' ? 'border-primary bg-primary/[0.04] ring-primary/30 shadow-xs ring-2' : 'border-border bg-card hover:border-primary/50 hover:bg-muted/30', ) " @click="selectOperator('op-ml-scoring')" @keydown.enter="selectOperator('op-ml-scoring')" @keydown.space.prevent="selectOperator('op-ml-scoring')" > <div class="space-y-3"> <div class="flex items-start justify-between gap-2"> <div class="flex items-center gap-2"> <div class="border-chart-2/30 bg-chart-2/10 text-chart-2 flex size-7 items-center justify-center rounded-md border" > <Cpu class="size-3.5" /> </div> <Badge variant="outline" class="border-chart-2/30 text-chart-2 font-mono text-xs"> CEP & ML </Badge> </div> <Badge variant="outline" class="font-mono text-xs">p=16</Badge> </div> <div> <div class="text-foreground font-mono text-xs leading-snug font-bold">ML Fraud Scoring Matcher</div> <p class="text-muted-foreground mt-1 font-mono text-xs">Inference: 1.2ms · RocksDB: 38.2 MB</p> </div> <div class="border-border/60 space-y-1.5 border-t pt-2.5 font-mono text-xs"> <div class="text-muted-foreground flex items-center justify-between"> <span>ONNX Inference:</span> <span class="text-success text-success font-semibold tabular-nums">p99 < 1.2ms</span> </div> <div class="text-muted-foreground flex items-center justify-between"> <span>Managed State:</span> <span class="text-foreground text-chart-2 font-semibold tabular-nums">38.2 MB</span> </div> </div> </div> <div class="border-border/60 mt-3 flex items-center justify-between border-t pt-2 text-xs"> <span class="text-muted-foreground font-mono">xgboost_v4.onnx</span> <ArrowRight class="text-muted-foreground group-hover:text-primary size-3.5 shrink-0 transition-colors" /> </div> </div> <!-- Node 4: Sink: Alert Dispatcher & Redis Cache --> <div role="button" tabindex="0" :class=" cn( 'group focus-visible:ring-ring relative flex cursor-pointer flex-col justify-between rounded-xl border p-4 transition-colors duration-200 focus-visible:ring-2 focus-visible:outline-none', selectedOperatorId === 'op-sink-dispatcher' ? 'border-primary bg-primary/[0.04] ring-primary/30 shadow-xs ring-2' : 'border-border bg-card hover:border-primary/50 hover:bg-muted/30', ) " @click="selectOperator('op-sink-dispatcher')" @keydown.enter="selectOperator('op-sink-dispatcher')" @keydown.space.prevent="selectOperator('op-sink-dispatcher')" > <div class="space-y-3"> <div class="flex items-start justify-between gap-2"> <div class="flex items-center gap-2"> <div class="border-success/30 bg-success/10 text-success flex size-7 items-center justify-center rounded-md border" > <Database class="size-3.5" /> </div> <Badge variant="outline" class="border-success/30 text-success font-mono text-xs"> SINK </Badge> </div> <Badge variant="outline" class="font-mono text-xs">p=8</Badge> </div> <div> <div class="text-foreground font-mono text-xs leading-snug font-bold">Sink: Alert Dispatcher</div> <p class="text-muted-foreground mt-1 font-mono text-xs">Redis Cache & Webhooks · 42.5k rec/s</p> </div> <div class="border-border/60 space-y-1.5 border-t pt-2.5 font-mono text-xs"> <div class="text-muted-foreground flex items-center justify-between"> <span>Semantics:</span> <span class="text-foreground font-semibold">2PC Exactly-Once</span> </div> <div class="text-muted-foreground flex items-center justify-between"> <span>Fan-Out:</span> <span class="text-foreground font-semibold tabular-nums">42.5k rec/s</span> </div> </div> </div> <div class="border-border/60 mt-3 flex items-center justify-between border-t pt-2 text-xs"> <span class="text-muted-foreground font-mono">redis:6379 + alerts.queue</span> <CheckCircle2 class="text-success size-3.5 shrink-0" /> </div> </div> </div> </div> </CardContent> </Card> <!-- 4. Operator Detailed Performance & State Inspector Panel --> <StreamOperatorDetail :operator="selectedOperator" :copied-key="copiedKey" @copy="copyText" /> </div> </template>app/components/blocks/StreamOperatorDetail.vue16.5 kB
<script setup lang="ts"> import { ref } from 'vue' import { Activity, Check, Copy, Server, ShieldCheck } from 'lucide-vue-next' import { cn } from '@/lib/utils' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' import { Progress } from '@/components/ui/progress' import { Separator } from '@/components/ui/separator' import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table' import type { FlinkOperatorNode } from './stream-topology-types' interface Props { operator: FlinkOperatorNode copiedKey: string | null } defineProps<Props>() const emit = defineEmits<{ copy: [key: string, value: string] }>() const activeInspectorTab = ref<'subtasks' | 'state' | 'resources' | 'config'>('subtasks') </script> <template> <Card class="shadow-xs"> <CardHeader class="pb-3"> <div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between"> <div class="space-y-1"> <div class="flex flex-wrap items-center gap-2"> <CardTitle class="font-mono text-base font-bold">{{ operator.name }}</CardTitle> <Badge variant="success" class="gap-1 font-mono text-xs"> <span class="bg-success size-1.5 rounded-full" /> {{ operator.status }} </Badge> <Badge variant="outline" class="font-mono text-xs"> Parallelism: {{ operator.parallelism }} subtasks </Badge> </div> <CardDescription class="text-xs"> {{ operator.description }} </CardDescription> </div> <!-- Inspector Tab Navigation Buttons --> <div class="bg-muted/60 flex flex-wrap items-center gap-1 rounded-lg border p-1"> <button type="button" :class=" cn( 'focus-visible:ring-ring rounded-md px-2.5 py-1 text-xs font-medium transition-colors focus-visible:ring-2 focus-visible:outline-none', activeInspectorTab === 'subtasks' ? 'bg-card text-foreground font-semibold shadow-xs' : 'text-muted-foreground hover:text-foreground', ) " @click="activeInspectorTab = 'subtasks'" > Subtasks & Distribution </button> <button type="button" :class=" cn( 'focus-visible:ring-ring rounded-md px-2.5 py-1 text-xs font-medium transition-colors focus-visible:ring-2 focus-visible:outline-none', activeInspectorTab === 'state' ? 'bg-card text-foreground font-semibold shadow-xs' : 'text-muted-foreground hover:text-foreground', ) " @click="activeInspectorTab = 'state'" > RocksDB Managed State </button> <button type="button" :class=" cn( 'focus-visible:ring-ring rounded-md px-2.5 py-1 text-xs font-medium transition-colors focus-visible:ring-2 focus-visible:outline-none', activeInspectorTab === 'resources' ? 'bg-card text-foreground font-semibold shadow-xs' : 'text-muted-foreground hover:text-foreground', ) " @click="activeInspectorTab = 'resources'" > JVM & GC Telemetry </button> <button type="button" :class=" cn( 'focus-visible:ring-ring rounded-md px-2.5 py-1 text-xs font-medium transition-colors focus-visible:ring-2 focus-visible:outline-none', activeInspectorTab === 'config' ? 'bg-card text-foreground font-semibold shadow-xs' : 'text-muted-foreground hover:text-foreground', ) " @click="activeInspectorTab = 'config'" > Configuration Parameters </button> </div> </div> </CardHeader> <!-- Key Operator Telemetry Bar --> <div class="bg-muted/20 divide-border/60 grid grid-cols-2 divide-x border-y sm:grid-cols-4 lg:grid-cols-6"> <div class="p-3"> <div class="text-muted-foreground text-xs font-medium">Input Rate</div> <div class="text-foreground mt-0.5 font-mono text-sm font-bold tabular-nums"> {{ operator.inputRecordsRate }} </div> <div class="text-muted-foreground font-mono text-xs tabular-nums">{{ operator.inputBytesRate }}</div> </div> <div class="p-3"> <div class="text-muted-foreground text-xs font-medium">Output Rate</div> <div class="text-foreground mt-0.5 font-mono text-sm font-bold tabular-nums"> {{ operator.outputRecordsRate }} </div> <div class="text-muted-foreground font-mono text-xs tabular-nums">{{ operator.outputBytesRate }}</div> </div> <div class="p-3"> <div class="text-muted-foreground text-xs font-medium">Managed State</div> <div class="text-primary mt-0.5 font-mono text-sm font-bold tabular-nums"> {{ operator.managedStateSize }} </div> <div class="text-muted-foreground truncate font-mono text-xs">{{ operator.stateBackend }}</div> </div> <div class="p-3"> <div class="text-muted-foreground text-xs font-medium">Watermark Lag</div> <div class="text-success text-success mt-0.5 font-mono text-sm font-bold tabular-nums"> {{ operator.watermarkLag }} </div> <div class="text-muted-foreground font-mono text-xs">Event Time OK</div> </div> <div class="p-3"> <div class="text-muted-foreground text-xs font-medium">CPU Core Load</div> <div class="text-foreground mt-0.5 font-mono text-sm font-bold tabular-nums"> {{ operator.cpuPeak }} </div> <div class="text-muted-foreground font-mono text-xs">Across {{ operator.parallelism }} cores</div> </div> <div class="p-3"> <div class="text-muted-foreground text-xs font-medium">GC Pause Time</div> <div class="text-foreground mt-0.5 font-mono text-sm font-bold tabular-nums"> {{ operator.gcTime }} </div> <div class="text-muted-foreground font-mono text-xs">G1 Garbage Collector</div> </div> </div> <CardContent class="p-0"> <!-- TAB 1: Subtasks Table --> <div v-if="activeInspectorTab === 'subtasks'" class="overflow-x-auto"> <Table density="cozy"> <TableHeader> <TableRow> <TableHead class="text-xs font-semibold">Subtask Index</TableHead> <TableHead class="text-xs font-semibold">Host / Pod</TableHead> <TableHead class="text-right text-xs font-semibold">Input Rate</TableHead> <TableHead class="text-right text-xs font-semibold">Output Rate</TableHead> <TableHead class="text-right text-xs font-semibold">CPU %</TableHead> <TableHead class="text-right text-xs font-semibold">Heap Usage</TableHead> <TableHead class="text-right text-xs font-semibold">Watermark Lag</TableHead> <TableHead class="text-right text-xs font-semibold">Checkpoint Ack</TableHead> <TableHead class="text-center text-xs font-semibold">Status</TableHead> </TableRow> </TableHeader> <TableBody> <TableRow v-for="subtask in operator.subtasks" :key="subtask.id" class="group"> <TableCell class="py-2.5"> <div class="flex items-center gap-2"> <span class="bg-success size-2 rounded-full" aria-hidden="true" /> <span class="font-mono text-xs font-semibold">Subtask #{{ subtask.id }}</span> </div> </TableCell> <TableCell class="text-muted-foreground py-2.5 font-mono text-xs"> {{ subtask.host }} </TableCell> <TableCell class="text-foreground py-2.5 text-right font-mono text-xs font-medium tabular-nums"> {{ subtask.inRate }} </TableCell> <TableCell class="text-foreground py-2.5 text-right font-mono text-xs font-medium tabular-nums"> {{ subtask.outRate }} </TableCell> <TableCell class="py-2.5 text-right font-mono text-xs tabular-nums"> <Badge variant="outline" class="font-mono text-xs">{{ subtask.cpu }}</Badge> </TableCell> <TableCell class="text-muted-foreground py-2.5 text-right font-mono text-xs tabular-nums"> {{ subtask.heapMemory }} </TableCell> <TableCell class="text-success text-success py-2.5 text-right font-mono text-xs font-medium tabular-nums"> {{ subtask.watermarkLag }} </TableCell> <TableCell class="text-muted-foreground py-2.5 text-right font-mono text-xs tabular-nums"> {{ subtask.checkpointAckMs }}ms </TableCell> <TableCell class="py-2.5 text-center"> <Badge variant="success" class="font-mono text-xs"> {{ subtask.status }} </Badge> </TableCell> </TableRow> </TableBody> </Table> </div> <!-- TAB 2: RocksDB Managed State --> <div v-else-if="activeInspectorTab === 'state'" class="space-y-5 p-5"> <div class="grid grid-cols-1 gap-4 sm:grid-cols-3"> <div class="bg-muted/10 space-y-2 rounded-lg border p-4"> <div class="text-muted-foreground text-xs font-medium">State Backend Engine</div> <div class="text-foreground font-mono text-base font-bold">{{ operator.stateBackend }}</div> <p class="text-muted-foreground text-xs"> Off-heap SSD persistence with block caching and incremental checkpoints </p> </div> <div class="bg-muted/10 space-y-2 rounded-lg border p-4"> <div class="text-muted-foreground text-xs font-medium">Total Live State Size</div> <div class="text-primary font-mono text-base font-bold">{{ operator.managedStateSize }}</div> <p class="text-muted-foreground text-xs">Incremental checkpoint delta: ~2.4 MB / snapshot</p> </div> <div class="bg-muted/10 space-y-2 rounded-lg border p-4"> <div class="text-muted-foreground text-xs font-medium">Key Eviction & TTL</div> <div class="text-foreground font-mono text-base font-bold">10-min Sliding TTL</div> <p class="text-muted-foreground text-xs"> Automatic compaction filter evicts expired cardholder keys on disk </p> </div> </div> <!-- RocksDB Performance Metrics Grid --> <div class="bg-card space-y-3 rounded-lg border p-4"> <h4 class="text-foreground text-xs font-semibold">RocksDB Native Engine Statistics</h4> <div class="grid grid-cols-2 gap-4 font-mono text-xs sm:grid-cols-4"> <div> <span class="text-muted-foreground">Block Cache Hit Ratio:</span> <div class="text-foreground mt-0.5 font-bold tabular-nums">99.42% (512 MB cache)</div> </div> <div> <span class="text-muted-foreground">MemTable Flush Rate:</span> <div class="text-foreground mt-0.5 font-bold tabular-nums">14.2 MB/min</div> </div> <div> <span class="text-muted-foreground">SST Files Count:</span> <div class="text-foreground mt-0.5 font-bold tabular-nums">128 active SSTs</div> </div> <div> <span class="text-muted-foreground">Level-0 Compaction Stall:</span> <div class="text-success text-success mt-0.5 font-bold tabular-nums">0.0ms (No stalls)</div> </div> </div> </div> </div> <!-- TAB 3: JVM & Resource Metrics --> <div v-else-if="activeInspectorTab === 'resources'" class="space-y-5 p-5"> <div class="grid grid-cols-1 gap-4 sm:grid-cols-2"> <div class="bg-muted/10 space-y-3 rounded-lg border p-4"> <div class="flex items-center justify-between text-xs"> <span class="text-foreground font-medium">JVM Heap Memory Utilization</span> <span class="text-muted-foreground font-mono">{{ operator.heapMemory }} / 4,096 MB</span> </div> <Progress :model-value="34" class="h-2" /> <div class="text-muted-foreground flex justify-between font-mono text-xs"> <span>Survivor: 64 MB</span> <span>Tenured / Old: 348 MB</span> </div> </div> <div class="bg-muted/10 space-y-3 rounded-lg border p-4"> <div class="flex items-center justify-between text-xs"> <span class="text-foreground font-medium">Managed Off-Heap & Native Memory</span> <span class="text-muted-foreground font-mono">{{ operator.managedMemory }} Allocated</span> </div> <Progress :model-value="50" class="h-2" /> <div class="text-muted-foreground flex justify-between font-mono text-xs"> <span>RocksDB Cache: 512 MB</span> <span>Network Buffers: 256 MB</span> </div> </div> </div> <div class="bg-card space-y-2 rounded-lg border p-4"> <div class="text-foreground text-xs font-semibold">Garbage Collection Profile</div> <p class="text-muted-foreground font-mono text-xs"> Collector: Garbage-First (G1) GC · Max Pause Target: 20ms · Current Observed Pause: {{ operator.gcTime }} </p> </div> </div> <!-- TAB 4: Configuration Parameters --> <div v-else class="space-y-4 p-5"> <div class="overflow-hidden rounded-lg border"> <div class="bg-muted/50 text-foreground flex items-center justify-between border-b p-3 text-xs font-semibold"> <span>Runtime Operator Configuration</span> <Badge variant="outline" class="font-mono text-xs">{{ operator.operatorClass }}</Badge> </div> <div class="divide-y font-mono text-xs"> <div v-for="(val, key) in operator.config" :key="key" class="hover:bg-muted/20 flex flex-col justify-between gap-2 p-3 transition-colors sm:flex-row sm:items-center" > <span class="text-muted-foreground font-medium">{{ key }}</span> <div class="flex items-center gap-2"> <span class="text-foreground font-semibold">{{ val }}</span> <Button :aria-label="`Copy ${key}`" variant="ghost" size="xs" class="text-muted-foreground hover:text-foreground h-6 w-6 p-0" @click="emit('copy', String(key), String(val))" > <Check v-if="copiedKey === String(key)" class="text-success size-3" /> <Copy v-else class="size-3" /> </Button> </div> </div> </div> </div> </div> </CardContent> <!-- Bottom Engine Metadata Strip --> <div class="bg-muted/40 flex flex-col gap-2.5 border-t px-4 py-3 font-mono text-xs sm:flex-row sm:items-center sm:justify-between" > <div class="text-muted-foreground flex flex-wrap items-center gap-4"> <div class="text-foreground flex items-center gap-1.5 font-medium"> <Activity class="text-success size-3.5" aria-hidden="true" /> <span>Pipeline Ingestion:</span> <span>42,500 records/sec (8.4 MB/s)</span> </div> <Separator orientation="vertical" class="hidden h-3.5 sm:block" /> <div class="flex items-center gap-1.5"> <Server class="text-info size-3.5" aria-hidden="true" /> <span>TaskManagers:</span> <span class="text-foreground">8 Kubernetes Pods</span> </div> <Separator orientation="vertical" class="hidden h-3.5 sm:block" /> <div class="flex items-center gap-1.5"> <ShieldCheck class="text-success size-3.5" aria-hidden="true" /> <span>State Consistency:</span> <span class="text-foreground">Exactly-Once (2PC)</span> </div> </div> <div class="text-muted-foreground flex items-center gap-2"> <span class="bg-success size-1.5 rounded-full" /> <span>Watermark Alignment: 42ms · Checkpoint #1,420 OK</span> </div> </div> </Card> </template>app/components/blocks/StreamTopologyTelemetry.vue6.3 kB
<script setup lang="ts"> import type { HTMLAttributes } from 'vue' import { Activity, Clock, Flame, Zap } from 'lucide-vue-next' import { cn } from '@/lib/utils' import { Badge } from '@/components/ui/badge' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { Progress } from '@/components/ui/progress' interface Props { class?: HTMLAttributes['class'] } const props = defineProps<Props>() </script> <template> <div :class="cn('grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4', props.class)"> <!-- Card 1: Current Ingestion Throughput --> <Card class="flex flex-col justify-between shadow-xs"> <CardHeader class="pb-2"> <div class="flex items-center justify-between gap-2"> <div class="flex min-w-0 items-center gap-2"> <div class="bg-muted text-muted-foreground border-border flex size-8 shrink-0 items-center justify-center rounded-lg border shadow-xs" aria-hidden="true" > <Activity class="text-primary size-4" /> </div> <CardTitle class="truncate text-xs font-medium">Current Ingestion Throughput</CardTitle> </div> <Badge variant="outline" class="shrink-0 text-xs font-normal tabular-nums"> +4.2% 10m </Badge> </div> </CardHeader> <CardContent class="space-y-3"> <div> <div class="text-foreground font-mono text-2xl font-bold tracking-tight tabular-nums">42,500 records/sec</div> <p class="text-muted-foreground font-mono text-xs tabular-nums">8.4 MB/s ingress bandwidth</p> </div> <div class="space-y-1.5"> <div class="text-muted-foreground flex items-center justify-between text-xs"> <span>Ingress Capacity</span> <span class="text-foreground font-mono font-medium tabular-nums">68% allocated</span> </div> <Progress :model-value="68" class="h-1.5" /> </div> </CardContent> </Card> <!-- Card 2: Checkpoint Duration --> <Card class="flex flex-col justify-between shadow-xs"> <CardHeader class="pb-2"> <div class="flex items-center justify-between gap-2"> <div class="flex min-w-0 items-center gap-2"> <div class="bg-muted text-muted-foreground border-border flex size-8 shrink-0 items-center justify-center rounded-lg border shadow-xs" aria-hidden="true" > <Clock class="text-success size-4" /> </div> <CardTitle class="truncate text-xs font-medium">Checkpoint Duration</CardTitle> </div> <Badge variant="success" class="shrink-0 font-mono text-xs"> 100% Succeeded </Badge> </div> </CardHeader> <CardContent class="space-y-3"> <div> <div class="text-foreground font-mono text-2xl font-bold tracking-tight tabular-nums">185ms duration</div> <p class="text-muted-foreground font-mono text-xs tabular-nums">42 MB state size · Checkpoint #1,420</p> </div> <div class="space-y-1.5"> <div class="text-muted-foreground flex items-center justify-between text-xs"> <span>SLA Budget (1,000ms)</span> <span class="text-foreground font-mono font-medium tabular-nums">18.5% of interval</span> </div> <Progress :model-value="18.5" class="h-1.5" /> </div> </CardContent> </Card> <!-- Card 3: Watermark Alignment Latency --> <Card class="flex flex-col justify-between shadow-xs"> <CardHeader class="pb-2"> <div class="flex items-center justify-between gap-2"> <div class="flex min-w-0 items-center gap-2"> <div class="bg-muted text-muted-foreground border-border flex size-8 shrink-0 items-center justify-center rounded-lg border shadow-xs" aria-hidden="true" > <Zap class="text-warning size-4" /> </div> <CardTitle class="truncate text-xs font-medium">Watermark Alignment</CardTitle> </div> <Badge variant="outline" class="border-success/30 text-success font-mono text-xs"> Event Time </Badge> </div> </CardHeader> <CardContent class="space-y-3"> <div> <div class="text-foreground font-mono text-2xl font-bold tracking-tight tabular-nums">Lag: 42ms</div> <p class="text-muted-foreground font-mono text-xs tabular-nums">Event Time Alignment · 0 Late Drops</p> </div> <div class="space-y-1.5"> <div class="text-muted-foreground flex items-center justify-between text-xs"> <span>Skew Drift</span> <span class="text-foreground font-mono font-medium tabular-nums">Max 12ms cross-partition</span> </div> <Progress :model-value="8.4" class="h-1.5" /> </div> </CardContent> </Card> <!-- Card 4: Max Backpressure Heat Ratio --> <Card class="flex flex-col justify-between shadow-xs"> <CardHeader class="pb-2"> <div class="flex items-center justify-between gap-2"> <div class="flex min-w-0 items-center gap-2"> <div class="bg-muted text-muted-foreground border-border flex size-8 shrink-0 items-center justify-center rounded-lg border shadow-xs" aria-hidden="true" > <Flame class="text-success size-4" /> </div> <CardTitle class="truncate text-xs font-medium">Max Backpressure Heat</CardTitle> </div> <Badge variant="success" class="shrink-0 font-mono text-xs"> Low Risk </Badge> </div> </CardHeader> <CardContent class="space-y-3"> <div> <div class="text-foreground font-mono text-2xl font-bold tracking-tight tabular-nums">0.0% Backpressure</div> <p class="text-muted-foreground font-mono text-xs tabular-nums">All 4 operators running at nominal load</p> </div> <div class="space-y-1.5"> <div class="text-muted-foreground flex items-center justify-between text-xs"> <span>Choked Subtasks</span> <span class="text-success text-success font-mono font-medium tabular-nums">0 / 48 active</span> </div> <Progress :model-value="0" class="h-1.5" /> </div> </CardContent> </Card> </div> </template>app/components/blocks/stream-topology-types.ts0.9 kB
export interface FlinkSubtask { id: number host: string status: 'RUNNING' | 'DEPLOYING' | 'RECONCILING' inRate: string outRate: string cpu: string heapMemory: string backpressure: number watermarkLag: string checkpointAckMs: number } export interface FlinkOperatorNode { id: string name: string shortTitle: string stageNumber: number category: 'source' | 'window' | 'inference' | 'sink' operatorClass: string parallelism: number status: 'RUNNING' | 'CANCELED' | 'FAILING' backpressure: number inputRecordsRate: string inputBytesRate: string outputRecordsRate: string outputBytesRate: string totalRecordsIn: number totalRecordsOut: number managedStateSize: string stateBackend: string heapMemory: string managedMemory: string gcTime: string cpuPeak: string watermarkLag: string currentWatermark: string description: string config: Record<string, string> subtasks: FlinkSubtask[] }app/components/blocks/stream-topology-data.ts19.2 kB
import type { FlinkOperatorNode } from './stream-topology-types' export const defaultFlinkOperators: FlinkOperatorNode[] = [ { id: 'op-kafka-source', name: 'Source: Kafka Ingestion (events.fraud.transactions)', shortTitle: 'Kafka Ingestion Source', stageNumber: 1, category: 'source', operatorClass: 'org.apache.flink.connector.kafka.source.KafkaSource', parallelism: 8, status: 'RUNNING', backpressure: 0.0, inputRecordsRate: '42,500 rec/s', inputBytesRate: '8.4 MB/s', outputRecordsRate: '42,500 rec/s', outputBytesRate: '8.4 MB/s', totalRecordsIn: 184290410, totalRecordsOut: 184290410, managedStateSize: '0 MB (Stateless)', stateBackend: 'None (Stateless Source)', heapMemory: '412 MB', managedMemory: '0 MB', gcTime: '0.8 ms/min', cpuPeak: '14%', watermarkLag: '28ms', currentWatermark: '2026-08-21 14:32:45.892 UTC', description: 'Consumes high-throughput payment transaction stream with snappy decompression and bounded out-of-orderness watermark generation.', config: { topic: 'events.fraud.transactions', 'consumer.group.id': 'flink-fraud-scoring-v2', 'scan.startup.mode': 'latest-offset', 'watermark.strategy': 'BoundedOutOfOrderness(Duration.ofMillis(200))', 'partition.discovery.interval.ms': '30000', 'deserializer.format': 'AvroConfluentSchemaRegistry', }, subtasks: [ { id: 0, host: 'taskmanager-pod-0.k8s', status: 'RUNNING', inRate: '5,320 rec/s', outRate: '5,320 rec/s', cpu: '14%', heapMemory: '52 MB', backpressure: 0.0, watermarkLag: '22ms', checkpointAckMs: 42, }, { id: 1, host: 'taskmanager-pod-0.k8s', status: 'RUNNING', inRate: '5,310 rec/s', outRate: '5,310 rec/s', cpu: '13%', heapMemory: '51 MB', backpressure: 0.0, watermarkLag: '24ms', checkpointAckMs: 44, }, { id: 2, host: 'taskmanager-pod-1.k8s', status: 'RUNNING', inRate: '5,315 rec/s', outRate: '5,315 rec/s', cpu: '15%', heapMemory: '53 MB', backpressure: 0.0, watermarkLag: '26ms', checkpointAckMs: 41, }, { id: 3, host: 'taskmanager-pod-1.k8s', status: 'RUNNING', inRate: '5,305 rec/s', outRate: '5,305 rec/s', cpu: '14%', heapMemory: '50 MB', backpressure: 0.0, watermarkLag: '23ms', checkpointAckMs: 45, }, { id: 4, host: 'taskmanager-pod-2.k8s', status: 'RUNNING', inRate: '5,325 rec/s', outRate: '5,325 rec/s', cpu: '14%', heapMemory: '52 MB', backpressure: 0.0, watermarkLag: '25ms', checkpointAckMs: 40, }, { id: 5, host: 'taskmanager-pod-2.k8s', status: 'RUNNING', inRate: '5,310 rec/s', outRate: '5,310 rec/s', cpu: '13%', heapMemory: '51 MB', backpressure: 0.0, watermarkLag: '24ms', checkpointAckMs: 43, }, { id: 6, host: 'taskmanager-pod-3.k8s', status: 'RUNNING', inRate: '5,308 rec/s', outRate: '5,308 rec/s', cpu: '15%', heapMemory: '52 MB', backpressure: 0.0, watermarkLag: '28ms', checkpointAckMs: 42, }, { id: 7, host: 'taskmanager-pod-3.k8s', status: 'RUNNING', inRate: '5,307 rec/s', outRate: '5,307 rec/s', cpu: '14%', heapMemory: '51 MB', backpressure: 0.0, watermarkLag: '25ms', checkpointAckMs: 43, }, ], }, { id: 'op-keyed-window', name: 'Keyed Sliding Window (5-min window, 10s slide)', shortTitle: 'Keyed Sliding Window', stageNumber: 2, category: 'window', operatorClass: 'org.apache.flink.streaming.runtime.operators.windowing.WindowOperator', parallelism: 16, status: 'RUNNING', backpressure: 0.0, inputRecordsRate: '42,500 rec/s', inputBytesRate: '8.4 MB/s', outputRecordsRate: '38,200 rec/s', outputBytesRate: '6.9 MB/s', totalRecordsIn: 184290410, totalRecordsOut: 165861369, managedStateSize: '18.4 MB (RocksDB)', stateBackend: 'EmbeddedRocksDBStateBackend', heapMemory: '780 MB', managedMemory: '1,024 MB', gcTime: '1.2 ms/min', cpuPeak: '28%', watermarkLag: '34ms', currentWatermark: '2026-08-21 14:32:45.886 UTC', description: 'Aggregates velocity, cumulative transaction volumes, and rapid card-testing frequency per cardholder ID across 5m windows triggered every 10s.', config: { 'window.assigner': 'SlidingEventTimeWindows.of(Time.minutes(5), Time.seconds(10))', 'allowed.lateness': '0ms', 'state.backend.ttl': '600000ms (10m TTL)', 'state.active.cardholders': '12,400 keys in state', trigger: 'EventTimeTrigger.create()', evictor: 'TimeEvictor.of(Time.minutes(5))', }, subtasks: [ { id: 0, host: 'taskmanager-pod-0.k8s', status: 'RUNNING', inRate: '2,660 rec/s', outRate: '2,390 rec/s', cpu: '28%', heapMemory: '49 MB', backpressure: 0.0, watermarkLag: '32ms', checkpointAckMs: 65, }, { id: 1, host: 'taskmanager-pod-0.k8s', status: 'RUNNING', inRate: '2,655 rec/s', outRate: '2,385 rec/s', cpu: '27%', heapMemory: '48 MB', backpressure: 0.0, watermarkLag: '34ms', checkpointAckMs: 68, }, { id: 2, host: 'taskmanager-pod-1.k8s', status: 'RUNNING', inRate: '2,662 rec/s', outRate: '2,392 rec/s', cpu: '29%', heapMemory: '50 MB', backpressure: 0.0, watermarkLag: '33ms', checkpointAckMs: 64, }, { id: 3, host: 'taskmanager-pod-1.k8s', status: 'RUNNING', inRate: '2,650 rec/s', outRate: '2,380 rec/s', cpu: '28%', heapMemory: '48 MB', backpressure: 0.0, watermarkLag: '35ms', checkpointAckMs: 66, }, { id: 4, host: 'taskmanager-pod-2.k8s', status: 'RUNNING', inRate: '2,658 rec/s', outRate: '2,388 rec/s', cpu: '27%', heapMemory: '49 MB', backpressure: 0.0, watermarkLag: '34ms', checkpointAckMs: 65, }, { id: 5, host: 'taskmanager-pod-2.k8s', status: 'RUNNING', inRate: '2,654 rec/s', outRate: '2,386 rec/s', cpu: '29%', heapMemory: '50 MB', backpressure: 0.0, watermarkLag: '33ms', checkpointAckMs: 67, }, { id: 6, host: 'taskmanager-pod-3.k8s', status: 'RUNNING', inRate: '2,660 rec/s', outRate: '2,390 rec/s', cpu: '28%', heapMemory: '49 MB', backpressure: 0.0, watermarkLag: '35ms', checkpointAckMs: 66, }, { id: 7, host: 'taskmanager-pod-3.k8s', status: 'RUNNING', inRate: '2,652 rec/s', outRate: '2,384 rec/s', cpu: '28%', heapMemory: '48 MB', backpressure: 0.0, watermarkLag: '34ms', checkpointAckMs: 64, }, { id: 8, host: 'taskmanager-pod-4.k8s', status: 'RUNNING', inRate: '2,659 rec/s', outRate: '2,389 rec/s', cpu: '29%', heapMemory: '50 MB', backpressure: 0.0, watermarkLag: '33ms', checkpointAckMs: 65, }, { id: 9, host: 'taskmanager-pod-4.k8s', status: 'RUNNING', inRate: '2,655 rec/s', outRate: '2,385 rec/s', cpu: '27%', heapMemory: '48 MB', backpressure: 0.0, watermarkLag: '36ms', checkpointAckMs: 68, }, { id: 10, host: 'taskmanager-pod-5.k8s', status: 'RUNNING', inRate: '2,661 rec/s', outRate: '2,391 rec/s', cpu: '28%', heapMemory: '49 MB', backpressure: 0.0, watermarkLag: '34ms', checkpointAckMs: 63, }, { id: 11, host: 'taskmanager-pod-5.k8s', status: 'RUNNING', inRate: '2,650 rec/s', outRate: '2,380 rec/s', cpu: '28%', heapMemory: '48 MB', backpressure: 0.0, watermarkLag: '35ms', checkpointAckMs: 66, }, { id: 12, host: 'taskmanager-pod-6.k8s', status: 'RUNNING', inRate: '2,657 rec/s', outRate: '2,387 rec/s', cpu: '27%', heapMemory: '49 MB', backpressure: 0.0, watermarkLag: '33ms', checkpointAckMs: 67, }, { id: 13, host: 'taskmanager-pod-6.k8s', status: 'RUNNING', inRate: '2,653 rec/s', outRate: '2,383 rec/s', cpu: '29%', heapMemory: '50 MB', backpressure: 0.0, watermarkLag: '34ms', checkpointAckMs: 65, }, { id: 14, host: 'taskmanager-pod-7.k8s', status: 'RUNNING', inRate: '2,658 rec/s', outRate: '2,388 rec/s', cpu: '28%', heapMemory: '49 MB', backpressure: 0.0, watermarkLag: '35ms', checkpointAckMs: 66, }, { id: 15, host: 'taskmanager-pod-7.k8s', status: 'RUNNING', inRate: '2,651 rec/s', outRate: '2,382 rec/s', cpu: '28%', heapMemory: '48 MB', backpressure: 0.0, watermarkLag: '34ms', checkpointAckMs: 64, }, ], }, { id: 'op-ml-scoring', name: 'ML Fraud Scoring Pattern Matcher (CEP + XGBoost ONNX)', shortTitle: 'ML Fraud Scoring Pattern Matcher', stageNumber: 3, category: 'inference', operatorClass: 'com.uipkge.fraud.engine.AsyncFraudScoringKeyedProcessFunction', parallelism: 16, status: 'RUNNING', backpressure: 0.0, inputRecordsRate: '38,200 rec/s', inputBytesRate: '6.9 MB/s', outputRecordsRate: '38,200 rec/s', outputBytesRate: '7.1 MB/s', totalRecordsIn: 165861369, totalRecordsOut: 165861369, managedStateSize: '38.2 MB (RocksDB)', stateBackend: 'EmbeddedRocksDBStateBackend', heapMemory: '1,420 MB', managedMemory: '2,048 MB', gcTime: '1.4 ms/min', cpuPeak: '38%', watermarkLag: '38ms', currentWatermark: '2026-08-21 14:32:45.882 UTC', description: 'Evaluates stateful complex event patterns (CEP) and executes low-latency XGBoost model scoring via embedded ONNX native runtime.', config: { 'model.runtime': 'ONNX Runtime 1.18 (AVX-512)', 'model.artifact': 's3://ml-models-registry/fraud/xgboost_v4_18.onnx', 'inference.latency.p99': '1.2ms', 'rocksdb.block.cache.size': '512 MB', 'rocksdb.compaction.style': 'LEVEL', 'alert.confidence.threshold': '0.85', }, subtasks: [ { id: 0, host: 'taskmanager-pod-0.k8s', status: 'RUNNING', inRate: '2,390 rec/s', outRate: '2,390 rec/s', cpu: '38%', heapMemory: '89 MB', backpressure: 0.0, watermarkLag: '37ms', checkpointAckMs: 110, }, { id: 1, host: 'taskmanager-pod-0.k8s', status: 'RUNNING', inRate: '2,385 rec/s', outRate: '2,385 rec/s', cpu: '37%', heapMemory: '88 MB', backpressure: 0.0, watermarkLag: '38ms', checkpointAckMs: 114, }, { id: 2, host: 'taskmanager-pod-1.k8s', status: 'RUNNING', inRate: '2,392 rec/s', outRate: '2,392 rec/s', cpu: '39%', heapMemory: '90 MB', backpressure: 0.0, watermarkLag: '36ms', checkpointAckMs: 108, }, { id: 3, host: 'taskmanager-pod-1.k8s', status: 'RUNNING', inRate: '2,380 rec/s', outRate: '2,380 rec/s', cpu: '38%', heapMemory: '87 MB', backpressure: 0.0, watermarkLag: '39ms', checkpointAckMs: 112, }, { id: 4, host: 'taskmanager-pod-2.k8s', status: 'RUNNING', inRate: '2,388 rec/s', outRate: '2,388 rec/s', cpu: '37%', heapMemory: '88 MB', backpressure: 0.0, watermarkLag: '38ms', checkpointAckMs: 111, }, { id: 5, host: 'taskmanager-pod-2.k8s', status: 'RUNNING', inRate: '2,386 rec/s', outRate: '2,386 rec/s', cpu: '39%', heapMemory: '90 MB', backpressure: 0.0, watermarkLag: '37ms', checkpointAckMs: 115, }, { id: 6, host: 'taskmanager-pod-3.k8s', status: 'RUNNING', inRate: '2,390 rec/s', outRate: '2,390 rec/s', cpu: '38%', heapMemory: '89 MB', backpressure: 0.0, watermarkLag: '39ms', checkpointAckMs: 110, }, { id: 7, host: 'taskmanager-pod-3.k8s', status: 'RUNNING', inRate: '2,384 rec/s', outRate: '2,384 rec/s', cpu: '37%', heapMemory: '87 MB', backpressure: 0.0, watermarkLag: '38ms', checkpointAckMs: 112, }, { id: 8, host: 'taskmanager-pod-4.k8s', status: 'RUNNING', inRate: '2,389 rec/s', outRate: '2,389 rec/s', cpu: '38%', heapMemory: '89 MB', backpressure: 0.0, watermarkLag: '37ms', checkpointAckMs: 109, }, { id: 9, host: 'taskmanager-pod-4.k8s', status: 'RUNNING', inRate: '2,385 rec/s', outRate: '2,385 rec/s', cpu: '37%', heapMemory: '88 MB', backpressure: 0.0, watermarkLag: '39ms', checkpointAckMs: 113, }, { id: 10, host: 'taskmanager-pod-5.k8s', status: 'RUNNING', inRate: '2,391 rec/s', outRate: '2,391 rec/s', cpu: '39%', heapMemory: '90 MB', backpressure: 0.0, watermarkLag: '37ms', checkpointAckMs: 108, }, { id: 11, host: 'taskmanager-pod-5.k8s', status: 'RUNNING', inRate: '2,380 rec/s', outRate: '2,380 rec/s', cpu: '38%', heapMemory: '87 MB', backpressure: 0.0, watermarkLag: '38ms', checkpointAckMs: 112, }, { id: 12, host: 'taskmanager-pod-6.k8s', status: 'RUNNING', inRate: '2,387 rec/s', outRate: '2,387 rec/s', cpu: '37%', heapMemory: '88 MB', backpressure: 0.0, watermarkLag: '36ms', checkpointAckMs: 111, }, { id: 13, host: 'taskmanager-pod-6.k8s', status: 'RUNNING', inRate: '2,383 rec/s', outRate: '2,383 rec/s', cpu: '39%', heapMemory: '90 MB', backpressure: 0.0, watermarkLag: '38ms', checkpointAckMs: 114, }, { id: 14, host: 'taskmanager-pod-7.k8s', status: 'RUNNING', inRate: '2,388 rec/s', outRate: '2,388 rec/s', cpu: '38%', heapMemory: '89 MB', backpressure: 0.0, watermarkLag: '39ms', checkpointAckMs: 110, }, { id: 15, host: 'taskmanager-pod-7.k8s', status: 'RUNNING', inRate: '2,382 rec/s', outRate: '2,382 rec/s', cpu: '38%', heapMemory: '88 MB', backpressure: 0.0, watermarkLag: '37ms', checkpointAckMs: 112, }, ], }, { id: 'op-sink-dispatcher', name: 'Sink: Alert Dispatcher & Redis Cache', shortTitle: 'Sink: Alert Dispatcher & Redis Cache', stageNumber: 4, category: 'sink', operatorClass: 'com.uipkge.fraud.sink.TwoPhaseCommitRedisAlertSink', parallelism: 8, status: 'RUNNING', backpressure: 0.0, inputRecordsRate: '38,200 rec/s', inputBytesRate: '7.1 MB/s', outputRecordsRate: '42,500 rec/s', outputBytesRate: '9.2 MB/s', totalRecordsIn: 165861369, totalRecordsOut: 165861369, managedStateSize: '0.4 MB (2PC WAL)', stateBackend: 'Transactional Two-Phase Commit', heapMemory: '380 MB', managedMemory: '128 MB', gcTime: '0.9 ms/min', cpuPeak: '16%', watermarkLag: '42ms', currentWatermark: '2026-08-21 14:32:45.878 UTC', description: 'Commits risk scoring states to Redis Cluster for zero-latency checkout authorization gates and pushes flagged transactions to incident queues.', config: { 'sink.semantics': 'EXACTLY_ONCE (2PC Transactional)', 'redis.cluster.nodes': 'redis-cluster.internal:6379 (6 nodes)', 'redis.ttl': '86400s (24-hour key expiration)', 'webhook.alert.queue': 'https://alerts.security.internal/v1/fraud', 'batch.flush.interval.ms': '50', }, subtasks: [ { id: 0, host: 'taskmanager-pod-0.k8s', status: 'RUNNING', inRate: '4,775 rec/s', outRate: '5,312 rec/s', cpu: '16%', heapMemory: '48 MB', backpressure: 0.0, watermarkLag: '41ms', checkpointAckMs: 52, }, { id: 1, host: 'taskmanager-pod-1.k8s', status: 'RUNNING', inRate: '4,770 rec/s', outRate: '5,308 rec/s', cpu: '15%', heapMemory: '47 MB', backpressure: 0.0, watermarkLag: '43ms', checkpointAckMs: 55, }, { id: 2, host: 'taskmanager-pod-2.k8s', status: 'RUNNING', inRate: '4,780 rec/s', outRate: '5,315 rec/s', cpu: '17%', heapMemory: '49 MB', backpressure: 0.0, watermarkLag: '40ms', checkpointAckMs: 51, }, { id: 3, host: 'taskmanager-pod-3.k8s', status: 'RUNNING', inRate: '4,765 rec/s', outRate: '5,305 rec/s', cpu: '16%', heapMemory: '46 MB', backpressure: 0.0, watermarkLag: '44ms', checkpointAckMs: 56, }, { id: 4, host: 'taskmanager-pod-4.k8s', status: 'RUNNING', inRate: '4,778 rec/s', outRate: '5,314 rec/s', cpu: '16%', heapMemory: '48 MB', backpressure: 0.0, watermarkLag: '42ms', checkpointAckMs: 53, }, { id: 5, host: 'taskmanager-pod-5.k8s', status: 'RUNNING', inRate: '4,772 rec/s', outRate: '5,310 rec/s', cpu: '15%', heapMemory: '47 MB', backpressure: 0.0, watermarkLag: '43ms', checkpointAckMs: 54, }, { id: 6, host: 'taskmanager-pod-6.k8s', status: 'RUNNING', inRate: '4,782 rec/s', outRate: '5,318 rec/s', cpu: '17%', heapMemory: '49 MB', backpressure: 0.0, watermarkLag: '41ms', checkpointAckMs: 50, }, { id: 7, host: 'taskmanager-pod-7.k8s', status: 'RUNNING', inRate: '4,768 rec/s', outRate: '5,308 rec/s', cpu: '16%', heapMemory: '47 MB', backpressure: 0.0, watermarkLag: '43ms', checkpointAckMs: 55, }, ], }, ]
Raw manifest:https://uipkge.dev/r/vue/stream-processing-flink-topology.json