<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:taxo="http://purl.org/rss/1.0/modules/taxonomy/" version="2.0">
  <channel>
    <title>article Apache Spark's Real-time Mode Use Case Focus: Ad Attribution in Technical Blog</title>
    <link>https://community.databricks.com/t5/technical-blog/apache-spark-s-real-time-mode-use-case-focus-ad-attribution/ba-p/146141</link>
    <description>&lt;H1 class="c22"&gt;Apache Spark's Real-time Mode Use Case Deep Dive: Ad Attribution&lt;/H1&gt;
&lt;P class="c24" data-unlink="true"&gt;&lt;SPAN&gt;This article is a companion to &lt;A href="https://www.databricks.com/blog/why-apache-spark-real-time-mode-game-changer-ad-attribution" target="_self"&gt;this&amp;nbsp;&lt;/A&gt;&lt;/SPAN&gt;&lt;A href="https://www.databricks.com/blog/why-apache-spark-real-time-mode-game-changer-ad-attribution" target="_self"&gt;&lt;SPAN&gt;Databricks blog&lt;/SPAN&gt;&lt;/A&gt;&amp;nbsp;&lt;SPAN&gt;&amp;nbsp;about an Ad Attribution use case, with a&amp;nbsp;&lt;/SPAN&gt;&lt;SPAN&gt;GitHub repo&lt;/SPAN&gt;&lt;SUP&gt;&lt;A id="cmnt_ref2" href="#cmnt2" target="_blank" rel="noopener"&gt;[b]&lt;/A&gt;&lt;/SUP&gt;&lt;SPAN&gt;&amp;nbsp;including sample code that you can use to see&lt;/SPAN&gt;&lt;SPAN class="c48"&gt;&lt;A class="c19" href="https://www.google.com/url?q=https://docs.databricks.com/aws/en/structured-streaming/real-time&amp;amp;sa=D&amp;amp;source=editors&amp;amp;ust=1769790659780565&amp;amp;usg=AOvVaw3Kagv-BWZf60HNaGBvVTG0" target="_blank" rel="noopener"&gt;&amp;nbsp;Real-Time Mode (RTM)&lt;/A&gt;&lt;/SPAN&gt;&lt;SPAN class="c5 c21"&gt;&amp;nbsp;in action.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class="c24"&gt;&lt;SPAN&gt;In this article, we go deep into the underlying architecture and mechanics of real-time mode (RTM) while reflecting on the Ad Attribution use case and the example code. First, we define "operational" use cases and explain how Apache Spark&lt;/SPAN&gt;&lt;SPAN class="c59"&gt;™&lt;/SPAN&gt;&lt;SPAN&gt;&amp;nbsp;can now handle a new set of latency-sensitive use cases. Second, we review the architectural changes in Spark that enable RTM to achieve such low latency. Lastly, we explain some&amp;nbsp;&lt;/SPAN&gt;&lt;SPAN class="c5 c21"&gt;&lt;SPAN&gt;sample code, showing how, when coupled with&amp;nbsp;&lt;A href="https://www.google.com/url?q=https://docs.databricks.com/aws/en/stateful-applications/&amp;amp;sa=D&amp;amp;source=editors&amp;amp;ust=1769790659781542&amp;amp;usg=AOvVaw2M6kZHpLSJK5ir2BPkrtqI" target="_blank" rel="noopener"&gt;transformWithState&lt;/A&gt;, ad attribution&amp;nbsp;&lt;/SPAN&gt;can be structured and plugged into a real-time stream. The sample code lets you compare the latency of RTM with that of the micro-batch execution mode.&lt;/SPAN&gt;&lt;/P&gt;
&lt;H2 class="c42"&gt;&lt;SPAN class="c39 c53 c21"&gt;Operational Workloads and Sub-Second Latency&lt;/SPAN&gt;&lt;/H2&gt;
&lt;H3 class="c12"&gt;&lt;SPAN class="c23"&gt;Apache Spark™ and Operational Use Cases&lt;/SPAN&gt;&lt;/H3&gt;
&lt;P class="c24"&gt;&lt;SPAN&gt;Apache Spark Structured Streaming has long served as the industry standard for analytical streaming workloads, excelling at data ingestion and transformation, following architectures such as the Medallion pattern. However, a significant gap historically existed when addressing truly operational workloads—those systems requiring immediate response to data to trigger downstream actions or decisions, such as real-time fraud flagging or dynamic promotional messaging. These operational systems are characterized by a stringent requirement for sub-second, and often single-digit millisecond, end-to-end latency. Achieving this level of performance historically necessitated the use of specialized streaming engines outside the standard Spark/Databricks environment. This fragmentation compelled organizations to manage disparate data architectures, resulting in increased total cost of ownership (TCO) due to duplicated infrastructure, increased deployment complexity, resource contention, and the need to maintain multiple, distinct skill sets. The introduction of Apache Spark’s real-time mode, available in Databricks Runtime 16.4 LTS and above, directly addresses this issue. RTM delivers continuous, ultra-low-latency processing, pushing p99 latencies down to the single-digit milliseconds, effectively unifying the platform for both analytical and highly demanding operational streaming.&lt;/SPAN&gt;&lt;/P&gt;
&lt;H3 class="c12"&gt;&lt;SPAN class="c23"&gt;Architectural Trade-Offs: Micro-Batch vs. Continuous Execution vs. Real-Time Mode&lt;/SPAN&gt;&lt;/H3&gt;
&lt;P class="c24"&gt;&lt;SPAN class="c5 c21"&gt;The evolution of Structured Streaming execution models has been driven by the perpetual trade-off between latency, throughput, and stability.&lt;/SPAN&gt;&lt;/P&gt;
&lt;H4 class="c49"&gt;&lt;SPAN class="c56 c58"&gt;Micro-Batch Execution (V1)&lt;/SPAN&gt;&lt;/H4&gt;
&lt;P class="c15"&gt;&lt;SPAN class="c4"&gt;Micro-batching relies on the MicroBatchExecution engine, accumulating data into small, discrete batches.&lt;/SPAN&gt;&lt;SPAN class="c35"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;SPAN class="c4"&gt;While this approach guarantees robust exactly-once processing and high throughput, its core limitation lies in the sequential nature of its execution loop. The execution phase cycles through distinct steps: fetching offsets, retrieving the batch data, planning the query, adding the batch, and finally, committing the write-ahead log (WAL). This sequential process, governed by a set trigger interval, introduces fixed scheduling latency that typically confines micro-batch performance to the seconds-latency range, making it unsuitable for true sub-second operational requirements.&lt;/SPAN&gt;&lt;/P&gt;
&lt;H4 class="c32"&gt;&lt;SPAN&gt;Legacy Continuous Execution&lt;/SPAN&gt;&lt;/H4&gt;
&lt;P class="c15"&gt;&lt;SPAN class="c5 c4"&gt;Prior to RTM, Spark offered a "Continuous" execution mode, which processed records individually for lower latency. This experimental mode and didn't support a rich feature set, such as stateful queries.&lt;/SPAN&gt;&lt;/P&gt;
&lt;H4 class="c49"&gt;&lt;SPAN class="c56 c58"&gt;Real-Time Mode (RTM)&lt;/SPAN&gt;&lt;/H4&gt;
&lt;P class="c15"&gt;&lt;SPAN class="c4"&gt;RTM represents a fundamental architectural evolution that resolves the stability-vs-speed dilemma. RTM runs batches for longer durations (the default length is 5 minutes).&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;SPAN class="c5 c4"&gt;Checkpoints, in this context, function similarly to batches.&lt;/SPAN&gt;&lt;SPAN class="c4"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;SPAN class="c4"&gt;Critically, within this long batch window, RTM processes data&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;SPAN class="c4 c62"&gt;as soon as it becomes available&lt;/SPAN&gt;&lt;SPAN class="c5 c4"&gt;&amp;nbsp;using concurrent scheduling. This design maintains the reliable, periodic checkpointing structure of a long-running batch while completely eliminating the scheduling coordination overhead of micro-batch mode. This hybridization strategy enables RTM to offer the robust fault tolerance associated with micro-batching, combined with sub-second latency performance.&lt;/SPAN&gt;&lt;/P&gt;
&lt;H2 class="c42"&gt;&lt;SPAN class="c39 c21 c53"&gt;Important: Cluster Configuration and Sizing&lt;/SPAN&gt;&lt;/H2&gt;
&lt;P class="c15"&gt;&lt;SPAN class="c5 c4"&gt;Successfully deploying RTM requires adhering to a precise set of cluster configurations and strict sizing methodologies. These parameters are not merely optimizations; they are the architectural keys that switch Spark’s behavior from micro-batching to continuous, concurrent processing.&lt;/SPAN&gt;&lt;/P&gt;
&lt;H3 class="c12"&gt;&lt;SPAN&gt;Required Spark Configurations: Analyzing the Setup&lt;/SPAN&gt;&lt;/H3&gt;
&lt;P class="c32"&gt;&lt;SPAN class="c5 c4"&gt;The Ad Attribution workload demo provides the definitive configuration template necessary for RTM activation and high-performance operation:&lt;/SPAN&gt;&lt;/P&gt;
&lt;UL class="c41 lst-kix_ik0d9knih2jt-0 start"&gt;
&lt;LI class="c32 c43 li-bullet-0"&gt;&lt;SPAN class="c28 c4"&gt;Activating RTM:&lt;/SPAN&gt;&lt;SPAN class="c4 c5"&gt;&amp;nbsp;Setting spark.databricks.streaming.realTimeMode.enabled true is the fundamental flag that enables the streaming query engine to support RTM.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;P class="c63"&gt;&lt;SPAN class="c4"&gt;In Databricks, you can set the Spark confs in the Advanced section of the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;SPAN class="c48"&gt;&lt;A class="c19" href="https://www.google.com/url?q=https://docs.databricks.com/aws/en/compute/configure&amp;amp;sa=D&amp;amp;source=editors&amp;amp;ust=1769790659789016&amp;amp;usg=AOvVaw2oNokaqaVWq3Lu_WvM7fQ7" target="_blank" rel="noopener"&gt;cluster configuration&lt;/A&gt;&lt;/SPAN&gt;&lt;SPAN class="c4"&gt;.&lt;/SPAN&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;SPAN class="c5 c21"&gt;Please note that this Spark conf is required for cluster bootup, unlike some other Spark confs that are session-scoped and changeable within runtime code.&lt;/SPAN&gt;&lt;/P&gt;
&lt;H3 class="c12"&gt;&lt;SPAN class="c23"&gt;The Task Slot Constraint: Ensuring Capacity Lockstep&lt;/SPAN&gt;&lt;/H3&gt;
&lt;P class="c15"&gt;&lt;SPAN class="c4"&gt;The most critical operational constraint in RTM sizing is the task slot requirement. To run effectively in ultra-low-latency mode, the total number of available task slots in the compute cluster&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;SPAN class="c4 c28"&gt;must be greater than or equal to the total number of tasks across all query stages&lt;/SPAN&gt;&lt;SPAN class="c4"&gt;.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class="c15"&gt;&lt;SPAN class="c4"&gt;This constraint is vital because the concurrent execution model requires every task in the query graph to have immediate access to dedicated compute resources. If the required number of slots is unavailable—for instance, if the workload generates 100 tasks but the cluster only provides 80 slots—an exception will be thrown that tells you the cluster does not have enough available slots.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class="c15"&gt;&lt;SPAN class="c4"&gt;This configuration necessitates a shift from reactive resource scaling (typical of batch ETL) to a proactive capacity reservation model. The cluster must be sized to meet the parallelism requirement of the peak load. Unlike micro-batch mode, where resources can be temporarily idled or shared, RTM tasks maintain their allocation within the long batch window, meaning precise right-sizing is crucial to prevent both resource exhaustion and unnecessary compute waste.&lt;/SPAN&gt;&lt;/P&gt;
&lt;H3 class="c12"&gt;&lt;SPAN class="c23"&gt;Tuning Data Parallelism: Optimizing Utilization&lt;/SPAN&gt;&lt;/H3&gt;
&lt;P class="c32"&gt;&lt;SPAN class="c5 c4"&gt;Since capacity is fixed and preemptively reserved, tuning the job’s parallelism is essential for optimizing resource utilization:&lt;/SPAN&gt;&lt;/P&gt;
&lt;UL class="c41 lst-kix_me6s2c2s59nk-0 start"&gt;
&lt;LI class="c32 c47 li-bullet-0"&gt;&lt;SPAN class="c28 c4"&gt;Source Parallelism (maxPartitions):&lt;/SPAN&gt;&lt;SPAN class="c5 c4"&gt;&amp;nbsp;For input systems like Kafka, which feeds the Ad Attribution pipeline, Databricks recommends setting maxPartitions, such that each task handles multiple Kafka partitions. This reduces task overhead and balances data flow across the allocated compute slots. maxPartitions is a new option in Databricks Kafka connection options that was introduced along with RTM.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI class="c1 li-bullet-0"&gt;&lt;SPAN class="c28 c4"&gt;Shuffle Parallelism:&lt;/SPAN&gt;&lt;SPAN class="c4"&gt;&amp;nbsp;For shuffle-heavy jobs, such as those involving stateful operations (transformWithState) or complex stream-stream joins, the number of shuffle partitions must be carefully chosen. Engineers should experiment to find the minimum number of shuffle partitions that prevent data backlogs. This count directly impacts the total required task slots (Source tasks + Shuffle tasks).&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI class="c1 li-bullet-0"&gt;&lt;SPAN class="c28 c4"&gt;Target Utilization:&lt;/SPAN&gt;&lt;SPAN class="c5 c4"&gt;&amp;nbsp;Tuning parallelism and overall cluster size often targets a goal utilization level (e.g., 50%). This buffer capacity ensures that the system can handle bursts of incoming data without violating the low-latency objective.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;P class="c14"&gt;&lt;SPAN class="c5 c4"&gt;The physical capacity (task slots) and the logical parallelism (source and shuffle partitions) are inherently linked. If an increase in logical parallelism occurs without a corresponding increase in physical capacity, the RTM job cannot meet its concurrency requirement, resulting in performance degradation.&lt;/SPAN&gt;&lt;/P&gt;
&lt;H3 class="c12"&gt;&lt;SPAN class="c23"&gt;Production Best Practices&lt;/SPAN&gt;&lt;/H3&gt;
&lt;P class="c32"&gt;&lt;SPAN class="c5 c4"&gt;For scheduling RTM workloads, several operational best practices must be observed:&lt;/SPAN&gt;&lt;/P&gt;
&lt;OL class="c41 lst-kix_gl8vof19ysde-0 start" start="1"&gt;
&lt;LI class="c32 c43 li-bullet-0"&gt;&lt;SPAN class="c28 c4"&gt;Compute Type:&lt;/SPAN&gt;&lt;SPAN class="c4"&gt;&amp;nbsp;Always schedule RTM streams as jobs using dedicated&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;SPAN class="c28 c4"&gt;Jobs Compute&lt;/SPAN&gt;&lt;SPAN class="c5 c4"&gt;. All-purpose compute clusters are not recommended for production streaming workloads.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI class="c30 li-bullet-0"&gt;&lt;SPAN class="c28 c4"&gt;Scheduling Mode:&lt;/SPAN&gt;&lt;SPAN class="c5 c4"&gt;&amp;nbsp;Jobs should be scheduled using the conceptual "Continuous mode," which configures the job to run indefinitely and automatically restart upon failure.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI class="c30 li-bullet-0"&gt;&lt;SPAN class="c28 c4"&gt;Autoscaling Prohibition:&lt;/SPAN&gt;&lt;SPAN class="c4"&gt;&amp;nbsp;Due to the rigid task slot constraint required by RTM,&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;SPAN class="c28 c4"&gt;autoscaling must be disabled&lt;/SPAN&gt;&lt;SPAN class="c4"&gt;.&lt;/SPAN&gt;&lt;SPAN class="c35"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;SPAN class="c5 c4"&gt;Scaling delays would destroy the low-latency guarantees.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/OL&gt;
&lt;H2 class="c42"&gt;&lt;SPAN class="c39 c53 c21"&gt;Advanced Stateful Logic: Implementing Ad Attribution with transformWithState&lt;/SPAN&gt;&lt;/H2&gt;
&lt;P class="c15"&gt;&lt;SPAN class="c5 c4"&gt;RTM provides a high-performance execution environment, but complex operational problems, such as ad attribution, require sophisticated state management capabilities, which the transformWithState operator provides.&lt;/SPAN&gt;&lt;/P&gt;
&lt;H3 class="c12"&gt;&lt;SPAN class="c23"&gt;The Attribution Challenge: Correlating Disparate Real-Time Events&lt;/SPAN&gt;&lt;/H3&gt;
&lt;P class="c15"&gt;&lt;SPAN class="c5 c4"&gt;The Ad Attribution use case involves correlating two distinct, high-volume event streams: Ad Requests and Ad Impressions. These events arrive asynchronously, often out of order, and need to be stitched together across different time windows. Think of the "Request" event as an intention to show an advertisement at a given spot in a video or movie, and the "Impression" or ("ack") as the event that signifies that the ad is being shown to the viewer. Why is the "ack" needed? Primarily for these reasons:&lt;/SPAN&gt;&lt;/P&gt;
&lt;OL class="c41 lst-kix_otsixhi2yo8s-0 start" start="1"&gt;
&lt;LI class="c15 c45 li-bullet-0"&gt;&lt;SPAN class="c5 c4"&gt;The ack payload may include additional intelligence (e.g., did the user click on or interact with the ad?)&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI class="c15 c45 li-bullet-0"&gt;&lt;SPAN class="c5 c4"&gt;Ad sales are often conditioned on an ad being shown to a specified number of viewers. Once that commitment has been met, the media provider has more ad slots to sell!&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/OL&gt;
&lt;P class="c15"&gt;&lt;SPAN class="c4"&gt;For simplicity's sake, we are focusing on these two event types in the sample code. In the real world, there would be additional payload types to help with downstream attribution, such as associating a purchase with an ad impression. For our example, we focus on measuring impressions, as they are a key driver for managing ad inventory. Regardless, the technological challenge is maintaining the state of an active ad request, matching subsequent impressions and callbacks to that request based on complex logic (e.g., matching a request to an impression within 5 minutes, and then the impression to a click within 15 minutes), and ensuring accurate state expiration.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class="c15"&gt;&lt;SPAN class="c5 c4"&gt;Standard Structured Streaming operators, such as built-in stream-stream joins, can handle some correlation, but they often lack the flexibility required for arbitrary, time-driven state cleanup and complex object modeling inherent in sophisticated event correlation (CEC).&lt;/SPAN&gt;&lt;/P&gt;
&lt;H3 class="c12"&gt;&lt;SPAN class="c23"&gt;Why transformWithState is Mandatory for Operational Correlation&lt;/SPAN&gt;&lt;/H3&gt;
&lt;P class="c32"&gt;&lt;SPAN class="c5 c4"&gt;The transformWithState operator was introduced to enhance Structured Streaming with flexible state management and event-driven programming. It enables developers to implement arbitrary logic that goes beyond the capabilities of built-in operators. For operational workloads like Ad Attribution, several features of transformWithState are mandatory:&lt;/SPAN&gt;&lt;/P&gt;
&lt;OL class="c41 lst-kix_l780j5j7apw3-0 start" start="1"&gt;
&lt;LI class="c32 c43 li-bullet-0"&gt;&lt;SPAN class="c28 c4"&gt;Object-Oriented State Management:&lt;/SPAN&gt;&lt;SPAN class="c4"&gt;&amp;nbsp;The operator allows the custom StatefulProcessor implementation to define and store complex, custom state objects (such as tracking the full history of a Request-Impression match object) rather than relying solely on simple key-value mappings.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI class="c30 li-bullet-0"&gt;&lt;SPAN class="c28 c4"&gt;Composite Data Types:&lt;/SPAN&gt;&lt;SPAN class="c4"&gt;&amp;nbsp;Complex correlations often require composite keys (e.g., combining a User Session ID with a Campaign Identifier) to ensure that all related events are processed by the same state instance. transformWithState handles these composite types efficiently.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI class="c30 li-bullet-0"&gt;&lt;SPAN class="c28 c4"&gt;Timer-Driven Logic:&lt;/SPAN&gt;&lt;SPAN class="c4"&gt;&amp;nbsp;This is the most crucial feature for attribution. Timers allow the system to register a future action, such as cleaning up state exactly 15 minutes after an impression fires, even if no further event arrives. This timer-driven logic ensures timely state garbage collection, which is essential for maintaining memory bounds and guaranteeing accurate attribution boundary enforcement in a high-volume pipeline.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/OL&gt;
&lt;P class="c14"&gt;&lt;SPAN class="c5 c4"&gt;The application logic and the execution performance (RocksDB health) are thus intrinsically linked. If state is not aggressively expired, the total state size grows, increasing I/O demands during checkpointing and taxing the bounded memory manager, thereby compromising the RTM latency goals.&lt;/SPAN&gt;&lt;/P&gt;
&lt;H3 class="c12"&gt;&lt;SPAN class="c23"&gt;StatefulProcessor Implementation Deep Dive&lt;/SPAN&gt;&lt;/H3&gt;
&lt;P class="c15"&gt;&lt;SPAN class="c4"&gt;The ad attribution architecture dictates a conceptual structure for the custom StatefulProcessor implementation. The input stream must first be grouped by a composite key (e.g., UserID and CampaignID) to guarantee co-location of related events at a single state instance. In the adtechdata_stream_processor_scala notebook, you will find this code that first groups the DataFrame by the transaction_id (think of this as an identifier for a viewing session) and then applies the transformWithState operator (LogRecordProcessor, a class that is defined in the same notebook):&lt;/SPAN&gt;&lt;/P&gt;
&lt;LI-CODE lang="java"&gt;// Group by transaction_id and apply stateful processing 
// using LogRecordProcessor
val transactionDF = sourceStream
  .as[InputRow]
  .groupByKey(_.transaction_id)
  .transformWithState(
    new LogRecordProcessor(timeout_duration = stateTimeoutInmin),
    TimeMode.ProcessingTime,
    OutputMode.Append
  )
  .toDF()&lt;/LI-CODE&gt;
&lt;P class="c15"&gt;&lt;SPAN class="c4"&gt;The state management involves defining a complex object that tracks the progress of the attribution attempt. LogRecordProcessor is an instance of StatefulProcessor, which is required for transformWithState. When we look at the signature for LogRecordProcessor, a number of schemas are involved:&lt;/SPAN&gt;&lt;/P&gt;
&lt;LI-CODE lang="java"&gt;class LogRecordProcessor(timeout_duration: Long) extends StatefulProcessor[String, InputRow, LogRecord] {&lt;/LI-CODE&gt;
&lt;OL class="c41 lst-kix_6w69tmh79ob-0 start" start="1"&gt;
&lt;LI class="c8 li-bullet-0"&gt;&lt;SPAN class="c5 c21"&gt;String: The grouping key is a String. This will be the viewer's session identifier (in the source DataFrame, transaction_id)&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI class="c8 li-bullet-0"&gt;&lt;SPAN class="c5 c21"&gt;InputRow: This case class (also defined in the notebook) represents the schema of arriving data.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI class="c8 li-bullet-0"&gt;&lt;SPAN class="c5 c21"&gt;LogRecord: The schema for outgoing records (emitted from the streaming query).&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/OL&gt;
&lt;H4 class="c49"&gt;&lt;SPAN class="c56 c58"&gt;Event Handling Logic (Conceptual Flow)&lt;/SPAN&gt;&lt;/H4&gt;
&lt;OL class="c41 lst-kix_7k2u59dzgcpg-0 start" start="1"&gt;
&lt;LI class="c32 c43 li-bullet-0"&gt;&lt;SPAN class="c28 c4"&gt;Ad Request Arrival:&lt;/SPAN&gt;&lt;SPAN class="c5 c4"&gt;&amp;nbsp;If no state exists for the key, the system initializes the state object, recording the request data and registering an Expiration Timer corresponding to the overall attribution window. In the sample code in the notebook, a timer is registered as rows for a grouping key arrive in the handleInputRows method. This helps keep the state data bounded over time.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI class="c30 li-bullet-0"&gt;&lt;SPAN class="c28 c4"&gt;Ad Impression ("ack") Arrival:&lt;/SPAN&gt;&lt;SPAN class="c5 c4"&gt;&amp;nbsp;The state is updated, the impression is correlated with the tracked request, and the system may update or clean up state or emit output records.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/OL&gt;
&lt;P class="c14"&gt;&lt;SPAN class="c5 c4"&gt;The combination of RTM’s concurrent, low-latency execution and transformWithState’s precise, custom state control allows Spark to execute these complex, real-time correlation patterns accurately.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class="c14"&gt;&lt;SPAN class="c4"&gt;The stateful logic is encapsulated in the handleInputRows method of the LogRecordProcessor class, which extends the StatefulProcessor class, making it suitable for use with the grouped DataFrame's transformWithState method. Look in the adtechdata_stream_processor_scala notebook for this method:&lt;/SPAN&gt;&lt;/P&gt;
&lt;LI-CODE lang="java"&gt; override def handleInputRows(
     key: String,
     inputRows: Iterator[InputRow],
     timerValues: TimerValues
 ): Iterator[LogRecord] = {
....&lt;/LI-CODE&gt;
&lt;P class="c27"&gt;&lt;SPAN class="c5 c21"&gt;It is inside this method where complex, stateful logic resides. Two state variables (defined in the init method) are used:&lt;/SPAN&gt;&lt;/P&gt;
&lt;OL class="c41 lst-kix_wpy1z8qtgfpe-0 start" start="1"&gt;
&lt;LI class="c8 li-bullet-0"&gt;&lt;SPAN class="c5 c21"&gt;_ackLogRecordListState: "ack" records are tracked in a list. It is possible for the "request" message to arrive late. By keeping ack records in a "list state" structure, once a matching "request" message arrives, the matching elements can finally be emitted. And, remember, if the "request" never arrives, the timer that was set for the grouping key will ensure that the system does not grow state data in an unbounded manner.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI class="c8 li-bullet-0"&gt;&lt;SPAN class="c5 c21"&gt;_requestLogRecordState: this "value state" state variable tracks the "request" message and uses it when the "ack" messages arrive to finally emit the matched records.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/OL&gt;
&lt;H2 class="c42"&gt;&lt;SPAN class="c39 c53 c21"&gt;High-Performance State Management Internals&lt;/SPAN&gt;&lt;/H2&gt;
&lt;P class="c15"&gt;&lt;SPAN class="c5 c4"&gt;RTM's performance is dependent on the efficiency and stability of the underlying state store, particularly when running sophisticated stateful operators like transformWithState.&lt;/SPAN&gt;&lt;/P&gt;
&lt;H3 class="c12"&gt;&lt;SPAN class="c23"&gt;RocksDB Integration and Bounded Memory&lt;/SPAN&gt;&lt;/H3&gt;
&lt;P class="c15"&gt;&lt;SPAN class="c5 c4"&gt;The stateful Structured Streaming queries on Databricks rely upon the high-performance, embedded key-value store, RocksDB. RocksDB is optimized for flash storage and provides the I/O capacity required for high-volume, low-latency processing.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class="c15"&gt;&lt;SPAN class="c5 c4"&gt;A key challenge in operational streaming is preventing unbounded memory growth. RTM pipelines handle immense state pressure, and if not managed correctly, state stores can consume all worker memory, leading to instability. To prevent this, RocksDB functionality is leveraged to enforce bounded memory usage.&lt;/SPAN&gt;&lt;/P&gt;
&lt;H2 id="h.ent35rkylt42" class="c42"&gt;&lt;SPAN class="c39 c53 c21"&gt;Running the Demo: Comparing Microbatch to RTM&lt;/SPAN&gt;&lt;/H2&gt;
&lt;P class="c15"&gt;&lt;SPAN class="c4"&gt;The&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;SPAN class="c28 c4"&gt;run_adtech_rtm_demo&lt;/SPAN&gt;&lt;SPAN class="c5 c4"&gt;&amp;nbsp;notebook contains everything you need to run the demo, which compares a streaming ad attribution stateful streaming query with RTM mode against the traditional micro-batch mode. However, you will need to change some configurations to use Kafka (or another supported streaming source/sink). The code includes detailed comments, and we'll cover some highlights here.&lt;/SPAN&gt;&lt;/P&gt;
&lt;H4 id="h.r06u343v4x0g" class="c49"&gt;&lt;SPAN class="c56 c58"&gt;Test Harness: AdTechStreamProcessor&lt;/SPAN&gt;&lt;/H4&gt;
&lt;LI-CODE lang="java"&gt;import com.demo.data.{AdTechDataHelper}
import com.demo.data.{AdTechStreamProcessor}


// Get the active Spark session
implicit val spark = org.apache.spark.sql.SparkSession.active


// Initialize helper and processor classes for adtech data
val dataHelper = new AdTechDataHelper(kafka_options, payload, schema)
val streamProcessor = new AdTechStreamProcessor(kafka_options, schema)&lt;/LI-CODE&gt;
&lt;P class="c27"&gt;&lt;SPAN class="c5 c21"&gt;AdTechDataHelper is defined in the adtechdata_producer_scala notebook and manages the generation of fake data for the stream.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class="c27"&gt;&lt;SPAN class="c5 c21"&gt;AdTechStreamProcessor is defined in the adtechdata_stream_processor_scala notebook. There you will find the class used for the stateful transformWithState operator (LogRecordProcessor), along with the class (AdTechStreamProcessor) that helps with the benchmarking by establishing the streaming queries. What is striking is that, from a code perspective, using RTM is very simple. AdTechStreamProcessor's processStream method includes this code snippet:&lt;/SPAN&gt;&lt;/P&gt;
&lt;LI-CODE lang="java"&gt;   // Write the processed records to the output Kafka topic
    val query = outputDf.select($"transaction_id".cast("string").alias("key"),
      to_json($"value").alias("value"))
      .writeStream
      .format("kafka")
      .options(kafkaConfig)
      .option("topic", outputTopicName)
      .option("checkpointLocation", checkpointLocation)
      .trigger(
        if (mode == "trigger") Trigger.ProcessingTime("0 minutes")
        else Trigger.RealTime
      )
      .outputMode("update")
      .start()&lt;/LI-CODE&gt;
&lt;P class="c32"&gt;&lt;SPAN class="c5 c21"&gt;The ".trigger" is the only code-related place where a developer needs to reason about using RTM or micro-batch mode. For the benchmark, the processStream method starts the queries and adds some timestamps used for measuring latency between the inbound and outbound Kafka streams.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class="c32"&gt;&lt;SPAN class="c5 c21"&gt;To recap, to use the demo code:&lt;/SPAN&gt;&lt;/P&gt;
&lt;OL class="c41 lst-kix_w8vg5i7ha9y-0 start" start="1"&gt;
&lt;LI class="c32 c45 li-bullet-0"&gt;&lt;SPAN class="c5 c21"&gt;Start with the README notebook.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI class="c32 c45 li-bullet-0"&gt;&lt;SPAN class="c5 c21"&gt;Adjust Kafka connection details in the util/common notebook.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI class="c32 c45 li-bullet-0"&gt;&lt;SPAN class="c5 c21"&gt;Use the run_adtech_rtm_demo notebook to run the benchmark code.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI class="c32 c45 li-bullet-0"&gt;&lt;SPAN class="c5 c21"&gt;Explore the other notebooks in the util folder to explore the code responsible for generating the fake data stream (adtechdata_producer_scala) and for providing the stateful streaming query(adtechdata_stream_processor_scala).&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/OL&gt;
&lt;H3 id="h.ukmvw63llx6" class="c54"&gt;&lt;SPAN class="c23"&gt;Benchmark Results&lt;/SPAN&gt;&lt;/H3&gt;
&lt;P class="c32"&gt;&lt;SPAN class="c5 c21"&gt;The results of the benchmark will display in the run_adtech_rtm_demo notebook, like this:&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="craig_lukasik_0-1769790863056.png" style="width: 863px;"&gt;&lt;img src="https://community.databricks.com/t5/image/serverpage/image-id/23489i9696FE158CB1739F/image-dimensions/863x427?v=v2" width="863" height="427" role="button" title="craig_lukasik_0-1769790863056.png" alt="craig_lukasik_0-1769790863056.png" /&gt;&lt;/span&gt;&lt;/P&gt;
&lt;P class="c32"&gt;&lt;SPAN&gt;The benchmark results, shown above, are from a run using DBR 18.0&lt;/SPAN&gt;&lt;SPAN class="c5 c21"&gt;&amp;nbsp;with three worker nodes (Standard_F8):&lt;/SPAN&gt;&lt;/P&gt;
&lt;LI-CODE lang="markup"&gt;{
    "cluster_name": "RTM_testing (compute optimized)",
    "spark_version": "18.0.x-scala2.13",
    "spark_conf": {
        "spark.databricks.streaming.realTimeMode.enabled": "true"
    },
    "azure_attributes": {
        "availability": "ON_DEMAND_AZURE"
    },
    "node_type_id": "Standard_F8",
    "driver_node_type_id": "Standard_E8_v3",
    "num_workers": 3,
}&lt;/LI-CODE&gt;
&lt;H2 class="c42"&gt;&lt;SPAN class="c39 c53 c21"&gt;Conclusions&lt;/SPAN&gt;&lt;/H2&gt;
&lt;P class="c15"&gt;&lt;SPAN class="c5 c4"&gt;Apache Spark’s real-time mode represents a significant architectural evolution, moving Structured Streaming from a platform primarily focused on analytical throughput to one capable of supporting low-latency operational decisioning. This capability is achieved through the fundamental shift to concurrent stage execution, facilitated by the ConcurrentStageDAGScheduler and the specialized MultiShuffleManager for in-memory data transfer.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class="c32"&gt;&lt;SPAN class="c5 c4"&gt;For technical audiences and developers building these mission-critical systems, successful deployments with stable, predictable latency should consider the following:&lt;/SPAN&gt;&lt;/P&gt;
&lt;OL class="c41 lst-kix_fa17jzgqh6n-0 start" start="1"&gt;
&lt;LI class="c32 c43 li-bullet-0"&gt;&lt;SPAN class="c28 c4"&gt;Configuration:&lt;/SPAN&gt;&lt;SPAN class="c4"&gt;&amp;nbsp;Activating RTM requires a simple cluster-level configuration.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI class="c30 li-bullet-0"&gt;&lt;SPAN class="c28 c4"&gt;Capacity Planning:&lt;/SPAN&gt;&lt;SPAN class="c5 c4"&gt;&amp;nbsp;The concurrency constraint—that total task slots must equal or exceed total tasks—prohibits autoscaling and demands proactive capacity reservation sized for peak parallelism. This requires continuous monitoring of input volumes to prevent violation of the sizing prerequisite.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI class="c30 li-bullet-0"&gt;&lt;SPAN class="c28 c4"&gt;State Management Optimization:&lt;/SPAN&gt;&lt;SPAN class="c5 c4"&gt;&amp;nbsp;Implementing complex correlation logic, such as Ad Attribution, relies on transformWithState for custom timer-driven logic and timers. This application-level logic must be paired with infrastructure optimization, particularly the mandatory activation of RocksDB changelog checkpointing, to stabilize the p99 latency and prevent periodic I/O spikes during checkpoint events.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/OL&gt;
&lt;P class="c40"&gt;&lt;SPAN class="c4"&gt;By enabling RTM and adopting&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;SPAN class="c4"&gt;the recommended best practices, RTM enables organizations to consolidate their data platforms, eliminating the need for fragmented, specialized streaming engines, while consistently delivering low-latency performance for the most demanding operational workloads.&lt;/SPAN&gt;&lt;/P&gt;</description>
    <pubDate>Wed, 11 Feb 2026 00:36:42 GMT</pubDate>
    <dc:creator>craig_lukasik</dc:creator>
    <dc:date>2026-02-11T00:36:42Z</dc:date>
    <item>
      <title>Apache Spark's Real-time Mode Use Case Focus: Ad Attribution</title>
      <link>https://community.databricks.com/t5/technical-blog/apache-spark-s-real-time-mode-use-case-focus-ad-attribution/ba-p/146141</link>
      <description>&lt;P&gt;&lt;SPAN class="appsElementsGenerativeaiAstAnimated" data-ast-node-id="1"&gt;Ready to go under the hood of Apache Spark™ Real-Time Mode? Dive deep into the architecture, mechanics, and cluster sizing requirements for ultra-low-latency operational workloads. See how&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;CODE class="appsElementsGenerativeaiAstInlineCode"&gt;transformWithState&lt;/CODE&gt;&lt;SPAN class="appsElementsGenerativeaiAstAnimated" data-ast-node-id="3"&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;powers complex, stateful ad attribution with sub-second performance. Read the deep dive—code and benchmarks included!&lt;/SPAN&gt;&lt;/P&gt;</description>
      <pubDate>Wed, 11 Feb 2026 00:36:42 GMT</pubDate>
      <guid>https://community.databricks.com/t5/technical-blog/apache-spark-s-real-time-mode-use-case-focus-ad-attribution/ba-p/146141</guid>
      <dc:creator>craig_lukasik</dc:creator>
      <dc:date>2026-02-11T00:36:42Z</dc:date>
    </item>
  </channel>
</rss>

