See it as a diagram
Everything below, as a diagram you can edit. Describe yours and see it in seconds.
No account needed · Editable canvas, not a picture
Line style for mode, labels for timing
Use a solid edge for a continuous hop and a dashed edge for a scheduled one. It is a two value convention, it needs no legend once a reader sees one labelled example, and it reads correctly even after the diagram has been shrunk into a wiki page.
Then let the labels carry the timing detail that line style cannot. On a batch edge, write the schedule the way the orchestrator expresses it, for example nightly 02:00 UTC or every 15 minutes, because that string is searchable in the DAG code. On a streaming edge, write the topic name and the latency target, for example orders.v2, p99 under 5s. Throughput belongs there too when it drives capacity decisions.
Resist a third line style. A diagram with solid, dashed, dotted, and thick edges becomes a puzzle. If you need a third mode, it is usually micro-batch, and micro-batch is better handled with a label than with a stroke pattern.
Micro-batch, CDC, and the mode boundary
Micro-batch is neither mode, and pretending otherwise causes arguments. A Structured Streaming query with a five minute processing time trigger is scheduled from the operator point of view and continuous from the code point of view. Draw it as a solid edge, since it is always running, and label it micro-batch, 5 min trigger. That single label prevents someone assuming sub-second latency they are not getting.
Change data capture has the opposite trap. The transport is continuous, but the consumption is very often scheduled: Debezium writes to a topic all day, and a job drains that topic on a schedule. Two hops, two line styles, with the topic as the node between them. Drawing it as one arrow from Postgres to the warehouse hides the exact place where the latency actually lives.
Wherever a solid edge meets a dashed edge, there is a node that changes the mode: a topic, a landing table, a queue, a staging area. Name that node. It is the buffer that absorbs a producer outage, and it is the first thing anyone inspects when latency grows.
orders_service ==[orders.v2, ~2k msg/s, p99 <5s]==> Kafka orders.v2 (solid: continuous) Kafka orders.v2 --[Spark micro-batch, 5 min trigger]--> lake.bronze_orders (solid, labelled) Postgres - -[JDBC extract, nightly 02:00 UTC]- -> lake.bronze_customers (dashed: scheduled) lake.bronze_* - -[dbt run, hourly]- -> lake.gold_orders_daily (dashed: scheduled)
Lambda, kappa, and the backfill lane
If a streaming path and a batch path both write to the same serving table, that convergence is the single most important fact on the diagram and it should be impossible to miss. Draw both lanes arriving at the same node, and put the reconciliation rule on the node itself: streaming path is provisional, batch path overwrites the last 48 hours. Readers who understand lambda architecture will recognize it, and readers who do not will still understand what happens.
A kappa style platform, where the batch path is a replay of the same stream rather than a separate pipeline, should look different: one lane, plus a replay arrow returning to the processing job from the retained log. Drawing kappa as two lanes makes it look like lambda and invites the wrong questions in review.
Draw the backfill path once, and mark it. Every real pipeline has one, most diagrams omit it, and it is the path that runs during the worst week of the quarter. One dashed arrow from the source or the archive into the processing node, labelled backfill, manual trigger, is enough.
Two lanes, one canvas
Every label should point at a setting somebody can open
A drawing convention is only worth having if it can be falsified. Each label above corresponds to a setting in a file, so a reviewer can check the diagram against the repository rather than against their memory of a design meeting.
For Spark the setting is the trigger on the writer. processingTime with an interval is the micro-batch case, availableNow drains everything currently available and then stops, and continuous runs a continuous query with a checkpoint interval. Trigger.Once has been deprecated since Spark 3.4 in favour of Trigger.AvailableNow, so an edge still annotated "one-time trigger" usually marks code nobody has revisited. Put the trigger on the edge and the checkpointLocation on the node, because that path is the first thing anyone needs when a streaming job has to be restarted.
For a scheduled hop the setting is the orchestrator schedule, and its string belongs on the edge verbatim rather than translated into prose. For a CDC hop there are two settings, the connector config and the consumer schedule, which is exactly why that hop is two edges rather than one. Where a label has no setting you can point at, it is a belief rather than a fact, and it is better to find that out before somebody sizes a cluster against it.
FAQ
How do you show batch and streaming on the same diagram?
How should micro-batch be drawn?
Where does change data capture belong on a diagram?
How do I draw a lambda architecture without confusing readers?
Should backfill paths appear on the diagram?