<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Chirag Bhatia</title>
    <description>The latest articles on DEV Community by Chirag Bhatia (@chiragbhatia94).</description>
    <link>https://dev.to/chiragbhatia94</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1090541%2F943f1e79-d590-4de7-ad7d-b4ec74738b18.jpeg</url>
      <title>DEV Community: Chirag Bhatia</title>
      <link>https://dev.to/chiragbhatia94</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/chiragbhatia94"/>
    <language>en</language>
    <item>
      <title>Your Spark job dies before Prometheus can even scrape it. How do you monitor dead containers without OOM crashing the Pushgateway?

Read: https://dev.to/thestaffblueprint/the-green-tick-fallacy-why-batch-observability-is-fundamentally-different-398c</title>
      <dc:creator>Chirag Bhatia</dc:creator>
      <pubDate>Mon, 18 May 2026 15:32:50 +0000</pubDate>
      <link>https://dev.to/chiragbhatia94/your-spark-job-dies-before-prometheus-can-even-scrape-it-how-do-you-monitor-dead-containers-2oei</link>
      <guid>https://dev.to/chiragbhatia94/your-spark-job-dies-before-prometheus-can-even-scrape-it-how-do-you-monitor-dead-containers-2oei</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/thestaffblueprint/the-green-tick-fallacy-why-batch-observability-is-fundamentally-different-398c" class="crayons-story__hidden-navigation-link"&gt;The Green Tick Fallacy — Why Batch Observability is Fundamentally Different&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;
          &lt;a class="crayons-logo crayons-logo--l" href="/thestaffblueprint"&gt;
            &lt;img alt="The Staff Blueprint logo" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F13373%2Fb3c1de4d-63dd-489d-a236-b913b00e7265.png" class="crayons-logo__image" width="192" height="192"&gt;
          &lt;/a&gt;

          &lt;a href="/chiragbhatia94" class="crayons-avatar  crayons-avatar--s absolute -right-2 -bottom-2 border-solid border-2 border-base-inverted  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1090541%2F943f1e79-d590-4de7-ad7d-b4ec74738b18.jpeg" alt="chiragbhatia94 profile" class="crayons-avatar__image" width="460" height="460"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/chiragbhatia94" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Chirag Bhatia
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Chirag Bhatia
                
              
              &lt;div id="story-author-preview-content-3686661" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/chiragbhatia94" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1090541%2F943f1e79-d590-4de7-ad7d-b4ec74738b18.jpeg" class="crayons-avatar__image" alt="" width="460" height="460"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Chirag Bhatia&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

            &lt;span&gt;
              &lt;span class="crayons-story__tertiary fw-normal"&gt; for &lt;/span&gt;&lt;a href="/thestaffblueprint" class="crayons-story__secondary fw-medium"&gt;The Staff Blueprint&lt;/a&gt;
            &lt;/span&gt;
          &lt;/div&gt;
          &lt;a href="https://dev.to/thestaffblueprint/the-green-tick-fallacy-why-batch-observability-is-fundamentally-different-398c" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;May 17&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/thestaffblueprint/the-green-tick-fallacy-why-batch-observability-is-fundamentally-different-398c" id="article-link-3686661"&gt;
          The Green Tick Fallacy — Why Batch Observability is Fundamentally Different
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/observability"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;observability&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/airflow"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;airflow&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/prometheus"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;prometheus&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/grafana"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;grafana&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
            &lt;a href="https://dev.to/thestaffblueprint/the-green-tick-fallacy-why-batch-observability-is-fundamentally-different-398c#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              &lt;span class="hidden s:inline"&gt;Add Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            4 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>dataengineering</category>
      <category>devops</category>
      <category>monitoring</category>
      <category>sre</category>
    </item>
    <item>
      <title>The Architecture — Prometheus, Grafana, and StatsD for Batch Workloads</title>
      <dc:creator>Chirag Bhatia</dc:creator>
      <pubDate>Sun, 17 May 2026 12:19:19 +0000</pubDate>
      <link>https://dev.to/thestaffblueprint/the-architecture-prometheus-grafana-and-statsd-for-batch-workloads-4opc</link>
      <guid>https://dev.to/thestaffblueprint/the-architecture-prometheus-grafana-and-statsd-for-batch-workloads-4opc</guid>
      <description>&lt;p&gt;&lt;em&gt;(This is Part 2 of the Batch Workloads Observability series. Read&lt;/em&gt; &lt;em&gt;&lt;a href="https://thestaffblueprint.substack.com/airflow-batch-observability-green-tick-fallacy" rel="noopener noreferrer"&gt;Part 1: The Green Tick Fallacy&lt;/a&gt;&lt;/em&gt; &lt;em&gt;first for context.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsubstackcdn.com%2Fimage%2Ffetch%2F%24s_%21BCFk%21%2Cf_auto%2Cq_auto%3Agood%2Cfl_progressive%3Asteep%2Fhttps%253A%252F%252Fsubstack-post-media.s3.amazonaws.com%252Fpublic%252Fimages%252F28557943-8a1e-46fc-9db7-a8e315caeb13_2746x1298.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsubstackcdn.com%2Fimage%2Ffetch%2F%24s_%21BCFk%21%2Cf_auto%2Cq_auto%3Agood%2Cfl_progressive%3Asteep%2Fhttps%253A%252F%252Fsubstack-post-media.s3.amazonaws.com%252Fpublic%252Fimages%252F28557943-8a1e-46fc-9db7-a8e315caeb13_2746x1298.png" alt="Architecture for batch workloads observability" width="800" height="378"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Default Choice: Prometheus
&lt;/h2&gt;

&lt;p&gt;When engineering teams realise they need to extract internal application metrics from their batch pipelines (to escape the “Green Tick Fallacy”), they inevitably reach for Prometheus.&lt;/p&gt;

&lt;p&gt;It makes perfect sense. Prometheus is the industry standard. Grafana integrates beautifully with it. PromQL is incredibly powerful. Your infrastructure team already has it running. &lt;em&gt;(If you want a detailed breakdown of each component and how the standard Prometheus architecture works under the hood, check out&lt;/em&gt; &lt;em&gt;&lt;a href="https://devopscube.com/prometheus-architecture/" rel="noopener noreferrer"&gt;this comprehensive guide by DevOpsCube&lt;/a&gt;.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;But there is a fundamental mismatch: &lt;strong&gt;Prometheus is a pull-based system.&lt;/strong&gt; It expects to scrape a &lt;code&gt;/metrics&lt;/code&gt; HTTP endpoint exposed by a continuously running service.&lt;/p&gt;

&lt;p&gt;Batch jobs, however, are ephemeral. A Spark job might spin up, process terabytes of data in 45 seconds, and vanish. By the time the Prometheus scraper comes around (typically every 15 to 30 seconds), the container is already dead. You cannot scrape batch jobs—you must &lt;strong&gt;push&lt;/strong&gt; telemetry out before the container dies.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Push Problem &amp;amp; The Proxy
&lt;/h2&gt;

&lt;p&gt;To solve the push-vs-pull mismatch, the standard architecture introduces the &lt;strong&gt;Prometheus Pushgateway&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The idea is simple: it acts as a middleman. Your ephemeral batch job pushes its metrics to the Pushgateway via HTTP just before exiting. The Pushgateway caches those metrics in memory. Prometheus then continuously scrapes the Pushgateway at its own pace.&lt;/p&gt;

&lt;p&gt;Problem solved, right? Not quite. This is where most batch observability architectures begin to rot. To understand why, we have to talk about dimensions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding Dimensions and Cardinality
&lt;/h2&gt;

&lt;p&gt;In Prometheus, data isn’t just stored as a flat list of numbers. It’s stored as &lt;strong&gt;Time Series&lt;/strong&gt; , defined by a metric name and a set of key-value pairs called labels (or dimensions).&lt;/p&gt;

&lt;p&gt;For example, a task failure metric might look like this: &lt;code&gt;airflow_task_status{dag_id="daily_etl", task_id="load_users", status="failed"}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Every unique combination of labels creates a brand new time series in the database. The total number of unique time series is called &lt;strong&gt;cardinality&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Low-Cardinality Labels:&lt;/strong&gt; &lt;code&gt;dag_id&lt;/code&gt; (maybe 50 total), &lt;code&gt;task_id&lt;/code&gt; (maybe 200 total), &lt;code&gt;status&lt;/code&gt; (success, failed, running). These are bounded. They are safe.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;High-Cardinality Labels:&lt;/strong&gt; &lt;code&gt;run_id&lt;/code&gt; (a unique UUID for every single execution), &lt;code&gt;user_id&lt;/code&gt;, or &lt;code&gt;error_message&lt;/code&gt;. These are unbounded. They grow infinitely.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When monitoring a batch workload, you naturally want to know: &lt;em&gt;Which DAG? Which task? Which specific run? How many rows did it process?&lt;/em&gt; So, engineers intuitively add a &lt;code&gt;run_id&lt;/code&gt; label to their Pushgateway metrics.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Dumb Proxy and The Race Condition
&lt;/h2&gt;

&lt;p&gt;If you &lt;em&gt;don’t&lt;/em&gt; use a unique label like &lt;code&gt;run_id&lt;/code&gt;, you hit an immediate wall.&lt;/p&gt;

&lt;p&gt;The Pushgateway is essentially a dumb proxy. It doesn’t aggregate metrics, it doesn’t add numbers together, and it doesn’t deduplicate. It simply acts as a key-value store. The “key” is the combination of your labels (the grouping key).&lt;/p&gt;

&lt;p&gt;If ten parallel Airflow tasks fail at the exact same millisecond, and they all push &lt;code&gt;failure_count = 1&lt;/code&gt; to the Pushgateway without a &lt;code&gt;run_id&lt;/code&gt;, they all write to the exact same key. The Pushgateway just overwrites the value ten times. Prometheus scrapes it once and sees: &lt;code&gt;failure_count = 1&lt;/code&gt;. You just lost nine failure records.&lt;/p&gt;

&lt;p&gt;This is a race condition. Parallel tasks overwrite each other because they share the same grouping key.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Fatal Fix: Fighting Prometheus
&lt;/h2&gt;

&lt;p&gt;The “obvious” fix is to add &lt;code&gt;run_id&lt;/code&gt; to the grouping key. Because every run has a unique ID, every task gets its own isolated slot in the Pushgateway. The race condition is solved!&lt;/p&gt;

&lt;p&gt;But you’ve just created a ticking time bomb.&lt;/p&gt;

&lt;p&gt;By adding &lt;code&gt;run_id&lt;/code&gt;, you have introduced unbounded cardinality. Every single task execution creates a brand new time series. Furthermore, the Pushgateway has &lt;strong&gt;no native TTL&lt;/strong&gt; (Time To Live). It holds every metric group in memory forever.&lt;/p&gt;

&lt;p&gt;Within a few days of high-volume DAG runs, the Pushgateway’s memory balloons until the container OOM (Out of Memory) crashes. When Prometheus tries to scrape it, the massive payload crashes the scraper.&lt;/p&gt;

&lt;p&gt;This happens because teams misunderstand what Prometheus is. &lt;strong&gt;Prometheus is designed for low-cardinality, continuous metrics.&lt;/strong&gt; It is not designed to store high-cardinality, event-based data. When you push per-run execution data into Prometheus, you are treating a time-series database like an event log.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Realization: Two Different Problems
&lt;/h2&gt;

&lt;p&gt;The breakthrough happens when you realize that “batch observability” isn’t a single problem. You are actually trying to answer two completely different questions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Operational Metrics:&lt;/strong&gt; &lt;em&gt;“Is the system healthy right now? What is the overall failure rate?”&lt;/em&gt; (Needs aggregation, low-cardinality).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;State Snapshots:&lt;/strong&gt; &lt;em&gt;“What is the current status of this specific task run?”&lt;/em&gt; (Needs per-run isolation, high-cardinality).&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Trying to force both of these through the Pushgateway is the core architectural mistake. We need two different data paths.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Architecture Stack
&lt;/h2&gt;

&lt;p&gt;Here is the robust, Staff-level architecture we use to solve this cleanly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Prometheus:&lt;/strong&gt; Time-series database — stores aggregated operational metrics and temporary task state snapshots.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Grafana:&lt;/strong&gt; Dashboarding and alerting — visualizes metrics from Prometheus.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;StatsD Exporter:&lt;/strong&gt;  &lt;strong&gt;The missing link&lt;/strong&gt; — catches high-frequency UDP bursts, aggregates them, and safely exposes them to Prometheus.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Pushgateway:&lt;/strong&gt; Push-based ingestion — restricted &lt;em&gt;only&lt;/em&gt; to temporary state snapshots.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Sweeper DAG:&lt;/strong&gt; Automated cleanup — deletes stale Pushgateway metric groups to prevent OOM crashes.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Path 1: Operational Metrics via StatsD
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Airflow Task → UDP packet → StatsD Exporter → Prometheus scrape → Grafana

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;StatsD solves the race condition at the network layer. It listens over UDP. When 10 tasks fire &lt;code&gt;airflow.task.failed:1|c&lt;/code&gt; at the exact same millisecond, StatsD catches all 10 packets in memory, adds them up, and flushes a single aggregated metric (&lt;code&gt;failure_count = 10&lt;/code&gt;) to Prometheus.&lt;/p&gt;

&lt;p&gt;No unique identifiers (&lt;code&gt;run_id&lt;/code&gt;) are needed. No race conditions. Zero cardinality explosion. Best of all, Airflow has StatsD support built into its core—you just have to enable it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Path 2: Task State Snapshots via Pushgateway
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Airflow Plugin → HTTP POST → Pushgateway → Prometheus scrape → Grafana

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;StatsD is perfect for counts, but it can’t answer: &lt;em&gt;“What is the current state of task X in run Y?”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;For per-task state visibility at a small-to-medium scale, we &lt;em&gt;do&lt;/em&gt; use the Pushgateway, and we &lt;em&gt;do&lt;/em&gt; use &lt;code&gt;run_id&lt;/code&gt; as a grouping key. However, we apply strict lifecycle management. We deploy an Airflow &lt;strong&gt;Sweeper DAG&lt;/strong&gt; that runs on a schedule, queries the Pushgateway REST API, and deletes metric groups older than a configured threshold.&lt;/p&gt;

&lt;p&gt;This prevents OOM crashes while giving us exactly enough runway to monitor active DAGs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Architectural Boundary:&lt;/strong&gt; It is critical to understand that this Pushgateway approach is a stepping stone. At a truly massive scale (tens of thousands of tasks per hour), Prometheus ingestion will still choke on the cardinality, and the Sweeper DAG itself becomes a bottleneck. For large-scale environments, you must abandon Prometheus for per-run tracking entirely and push task events to a dedicated event log system (like Elasticsearch, Grafana Loki, or ClickHouse).&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Prometheus is not an event log:&lt;/strong&gt; It is built for low-cardinality, continuous metrics. Do not use it for durable, per-run audit trails.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The Pushgateway is a dumb proxy:&lt;/strong&gt; Pushing concurrent metrics without unique keys causes race conditions. Adding unique keys causes OOM crashes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;StatsD is the missing link:&lt;/strong&gt; It aggregates concurrent events at the network layer, completely eliminating race conditions and cardinality bloat for operational metrics.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Scale dictates your state architecture:&lt;/strong&gt; Use the Pushgateway + Sweeper DAG for medium-scale task state snapshots. For massive scale, move per-run state tracking entirely to an event logging system like Elasticsearch or Loki.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/TheStaffBlueprint/batch-workloads-observability" rel="noopener noreferrer"&gt;TheStaffBlueprint/batch-workloads-observability (Companion Repo)&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://airflow.apache.org/docs/apache-airflow/stable/administration-and-deployment/logging-monitoring/metrics.html" rel="noopener noreferrer"&gt;Apache Airflow Metrics Configuration&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://prometheus.io/docs/practices/pushing/" rel="noopener noreferrer"&gt;Prometheus Official: When to use the Pushgateway&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>architecture</category>
      <category>devops</category>
      <category>monitoring</category>
      <category>sre</category>
    </item>
    <item>
      <title>The Green Tick Fallacy — Why Batch Observability is Fundamentally Different</title>
      <dc:creator>Chirag Bhatia</dc:creator>
      <pubDate>Sun, 17 May 2026 06:40:21 +0000</pubDate>
      <link>https://dev.to/thestaffblueprint/the-green-tick-fallacy-why-batch-observability-is-fundamentally-different-398c</link>
      <guid>https://dev.to/thestaffblueprint/the-green-tick-fallacy-why-batch-observability-is-fundamentally-different-398c</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu603km0a14asfb13zuil.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu603km0a14asfb13zuil.png" alt="The Green Tick Fallacy" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The Green Tick Fallacy&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;There is a dangerous assumption that every junior data engineer makes: &lt;em&gt;If the Airflow task turns green, the job was successful.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This is the “Green Tick Fallacy.” When your Spark job finishes, Airflow checks exactly one thing: did the container return an &lt;code&gt;exit 0&lt;/code&gt; status code? It has absolutely no idea if your job processed 10 billion rows flawlessly, or if it processed 0 rows because an upstream partition was empty. It just knows the container didn’t crash.&lt;/p&gt;

&lt;p&gt;Relying on the green tick is how you get paged at 3 AM for silent data corruption. To build true batch workload observability, you have to extract internal application metrics — and doing that for batch workloads is fundamentally harder than for services.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Why Batch Observability is Hard&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Traditional microservice observability is straightforward. The service runs 24/7, exposing a &lt;code&gt;/metrics&lt;/code&gt; HTTP endpoint. Prometheus scrapes it every 15 seconds. The process is always alive to respond.&lt;/p&gt;

&lt;p&gt;Batch jobs are ephemeral. They spin up, chew through a terabyte of data in 45 seconds, and vanish. By the time Prometheus tries to scrape them, the process is already dead. You cannot scrape batch jobs — you must &lt;strong&gt;push&lt;/strong&gt; telemetry from inside the code out to an aggregator before the container dies.&lt;/p&gt;

&lt;p&gt;This creates a fundamentally different architectural challenge. In the world of services, your observability tool pulls data. In the world of batch, your job pushes data. And the tools designed for pulling don’t work cleanly for pushing.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What Observability Actually Means for Batch&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Before diving into tools, it’s worth defining what “observability” actually means for batch workloads. There are three distinct categories of data you need, and each demands a different architectural approach:&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;1. Operational Metrics&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;“Is the system healthy right now?”&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;How many DAGs ran today?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;What’s the average task duration?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;How many tasks failed in the last hour?&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are &lt;strong&gt;low-cardinality, aggregate&lt;/strong&gt; numbers. You don’t need per-run-id granularity. You need rates, counts, and histograms. These are the bread and butter of Prometheus.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;2. Task State Snapshots&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;“What is the current state of this specific task?”&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Is task &lt;code&gt;load_customers&lt;/code&gt; in the &lt;code&gt;daily_etl&lt;/code&gt; DAG currently running, failed, or succeeded?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;What was the duration of this specific execution?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Did the task retry, and what is its final state?&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are &lt;strong&gt;point-in-time state snapshots&lt;/strong&gt; with moderate-to-high cardinality. Each task execution has a unique identity (&lt;code&gt;run_id&lt;/code&gt;), and the state may change over the lifecycle (running → failed → retried → success). These can live in Prometheus temporarily, but require careful lifecycle management.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;3. Execution History &amp;amp; Audit&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;“What exactly happened in run XYZ?”&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;How many rows did &lt;code&gt;run_id=abc123&lt;/code&gt; process?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;What was the data quality score for this specific schema version?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;What was the exact error message and stack trace?&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is &lt;strong&gt;high-cardinality, per-execution, durable data&lt;/strong&gt;. It must be queryable weeks or months later for debugging, reconciliation, and compliance. This data does &lt;strong&gt;not&lt;/strong&gt; belong in Prometheus.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The Trap: Forcing Everything into One Tool&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The mistake most teams make is trying to force all three categories into a single observability system — usually Prometheus via the Pushgateway. This leads to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Pushgateway abuse&lt;/strong&gt; : Pushing per-&lt;code&gt;run_id&lt;/code&gt; metrics to Prometheus via Pushgateway, creating unbounded cardinality&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;OOM crashes&lt;/strong&gt; : Pushgateway has no native TTL, so dynamically labelled metrics accumulate in memory forever&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Semantic mismatches&lt;/strong&gt; : Using the wrong metric type (Counters where Gauges belong), leading to inflated or incorrect dashboard numbers when tasks retry&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this series, we’ll build a clean architecture that uses the right tool for each category. We’ll show you exactly how to set up the stack, what code to write, and what traps to avoid.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What’s Coming in This Series&lt;/strong&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Part 2: The Architecture&lt;/strong&gt; — How to design a Prometheus + Grafana + StatsD architecture for batch workloads. What belongs in Prometheus, what doesn’t, and where StatsD fits.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Part 3: Metric Granularity &amp;amp; Classification&lt;/strong&gt; — What level of observability to achieve where. Why Gauges are semantically correct for batch state (and why it’s NOT about performance). What data belongs in Prometheus vs. structured logs and OLAP stores for auditing and traceability.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Part 4: The Implementation&lt;/strong&gt; — Building a production-ready Airflow plugin with Gauges, configuring StatsD, designing a Sweeper DAG, and setting up Grafana dashboards. Complete code walkthrough.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Part 5: Future Scope&lt;/strong&gt; — How to build durable, per-execution audit trails using event streams, OLAP stores, distributed tracing, and data lineage for the data that should never touch Prometheus.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Key Takeaways&lt;/strong&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;For Juniors:&lt;/strong&gt; The green tick lies. &lt;code&gt;exit 0&lt;/code&gt; means the container didn’t crash, not that it processed data correctly. You must push application metrics out of your batch jobs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;For Seniors:&lt;/strong&gt; Batch observability has three distinct data categories (operational metrics, state snapshots, execution history). Forcing all three into Prometheus is a common and expensive mistake.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The Rule:&lt;/strong&gt; Match your data to the right storage backend. Not everything belongs in a time-series database.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;References&lt;/strong&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/TheStaffBlueprint/batch-workloads-observability" rel="noopener noreferrer"&gt;TheStaffBlueprint/batch-workloads-observability (Companion Repo)&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="https://airflow.apache.org/docs/apache-airflow/stable/administration-and-deployment/logging-monitoring/metrics.html" rel="noopener noreferrer"&gt;Apache Airflow Metrics Configuration&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="https://prometheus.io/docs/practices/pushing/" rel="noopener noreferrer"&gt;Prometheus Official: When to use the Pushgateway&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>observability</category>
      <category>airflow</category>
      <category>prometheus</category>
      <category>grafana</category>
    </item>
  </channel>
</rss>
