<?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: Sudeep Hazra</title>
    <description>The latest articles on DEV Community by Sudeep Hazra (@sudeephazra).</description>
    <link>https://dev.to/sudeephazra</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4036287%2F0857a768-5c60-4a38-8f60-810e25f439d1.jpg</url>
      <title>DEV Community: Sudeep Hazra</title>
      <link>https://dev.to/sudeephazra</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sudeephazra"/>
    <language>en</language>
    <item>
      <title>To SQL or to Python; there is another problem</title>
      <dc:creator>Sudeep Hazra</dc:creator>
      <pubDate>Thu, 22 Oct 2026 18:23:27 +0000</pubDate>
      <link>https://dev.to/sudeephazra/to-sql-or-to-python-there-is-another-problem-26gc</link>
      <guid>https://dev.to/sudeephazra/to-sql-or-to-python-there-is-another-problem-26gc</guid>
      <description>&lt;p&gt;A recent &lt;a href="https://www.reddit.com/r/dataengineering/comments/1wz004g/data_engineer_is_adamant_on_streamlining_all/" rel="noopener noreferrer"&gt;r/dataengineering discussion&lt;/a&gt; described a familiar disagreement. A team was moving millions of daily records into Databricks. Python already handled ingestion, but one engineer wanted the silver-layer transformations in Python as well. Other team members preferred SQL because more people could read and maintain it.&lt;/p&gt;

&lt;p&gt;The discussion quickly became a vote for a language. SQL is easier to read. Python is easier to test. SQL is declarative. Python is proper engineering. One will scale; the other will not.&lt;/p&gt;

&lt;p&gt;Most of those claims are too broad to guide a platform decision.&lt;/p&gt;

&lt;p&gt;For structured Spark transformations, SQL and the DataFrame API often describe the same work. The more useful decision is about ownership: who must understand the transformation, what kind of change it receives, how it will be tested, and where its operational boundary belongs.&lt;/p&gt;

&lt;p&gt;I would not standardize an entire data platform on one language. I would standardize the rules for choosing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The execution engine removes one easy argument
&lt;/h2&gt;

&lt;p&gt;Apache Spark's &lt;a href="https://spark.apache.org/docs/latest/sql-programming-guide.html" rel="noopener noreferrer"&gt;SQL and DataFrames guide&lt;/a&gt; makes an important point: SQL and the Dataset or DataFrame APIs use the same execution engine. Spark receives more information about structured data and the computation, then uses that information for optimization.&lt;/p&gt;

&lt;p&gt;That does not guarantee identical performance for every pair of implementations. A Python user-defined function can block optimizations that a built-in SQL expression would preserve. A poorly written SQL query can create unnecessary shuffles. Different APIs can expose different features.&lt;/p&gt;

&lt;p&gt;It does mean that "Python is faster" and "SQL scales better" are weak starting positions. If two implementations resolve to the same logical and physical plan, the syntax is not the main performance decision.&lt;/p&gt;

&lt;p&gt;The team should compare evidence:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;representative input
        ↓
SQL implementation + Python implementation
        ↓
logical and physical plans
        ↓
runtime, shuffle, spill, skew, cost
        ↓
maintenance and test review
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The benchmark should use production-shaped data. A transformation that looks fine on ten thousand evenly distributed rows can behave very differently when one customer owns a third of the records.&lt;/p&gt;

&lt;p&gt;Performance matters. It just has to be measured after the two options perform the same work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choose the language from the shape of the transformation
&lt;/h2&gt;

&lt;p&gt;Databricks now documents this choice directly. Its &lt;a href="https://docs.databricks.com/aws/en/ldp/developer/sql-vs-python" rel="noopener noreferrer"&gt;Lakeflow guidance for SQL and Python&lt;/a&gt; says that both interfaces produce the same underlying dataflow graph and provide equivalent functionality for most processing. It recommends SQL for logic that can be expressed clearly in SQL, and Python when the pipeline needs programmatic control, an external library, or a Python-only feature.&lt;/p&gt;

&lt;p&gt;That is a much better boundary than bronze, silver, and gold.&lt;/p&gt;

&lt;p&gt;A layer name tells us how refined the data is supposed to be. It does not tell us whether the implementation needs loops, conditional generation, an external API, a custom library, or a readable set of joins and aggregations.&lt;/p&gt;

&lt;p&gt;I would use a decision table like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Transformation shape&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;Reason&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Joins, filters, windows, aggregations&lt;/td&gt;
&lt;td&gt;SQL&lt;/td&gt;
&lt;td&gt;Direct expression of relational logic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Business-facing dimensional models&lt;/td&gt;
&lt;td&gt;SQL&lt;/td&gt;
&lt;td&gt;Wider reviewer and maintainer pool&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reusable DataFrame algorithm&lt;/td&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;td&gt;Functions and modules provide a clean boundary&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dynamic pipeline generation&lt;/td&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;td&gt;The logic needs programmatic control&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;External library or non-tabular processing&lt;/td&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;td&gt;SQL is no longer the natural interface&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A pipeline containing both shapes&lt;/td&gt;
&lt;td&gt;Both&lt;/td&gt;
&lt;td&gt;Keep each part in the clearest interface&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The last row is easy to overlook. Databricks supports &lt;a href="https://docs.databricks.com/aws/en/ldp/load" rel="noopener noreferrer"&gt;mixing SQL and Python sources in one pipeline&lt;/a&gt;. A platform standard does not need to turn a mixed workload into an ideological contest.&lt;/p&gt;

&lt;p&gt;The standard can say that relational transformations default to SQL, while Python is available when the transformation crosses a documented threshold. That gives the team consistency without making SQL imitate a programming language or making Python wrap every &lt;code&gt;select&lt;/code&gt;, &lt;code&gt;join&lt;/code&gt;, and &lt;code&gt;groupBy&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  A silver layer is not one kind of work
&lt;/h2&gt;

&lt;p&gt;"Put the silver layer in Python" sounds precise because the layer has a name. In practice, silver often contains several different responsibilities:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;parsing source records and handling malformed values&lt;/li&gt;
&lt;li&gt;deduplicating events&lt;/li&gt;
&lt;li&gt;applying CDC semantics&lt;/li&gt;
&lt;li&gt;standardizing identifiers and units&lt;/li&gt;
&lt;li&gt;joining reference data&lt;/li&gt;
&lt;li&gt;enforcing quality expectations&lt;/li&gt;
&lt;li&gt;preparing reusable domain entities&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some of that work is straightforward relational logic. Some of it needs stateful streaming behavior. Some of it depends on a library. Some of it encodes a business definition that an analyst or domain engineer must review.&lt;/p&gt;

&lt;p&gt;One language across the layer can make the diagram tidy while making ownership worse.&lt;/p&gt;

&lt;p&gt;Suppose the finance team must approve revenue adjustments. A SQL model that exposes the joins and conditions may be easier for that review, even if a data engineer could write the same transformation in PySpark. Now suppose the platform must create fifty similar ingestion flows from metadata. Python can generate those definitions without copying fifty SQL files.&lt;/p&gt;

&lt;p&gt;The language changes because the work changes.&lt;/p&gt;

&lt;p&gt;I would split the layer by stable responsibilities, then choose the interface for each responsibility:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;source-specific parsing       -&amp;gt; Python when libraries or custom parsing are needed
generic row validation        -&amp;gt; SQL expectations or built-in expressions
relational conformance        -&amp;gt; SQL
shared algorithm              -&amp;gt; unit-tested Python module
business model                -&amp;gt; SQL owned with the domain
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This also gives code review a useful question. The reviewer is no longer deciding whether they like SQL or Python. They are checking whether the implementation matches the kind of change the component is expected to receive.&lt;/p&gt;

&lt;h2&gt;
  
  
  Readability belongs to a named reader
&lt;/h2&gt;

&lt;p&gt;Teams often say that one language is more readable without naming the reader.&lt;/p&gt;

&lt;p&gt;Readable to whom?&lt;/p&gt;

&lt;p&gt;The engineer maintaining ingestion may prefer Python because the surrounding framework, tests, and deployment code are already Python. The analytics engineer validating a customer metric may prefer SQL because the transformation is close to the query they use for reconciliation. The on-call engineer may prefer whichever version exposes input tables, output tables, and failure conditions without three layers of abstraction.&lt;/p&gt;

&lt;p&gt;Readability is part of the operating model. I would identify at least four readers for an important transformation:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The author making the first change.&lt;/li&gt;
&lt;li&gt;The reviewer checking correctness.&lt;/li&gt;
&lt;li&gt;The on-call engineer diagnosing a failed run.&lt;/li&gt;
&lt;li&gt;The next owner changing it six months later.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If only the first reader benefits from the chosen language, the team is optimizing for writing rather than ownership.&lt;/p&gt;

&lt;p&gt;This is where clever frameworks become expensive. A metadata-driven Python abstraction can remove repeated code, but it can also hide the generated query, input dependencies, and quality rules. The abstraction earns its place when the repeated behavior is stable, the generated plan is observable, and the team can test one definition without running the entire framework.&lt;/p&gt;

&lt;p&gt;If every transformation needs an exception in the metadata, the framework has stopped reducing complexity. It has merely moved the complexity into configuration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing is different, but neither language gets a free pass
&lt;/h2&gt;

&lt;p&gt;Python has a familiar unit-testing story. Databricks recommends separating transformation logic into plain PySpark functions so they can be tested locally with &lt;code&gt;pytest&lt;/code&gt;; its &lt;a href="https://docs.databricks.com/aws/en/ldp/develop-locally" rel="noopener noreferrer"&gt;local pipeline development guide&lt;/a&gt; also describes validation and workspace execution as complementary layers.&lt;/p&gt;

&lt;p&gt;That is useful, especially for functions with branching logic. It does not make a mocked DataFrame test proof that the production pipeline is correct.&lt;/p&gt;

&lt;p&gt;SQL can also be tested with small input relations, expected outputs, schema checks, and integration runs. The syntax is different, but the required evidence is similar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;representative happy-path rows&lt;/li&gt;
&lt;li&gt;nulls and malformed values&lt;/li&gt;
&lt;li&gt;duplicates and late records&lt;/li&gt;
&lt;li&gt;corrections and deletions&lt;/li&gt;
&lt;li&gt;schema changes&lt;/li&gt;
&lt;li&gt;row-count and value reconciliation&lt;/li&gt;
&lt;li&gt;a production execution plan&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The team should define these checks once and require them from either interface.&lt;/p&gt;

&lt;p&gt;I would add one more rule: test the published table contract separately from the implementation. If a Python transformation is replaced by SQL, consumers should not discover the change through missing columns or altered null behavior. The same applies in the other direction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Write a small standard, not a language constitution
&lt;/h2&gt;

&lt;p&gt;A workable standard can fit on one page:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Default to SQL for declarative relational transformations.

Use Python for programmatic control, shared algorithms,
external libraries, or features unavailable in SQL.

Mix them when one pipeline contains both kinds of work.

Require the same contract tests, reconciliation, lineage,
and operational metadata from both.

Benchmark performance claims with representative data
and inspect the execution plan.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then record exceptions with a short decision note. State the transformation shape, intended maintainers, test approach, runtime evidence, and the condition that would trigger reconsideration.&lt;/p&gt;

&lt;p&gt;This avoids two bad outcomes. The first is a Python-only platform where ordinary relational logic is hidden behind builders and helper functions. The second is a SQL-only platform where procedural logic leaks into generated statements and fragile templating.&lt;/p&gt;

&lt;p&gt;For the Databricks migration in the Reddit discussion, I would keep ingestion in the Python framework that already works. I would default ordinary silver transformations to SQL when they are relational and need broad review. I would keep Python for custom parsing, reusable algorithms, dynamic definitions, and features that SQL does not cover. Both paths would publish the same ownership, test, lineage, and recovery information.&lt;/p&gt;

&lt;p&gt;The language decision matters. The team identity attached to it does not.&lt;/p&gt;

&lt;p&gt;Pick the interface that makes the transformation easiest to understand, verify, and operate by the people who own it. If that produces a mixed pipeline, that is a reasonable design, not a failure to standardize.&lt;/p&gt;

</description>
      <category>dataengineering</category>
      <category>python</category>
      <category>sql</category>
    </item>
    <item>
      <title>Data platform teams are becoming exception handlers</title>
      <dc:creator>Sudeep Hazra</dc:creator>
      <pubDate>Thu, 22 Oct 2026 17:57:35 +0000</pubDate>
      <link>https://dev.to/sudeephazra/data-platform-teams-are-becoming-exception-handlers-5964</link>
      <guid>https://dev.to/sudeephazra/data-platform-teams-are-becoming-exception-handlers-5964</guid>
      <description>&lt;p&gt;An &lt;a href="https://www.reddit.com/r/dataengineering/comments/1veof8v/is_anyone_else_losing_interest_in_data_engineering/" rel="noopener noreferrer"&gt;r/dataengineering thread about losing interest in the field&lt;/a&gt; described a data platform engineer who knew the full route from source systems to business value. They maintained Snowflake, dbt, Airflow, ingestion code, an internal library, and a collection of BI tools. They were also pulled into dbt failures, model performance problems, and Snowflake troubleshooting.&lt;/p&gt;

&lt;p&gt;That breadth sounds valuable. It is also how a platform engineer becomes the human exception handler for an entire data organization.&lt;/p&gt;

&lt;p&gt;Every problem that does not have an obvious owner lands with the person who understands the whole system. They fix the pipeline, explain the metric, repair the framework, and help the next team use it. Because they solve the problem, the organization sends them the next one.&lt;/p&gt;

&lt;p&gt;The platform becomes more important while its engineers have less time to improve it.&lt;/p&gt;

&lt;p&gt;I would treat this as an architecture problem. Team capacity, support boundaries, and ownership are part of the platform design.&lt;/p&gt;

&lt;h2&gt;
  
  
  The platform sits at every boundary
&lt;/h2&gt;

&lt;p&gt;A data platform connects systems that are owned by different groups:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;source application
        ↓
ingestion service
        ↓
storage and processing
        ↓
domain models
        ↓
BI, ML, applications, and agents
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When a number is wrong, any arrow could be responsible. The source may have changed its deletion behavior. Ingestion may have skipped a partition. A transformation may have duplicated records. A dashboard may apply another filter. The platform team has visibility across the route, so asking it for help is reasonable.&lt;/p&gt;

&lt;p&gt;The problem begins when visibility is confused with ownership.&lt;/p&gt;

&lt;p&gt;The platform team should own the shared capabilities it provides: execution environments, deployment paths, observability conventions, access patterns, templates, and supported interfaces. It should not silently inherit every pipeline's business rules, every source system's semantics, and every consumer's interpretation.&lt;/p&gt;

&lt;p&gt;Without that distinction, the platform is a shared service in the diagram and a catch-all team in practice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Support work has to return information to the platform
&lt;/h2&gt;

&lt;p&gt;Google's SRE guidance calls repetitive, manual, service-related work that scales with growth &lt;a href="https://sre.google/sre-book/eliminating-toil/" rel="noopener noreferrer"&gt;toil&lt;/a&gt;. It also warns that excessive toil slows engineering progress, confuses the team's role, and can drive experienced engineers away.&lt;/p&gt;

&lt;p&gt;A support request is not automatically toil. Diagnosing a new failure mode can teach the team something important. Helping an early adopter can reveal a missing interface. A production incident may deserve immediate collaboration across every boundary.&lt;/p&gt;

&lt;p&gt;The work becomes toil when the same class of issue returns without improving the system.&lt;/p&gt;

&lt;p&gt;I would require every recurring support case to take one of four exits:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Exit&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Platform fix&lt;/td&gt;
&lt;td&gt;The shared retry behavior is wrong&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Paved-road improvement&lt;/td&gt;
&lt;td&gt;A template needs a default freshness check&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Owner runbook&lt;/td&gt;
&lt;td&gt;The domain team needs a documented backfill procedure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Explicit exception&lt;/td&gt;
&lt;td&gt;A temporary legacy workflow needs named support and an end date&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Closing a ticket is not enough. The ticket should leave behind a safer interface, better diagnostic evidence, clearer ownership, or a conscious decision to keep paying the support cost.&lt;/p&gt;

&lt;p&gt;Google's chapter on &lt;a href="https://sre.google/sre-book/operational-overload/" rel="noopener noreferrer"&gt;operational overload&lt;/a&gt; recommends identifying the sources of stress, separating avoidable toil from legitimate operational work, and using service-level objectives to restore focus. The same approach works for a data platform. Count the interruptions. Group them by cause. Find the few categories consuming most of the time.&lt;/p&gt;

&lt;p&gt;The first useful dashboard may be about the platform team rather than the platform:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;support requests by producing team
repeated requests by failure class
hours spent on unplanned work
requests missing an owner or runbook
issues converted into platform improvements
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the team cannot see its operational load, it will plan as though every engineer is available for roadmap work. The roadmap will slip, support will feel like an interruption, and the people doing both will carry the discrepancy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Define a support contract before adding a portal
&lt;/h2&gt;

&lt;p&gt;Platform engineering is often presented as a self-service portal. The portal is the visible part. The important part is the contract behind it.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://tag-app-delivery.cncf.io/whitepapers/platforms/" rel="noopener noreferrer"&gt;CNCF Platforms White Paper&lt;/a&gt; describes an internal platform as a collection of capabilities shaped around user needs, with consistent experiences such as APIs, templates, documentation, and support. It also emphasizes that platform teams are responsible for the interfaces and user experience, while the underlying capability may be run by another provider or team.&lt;/p&gt;

&lt;p&gt;That separation is useful for data platforms. A support contract can define three areas.&lt;/p&gt;

&lt;h3&gt;
  
  
  What the platform owns
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;the supported pipeline runtime and deployment path&lt;/li&gt;
&lt;li&gt;authentication and access interfaces&lt;/li&gt;
&lt;li&gt;shared monitoring and lineage integration&lt;/li&gt;
&lt;li&gt;platform upgrades and compatibility windows&lt;/li&gt;
&lt;li&gt;documented recovery controls offered by the platform&lt;/li&gt;
&lt;li&gt;defects in templates, libraries, and managed services the team publishes&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What the workload owner owns
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;source and business semantics&lt;/li&gt;
&lt;li&gt;transformation correctness&lt;/li&gt;
&lt;li&gt;data-quality rules for its domain&lt;/li&gt;
&lt;li&gt;schema-change coordination with consumers&lt;/li&gt;
&lt;li&gt;backfill approval and validation&lt;/li&gt;
&lt;li&gt;first response to alerts for the workload&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What requires collaboration
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;failures that cross several platform services&lt;/li&gt;
&lt;li&gt;incidents where the cause is still unknown&lt;/li&gt;
&lt;li&gt;capacity changes that affect shared infrastructure&lt;/li&gt;
&lt;li&gt;migrations that change a supported interface&lt;/li&gt;
&lt;li&gt;security or data-loss events&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The collaborative area matters. A boundary should route work, not provide an excuse to throw a ticket back over the wall.&lt;/p&gt;

&lt;p&gt;The contract should also name service levels. A production outage, a failing scheduled pipeline, a design consultation, and a request for a new connector do not need the same response time. When everything arrives through a direct message marked urgent, the platform team cannot make a sensible trade-off.&lt;/p&gt;

&lt;h2&gt;
  
  
  Require evidence at the support boundary
&lt;/h2&gt;

&lt;p&gt;Many data incidents begin with a sentence like "the numbers look wrong." That is a valid symptom, but it is not enough information for efficient triage.&lt;/p&gt;

&lt;p&gt;The support interface should collect a small diagnostic envelope:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;affected dataset or job
business impact
first known bad time or partition
expected result and observed result
recent deployment or source change
run identifier and relevant logs
checks already performed
named workload owner
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is not bureaucracy for its own sake. It prevents the platform engineer from spending the first hour discovering which table, date, environment, and dashboard the request refers to.&lt;/p&gt;

&lt;p&gt;The platform should make the evidence easy to obtain. A job page can expose the run identifier, input versions, row counts, quality results, owner, and runbook. A template can register the support channel and escalation policy when a new pipeline is created. An alert can link directly to the failed partition and recovery action.&lt;/p&gt;

&lt;p&gt;Self-service is credible when it includes failure, not only creation.&lt;/p&gt;

&lt;p&gt;The happy path may let a team create a pipeline in five minutes. If recovery still requires finding the one platform engineer who remembers a private command, the platform has automated provisioning while preserving dependency.&lt;/p&gt;

&lt;h2&gt;
  
  
  Route data meaning to the people who know it
&lt;/h2&gt;

&lt;p&gt;Central platform teams often accumulate business logic because they are the only team with the skills or access to change the pipeline. That may be necessary during an early migration. It should not become invisible permanent ownership.&lt;/p&gt;

&lt;p&gt;Google Cloud's overview of &lt;a href="https://cloud.google.com/discover/what-is-data-mesh" rel="noopener noreferrer"&gt;data mesh&lt;/a&gt; describes domain-oriented ownership and a self-service data platform as separate principles. The useful part is not the label. It is the recognition that the people closest to a domain understand its semantics, while a shared platform can remove the infrastructure burden.&lt;/p&gt;

&lt;p&gt;Distributed ownership still costs something. Domain teams need training, access, time, and an escalation path. Giving a team a repository and declaring it the owner does not create operational capability.&lt;/p&gt;

&lt;p&gt;I would transfer ownership in stages:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The platform team operates the workflow while documenting failure modes.&lt;/li&gt;
&lt;li&gt;The domain team joins incident review and validates business outcomes.&lt;/li&gt;
&lt;li&gt;The platform provides a runbook, dashboards, and a safe recovery action.&lt;/li&gt;
&lt;li&gt;The domain team becomes first responder for its workload.&lt;/li&gt;
&lt;li&gt;The platform remains responsible for shared-service defects and difficult cross-boundary failures.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is slower than changing an owner field. It is also much more likely to survive the first incident.&lt;/p&gt;

&lt;p&gt;For critical datasets, record ownership in the same place engineers see operational state. A catalog entry that names a business owner but does not connect to the runbook, alert route, and deployment repository is useful documentation, but it is not an operating model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Protect enough capacity to improve the platform
&lt;/h2&gt;

&lt;p&gt;Google's &lt;a href="https://sre.google/sre-book/introduction/" rel="noopener noreferrer"&gt;SRE model uses a 50% upper bound&lt;/a&gt; for operational work as a forcing function, not as a universal staffing formula. A data platform team can choose a different threshold. The important part is to choose and measure one.&lt;/p&gt;

&lt;p&gt;If unplanned support consumes most of the team's week, it cannot build the self-service features expected to reduce support. That feedback loop is the dangerous part:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;more unsupported workloads
        ↓
more manual help
        ↓
less platform engineering
        ↓
more missing capabilities
        ↓
more manual help
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Breaking the loop may require temporarily slowing onboarding. It may require a support rotation so interruptions do not reach every engineer. It may require deleting a rarely used feature whose maintenance cost exceeds its value. It may require one quarter focused on observability and recovery rather than another set of integrations.&lt;/p&gt;

&lt;p&gt;I would reserve capacity for the top recurring sources of support and review the balance each month. If the limit is exceeded, leadership should decide what work stops. The answer cannot be an invisible extension of the team's working day.&lt;/p&gt;

&lt;h2&gt;
  
  
  The platform should remove dependency, not collect it
&lt;/h2&gt;

&lt;p&gt;A strong platform team will still be asked difficult questions. That is part of serving many workloads across shared infrastructure. The goal is not zero support.&lt;/p&gt;

&lt;p&gt;The goal is for each support interaction to strengthen the system. A repeated request becomes an API, a template, an alert, a runbook, a training session, or a clearer boundary. Workload owners gain enough context to handle ordinary failures. Platform engineers keep responsibility for the capabilities only they can change.&lt;/p&gt;

&lt;p&gt;For the engineer in the Reddit discussion, knowing the entire data route was an asset. The operating model turned that asset into a dependency.&lt;/p&gt;

&lt;p&gt;I would fix the return path. Name who owns data meaning, workload operation, and shared infrastructure. Collect evidence at the support boundary. Measure recurring interruption. Protect enough engineering capacity to remove the next class of tickets.&lt;/p&gt;

&lt;p&gt;A platform is successful when capable teams can use it without borrowing one of its engineers for every exception.&lt;/p&gt;

</description>
      <category>career</category>
      <category>dataengineering</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>Your specification is not a code review</title>
      <dc:creator>Sudeep Hazra</dc:creator>
      <pubDate>Mon, 12 Oct 2026 10:37:25 +0000</pubDate>
      <link>https://dev.to/sudeephazra/your-specification-is-not-a-code-review-2l58</link>
      <guid>https://dev.to/sudeephazra/your-specification-is-not-a-code-review-2l58</guid>
      <description>&lt;p&gt;A comment in the September &lt;a href="https://www.reddit.com/r/dataengineering/comments/1w4g6b8/monthly_general_discussion_sep_2026/" rel="noopener noreferrer"&gt;r/dataengineering discussion thread&lt;/a&gt; stopped me for a moment. A team had embraced spec-driven development so completely that people were proud of not reading the code their agents produced.&lt;/p&gt;

&lt;p&gt;I like specifications. They force a team to describe the behaviour it wants before a coding session starts inventing behaviour on its behalf. GitHub's &lt;a href="https://github.github.com/spec-kit/" rel="noopener noreferrer"&gt;Spec Kit&lt;/a&gt; makes the idea concrete: define intent, plan the work, break it into tasks, implement, and converge on evidence.&lt;/p&gt;

&lt;p&gt;A useful specification becomes dangerous when the team treats it as proof that the implementation is correct.&lt;/p&gt;

&lt;p&gt;A spec tells us what should happen. The code still decides what &lt;em&gt;can&lt;/em&gt; happen, including the error paths, permission boundaries, resource usage, rollback behaviour, and accidental side effects that never made it into the happy-path requirements.&lt;/p&gt;

&lt;p&gt;If nobody can explain the resulting change, the team did not remove engineering work. It moved that work to production.&lt;/p&gt;

&lt;h2&gt;
  
  
  A good spec improves the review
&lt;/h2&gt;

&lt;p&gt;Spec-driven development gives reviewers something better than a vague ticket. A useful spec should name the user-visible behaviour, constraints, acceptance criteria, and conditions that must remain true.&lt;/p&gt;

&lt;p&gt;For example, "add bulk retry" is not much of a contract. A better spec would say:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Only failed items are eligible for retry.&lt;/li&gt;
&lt;li&gt;A retry must not duplicate a successful write.&lt;/li&gt;
&lt;li&gt;The caller may retry the same request after a timeout.&lt;/li&gt;
&lt;li&gt;Authorization is checked again when the retry begins.&lt;/li&gt;
&lt;li&gt;The operation records which items succeeded, failed, or were skipped.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That list makes the implementation easier to challenge. A reviewer can trace each condition into code and tests. It also exposes questions before code exists. What identifies the same request? How long is that identity retained? What happens when authorization changes between attempts?&lt;/p&gt;

&lt;p&gt;The spec does not answer those questions unless someone puts the answers into it. Even then, the implementation may violate them.&lt;/p&gt;

&lt;p&gt;GitHub's own &lt;a href="https://github.com/github/spec-kit/blob/main/docs/guides/agentic-sdlc.md" rel="noopener noreferrer"&gt;agentic SDLC guide&lt;/a&gt; is more restrained than the popular interpretation of spec-driven development. It describes agents layered onto a conventional lifecycle, with small changes using the normal issue, pull request, test, and review path. Larger changes may use deeper specification and planning. Code still faces automated checks and maintainer review.&lt;/p&gt;

&lt;p&gt;That model keeps the useful part of spec-driven development. The specification sharpens the review. It does not cancel it.&lt;/p&gt;

&lt;h2&gt;
  
  
  There are four different contracts
&lt;/h2&gt;

&lt;p&gt;I would separate the work into four contracts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Intent
What behaviour do we want?

Implementation
How does this change create that behaviour?

Verification
What evidence shows that it works and remains safe?

Operation
How will we detect, contain, and recover from failure?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A feature spec mostly owns intent. An implementation plan connects intent to the repository. Tests and analysis provide verification. Runbooks, telemetry, deployment controls, and rollback plans cover operation.&lt;/p&gt;

&lt;p&gt;An agent can help produce every artifact in that chain. It cannot make the artifacts equivalent.&lt;/p&gt;

&lt;p&gt;A passing unit test may prove that a retry helper returns the expected result for three inputs. It does not prove that two workers cannot process the same item concurrently. A correct implementation may still need a database constraint, idempotency key, or conditional write. Those decisions live below the level of the feature sentence.&lt;/p&gt;

&lt;p&gt;This is why reviewing only the final agent summary is risky. The summary is another generated artifact. It can be clear, persuasive, and wrong in exactly the same direction as the code.&lt;/p&gt;

&lt;p&gt;Review the repository state: the diff, tests, dependency changes, generated files, migrations, permissions, and runtime configuration. Narration is useful for navigation. It is not evidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make the change small enough to understand
&lt;/h2&gt;

&lt;p&gt;AI makes it cheap to generate a large change. It does not make a large change cheap to review.&lt;/p&gt;

&lt;p&gt;A 2,000-line pull request that modifies 100 data models may contain correct code. It is still a poor unit of review if nobody can reason about its blast radius. The problem is not an arbitrary line limit. It is the number of independent claims the reviewer must verify at once.&lt;/p&gt;

&lt;p&gt;I would slice work by behaviour and risk:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;specification
    ↓
schema or interface change
    ↓
core behaviour
    ↓
integration path
    ↓
operational controls
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each slice should leave the repository in a valid state and produce its own evidence. A database migration can be reviewed before application logic depends on it. A new API can land behind a disabled route or feature flag. Observability can be added with the behaviour rather than appearing in a final "production readiness" pull request that nobody wants to delay.&lt;/p&gt;

&lt;p&gt;Small changes also make agent mistakes easier to diagnose. If a test fails after one bounded edit, the search space is manageable. If an agent rewrites a subsystem, updates dependencies, changes infrastructure, and reformats adjacent files in one pass, the team has created a debugging project.&lt;/p&gt;

&lt;p&gt;The agent is not the only source of this problem. Humans have always produced oversized pull requests. Faster generation simply removes the natural friction that used to slow us down.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verification must come from outside the implementation
&lt;/h2&gt;

&lt;p&gt;If the same prompt produces the code, the tests, and the explanation, all three may share the same blind spot.&lt;/p&gt;

&lt;p&gt;That does not make agent-written tests useless. It means the acceptance evidence should not depend only on the implementation's own assumptions.&lt;/p&gt;

&lt;p&gt;For the bulk retry example, I would require evidence at several levels:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Evidence&lt;/th&gt;
&lt;th&gt;What it checks&lt;/th&gt;
&lt;th&gt;What it can miss&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Unit tests&lt;/td&gt;
&lt;td&gt;Local state transitions and edge cases&lt;/td&gt;
&lt;td&gt;Database and concurrency behaviour&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Integration tests&lt;/td&gt;
&lt;td&gt;Persistence, transactions, and authorization&lt;/td&gt;
&lt;td&gt;Production scale and timing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Static analysis&lt;/td&gt;
&lt;td&gt;Known unsafe patterns and policy violations&lt;/td&gt;
&lt;td&gt;Business correctness&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Load or fault tests&lt;/td&gt;
&lt;td&gt;Contention, timeouts, and retry storms&lt;/td&gt;
&lt;td&gt;Rare production combinations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Human review&lt;/td&gt;
&lt;td&gt;Design fit, hidden coupling, and maintainability&lt;/td&gt;
&lt;td&gt;Errors outside the reviewer's context&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;No row is sufficient by itself. Together they make a much stronger argument.&lt;/p&gt;

&lt;p&gt;NIST's current &lt;a href="https://pages.nist.gov/nccoe-devsecops/notational-reference-model.html" rel="noopener noreferrer"&gt;DevSecOps reference model&lt;/a&gt; takes the same position: AI-generated output should pass through peer review, security validation, automated testing, and approval workflows. The model treats generated corrective actions as proposed inputs, not self-approving changes to software or configuration.&lt;/p&gt;

&lt;p&gt;Calling the output a &lt;em&gt;proposed input&lt;/em&gt; keeps accountability with the engineering process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Review based on risk, not authorship
&lt;/h2&gt;

&lt;p&gt;I would not create one ceremonial review policy for "AI code" and another for human code. The author does not determine the blast radius.&lt;/p&gt;

&lt;p&gt;A one-line IAM wildcard can be more dangerous than a generated client with 500 lines. A documentation edit may need little more than link and rendering checks. A payment calculation deserves domain review whether a staff engineer, a junior developer, or an agent wrote it.&lt;/p&gt;

&lt;p&gt;Classify the change instead:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does it alter authentication, authorization, secrets, or network access?&lt;/li&gt;
&lt;li&gt;Does it modify a schema, public API, or event contract?&lt;/li&gt;
&lt;li&gt;Can it destroy, duplicate, or expose data?&lt;/li&gt;
&lt;li&gt;Does it change infrastructure or deployment behaviour?&lt;/li&gt;
&lt;li&gt;Can failure affect money, compliance, or customer access?&lt;/li&gt;
&lt;li&gt;Is rollback tested and practical?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Higher-risk answers require stronger evidence and named reviewers. Low-risk work can move quickly with automation. This avoids turning review into theatre while preserving scrutiny where it matters.&lt;/p&gt;

&lt;p&gt;GitHub's &lt;a href="https://docs.github.com/en/copilot/responsible-use/agents" rel="noopener noreferrer"&gt;responsible-use guidance for Copilot agents&lt;/a&gt; explicitly presents generated changes, CI results, review activity, and session history so people can inspect work before merge. The workflow expects a human decision. Enabling automatic agent merge does not change who owns the outcome.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep a short evidence note with the pull request
&lt;/h2&gt;

&lt;p&gt;I would ask every non-trivial agent-authored change to include a compact evidence note:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;Behaviour changed:
&lt;span class="p"&gt;-&lt;/span&gt; Failed items can be retried without duplicating successful writes.

Constraints preserved:
&lt;span class="p"&gt;-&lt;/span&gt; Authorization is evaluated at retry time.
&lt;span class="p"&gt;-&lt;/span&gt; The existing single-item endpoint is unchanged.

Evidence:
&lt;span class="p"&gt;-&lt;/span&gt; Unit and integration suites passed.
&lt;span class="p"&gt;-&lt;/span&gt; Concurrent retry test produced one committed write.
&lt;span class="p"&gt;-&lt;/span&gt; Migration was applied and reversed in a clean environment.

Not verified:
&lt;span class="p"&gt;-&lt;/span&gt; Retry behaviour above 500 requests per second.

Rollback:
&lt;span class="p"&gt;-&lt;/span&gt; Disable &lt;span class="sb"&gt;`bulk_retry_enabled`&lt;/span&gt;; no schema rollback required.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This should be a set of claims a reviewer can inspect, not an essay about what the code probably does. The "not verified" section is especially useful because it stops missing evidence from being disguised as confidence.&lt;/p&gt;

&lt;p&gt;The note also gives operations a head start. If the feature fails, the team already knows the intended boundary and the first containment action.&lt;/p&gt;

&lt;h2&gt;
  
  
  Specifications should make ownership clearer
&lt;/h2&gt;

&lt;p&gt;Spec-driven development is worth adopting when it makes intent explicit, catches ambiguity early, and gives agents a bounded problem. It becomes dangerous when the organization uses the spec as permission to stop understanding its software.&lt;/p&gt;

&lt;p&gt;My preferred workflow is simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;reviewed intent
      ↓
small implementation slice
      ↓
independent verification
      ↓
risk-based human review
      ↓
controlled release and observation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agents can accelerate every step. They can draft the spec, explore the repository, implement a slice, run tests, and assemble the evidence. That is useful work.&lt;/p&gt;

&lt;p&gt;The engineering team still has to decide whether the evidence is sufficient. It still has to understand the failure modes and support the result after deployment.&lt;/p&gt;

&lt;p&gt;A specification can tell an agent what to build. It cannot take the pager.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>softwaredevelopment</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>UTC is not a data contract. Period.</title>
      <dc:creator>Sudeep Hazra</dc:creator>
      <pubDate>Mon, 12 Oct 2026 10:36:09 +0000</pubDate>
      <link>https://dev.to/sudeephazra/utc-is-not-a-data-contract-period-51dj</link>
      <guid>https://dev.to/sudeephazra/utc-is-not-a-data-contract-period-51dj</guid>
      <description>&lt;p&gt;Someone in the October &lt;a href="https://www.reddit.com/r/dataengineering/comments/1wv3alk/monthly_general_discussion_oct_2026/" rel="noopener noreferrer"&gt;r/dataengineering discussion thread&lt;/a&gt; described losing three days to a timezone mismatch. The production dashboard looked correct for the first two hours each morning, then drifted by four or five hours.&lt;/p&gt;

&lt;p&gt;That is painfully believable. Timestamp bugs often survive because every value looks like a valid date. The pipeline runs, rows arrive, and the dashboard renders. Only the meaning moved.&lt;/p&gt;

&lt;p&gt;The usual prevention advice is "store everything in UTC." I would store event instants in UTC too, but that sentence is not a complete data contract.&lt;/p&gt;

&lt;p&gt;UTC can identify when something happened. It cannot always tell us which local business day it belonged to, which timezone rules were applied, or whether &lt;code&gt;2026-11-01 01:30&lt;/code&gt; referred to the first or second occurrence of that wall-clock time.&lt;/p&gt;

&lt;p&gt;We need to model time according to the question the data must answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  One timestamp can carry several meanings
&lt;/h2&gt;

&lt;p&gt;Consider a restaurant booking recorded as &lt;code&gt;2026-11-01 01:30&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That value might mean:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;an instant when the booking was created&lt;/li&gt;
&lt;li&gt;a local reservation time chosen by a customer&lt;/li&gt;
&lt;li&gt;a daily reporting boundary&lt;/li&gt;
&lt;li&gt;a recurring schedule such as "every Sunday at 01:30"&lt;/li&gt;
&lt;li&gt;text copied from a source that never supplied a timezone&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those are different data types even if the source puts all of them into a column called &lt;code&gt;timestamp&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I would distinguish at least four concepts:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concept&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;th&gt;Useful representation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Instant&lt;/td&gt;
&lt;td&gt;Payment received&lt;/td&gt;
&lt;td&gt;UTC timestamp&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Local wall-clock time&lt;/td&gt;
&lt;td&gt;Store opens at 09:00&lt;/td&gt;
&lt;td&gt;Local date/time plus timezone name&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Business date&lt;/td&gt;
&lt;td&gt;Trading day or service date&lt;/td&gt;
&lt;td&gt;Explicit date derived by an owned rule&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Observed offset&lt;/td&gt;
&lt;td&gt;Source sent &lt;code&gt;+05:30&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Offset retained as source evidence&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;An offset is not a timezone. &lt;code&gt;+01:00&lt;/code&gt; tells us the relationship to UTC for one value. It does not carry the historical and future daylight-saving rules of &lt;code&gt;Europe/London&lt;/code&gt; or another named zone.&lt;/p&gt;

&lt;p&gt;IANA published &lt;a href="https://data.iana.org/time-zones/releases/" rel="noopener noreferrer"&gt;tzdata 2026e on September 30&lt;/a&gt;. Timezone rules are maintained data, not a law of nature embedded permanently in every runtime. IANA notes that downstream distributors may wait for a timezone database release before updating &lt;a href="https://data.iana.org/time-zones/tz-link.html" rel="noopener noreferrer"&gt;consumer software&lt;/a&gt;. Two services can therefore disagree even when both use a legitimate timezone name.&lt;/p&gt;

&lt;h2&gt;
  
  
  Database types do not rescue an ambiguous source
&lt;/h2&gt;

&lt;p&gt;Type names are not consistent enough to infer intent by sight.&lt;/p&gt;

&lt;p&gt;PostgreSQL documents that plain &lt;a href="https://www.postgresql.org/docs/17/datatype-datetime.html" rel="noopener noreferrer"&gt;&lt;code&gt;timestamp&lt;/code&gt; means timestamp without time zone&lt;/a&gt;. A value with a timezone indicator can be silently interpreted according to the target type and session settings. PostgreSQL's &lt;code&gt;timestamptz&lt;/code&gt; represents an instant and displays it in the current session timezone. It does not preserve the original timezone name as a business attribute.&lt;/p&gt;

&lt;p&gt;Snowflake has three timestamp variants with different behaviour. Its &lt;a href="https://docs.snowflake.com/en/sql-reference/data-types-datetime" rel="noopener noreferrer"&gt;date and time documentation&lt;/a&gt; says:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;TIMESTAMP_NTZ&lt;/code&gt; stores wall-clock time without timezone consideration.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;TIMESTAMP_LTZ&lt;/code&gt; stores UTC internally but performs operations in the session timezone.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;TIMESTAMP_TZ&lt;/code&gt; stores UTC with an associated offset, not the original timezone name.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Snowflake also warns that adding six months to a &lt;code&gt;TIMESTAMP_TZ&lt;/code&gt; value can retain the original offset even when daylight-saving rules would give the named location a different offset in the new month. The zone name is no longer there to recalculate it.&lt;/p&gt;

&lt;p&gt;None of these behaviours is inherently wrong. The mistake is expecting a database type to recover context the source did not provide.&lt;/p&gt;

&lt;p&gt;If the source sends &lt;code&gt;2026-10-10 09:00:00&lt;/code&gt;, the ingestion layer needs a declared rule. Is that UTC? The source system's configured zone? The user's zone? The location attached to the record? Guessing from the server timezone turns deployment configuration into business logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Put time semantics into the contract
&lt;/h2&gt;

&lt;p&gt;For an event, I would prefer a payload shaped like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"event_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"evt_7192"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"occurred_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-10-10T03:30:00Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"source_local_datetime"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-10-10T09:00:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"source_timezone"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Asia/Kolkata"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"source_utc_offset"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"+05:30"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"recorded_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-10-10T03:30:04Z"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not every event needs every field. Keep the values when they answer different questions.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;occurred_at&lt;/code&gt; supports global ordering and elapsed-time calculations. &lt;code&gt;source_local_datetime&lt;/code&gt; preserves what the source or user saw. &lt;code&gt;source_timezone&lt;/code&gt; allows the local value to be interpreted using named rules. &lt;code&gt;source_utc_offset&lt;/code&gt; records what the producer applied at that moment. &lt;code&gt;recorded_at&lt;/code&gt; helps measure ingestion delay.&lt;/p&gt;

&lt;p&gt;When the event only represents a machine action and no local-calendar meaning exists, the UTC instant may be enough. When the event is a future appointment, store the local date/time and timezone name because the offset can change before the appointment occurs.&lt;/p&gt;

&lt;p&gt;I would make the contract explicit about these questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which fields are instants and which are wall-clock values?&lt;/li&gt;
&lt;li&gt;Is a timezone name required, and where does it come from?&lt;/li&gt;
&lt;li&gt;Is the source offset preserved?&lt;/li&gt;
&lt;li&gt;Which timezone database version did the producer use?&lt;/li&gt;
&lt;li&gt;What happens when a local time is ambiguous or does not exist?&lt;/li&gt;
&lt;li&gt;Who owns the definition of business date?&lt;/li&gt;
&lt;li&gt;Are late events assigned by occurrence time or arrival time?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;"All timestamps are UTC" answers only part of the first question.&lt;/p&gt;

&lt;h2&gt;
  
  
  Business dates deserve their own rule
&lt;/h2&gt;

&lt;p&gt;Many reporting errors happen after ingestion, when an instant becomes a date.&lt;/p&gt;

&lt;p&gt;Imagine a payment at &lt;code&gt;2026-10-10T23:30:00Z&lt;/code&gt;. In London, Kolkata, and Los Angeles it can belong to different local dates. If finance reports by the merchant's trading day while support reports by the customer's local day, both teams may be correct and still disagree.&lt;/p&gt;

&lt;p&gt;Do not let each dashboard rediscover the rule with a different call to &lt;code&gt;DATE()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Model the derived date and its ownership:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;occurred_at_utc
      +
merchant_timezone
      +
trading_day_cutoff
      ↓
merchant_business_date
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The cutoff may not be midnight. Hotels, transport operators, markets, and overnight venues often define an operating day around a business boundary. That is domain logic, not timestamp formatting.&lt;/p&gt;

&lt;p&gt;Keep the original instant so the date can be recomputed if the rule changes. If reports need to remain historically reproducible, version the rule or materialize the assigned business date with the rule version used.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test the conversion path, not just the parser
&lt;/h2&gt;

&lt;p&gt;A parser test proves that the string is syntactically valid. It does not prove that the pipeline preserved the same instant.&lt;/p&gt;

&lt;p&gt;I would build a small set of timezone fixtures and run them through every important boundary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;source payload
    ↓
application parser
    ↓
message or file serialization
    ↓
warehouse column
    ↓
transformation model
    ↓
reporting query
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The fixture set should include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a normal UTC value&lt;/li&gt;
&lt;li&gt;a non-whole-hour offset such as &lt;code&gt;+05:30&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;the hour before and after a daylight-saving transition&lt;/li&gt;
&lt;li&gt;an ambiguous local time during a fall-back transition&lt;/li&gt;
&lt;li&gt;a nonexistent local time during a spring-forward transition&lt;/li&gt;
&lt;li&gt;a historical date whose timezone rules differ from today&lt;/li&gt;
&lt;li&gt;a future scheduled time&lt;/li&gt;
&lt;li&gt;a source value with no timezone, if the contract permits one&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For every fixture, record the expected instant, local rendering, business date, and rejection behaviour. These values should come from an agreed reference, not from whatever the current test runtime happens to return.&lt;/p&gt;

&lt;p&gt;Snowflake's &lt;a href="https://docs.snowflake.com/en/sql-reference/functions/convert_timezone" rel="noopener noreferrer"&gt;&lt;code&gt;CONVERT_TIMEZONE&lt;/code&gt;&lt;/a&gt; documentation makes one source of accidental behaviour clear. A &lt;code&gt;TIMESTAMP_NTZ&lt;/code&gt; has no timezone, so the two-argument form interprets it using the current session timezone. A warehouse task and an analyst session can therefore produce different answers if their session parameters differ.&lt;/p&gt;

&lt;p&gt;Set session timezone configuration explicitly in pipelines. Then test it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add invariants that expose drift
&lt;/h2&gt;

&lt;p&gt;The first useful alert is not "the timestamp column is non-null." A wrong timestamp is usually non-null.&lt;/p&gt;

&lt;p&gt;I would monitor invariants such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the difference between &lt;code&gt;occurred_at&lt;/code&gt; and &lt;code&gt;recorded_at&lt;/code&gt; stays within an expected range&lt;/li&gt;
&lt;li&gt;source offsets match the named timezone for the supplied local date/time&lt;/li&gt;
&lt;li&gt;no new timezone names appear without contract approval&lt;/li&gt;
&lt;li&gt;parsing failures and ambiguous local times remain visible&lt;/li&gt;
&lt;li&gt;event counts by UTC hour and business hour remain within expected patterns&lt;/li&gt;
&lt;li&gt;source and target daily totals reconcile across the timezone boundary&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last check catches the classic failure where no rows disappear but several move into the previous or next day.&lt;/p&gt;

&lt;p&gt;Compare closed windows after an allowed lateness period. A live source count and a live warehouse count can differ because events are still in flight. Once the window closes, reconcile both total rows and the distribution around the day boundary.&lt;/p&gt;

&lt;p&gt;If a pipeline begins drifting only after a particular hour, compare its session timezone, runtime timezone database, source offset, and conversion function. The shape of the error often points toward the layer that changed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Treat timezone data like a dependency
&lt;/h2&gt;

&lt;p&gt;Timezone rules change. That means the database version used by the operating system, language runtime, database engine, and warehouse is part of the pipeline's dependency set.&lt;/p&gt;

&lt;p&gt;I would record those versions during deployment and expose them in diagnostics. When IANA publishes an update, test the zones relevant to the business before rolling it through every service. The goal is not to freeze old rules forever. It is to know when a conversion result changed and whether historical values need to remain reproducible.&lt;/p&gt;

&lt;p&gt;For future schedules, recalculate derived UTC instants when the governing timezone data changes. For completed events, preserve the original instant and source evidence. Those are different migration policies because future intent and historical fact are different things.&lt;/p&gt;

&lt;h2&gt;
  
  
  Store the meaning, not only the value
&lt;/h2&gt;

&lt;p&gt;UTC is the right representation for many event instants. I would still use it as the common ordering and transport form.&lt;/p&gt;

&lt;p&gt;I would not ask it to carry local business meaning that was discarded earlier in the pipeline.&lt;/p&gt;

&lt;p&gt;Name the concepts, preserve the source context that matters, define the conversion owner, and test the full route from producer to report. Then a timezone database update becomes a dependency change we can evaluate instead of a mystery that moves five hours of revenue into the wrong day.&lt;/p&gt;

&lt;p&gt;A timestamp is easy to store. Time is part of the domain model.&lt;/p&gt;

</description>
      <category>backend</category>
      <category>database</category>
      <category>dataengineering</category>
    </item>
    <item>
      <title>Design semantic views around business questions, not agents</title>
      <dc:creator>Sudeep Hazra</dc:creator>
      <pubDate>Mon, 05 Oct 2026 12:41:19 +0000</pubDate>
      <link>https://dev.to/sudeephazra/design-semantic-views-around-business-questions-not-agents-5e77</link>
      <guid>https://dev.to/sudeephazra/design-semantic-views-around-business-questions-not-agents-5e77</guid>
      <description>&lt;p&gt;A question in the October &lt;a href="https://www.reddit.com/r/dataengineering/comments/1wv3alk/monthly_general_discussion_oct_2026/" rel="noopener noreferrer"&gt;r/dataengineering discussion thread&lt;/a&gt; caught my attention. A team was building Snowflake Semantic Views for business users and could not decide how to divide them. Should each topic have its own view, or should several views be connected to an agent?&lt;/p&gt;

&lt;p&gt;That sounds like a Snowflake configuration question. It is really a modelling boundary question.&lt;/p&gt;

&lt;p&gt;I would build semantic views around coherent business questions and stable metric definitions. I would let the agent route users to those views. The agent is an interface and orchestration boundary. It should not determine where the business model begins and ends.&lt;/p&gt;

&lt;p&gt;This distinction matters because agents change quickly. Revenue, booking, inventory, and customer definitions usually need to outlive whichever chat interface happens to be fashionable this quarter.&lt;/p&gt;

&lt;h2&gt;
  
  
  A semantic view is a business contract
&lt;/h2&gt;

&lt;p&gt;Snowflake defines a &lt;a href="https://docs.snowflake.com/en/user-guide/views-semantic/overview" rel="noopener noreferrer"&gt;Semantic View&lt;/a&gt; as a schema-level object that maps business concepts to physical data. It contains logical tables, dimensions, facts, metrics, and the relationships between them. Cortex Analyst uses that metadata to generate SQL.&lt;/p&gt;

&lt;p&gt;The useful part is not that the model sees friendlier column names. The view decides what a term means.&lt;/p&gt;

&lt;p&gt;Consider a travel company asking for "booked revenue." That phrase needs decisions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does revenue use booking date, departure date, or payment date?&lt;/li&gt;
&lt;li&gt;Are cancelled bookings excluded?&lt;/li&gt;
&lt;li&gt;How are refunds represented?&lt;/li&gt;
&lt;li&gt;Which currency conversion rate applies?&lt;/li&gt;
&lt;li&gt;Is the metric additive across every dimension?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A description such as "total revenue from bookings" does not answer those questions. It merely gives the ambiguity a nicer label.&lt;/p&gt;

&lt;p&gt;The semantic view should capture the accepted definition and the valid join paths. That makes it closer to a versioned business contract than a prompt accessory.&lt;/p&gt;

&lt;p&gt;This is also why I would not start from the agent. An agent called &lt;code&gt;travel_assistant&lt;/code&gt; may need bookings today, customer value tomorrow, and disruption operations next month. If the semantic model copies the agent's current scope, every new use case pushes unrelated concepts into the same view.&lt;/p&gt;

&lt;h2&gt;
  
  
  Avoid one view per physical table
&lt;/h2&gt;

&lt;p&gt;Creating one semantic view for every warehouse table feels tidy. It preserves the physical architecture and gives each object a manageable size.&lt;/p&gt;

&lt;p&gt;It also asks the consumer to reconstruct the business model.&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;fact_booking&lt;/code&gt;, &lt;code&gt;fact_payment&lt;/code&gt;, &lt;code&gt;dim_route&lt;/code&gt;, and &lt;code&gt;dim_customer&lt;/code&gt; become four separate semantic views, where does "revenue by destination" live? Which object owns the join between bookings and payments? Which view defines whether a refund reduces booked revenue or appears as a separate measure?&lt;/p&gt;

&lt;p&gt;Snowflake's own &lt;a href="https://docs.snowflake.com/en/user-guide/views-semantic/overview#getting-started" rel="noopener noreferrer"&gt;getting-started guidance&lt;/a&gt; begins with business entities, their relationships, metrics, and dimensions. It then maps those concepts to physical data and recommends starting with a simple star schema. That sequence is important. The warehouse tables support the model; they do not define its audience.&lt;/p&gt;

&lt;p&gt;A useful semantic view may include several logical tables when they form one understandable analytical subject. For example:&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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F86khsw88g393dve78k1w.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F86khsw88g393dve78k1w.png" alt=" " width="799" height="626"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The boundary is not "all travel data." It is the set of concepts needed to answer booking performance questions with consistent grain and joins.&lt;/p&gt;

&lt;h2&gt;
  
  
  Avoid one giant enterprise view
&lt;/h2&gt;

&lt;p&gt;The opposite design is one semantic view that contains every table, metric, synonym, instruction, and verified query in the warehouse.&lt;/p&gt;

&lt;p&gt;This removes the routing decision by making every concept available everywhere. It also creates a model that few people can reason about.&lt;/p&gt;

&lt;p&gt;Large views accumulate ambiguous names. &lt;code&gt;status&lt;/code&gt; may refer to a booking, payment, flight, customer account, or support case. Relationships multiply, and valid join paths become harder to distinguish from technically possible ones. A change to one business area requires evaluating a shared object used by everyone else.&lt;/p&gt;

&lt;p&gt;There is a governance problem too. Different domains may have different owners, release schedules, and access rules. Putting them into one object couples those decisions. The result may be convenient for the first demo and awkward for every production change after it (the usual bargain).&lt;/p&gt;

&lt;p&gt;I would split the model when one or more of these conditions appear:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The questions serve different business decisions.&lt;/li&gt;
&lt;li&gt;The core facts have different grains.&lt;/li&gt;
&lt;li&gt;The metrics are owned and approved by different teams.&lt;/li&gt;
&lt;li&gt;The relationships between the subjects are optional or ambiguous.&lt;/li&gt;
&lt;li&gt;The access boundary is different.&lt;/li&gt;
&lt;li&gt;A change requires a separate evaluation and release cycle.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a travel platform, that might produce views such as &lt;code&gt;booking_performance&lt;/code&gt;, &lt;code&gt;customer_value&lt;/code&gt;, and &lt;code&gt;service_disruption&lt;/code&gt;. These are examples of business subjects, not a universal naming scheme.&lt;/p&gt;

&lt;p&gt;Some questions will cross those boundaries. If cross-domain questions are frequent and have agreed semantics, that is evidence for a composed view designed for that purpose. It is not a reason to merge everything in advance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let routing remain routing
&lt;/h2&gt;

&lt;p&gt;Snowflake's &lt;a href="https://docs.snowflake.com/en/user-guide/snowflake-cortex/cortex-analyst/rest-api" rel="noopener noreferrer"&gt;Cortex Analyst REST API&lt;/a&gt; accepts an array of semantic models or views. For each query, Cortex Analyst chooses the most appropriate one from the list. A client can therefore expose several focused views without asking the user to choose a data source before every question.&lt;/p&gt;

&lt;p&gt;That capability gives us a useful separation:&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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fiai87ha0zljb4fzo17gl.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fiai87ha0zljb4fzo17gl.png" alt=" " width="800" height="705"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Routing answers, "Which model can handle this question?"&lt;/p&gt;

&lt;p&gt;The semantic view answers, "What do these business terms mean, and which SQL relationships are allowed?"&lt;/p&gt;

&lt;p&gt;Combining those responsibilities makes both harder to test. When an answer is wrong, the team cannot easily tell whether the router chose the wrong domain, the view defined the metric incorrectly, or SQL generation failed within an otherwise correct model.&lt;/p&gt;

&lt;p&gt;There is one limit worth making explicit. Selecting from multiple views does not magically create a governed join across them. Snowflake's API documentation says Cortex Analyst chooses the most appropriate view for a query. If a single question genuinely needs concepts from two domains, model and test that combined analytical path rather than assuming the agent will invent it safely.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verified queries are tests, not decoration
&lt;/h2&gt;

&lt;p&gt;Descriptions and synonyms help, but a semantic view needs executable evidence.&lt;/p&gt;

&lt;p&gt;Snowflake supports verified queries that pair a natural-language question with expected SQL. Its &lt;a href="https://docs.snowflake.com/en/user-guide/snowflake-cortex/cortex-analyst-evaluations" rel="noopener noreferrer"&gt;Cortex Analyst evaluation&lt;/a&gt; runs generated SQL and compares the result with the verified query. During an evaluation, selected queries are removed from the temporary view so they cannot guide the generation they are supposed to test.&lt;/p&gt;

&lt;p&gt;That is a useful design. It turns a familiar business question into regression evidence instead of another prompt example that always looks correct in the demo.&lt;/p&gt;

&lt;p&gt;I would keep two sets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Guidance set
Questions that teach common language and approved patterns

Evaluation set
Questions held out to detect wrong SQL and regressions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The evaluation set should cover more than happy paths. Include common synonyms, ambiguous time periods, filters that previously failed, joins near the edge of the domain, and metrics that look similar but have different definitions.&lt;/p&gt;

&lt;p&gt;For &lt;code&gt;booking_performance&lt;/code&gt;, useful cases might include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;booked revenue for departures next month&lt;/li&gt;
&lt;li&gt;payments received this month for bookings created earlier&lt;/li&gt;
&lt;li&gt;cancelled passengers by route&lt;/li&gt;
&lt;li&gt;refund amount without double-counting partial refunds&lt;/li&gt;
&lt;li&gt;a question the view should decline because it belongs to service disruption&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last case matters. A reliable semantic boundary needs to say what it does not know.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ownership matters more than view count
&lt;/h2&gt;

&lt;p&gt;There is no correct number of semantic views. Three coherent views can be better than twelve, and twelve focused views can be better than one. Count is an outcome of the ownership model and question space.&lt;/p&gt;

&lt;p&gt;For each view, I would record:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the business owner who approves metric meaning&lt;/li&gt;
&lt;li&gt;the engineering owner who maintains mappings and relationships&lt;/li&gt;
&lt;li&gt;the source models and expected grain&lt;/li&gt;
&lt;li&gt;the questions it is designed to answer&lt;/li&gt;
&lt;li&gt;the questions it must reject or route elsewhere&lt;/li&gt;
&lt;li&gt;the verified-query guidance and evaluation sets&lt;/li&gt;
&lt;li&gt;the access roles and release process&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then I would monitor how the model behaves in use. Snowflake exposes an administrator view containing &lt;a href="https://docs.snowflake.com/en/user-guide/snowflake-cortex/cortex-analyst/admin-observability" rel="noopener noreferrer"&gt;Cortex Analyst requests across semantic models and views&lt;/a&gt;. That history can show questions that route poorly, common phrases missing from the model, and domains users try to combine.&lt;/p&gt;

&lt;p&gt;Production usage should inform a change, not write directly into the semantic contract. A frequent query may reveal a missing synonym. It may also reveal that two departments use the same phrase for different metrics. Someone still needs to decide which interpretation is valid.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with one subject and make it boring
&lt;/h2&gt;

&lt;p&gt;I would begin with one business subject that has an engaged owner, stable source data, and a small set of questions people already ask. Define the grain. Add the metrics and relationships needed for those questions. Build verified queries, keep some for evaluation, and measure the results.&lt;/p&gt;

&lt;p&gt;Only then add another subject and routing.&lt;/p&gt;

&lt;p&gt;The first success criterion is not that the agent can answer something impressive. It is that the same approved question produces the same correct result after the semantic view changes.&lt;/p&gt;

&lt;p&gt;Design the views around business meaning. Let agents discover and route to them. When the interface changes, the definitions should remain useful. That is a much better test of a semantic layer than whether the first chatbot demo looked clever.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>analytics</category>
      <category>architecture</category>
      <category>database</category>
    </item>
    <item>
      <title>Your data engineer job description is trying to hire a team</title>
      <dc:creator>Sudeep Hazra</dc:creator>
      <pubDate>Mon, 05 Oct 2026 12:18:58 +0000</pubDate>
      <link>https://dev.to/sudeephazra/your-data-engineer-job-description-is-trying-to-hire-a-team-5f1k</link>
      <guid>https://dev.to/sudeephazra/your-data-engineer-job-description-is-trying-to-hire-a-team-5f1k</guid>
      <description>&lt;p&gt;A recent &lt;a href="https://www.reddit.com/r/dataengineering/comments/1wu7drd/we_were_struggling_to_find_data_engineers/" rel="noopener noreferrer"&gt;r/dataengineering discussion&lt;/a&gt; started with a frustrating result. A company interviewed 22 people for a data engineering role and could not find a match. After working to create data teams &lt;em&gt;(specifically data engineering teams)&lt;/em&gt; for over a decade, I feel like this reflected a lot of the observations I also have had over these years. &lt;/p&gt;

&lt;p&gt;The job needed someone with software engineering fundamentals who could work on a lakehouse and integrate it with the company's main application. The requested stack included Spark, AWS Glue, Lambda, Airflow, and dbt. Some applicants had analytics backgrounds, some came from software engineering without Spark experience, and some qualified data engineers wanted more money than the budget allowed.&lt;/p&gt;

&lt;p&gt;The comments quickly became a debate about salary, training, and what the title "data engineer" is supposed to mean. That debate is useful because the hiring problem was not really a shortage of keywords. The role combined several kinds of ownership without saying which ones mattered most.&lt;/p&gt;

&lt;p&gt;If a team needs one person to model business data, build distributed processing jobs, write application integrations, manage cloud infrastructure, operate production pipelines, and know its exact toolchain, it may be trying to hire a small team through one job description.&lt;/p&gt;

&lt;p&gt;I would fix the role before adding another interview round.&lt;/p&gt;

&lt;h2&gt;
  
  
  A tool list does not describe the job
&lt;/h2&gt;

&lt;p&gt;Spark, Glue, Lambda, Airflow, and dbt can appear in the same platform. They do not represent one coherent skill.&lt;/p&gt;

&lt;p&gt;Someone may be excellent at dimensional modelling and dbt but have limited experience writing a service that exposes data to an application. Another engineer may understand distributed systems, IAM, deployment, and failure recovery but need a few weeks to learn Glue. A third may know every service name in the description because their last company used the same stack, yet struggle to explain what happens when a pipeline partially writes its output and retries.&lt;/p&gt;

&lt;p&gt;The third candidate often passes the keyword screen. The first two may be more useful once the system reaches production.&lt;/p&gt;

&lt;p&gt;AWS's &lt;a href="https://docs.aws.amazon.com/wellarchitected/latest/analytics-lens/design-principles-by-pillar.html" rel="noopener noreferrer"&gt;Data Analytics Lens&lt;/a&gt; is a good reminder of how broad production data work becomes. Its design principles cover operational excellence, security, reliability, performance, cost, and governance of data and metadata changes. Knowing the SDK call that starts a Glue job is a small part of that responsibility.&lt;/p&gt;

&lt;p&gt;I would define the role through the system it owns:&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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwzvnib5hg05mxe7mgz80.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwzvnib5hg05mxe7mgz80.png" alt=" " width="799" height="355"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then I would mark the boundaries this engineer owns directly, the boundaries they influence, and the boundaries another team operates.&lt;/p&gt;

&lt;p&gt;That makes the requirement much clearer. "Own ingestion through serving for the customer lakehouse" describes a job. "Must know Spark, Glue, Lambda, Airflow, dbt, Terraform, Kafka, and Snowflake" describes a shopping trip.&lt;/p&gt;

&lt;h2&gt;
  
  
  Software engineering fundamentals need a concrete meaning
&lt;/h2&gt;

&lt;p&gt;"Clean code" and "DRY" sound reasonable in a job description. They are also difficult to assess because two experienced engineers can disagree about both.&lt;/p&gt;

&lt;p&gt;For a data platform, I would translate software engineering fundamentals into observable behaviours:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Can the candidate split a pipeline into components with clear inputs and outputs?&lt;/li&gt;
&lt;li&gt;Can they make a write idempotent or explain why it cannot be?&lt;/li&gt;
&lt;li&gt;Can they test transformation logic without deploying the whole platform?&lt;/li&gt;
&lt;li&gt;Can they evolve a schema without surprising downstream consumers?&lt;/li&gt;
&lt;li&gt;Can they trace a failed run, identify the damaged data, and describe recovery?&lt;/li&gt;
&lt;li&gt;Can they review a change for security, cost, and operational impact?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These questions are closer to the work. They also give candidates from adjacent backgrounds a fair chance to show transferable judgement.&lt;/p&gt;

&lt;p&gt;Google's SRE guidance treats a data pipeline as a service. The &lt;a href="https://sre.google/workbook/data-processing/" rel="noopener noreferrer"&gt;Data Processing Pipelines chapter&lt;/a&gt; recommends defining customer-facing objectives, detecting freshness and correctness failures, documenting the system, and building a development lifecycle that catches problems before production. That is software engineering applied to data. It is not a particular folder structure or an argument about whether a helper function should contain six lines or eight.&lt;/p&gt;

&lt;p&gt;The interview should therefore test a change over time, not a snapshot of remembered syntax.&lt;/p&gt;

&lt;p&gt;Give the candidate a small pipeline design. Then change one assumption:&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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftjbmr04urp9xz1t69okn.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftjbmr04urp9xz1t69okn.png" alt=" " width="708" height="918"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The useful signal is how the candidate updates the design. Do they notice that corrections change the merge strategy? Do they separate freshness from correctness? Do they ask about deletion propagation, backups, and derived datasets? Do they add streaming infrastructure immediately, or first test whether frequent incremental batches meet the requirement?&lt;/p&gt;

&lt;p&gt;That conversation tells me more than asking someone to name five Spark transformations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decide what must be known on day one
&lt;/h2&gt;

&lt;p&gt;Every role has skills that cannot wait. The mistake is treating the entire current stack as one of them.&lt;/p&gt;

&lt;p&gt;I would divide requirements into three groups:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Group&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Required judgement&lt;/td&gt;
&lt;td&gt;Hard to acquire during onboarding&lt;/td&gt;
&lt;td&gt;Failure recovery, data correctness, security boundaries&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transferable implementation skill&lt;/td&gt;
&lt;td&gt;Needed, but not tied to one vendor&lt;/td&gt;
&lt;td&gt;Distributed processing, orchestration, SQL, testing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Local stack knowledge&lt;/td&gt;
&lt;td&gt;Can be learned with access and support&lt;/td&gt;
&lt;td&gt;Glue configuration, repository conventions, one dbt package&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This does not mean tools never matter. A team migrating a complex Spark estate may reasonably need someone who has debugged Spark executors under load. A three-month delivery deadline may leave little time for cloud onboarding. Those are constraints, and the job description should say so.&lt;/p&gt;

&lt;p&gt;The list becomes suspicious when every item is mandatory but none is identified as decisive.&lt;/p&gt;

&lt;p&gt;For a mid-level lakehouse role, I would usually make production pipeline ownership and solid programming ability mandatory. I would require depth in one distributed processing environment, not necessarily the exact managed service. I would treat the local orchestration and transformation tools as learnable unless the person will own them alone from their first week.&lt;/p&gt;

&lt;p&gt;If the team cannot support any learning, that is another requirement worth stating. It also means the role is more senior than the number of years in the description may suggest.&lt;/p&gt;

&lt;h2&gt;
  
  
  A better interview loop
&lt;/h2&gt;

&lt;p&gt;I would keep the loop small and connect every stage to a decision.&lt;/p&gt;

&lt;p&gt;The first conversation should establish scope. Ask the candidate to describe a data system they owned, where their responsibility started, where it ended, and what failure they were expected to handle. This prevents a familiar product name from standing in for actual ownership.&lt;/p&gt;

&lt;p&gt;The technical exercise should be a short design problem using a domain anyone can understand. Orders, invoices, or device readings are enough. Provide volumes, latency, and recovery requirements. Let the candidate ask for missing constraints.&lt;/p&gt;

&lt;p&gt;The code review should contain an ordinary pipeline bug: a non-idempotent retry, a timestamp interpreted in local time, an unbounded read, or a write that can expose partial output. Ask what they would change and how they would prove the fix.&lt;/p&gt;

&lt;p&gt;The operational discussion should begin with evidence:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The job is green.
The dashboard is missing yesterday's revenue.
What do you inspect first?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A good answer separates orchestration status from data correctness. It moves through source arrival, processing boundaries, output counts, freshness, and reconciliation. The exact monitoring product is secondary.&lt;/p&gt;

&lt;p&gt;Finally, include one learning question. Pick a tool the candidate has not used and provide a short piece of documentation. Ask them to explain how they would validate it in a proof of concept. This tests the skill the job will require repeatedly: learning the next service without pretending its logo is an architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  The role may still be too wide
&lt;/h2&gt;

&lt;p&gt;Improving the interview cannot repair an incoherent operating model.&lt;/p&gt;

&lt;p&gt;If one engineer is expected to own business definitions, application APIs, Spark performance, IAM, Terraform, on-call response, and stakeholder reporting, the team needs to choose. It can narrow the role, split the responsibilities, raise the seniority and compensation, or hire for potential and provide support.&lt;/p&gt;

&lt;p&gt;There is no interview technique that creates a candidate who is simultaneously specialised in every layer, available within the current budget, and productive without onboarding. Sometimes the market is giving feedback about the design of the job.&lt;/p&gt;

&lt;p&gt;My recommendation is to hire for the hardest judgement the team cannot teach quickly. Make the owned system explicit. Test engineering through realistic changes and failures. Treat vendor knowledge as a preference unless there is a concrete reason it must exist on day one.&lt;/p&gt;

&lt;p&gt;The goal is not to find someone who has seen the same collection of tools. It is to find someone who can keep the data system correct when the collection changes. That is a rational and practical approach to a never-ending cycle.&lt;/p&gt;

</description>
      <category>dataengineering</category>
      <category>recruitment</category>
      <category>interview</category>
    </item>
    <item>
      <title>Your Kafka cluster is healthy. But the event is still missing</title>
      <dc:creator>Sudeep Hazra</dc:creator>
      <pubDate>Sat, 26 Sep 2026 08:42:01 +0000</pubDate>
      <link>https://dev.to/sudeephazra/your-kafka-cluster-is-healthy-but-the-event-is-still-missing-5e18</link>
      <guid>https://dev.to/sudeephazra/your-kafka-cluster-is-healthy-but-the-event-is-still-missing-5e18</guid>
      <description>&lt;p&gt;A recent &lt;a href="https://www.reddit.com/r/dataengineering/comments/1w3pocx/monitoring_tools_available/" rel="noopener noreferrer"&gt;r/dataengineering question&lt;/a&gt; described a familiar setup: many microservices publish JSON events to Kafka, each project has an expected throughput, and the business wants an alert when those events stop arriving.&lt;/p&gt;

&lt;p&gt;The natural response is to look for a Kafka monitoring tool. That is useful, but it solves only part of the problem.&lt;/p&gt;

&lt;p&gt;A Kafka dashboard can tell you that the brokers are available, partitions have leaders, replicas are in sync, and consumers are not falling behind. Every panel can be green while an upstream service has stopped producing the one event the business needs.&lt;/p&gt;

&lt;p&gt;The system is healthy. The data product is not.&lt;/p&gt;

&lt;p&gt;That distinction changes what we need to monitor.&lt;/p&gt;

&lt;h2&gt;
  
  
  Kafka health and business health are different signals
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://kafka.apache.org/43/operations/monitoring/" rel="noopener noreferrer"&gt;Apache Kafka's monitoring guidance&lt;/a&gt; covers the platform well. It recommends watching message and byte rates, request latency, fetch rates, replica state, and consumer lag. Those metrics answer important operational questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Can producers reach the cluster?&lt;/li&gt;
&lt;li&gt;Are brokers accepting requests?&lt;/li&gt;
&lt;li&gt;Are replicas keeping up?&lt;/li&gt;
&lt;li&gt;Are consumers processing records fast enough?&lt;/li&gt;
&lt;li&gt;Is one partition behaving differently from the others?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They do not answer whether the expected &lt;code&gt;invoice.created&lt;/code&gt; event arrived for a particular market during the last fifteen minutes.&lt;/p&gt;

&lt;p&gt;Consumer lag is a good example. A lag of zero sounds healthy. It can also mean that no records were produced. If the source application failed before publishing, the consumer has nothing to read and therefore nothing to lag behind.&lt;/p&gt;

&lt;p&gt;The same problem appears with throughput. An aggregate topic rate may look normal while one tenant, event type, or producer has gone silent. A busy topic can hide a very specific outage.&lt;/p&gt;

&lt;p&gt;I would separate monitoring into four layers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Kafka infrastructure
        ↓
Message flow
        ↓
Event contract
        ↓
Business expectation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each layer needs different evidence and a different owner.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 1: prove the platform can carry messages
&lt;/h2&gt;

&lt;p&gt;The first layer is normal Kafka operations. Watch broker availability, request errors, under-replicated partitions, ISR changes, disk pressure, network saturation, produce latency, fetch latency, and authentication failures.&lt;/p&gt;

&lt;p&gt;This is where JMX exporters, a managed Kafka metrics API, Prometheus, Grafana, or an observability platform fit. The product matters less than collecting the right broker and client signals.&lt;/p&gt;

&lt;p&gt;Consumer lag belongs here too, although I would treat it as a flow signal rather than a complete service-level indicator. Kafka exposes &lt;code&gt;records-lag&lt;/code&gt;, &lt;code&gt;records-lag-max&lt;/code&gt;, fetch rates, commit latency, and time between polls. Those metrics can identify a slow or stuck consumer.&lt;/p&gt;

&lt;p&gt;An alert such as this is useful:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;consumer lag is increasing
AND
records consumed per second is below the normal range
FOR ten minutes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It tells us that records exist and the consumer is not keeping up. It still says nothing about records that were never published.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 2: prove messages move through the expected path
&lt;/h2&gt;

&lt;p&gt;The next layer measures flow at boundaries. For every important producer and consumer, record a small set of counters and timestamps:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;events produced
events accepted by Kafka
events consumed
events processed successfully
events rejected or sent to a dead-letter path
timestamp of the newest event
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The labels need care. &lt;code&gt;service&lt;/code&gt;, &lt;code&gt;event_type&lt;/code&gt;, &lt;code&gt;environment&lt;/code&gt;, and perhaps &lt;code&gt;region&lt;/code&gt; are usually manageable. Raw customer IDs, transaction IDs, and message IDs are not. Putting high-cardinality business identifiers into a metrics system is an efficient way to turn an observability improvement into a cost incident.&lt;/p&gt;

&lt;p&gt;Keep per-message identifiers in logs or traces. Keep metrics dimensions bounded.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://opentelemetry.io/docs/specs/semconv/registry/attributes/messaging/" rel="noopener noreferrer"&gt;OpenTelemetry's messaging conventions&lt;/a&gt; provide common attributes for messaging systems, destinations, operations, consumer groups, and message context. The conventions are still marked as development in several areas, so I would pin the version used by the instrumentation instead of assuming the attribute names will never change.&lt;/p&gt;

&lt;p&gt;Tracing is helpful when a business operation crosses several services. A correlation or conversation ID can connect the API request, producer span, Kafka operation, consumer span, and downstream write. That gives an engineer a route through the failure instead of a collection of unrelated charts.&lt;/p&gt;

&lt;p&gt;Tracing every message may be too expensive at high volume. Sampling is reasonable for diagnostics. Counts and freshness metrics should remain complete because they drive alerts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 3: prove the event is usable
&lt;/h2&gt;

&lt;p&gt;Arrival is not success.&lt;/p&gt;

&lt;p&gt;A producer can publish malformed JSON at the expected rate. A serializer can omit a required field. A schema change can preserve valid syntax while changing the meaning of a value. The throughput chart will look excellent right up to the point where someone opens the downstream report.&lt;/p&gt;

&lt;p&gt;Validate the contract where ownership is clearest. That may be in the producer before publish, in a schema registry, or at the consumer boundary. Record at least:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;schema validation failures&lt;/li&gt;
&lt;li&gt;unsupported schema versions&lt;/li&gt;
&lt;li&gt;deserialization failures&lt;/li&gt;
&lt;li&gt;required-field failures&lt;/li&gt;
&lt;li&gt;duplicate or out-of-order events when those conditions matter&lt;/li&gt;
&lt;li&gt;dead-letter volume and age&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not collapse all of these into &lt;code&gt;processing_error_total&lt;/code&gt;. The response to a broken schema is different from the response to a timeout writing to a database.&lt;/p&gt;

&lt;p&gt;I would also resist putting the full payload into observability events. It creates a second, poorly governed copy of potentially sensitive data. Record the failure category, event type, schema version, and a safe lookup reference. Keep payload access inside the data platform's normal security boundary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 4: encode the business expectation
&lt;/h2&gt;

&lt;p&gt;This is the part a Kafka dashboard cannot infer.&lt;/p&gt;

&lt;p&gt;"Expected TPS" sounds like a threshold, but a single number is rarely enough. Traffic changes by hour, weekday, region, and business calendar. Some event types are continuous. Others arrive in bursts after a batch closes. A flat minimum can create noise during quiet periods and miss a partial outage during busy ones.&lt;/p&gt;

&lt;p&gt;Represent the expectation explicitly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;event_type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;invoice.created&lt;/span&gt;
&lt;span class="na"&gt;producer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;billing-service&lt;/span&gt;
&lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;production&lt;/span&gt;
&lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;weekdays 08:00-20:00 Europe/London&lt;/span&gt;
&lt;span class="na"&gt;minimum_events&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;300&lt;/span&gt;
&lt;span class="na"&gt;window&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5m&lt;/span&gt;
&lt;span class="na"&gt;maximum_silence&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10m&lt;/span&gt;
&lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;billing-platform&lt;/span&gt;
&lt;span class="na"&gt;severity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;high&lt;/span&gt;
&lt;span class="na"&gt;runbook&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://example.internal/runbooks/invoice-events&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is configuration, not dashboard decoration. Put it in version control. Review changes. Give each expectation an owner.&lt;/p&gt;

&lt;p&gt;The evaluation process can be simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;expected event rules
        +
observed event counters and freshness
        ↓
evaluation job
        ↓
pass, warn, fail, or no-data
        ↓
alert with owner and runbook
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;no-data&lt;/code&gt; state matters. Monitoring systems often treat a missing time series differently from a zero value. Your evaluator must decide what absence means for each rule. &lt;a href="https://prometheus.io/docs/prometheus/latest/configuration/alerting_rules/" rel="noopener noreferrer"&gt;Prometheus alerting rules&lt;/a&gt; support duration-based conditions through &lt;code&gt;for&lt;/code&gt;, which helps avoid paging on a brief gap. The business rule still has to define how long a gap is acceptable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reconcile counts instead of trusting one point
&lt;/h2&gt;

&lt;p&gt;For important flows, compare counts across boundaries over the same business window:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;producer accepted:  100,000
Kafka observed:      99,998
consumer processed:  99,990
target committed:    99,987
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those numbers do not prove which eight or thirteen records are missing, but they tell you where to investigate.&lt;/p&gt;

&lt;p&gt;Use event IDs or business keys for periodic reconciliation when exact completeness matters. Counters are operational signals. Reconciliation is evidence.&lt;/p&gt;

&lt;p&gt;The window must account for retries and late arrivals. Comparing two live counters at the current second will generate false differences because each stage observes the event at a different time. Close a window, allow a defined lateness period, then evaluate it. Streaming does not remove the need for accounting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Alert the team that can act
&lt;/h2&gt;

&lt;p&gt;The Kafka platform team owns broker capacity, replication, cluster access, and platform availability. The producer team owns whether it emits the promised event. The consumer team owns processing and downstream delivery. A data product owner may own the business expectation.&lt;/p&gt;

&lt;p&gt;Sending every alert to the Kafka team recreates the old middleware support queue with newer software.&lt;/p&gt;

&lt;p&gt;An alert should include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;what expectation failed&lt;/li&gt;
&lt;li&gt;when the last valid event arrived&lt;/li&gt;
&lt;li&gt;expected and observed counts&lt;/li&gt;
&lt;li&gt;the affected producer, topic, event type, and consumer&lt;/li&gt;
&lt;li&gt;whether Kafka itself is healthy&lt;/li&gt;
&lt;li&gt;the owning team&lt;/li&gt;
&lt;li&gt;the first diagnostic query or dashboard&lt;/li&gt;
&lt;li&gt;the runbook&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;"Kafka events missing" is not enough. "&lt;code&gt;invoice.created&lt;/code&gt; from &lt;code&gt;billing-service&lt;/code&gt; has been silent for 12 minutes; brokers are healthy and other producers are active" is actionable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with the important flows
&lt;/h2&gt;

&lt;p&gt;I would not begin by buying a broad data-observability platform or instrumenting every event type. Start with the ten flows whose absence causes money, compliance, or customer impact.&lt;/p&gt;

&lt;p&gt;For each one, define:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The event contract.&lt;/li&gt;
&lt;li&gt;The expected schedule and volume.&lt;/li&gt;
&lt;li&gt;The maximum acceptable silence.&lt;/li&gt;
&lt;li&gt;The producer and consumer owners.&lt;/li&gt;
&lt;li&gt;The reconciliation rule.&lt;/li&gt;
&lt;li&gt;The response when the rule fails.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Then use the monitoring stack already in place. Add a new product only when the existing stack cannot express, evaluate, or route these rules without unreasonable work.&lt;/p&gt;

&lt;p&gt;The useful dashboard is not the one with the most Kafka metrics. It is the one that can distinguish a broken broker, a slow consumer, an invalid event, and a producer that stopped doing its job.&lt;/p&gt;

&lt;p&gt;Kafka health is necessary. The business still needs proof that the right event arrived.&lt;/p&gt;

</description>
      <category>dataengineering</category>
      <category>kafka</category>
      <category>microservices</category>
      <category>monitoring</category>
    </item>
    <item>
      <title>Your agent does not need to write an essay to make a decision</title>
      <dc:creator>Sudeep Hazra</dc:creator>
      <pubDate>Sat, 26 Sep 2026 08:15:32 +0000</pubDate>
      <link>https://dev.to/sudeephazra/your-agent-does-not-need-to-write-an-essay-to-make-a-decision-1aen</link>
      <guid>https://dev.to/sudeephazra/your-agent-does-not-need-to-write-an-essay-to-make-a-decision-1aen</guid>
      <description>&lt;p&gt;Something interesting appeared in a recent &lt;a href="https://www.reddit.com/r/LocalLLaMA/comments/1wgcpww/biweekly_megathread_project_showcase/" rel="noopener noreferrer"&gt;LocalLLaMA project showcase&lt;/a&gt;. Several projects were exploring small models that do not generate prose. They take a state, a question, and a fixed set of choices, then return a probability for each choice.&lt;/p&gt;

&lt;p&gt;That sounds less impressive than an autonomous agent narrating its plan. It may also be exactly what many agent loops need.&lt;/p&gt;

&lt;p&gt;Consider the decisions inside a typical tool-using workflow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which tool should handle this request?&lt;/li&gt;
&lt;li&gt;Is this command safe enough to run automatically?&lt;/li&gt;
&lt;li&gt;Did the previous step succeed?&lt;/li&gt;
&lt;li&gt;Should the workflow retry, stop, or ask a person?&lt;/li&gt;
&lt;li&gt;Which retrieved document should be examined next?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are closed-set decisions. Asking a generative model to produce a paragraph, parsing that paragraph, and hoping it stayed inside the allowed choices is a surprisingly elaborate way to select one value from an enum.&lt;/p&gt;

&lt;p&gt;My current view is simple: use generative models for open-ended reasoning and content. Use constrained decision components when the answer space is already known.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generation and decision are different jobs
&lt;/h2&gt;

&lt;p&gt;A generative model is useful when the output cannot be enumerated in advance. Writing a migration plan, diagnosing an unfamiliar failure, explaining a trade-off, or proposing a patch all need flexible output.&lt;/p&gt;

&lt;p&gt;Routing a ticket to one of twelve queues does not.&lt;/p&gt;

&lt;p&gt;We often use the same large model for both jobs because the API is convenient. The model receives a prompt such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Choose one action: retry, stop, escalate.
Return JSON only.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application then validates the response, repairs malformed JSON, rejects invented actions, and perhaps asks the same model to try again. We added a language generator, then built a fence around its language generation.&lt;/p&gt;

&lt;p&gt;Structured output improves the interface, but it does not change the underlying job. The system still generates tokens to choose from a fixed set.&lt;/p&gt;

&lt;p&gt;Projects such as &lt;a href="https://github.com/Contrastive-LM/CLM" rel="noopener noreferrer"&gt;Contrastive Language Models&lt;/a&gt; approach the problem differently. CLM encodes the current state and candidate actions, scores their relationship, and returns a distribution over the supplied actions. The repository describes uses such as tool routing, trajectory ranking, and retrieval shortlisting.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Mapika/decider" rel="noopener noreferrer"&gt;Decider&lt;/a&gt; takes another route. It reads a state and typed questions, then produces probabilities for fixed choices, yes-or-no decisions, or described score levels in one pass. Its core promise is deliberately narrow: no free-form decoding and no output outside the choices supplied by the application.&lt;/p&gt;

&lt;p&gt;These projects are early and their benchmark claims need independent validation. The architecture is still worth paying attention to.&lt;/p&gt;

&lt;h2&gt;
  
  
  The application should own the choice set
&lt;/h2&gt;

&lt;p&gt;In a production agent, the model should not decide which actions exist. The application should.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;application state
        +
allowed actions
        ↓
decision component
        ↓
probabilities
        ↓
policy and threshold
        ↓
execute, ask, or stop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That boundary gives us several useful properties.&lt;/p&gt;

&lt;p&gt;First, impossible actions stay impossible. If &lt;code&gt;delete_customer&lt;/code&gt; is not in the candidate set, the decision component cannot select it. This is stronger than telling a model in a prompt not to mention deletion.&lt;/p&gt;

&lt;p&gt;Second, the result is inspectable. A distribution such as this is easier to evaluate than a persuasive paragraph:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"retry"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.18&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"stop"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.07&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"escalate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.75&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Third, the application can apply policy after inference. A high score is evidence, not authority.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;decision&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;retry&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;probability&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.90&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;retry_count&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;retry&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;request_review&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model proposes. Code decides what that proposal is allowed to do.&lt;/p&gt;

&lt;h2&gt;
  
  
  Probability is useful only when it is calibrated
&lt;/h2&gt;

&lt;p&gt;A number that looks like confidence is not automatically a trustworthy probability.&lt;/p&gt;

&lt;p&gt;If a model assigns roughly 0.8 to one hundred decisions, we would like about eighty of them to be correct. That is calibration. A model can have good top-choice accuracy and still be overconfident, which makes threshold-based automation dangerous.&lt;/p&gt;

&lt;p&gt;This is where a typed decision interface improves the engineering conversation. We can measure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;accuracy by decision type&lt;/li&gt;
&lt;li&gt;false-allow and false-deny rates&lt;/li&gt;
&lt;li&gt;calibration error&lt;/li&gt;
&lt;li&gt;coverage at a chosen threshold&lt;/li&gt;
&lt;li&gt;latency and cost per decision&lt;/li&gt;
&lt;li&gt;performance when none of the supplied choices is correct&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The last case is easy to miss. A closed set can be wrong. If the workflow offers &lt;code&gt;retry&lt;/code&gt;, &lt;code&gt;stop&lt;/code&gt;, and &lt;code&gt;escalate&lt;/code&gt;, but the correct action is &lt;code&gt;refresh_credentials&lt;/code&gt;, the model cannot repair the application's incomplete choice set.&lt;/p&gt;

&lt;p&gt;Include an &lt;code&gt;unknown&lt;/code&gt; or &lt;code&gt;none_of_the_above&lt;/code&gt; option where appropriate. More importantly, evaluate whether it is selected when the input falls outside the known cases.&lt;/p&gt;

&lt;p&gt;The threshold also depends on consequence. A 0.75 routing decision may be acceptable for choosing a knowledge-base category. It is not enough evidence to run a destructive infrastructure command.&lt;/p&gt;

&lt;h2&gt;
  
  
  Guardrails belong around the tool, not inside the prompt
&lt;/h2&gt;

&lt;p&gt;The decision model is not a security boundary.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/openai/openai-agents-python/blob/main/docs/guardrails.md" rel="noopener noreferrer"&gt;OpenAI Agents SDK guardrail documentation&lt;/a&gt; makes a useful separation: tool input guardrails run before execution, tool output guardrails run after execution, and either can reject content or halt the run. The SDK also documents important coverage limits for different tool types.&lt;/p&gt;

&lt;p&gt;The exact framework is less important than the placement. Authorization and safety checks must sit on the execution path.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;model chooses candidate action
        ↓
schema validation
        ↓
authorization check
        ↓
risk policy
        ↓
human approval if required
        ↓
tool execution
        ↓
result validation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do not ask the model, "Is the user allowed to delete this resource?" when the target system can answer that question from its own permission model. Do not treat a 0.99 probability as permission. Use identity, authorization, resource state, and explicit policy.&lt;/p&gt;

&lt;p&gt;A decision component can help classify risk or select a route. It cannot grant authority the caller does not have.&lt;/p&gt;

&lt;h2&gt;
  
  
  Small decisions can make large agents easier to operate
&lt;/h2&gt;

&lt;p&gt;Agent diagrams tend to focus on the large reasoning step. Production failures often happen in the small transitions around it.&lt;/p&gt;

&lt;p&gt;Should the agent retry after a timeout? Was the tool response complete? Does the evidence support the conclusion? Is the next operation reversible? Those decisions determine whether an isolated model error becomes a repeated side effect.&lt;/p&gt;

&lt;p&gt;Breaking them out creates observable control points:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;request
   ↓
route decision
   ↓
reasoning or retrieval
   ↓
action decision
   ↓
policy check
   ↓
tool
   ↓
success decision
   ↓
continue or stop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each decision can have its own dataset, threshold, fallback, and owner. That is less magical than one agent prompt. It is also much easier to test.&lt;/p&gt;

&lt;p&gt;This direction fits the broader advice in Anthropic's &lt;a href="https://www.anthropic.com/engineering/building-effective-agents" rel="noopener noreferrer"&gt;Building effective agents&lt;/a&gt;: start with the simplest workable pattern, prefer predefined workflows when the path is known, and add agent autonomy when flexibility justifies the added cost and risk.&lt;/p&gt;

&lt;p&gt;The interesting part is not replacing every generative call with a classifier. It is noticing which calls were never generative problems in the first place.&lt;/p&gt;

&lt;h2&gt;
  
  
  Evaluation needs real workflow data
&lt;/h2&gt;

&lt;p&gt;A benchmark can tell us whether a model distinguishes choices in a prepared dataset. It cannot tell us whether those choices represent the messy states in our system.&lt;/p&gt;

&lt;p&gt;I would build the evaluation set from production-shaped examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;normal cases that should pass automatically&lt;/li&gt;
&lt;li&gt;ambiguous cases that should request review&lt;/li&gt;
&lt;li&gt;rare failures that previously caused incidents&lt;/li&gt;
&lt;li&gt;adversarial input that tries to steer the decision&lt;/li&gt;
&lt;li&gt;stale or incomplete context&lt;/li&gt;
&lt;li&gt;cases where the correct choice is absent&lt;/li&gt;
&lt;li&gt;repeated attempts after a tool failure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Label them with the decision that the workflow should take, not the explanation we hope the model writes.&lt;/p&gt;

&lt;p&gt;Then compare at least three baselines:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Deterministic rules.&lt;/li&gt;
&lt;li&gt;A general generative model with structured output.&lt;/li&gt;
&lt;li&gt;A specialized or constrained decision model.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Rules may win. For example, &lt;code&gt;retry_count &amp;gt;= 2&lt;/code&gt; does not need inference. A model becomes useful when the state contains language or evidence that code cannot classify reliably with a small, stable rule set.&lt;/p&gt;

&lt;p&gt;Run shadow evaluations before granting automation. Record the model's decision and probability without executing it. Compare that output with the actual operator or workflow decision. This shows where a threshold would automate safely and where it would merely automate confidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do not create a second platform by accident
&lt;/h2&gt;

&lt;p&gt;There is a predictable failure mode here. A team sees specialized decision models, creates a separate serving stack, a feature store, a training pipeline, an evaluation service, and a new control plane before proving that one decision benefits from any of it.&lt;/p&gt;

&lt;p&gt;Start with one bounded choice that occurs frequently enough to matter.&lt;/p&gt;

&lt;p&gt;Tool routing is a reasonable candidate. Define the allowed tools, create a representative test set, compare rules and models, and measure error cost. Serve a smaller model only if it produces a material latency, cost, privacy, or accuracy benefit.&lt;/p&gt;

&lt;p&gt;For low-volume workflows, a general model with strict structured output may remain the simplest operational choice. Fewer services can be worth a little extra inference cost.&lt;/p&gt;

&lt;p&gt;The recommendation changes when decision calls dominate the loop. If an agent makes dozens of small choices per task, generation latency and token cost accumulate. A fast decision component can then reduce both, while giving the application a consistent probability interface.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep the agent for the parts that need an agent
&lt;/h2&gt;

&lt;p&gt;Open-ended work still benefits from capable generative models. A decision model will not investigate an unfamiliar production failure, compare architecture options, or write a useful migration plan.&lt;/p&gt;

&lt;p&gt;It can decide which diagnostic to run next from an approved list. It can estimate whether the evidence supports another step. It can route uncertainty to a person before the workflow turns a weak guess into an action.&lt;/p&gt;

&lt;p&gt;That is enough.&lt;/p&gt;

&lt;p&gt;An agent does not become less intelligent because some decisions move into smaller, constrained components. The system becomes clearer about where intelligence is needed, where policy belongs, and where ordinary code is still the better tool.&lt;/p&gt;

&lt;p&gt;If the answer must be one of five choices, make the system choose one of five choices. Save the essay for the part that needs an explanation.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>llm</category>
    </item>
    <item>
      <title>Before You Add an Event Bus, Name the Failure</title>
      <dc:creator>Sudeep Hazra</dc:creator>
      <pubDate>Mon, 21 Sep 2026 11:53:00 +0000</pubDate>
      <link>https://dev.to/sudeephazra/before-you-add-an-event-bus-name-the-failure-25k</link>
      <guid>https://dev.to/sudeephazra/before-you-add-an-event-bus-name-the-failure-25k</guid>
      <description>&lt;p&gt;A &lt;a href="https://www.reddit.com/r/softwarearchitecture/comments/1ucjpmv/has_eventdriven_architecture_become_the_new/" rel="noopener noreferrer"&gt;discussion about event-driven architecture&lt;/a&gt; caught my attention because the replies disagreed on something more useful than Kafka versus a database. People were describing different problems with the same word: &lt;em&gt;event&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;One team needs to run a slow task after a transaction. Another needs to tell several independent systems that a business fact has changed. A third needs an audit history it can replay. All three may produce a message, but they have different requirements for ownership, ordering, and recovery.&lt;/p&gt;

&lt;p&gt;I would start by naming the failure that the current design cannot handle. If that failure is a web request waiting on slow work, a database-backed job may be enough. If independent consumers need a durable record of a business change, publishing an event may be worth the extra machinery. That gives the team a reason for the extra component.&lt;/p&gt;

&lt;h2&gt;
  
  
  A slow operation is not automatically an event
&lt;/h2&gt;

&lt;p&gt;Imagine an application that accepts an order and generates a PDF receipt. The user needs to know whether the order was accepted. They do not need to hold the connection open while the PDF renderer runs.&lt;/p&gt;

&lt;p&gt;The first design I would test is simple:&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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fppda9h5ozscifqa9eg4k.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fppda9h5ozscifqa9eg4k.png" alt="order_processing_workflow" width="799" height="245"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The order and job are written in one database transaction. If the transaction rolls back, neither exists. If the renderer fails, the job remains visible for a retry or investigation. The application can show the user an order state and a separate receipt state. There is no broker to operate merely because work happens later.&lt;/p&gt;

&lt;p&gt;That job still needs engineering. Give it a stable ID, an attempt count, a timeout, and a clear terminal failure state. Decide how a worker claims a job and how another worker takes over after a crash. PostgreSQL documents &lt;a href="https://www.postgresql.org/docs/current/sql-select.html" rel="noopener noreferrer"&gt;&lt;code&gt;SKIP LOCKED&lt;/code&gt;&lt;/a&gt; as useful for avoiding contention among consumers of a queue-like table, while warning that it gives an inconsistent view of rows. That is a tool for workers, not a way to make arbitrary reporting queries faster.&lt;/p&gt;

&lt;p&gt;A database queue has limits. Heavy fan-out, many unrelated consumers, a large backlog, or retention far beyond the operational life of a job can make the table and its cleanup awkward. Those are measurable reasons to consider messaging infrastructure. “We want async” is not yet one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Publish facts when other owners need them
&lt;/h2&gt;

&lt;p&gt;Now add a finance service, a customer notification service, and a fulfillment system. Each has its own release schedule and its own idea of what to do when an order is paid. The order service should not need to call all three during the payment request and wait for their health before it can record payment.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;OrderPaid&lt;/code&gt; can be a useful event here. It says something that has happened, with an order ID, event ID, and version of the contract. The consumers decide what that fact means for their own systems. This is a better reason for an event than the mere existence of multiple functions in one application.&lt;/p&gt;

&lt;p&gt;The awkward part is the boundary between the order database and the broker. This code is unsafe:&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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3rqb6bw7rrqrc5qbjqvt.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3rqb6bw7rrqrc5qbjqvt.png" alt="process_order_payment" width="684" height="471"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The process can die between those lines. Reversing the lines creates the opposite problem: a consumer may act on an event for a payment that never committed. &lt;a href="https://docs.aws.amazon.com/prescriptive-guidance/latest/cloud-design-patterns/transactional-outbox.html" rel="noopener noreferrer"&gt;AWS describes the transactional outbox pattern&lt;/a&gt; for this dual-write problem. Store the business update and an outbox record in the same transaction; a separate publisher sends committed records to the broker.&lt;/p&gt;

&lt;p&gt;The outbox improves the handoff. It does not make the rest of the system atomic. The publisher can send an event and crash before marking it delivered. The consumer must cope with that event arriving again. A stable event ID and a consumer-side record of processed IDs are more useful than a diagram that promises “exactly once” without saying where the guarantee ends.&lt;/p&gt;

&lt;p&gt;For example, a notification consumer can store &lt;code&gt;OrderPaid:event-123&lt;/code&gt; before or with the state change that schedules an email. If it sees the same event again, it can skip the duplicate scheduling step. A payment consumer has a higher bar: it must establish whether an external charge already succeeded before retrying. Its idempotency boundary is the payment provider and the local record together, not only the broker.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decide what the user must know now
&lt;/h2&gt;

&lt;p&gt;Asynchrony changes the product contract. Suppose checkout returns “payment complete” immediately, but fulfillment might reject the order ten minutes later because stock ran out. That may be a valid business process, but the UI and support team need to know what “complete” meant.&lt;/p&gt;

&lt;p&gt;For each step, I would write down three states:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Question&lt;/th&gt;
&lt;th&gt;Example answer&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;What is committed before the response?&lt;/td&gt;
&lt;td&gt;Payment authorization and order record.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What can finish later?&lt;/td&gt;
&lt;td&gt;Receipt generation and loyalty points.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What happens if later work fails?&lt;/td&gt;
&lt;td&gt;Retry, visible pending state, then support action.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If the next operation must succeed before the user can proceed, a synchronous API call may be easier to reason about. An event can still be emitted afterward for observers. If the user can wait, an accepted response plus a status endpoint can make the delay explicit. &lt;a href="https://docs.aws.amazon.com/prescriptive-guidance/latest/modernization-integrating-microservices/asynchronous.html" rel="noopener noreferrer"&gt;AWS's asynchronous communication guidance&lt;/a&gt; describes that claim-check approach: acknowledge the request, return an identifier, and let the client retrieve the later result.&lt;/p&gt;

&lt;p&gt;The delay has a user-facing cost: the text on a screen, the retry a user may press, and the question a support engineer has to answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Delivery guarantees do not finish the design
&lt;/h2&gt;

&lt;p&gt;The team needs to decide what duplicates or ordering would do to the business operation before selecting a broker. A standard Amazon SQS queue, for example, delivers messages &lt;a href="https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/standard-queues-at-least-once-delivery.html" rel="noopener noreferrer"&gt;at least once&lt;/a&gt;. The same message may be received again. Its standard queue type also gives &lt;a href="https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-queue-types.html" rel="noopener noreferrer"&gt;best-effort ordering&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Those properties are fine for many tasks. A consumer updating a current projection can ignore an event it has already processed. A consumer applying account balance deltas cannot casually apply the same delta twice. It may also need per-account ordering. Picking a FIFO queue can help with ordering and broker deduplication, but it does not remove the need to reason about retries across external systems and the full business workflow.&lt;/p&gt;

&lt;p&gt;I would ask the following before choosing the transport:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Is this a command for one worker, or a fact several owners may consume?&lt;/li&gt;
&lt;li&gt;What state is committed when the message is created?&lt;/li&gt;
&lt;li&gt;Can the same message be processed twice without damage?&lt;/li&gt;
&lt;li&gt;Must messages be ordered globally, per entity, or not at all?&lt;/li&gt;
&lt;li&gt;How long must a consumer be able to recover after an outage?&lt;/li&gt;
&lt;li&gt;Who notices a stuck consumer, and how is the backlog cleared?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The answers narrow the choice considerably. A one-owner receipt job does not need the same infrastructure as a company-wide change stream. A durable business event deserves more thought than a callback disguised as a topic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Operate the failure path before scaling the happy path
&lt;/h2&gt;

&lt;p&gt;Events make producers and consumers independent in useful ways. They also make a transaction harder to follow across time. An API request may be complete while the business process is still pending in another service. A dashboard that shows only broker throughput cannot tell you whether orders are actually progressing.&lt;/p&gt;

&lt;p&gt;I would give each event a correlation ID, a producer timestamp, a contract version, and an owning service. Then I would measure the delay from publication to the consumer's business outcome, not merely the time until a message is read. A dead-letter queue is a holding area for work that failed repeatedly. It is not a recovery plan until someone owns inspection, correction, and replay.&lt;/p&gt;

&lt;p&gt;Changes to the event contract need similar care. If consumers deploy independently, a producer cannot assume everyone upgrades on the same day. Add fields in a compatible way, document their meaning, and test old consumers against new payloads. When the meaning of a fact changes, a new event version may be clearer than silently reusing an old name.&lt;/p&gt;

&lt;p&gt;This is the cost side of the architecture decision. If a system has one application and one database, a job table may give the team all the separation it needs. If several autonomous systems depend on the same business fact and must recover independently, the broker, outbox, consumer idempotency, and operational tooling earn their place.&lt;/p&gt;

&lt;p&gt;My rule is to write down the failure and the recovery path first. Once those are concrete, the decision between a worker, an API call, and an event is usually much less mysterious.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>softwareengineering</category>
      <category>systemdesign</category>
    </item>
    <item>
      <title>What Stays in Secrets Manager After Workload Identity?</title>
      <dc:creator>Sudeep Hazra</dc:creator>
      <pubDate>Mon, 21 Sep 2026 11:41:03 +0000</pubDate>
      <link>https://dev.to/sudeephazra/what-stays-in-secrets-manager-after-workload-identity-3e5a</link>
      <guid>https://dev.to/sudeephazra/what-stays-in-secrets-manager-after-workload-identity-3e5a</guid>
      <description>&lt;p&gt;I liked the question in a recent &lt;a href="https://www.reddit.com/r/devops/comments/1vzyd12/after_moving_to_workload_identity_whats_left_in/" rel="noopener noreferrer"&gt;r/devops discussion&lt;/a&gt;: after moving workloads to identity federation, how many secrets are left?&lt;/p&gt;

&lt;p&gt;The answer depends on where each credential crosses a trust boundary.&lt;/p&gt;

&lt;p&gt;GitHub Actions can exchange an OIDC token for &lt;a href="https://docs.github.com/en/actions/concepts/security/openid-connect" rel="noopener noreferrer"&gt;short lived cloud credentials&lt;/a&gt;. An application on AWS can use an IAM role instead of carrying an access key. AWS even supports &lt;a href="https://docs.aws.amazon.com/rolesanywhere/latest/userguide/introduction.html" rel="noopener noreferrer"&gt;temporary credentials for workloads outside AWS&lt;/a&gt;, provided you operate the certificate infrastructure needed to establish that trust.&lt;/p&gt;

&lt;p&gt;Those changes remove a class of long lived credentials. They do not cause a payment provider, an older database, or a webhook sender to accept your cloud identity. Some secrets remain because the other system controls its own authentication method.&lt;/p&gt;

&lt;p&gt;My recommendation is to migrate the paths that support federation, then give the remaining credentials more deliberate ownership. &lt;strong&gt;The useful measure is how much risk and work remain per credential, not the number of entries in a vault.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Draw the boundary before deleting anything
&lt;/h2&gt;

&lt;p&gt;Consider a service that writes to S3, calls an external billing API, receives signed webhooks, and connects to an older SQL Server.&lt;/p&gt;

&lt;p&gt;The S3 access key is a good candidate for removal. Give the workload an identity and a narrowly scoped role. The billing API key still exists because that API expects a key. The webhook signing secret still exists because the receiver must verify what the sender signed. The database password might remain until that particular server version, driver, and deployment can use another supported authentication path.&lt;/p&gt;

&lt;p&gt;I would draw that as four separate edges:&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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9rf6gdonbwve2dhtjl6m.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9rf6gdonbwve2dhtjl6m.png" alt="service_auth_methods" width="756" height="659"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The last line needs investigation, not an assumption. Some SQL Server deployments can use Microsoft Entra authentication; an older on premises deployment may have different constraints. Check the exact version and connection path before promising a passwordless migration.&lt;/p&gt;

&lt;p&gt;There is another subtle point here. A workload role can authorize a service to read a secret from a manager. It does not remove the secret stored there. It improves how the service obtains that value and who may read it. The provider credential still needs scoping, rotation, revocation, and an owner.&lt;/p&gt;

&lt;p&gt;A report saying “we use identity for all workloads” tells me little about the secrets inventory. I still want to know how each destination authenticates the caller.&lt;/p&gt;

&lt;h2&gt;
  
  
  The leftovers are often harder than the keys you removed
&lt;/h2&gt;

&lt;p&gt;Cloud access keys are painful when they spread through CI settings and configuration files, but cloud platforms give us mature replacements. The remaining secrets may have awkward lifecycles.&lt;/p&gt;

&lt;p&gt;One vendor lets you create a replacement key while the old key stays valid. Another gives you one active credential, so rotation requires a coordinated cutover. A webhook endpoint may accept more than one signing secret during a transition, or it may not. An appliance may require a manual change through a browser. A shared password may have several consumers that nobody documented.&lt;/p&gt;

&lt;p&gt;These differences determine the operating procedure. A scheduled rotation button is useful only when the destination also changes and every consumer picks up the new value. &lt;a href="https://docs.aws.amazon.com/secretsmanager/latest/userguide/rotating-secrets.html" rel="noopener noreferrer"&gt;AWS Secrets Manager's rotation documentation&lt;/a&gt; is explicit about updating both the stored secret and the database or service. Changing the vault entry alone is a reliable way to schedule an outage.&lt;/p&gt;

&lt;p&gt;For each remaining credential, I would record:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Question&lt;/th&gt;
&lt;th&gt;Why it matters&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Who issues and revokes it?&lt;/td&gt;
&lt;td&gt;Determines the actual recovery path.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Which workload uses it?&lt;/td&gt;
&lt;td&gt;Limits access and exposes accidental sharing.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Can old and new values overlap?&lt;/td&gt;
&lt;td&gt;Determines whether rotation can avoid downtime.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;How does the workload reload it?&lt;/td&gt;
&lt;td&gt;A new vault value is useless to a process holding the old one.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What happens if it leaks?&lt;/td&gt;
&lt;td&gt;Sets urgency and containment steps.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Who can test the replacement?&lt;/td&gt;
&lt;td&gt;Makes rotation an operation rather than a calendar reminder.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That inventory is more informative than “37 secrets remaining.” Two of those 37 might be broad production credentials with no tested revocation path. They deserve attention before twenty low privilege integration tokens.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make migration reversible until the new path is proven
&lt;/h2&gt;

&lt;p&gt;I would migrate one workload edge at a time. Start with a well understood cloud API call. Give the workload a role with only the permissions it needs, then run a real operation through the new identity. Check which principal appears in the audit trail. Check that the workload refreshes credentials before expiry. Run the job long enough to exercise a refresh, not merely its startup path.&lt;/p&gt;

&lt;p&gt;Then remove the old key from the application's configuration and rerun the job. Only after that should you revoke the old key. Finally, search the places where copies tend to survive: CI secrets, deployment templates, local environment files, support scripts, and scheduled jobs. The old key may be unused by the main service but still active elsewhere.&lt;/p&gt;

&lt;p&gt;For GitHub Actions, restrict which repository, branch, or environment can assume the cloud role. OIDC removes a stored cloud key, but a broad trust policy can still authorize the wrong workflow. The token exchange is only as narrow as the trust conditions and permissions on the role.&lt;/p&gt;

&lt;p&gt;I would also test failure behavior. If federation is unavailable, does the job fail with a clear error? Does someone have a documented recovery path? Secretless authentication can still have a dependency on an identity provider, metadata endpoint, certificate authority, or token service. Those dependencies deserve monitoring.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep the secrets manager, but give it a smaller job
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/secretsmanager/latest/userguide/intro.html" rel="noopener noreferrer"&gt;AWS describes Secrets Manager&lt;/a&gt; as a place for database credentials, application credentials, OAuth tokens, API keys, and other secrets. After federation, that list may shrink. The service still has a useful job: controlling access to values that cannot be replaced by an identity handshake.&lt;/p&gt;

&lt;p&gt;I would organize the remaining inventory by system boundary and owner rather than by a generic &lt;code&gt;prod/secrets&lt;/code&gt; folder. A billing key should be readable only by the billing workload. A webhook signing secret should be readable only by the verifier and rotation procedure. A database credential should have permissions that match the application's actual queries, with separate credentials for migration or administration.&lt;/p&gt;

&lt;p&gt;Set rotation intervals according to the system's capability and exposure. Do not promise automatic rotation for a vendor that offers no safe API or overlapping credentials. For those cases, write a cutover runbook and rehearse it. A manual procedure that has been tested is better than an “automated” rotation that only changes one side of the connection.&lt;/p&gt;

&lt;p&gt;There is a cost decision too. If a team has a small number of static credentials, it may not need another vault product or a custom broker. Reuse the secrets service already in the platform, provided it gives the team access control, audit history, and a workable recovery path. Additional infrastructure should solve a specific remaining problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would measure
&lt;/h2&gt;

&lt;p&gt;The percentage of credentials replaced is easy to celebrate and easy to misread. I would track four operational results instead:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Which long lived cloud keys have actually been revoked?&lt;/li&gt;
&lt;li&gt;Which remaining secrets have a named owner and known consumers?&lt;/li&gt;
&lt;li&gt;Which credentials can be rotated without an outage, and when was that last tested?&lt;/li&gt;
&lt;li&gt;How quickly can a leaked credential be identified and disabled?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Those answers tell us whether the migration changed production risk. They also expose the next improvement: a vendor integration to replace, a database auth path to modernize, or a shared key to split by workload.&lt;/p&gt;

&lt;p&gt;Workload identity is worth adopting where it fits. It removes credentials that never needed to be stored in the first place. The interesting work begins after that, when the remaining list is short enough to read and inconvenient enough to deserve proper attention.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>aws</category>
      <category>devops</category>
      <category>security</category>
    </item>
    <item>
      <title>Your Orchestrator Is an Operating Model, Not a Feature List</title>
      <dc:creator>Sudeep Hazra</dc:creator>
      <pubDate>Wed, 16 Sep 2026 19:20:11 +0000</pubDate>
      <link>https://dev.to/sudeephazra/your-orchestrator-is-an-operating-model-not-a-feature-list-51mf</link>
      <guid>https://dev.to/sudeephazra/your-orchestrator-is-an-operating-model-not-a-feature-list-51mf</guid>
      <description>&lt;p&gt;Data orchestration became interesting again, although I am not sure it ever stopped being complicated.&lt;/p&gt;

&lt;p&gt;In July, &lt;a href="https://dagster.io/prefect" rel="noopener noreferrer"&gt;Prefect announced that it was acquiring Dagster Labs&lt;/a&gt;. The announcement says Dagster will keep its name, support, and open-source license, with no migration required for customers. In September, &lt;a href="https://kestra.io/blogs/release-2-0" rel="noopener noreferrer"&gt;Kestra released version 2.0&lt;/a&gt; with a rewritten engine, separate control and data planes, remote workers, and flows that agents can call as tools.&lt;/p&gt;

&lt;p&gt;Naturally, the familiar question returned: which orchestrator should we use?&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.reddit.com/r/dataengineering/comments/1uvnu24/what_orchestrator_should_you_use/" rel="noopener noreferrer"&gt;Reddit discussion&lt;/a&gt; includes almost every answer you would expect. Stay with Dagster. Use Airflow because it is established. Choose a managed service. Try something smaller for a lean team. A few vendors also arrived to recommend their own products, because some laws of distributed systems are social.&lt;/p&gt;

&lt;p&gt;I think the product comparison is starting one step too late.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An orchestrator encodes how a team owns work.&lt;/strong&gt; The syntax and user interface matter, but the bigger decision is who defines workflows, where code runs, how failures are recovered, and which boundaries the platform enforces.&lt;/p&gt;

&lt;h2&gt;
  
  
  The DAG is the easy part
&lt;/h2&gt;

&lt;p&gt;Most tools can express this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Extract
   |
   v
Transform
   |
   v
Validate
   |
   v
Publish
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A demo usually ends when those four boxes turn green. Production starts when the second box runs for five hours, the third box discovers bad data after the publish step, and someone needs to backfill Tuesday without rerunning Wednesday.&lt;/p&gt;

&lt;p&gt;That is where orchestrators differ in ways a feature matrix struggles to capture.&lt;/p&gt;

&lt;p&gt;Does the platform think in tasks, assets, events, or deployments? Does it store enough state to reconstruct what happened? Can one team update a workflow without gaining access to every credential used by the worker? Can workloads run inside the network where the data already lives? How are upgrades tested, rolled back, and supported?&lt;/p&gt;

&lt;p&gt;The answers shape team behaviour. They also determine who gets paged.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with the ownership boundary
&lt;/h2&gt;

&lt;p&gt;Before comparing Airflow, Dagster, Prefect, Kestra, or a cloud-native scheduler, I would draw the boundary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Platform team owns
  - control plane
  - identity and secrets
  - worker environments
  - deployment path
  - shared monitoring

Data teams own
  - workflow definitions
  - transformation code
  - data-quality rules
  - schedules and dependencies
  - domain runbooks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is only one model, but it makes the questions concrete. If every data team needs the platform team to install a Python dependency, self-service is mostly a logo. If every workflow author can run arbitrary code with shared production credentials, self-service has gone too far in the other direction.&lt;/p&gt;

&lt;p&gt;The orchestrator should make the desired boundary easier to enforce.&lt;/p&gt;

&lt;p&gt;Kestra 2.0 is interesting in this context because its release notes describe workers connecting to the control plane through an outbound gRPC stream. User code runs in the data plane, and workers can sit in another region, cloud, or outbound-only network. That is more than an engine detail. It supports an operating model where a central team manages orchestration while execution stays near the workload.&lt;/p&gt;

&lt;p&gt;It also comes with migration work. The 2.0 release removes or changes several constructs, requires an upgrade through 1.3.x, and changes the default behaviour for unmatched worker routing from waiting to failing. Those details belong in the decision because an orchestrator is a long-lived operational dependency, not a library we casually swap on Friday afternoon.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decide what the system is organizing
&lt;/h2&gt;

&lt;p&gt;The task-versus-asset distinction is another operating choice.&lt;/p&gt;

&lt;p&gt;A task-oriented model asks, "What should run next?" It maps naturally to jobs, scripts, APIs, and infrastructure operations. Teams with mixed workloads often find this easy to reason about.&lt;/p&gt;

&lt;p&gt;An asset-oriented model asks, "What data should exist, and what does it depend on?" That can make partitions, lineage, freshness, and backfills easier to express for data-heavy platforms.&lt;/p&gt;

&lt;p&gt;Neither model is universally better. The useful question is what engineers spend their time debugging.&lt;/p&gt;

&lt;p&gt;If incidents usually sound like "this job did not run," task state may be the natural center. If they sound like "the customer dimension is missing yesterday's partition," asset state may be more useful. If the same platform also runs infrastructure automation, ML training, and API workflows, a strongly data-specific abstraction can become awkward.&lt;/p&gt;

&lt;p&gt;Pick the model that matches the failure language of the team.&lt;/p&gt;

&lt;h2&gt;
  
  
  An acquisition is a signal, not a migration plan
&lt;/h2&gt;

&lt;p&gt;Prefect's acquisition of Dagster creates legitimate questions about long-term product direction. It does not, by itself, make a running Dagster deployment unsafe.&lt;/p&gt;

&lt;p&gt;The public announcement commits to continued support, the existing name, and the existing open-source license. That is the fact available today. Future convergence is a possibility, not a documented migration requirement.&lt;/p&gt;

&lt;p&gt;I would respond with boring engineering work:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;pin the current version and test upgrades in a representative environment;&lt;/li&gt;
&lt;li&gt;document the APIs, metadata, and deployment assumptions that create lock-in;&lt;/li&gt;
&lt;li&gt;keep transformation logic outside orchestration definitions where practical;&lt;/li&gt;
&lt;li&gt;export workflow and run metadata needed for audit or migration;&lt;/li&gt;
&lt;li&gt;define the event that would trigger a reevaluation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last item is useful. "We feel nervous" is difficult to act on. "Security fixes fall outside our required window" or "the supported deployment no longer fits our network boundary" is a decision trigger.&lt;/p&gt;

&lt;p&gt;The same discipline applies to every orchestrator. Project ownership can change. Cloud pricing can change. A managed feature can move tiers. Even a stable open-source project can become difficult for a small team to operate.&lt;/p&gt;

&lt;p&gt;An exit path is part of the architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run a failure-focused proof of concept
&lt;/h2&gt;

&lt;p&gt;Happy-path evaluations flatter every product. I would test a small workflow and then deliberately make it unpleasant.&lt;/p&gt;

&lt;p&gt;Use one hourly ingestion, one daily aggregation, and one data-quality rule. Add a partitioned backfill. Run a task longer than the worker lifetime. Rotate its credential. Remove network access halfway through a run. Deploy an incompatible workflow change and roll it back.&lt;/p&gt;

&lt;p&gt;Then score the products on the work the team will actually own:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Decision area&lt;/th&gt;
&lt;th&gt;Question to answer&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Deployment&lt;/td&gt;
&lt;td&gt;Can we upgrade and roll back without improvisation?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Isolation&lt;/td&gt;
&lt;td&gt;Can workloads use separate identities and dependencies?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Recovery&lt;/td&gt;
&lt;td&gt;Can an operator restart the correct unit without duplicating data?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backfills&lt;/td&gt;
&lt;td&gt;Can historical work run without starving current schedules?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Observability&lt;/td&gt;
&lt;td&gt;Can we identify the failed data, code version, and owner quickly?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Self-service&lt;/td&gt;
&lt;td&gt;Can a team ship safely without platform tickets?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost&lt;/td&gt;
&lt;td&gt;What grows with workflow count, run count, retention, and worker size?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exit&lt;/td&gt;
&lt;td&gt;How much domain logic is trapped in the orchestrator?&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two days of deliberate failure will tell you more than two weeks of clicking through feature pages.&lt;/p&gt;

&lt;h2&gt;
  
  
  My recommendation changes with the starting point
&lt;/h2&gt;

&lt;p&gt;For an organization already running Airflow reliably, I would keep it unless there is a measured problem. A newer orchestration model may look cleaner, but migration creates parallel operations, retraining, rewritten workflows, and a new failure surface. Familiarity is not glamorous. It is still an asset.&lt;/p&gt;

&lt;p&gt;For a new data platform, I would compare one task-oriented and one asset-oriented option using a real pipeline. If the team is small, include the cost of operating the control plane, not just the speed of writing the first workflow. A managed service may be cheaper than months of occasional maintenance even when its invoice is higher.&lt;/p&gt;

&lt;p&gt;For workloads spread across restricted networks or multiple clouds, I would test the control-plane and worker boundary first. Kestra 2.0 now makes an explicit architectural claim in that area. The PoC should verify identity, connectivity, failure isolation, and upgrade behaviour rather than accepting the diagram.&lt;/p&gt;

&lt;p&gt;For a team whose warehouse or data platform already provides adequate scheduling, I would start there. Another orchestrator must earn its database, deployment, alerting, and support burden.&lt;/p&gt;

&lt;p&gt;The orchestrator decision is not Airflow versus Dagster versus Prefect versus Kestra. It is centralized versus federated ownership, tasks versus assets, shared execution versus isolated workers, and product convenience versus operational control.&lt;/p&gt;

&lt;p&gt;Choose those boundaries first. The product shortlist usually becomes much smaller after that.&lt;/p&gt;

</description>
      <category>dataengineering</category>
    </item>
    <item>
      <title>Your Data Engineering Roadmap Is Probably Too Long</title>
      <dc:creator>Sudeep Hazra</dc:creator>
      <pubDate>Wed, 16 Sep 2026 19:19:04 +0000</pubDate>
      <link>https://dev.to/sudeephazra/your-data-engineering-roadmap-is-probably-too-long-2eam</link>
      <guid>https://dev.to/sudeephazra/your-data-engineering-roadmap-is-probably-too-long-2eam</guid>
      <description>&lt;p&gt;A data engineering roadmap crossed my feed recently. It is thoughtful, detailed, and 500+ lines long. The associated Reddit discussion had a predictable reaction: useful reference, terrifying learning plan.&lt;/p&gt;

&lt;p&gt;That distinction matters.&lt;/p&gt;

&lt;p&gt;A map can show every road in a country. It does not mean you need to drive every road before you are allowed to leave home.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/ErdemOzgen/Data-Engineering-Roadmap/blob/main/DATA_ENGINEERING_ROADMAP_2026.md" rel="noopener noreferrer"&gt;Data Engineering Roadmap 2026&lt;/a&gt; covers software engineering, Python, SQL, operational databases, warehouses, lakehouse storage, orchestration, observability, security, and cost. As an inventory of the field, that is helpful. As a checklist for becoming employable, it is too easy to read it as an entrance exam that never ends.&lt;/p&gt;

&lt;p&gt;Several engineers in the &lt;a href="https://www.reddit.com/r/dataengineering/comments/1vzoiwh/2026_data_engineering_roadmap/" rel="noopener noreferrer"&gt;Reddit discussion&lt;/a&gt; made the same point in different ways. Learn SQL, Python, modelling, and the fundamentals of transformation. Add tools when the problem asks for them. The disagreement was mostly about how much product knowledge someone needs before starting.&lt;/p&gt;

&lt;p&gt;My view is simpler: &lt;strong&gt;learn one complete data system before collecting ten disconnected technologies.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Tools are the visible part of the job
&lt;/h2&gt;

&lt;p&gt;Job descriptions make data engineering look like a shopping list:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Python + SQL + Spark + Kafka + Airflow
+ dbt + Snowflake + Databricks + AWS
+ whatever was added to the platform last Tuesday
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The list is visible because product names are easy to search for. The harder skills hide underneath it.&lt;/p&gt;

&lt;p&gt;Can you tell whether a source is producing inserts, updates, or replacements? Can you make a load safe to retry? What happens when a column changes type? How do you backfill three months without corrupting today's incremental run? Who gets alerted when the pipeline finishes successfully but loads zero rows?&lt;/p&gt;

&lt;p&gt;Those questions survive tool changes.&lt;/p&gt;

&lt;p&gt;Airflow can become Dagster. Redshift can become Snowflake. Spark can become a warehouse query or a small Python process because the data was never large enough to justify a cluster. The names move around. Ordering, idempotency, schema evolution, reconciliation, and operational ownership remain.&lt;/p&gt;

&lt;p&gt;That is why a roadmap built around products creates a strange learning pattern. A person can finish six courses and still not know how to recover a failed pipeline. They have seen every component, but they have not owned a system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build one pipeline with consequences
&lt;/h2&gt;

&lt;p&gt;I would structure the learning path around one modest project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PostgreSQL source
       |
       v
Incremental extraction
       |
       v
Object storage as Parquet
       |
       v
Warehouse tables
       |
       v
Quality checks and reconciliation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The business story can be ordinary: orders, payments, support tickets, or device events. Ordinary is good. The learning comes from making the pipeline dependable, not from inventing a futuristic use case.&lt;/p&gt;

&lt;p&gt;Start with a full load. Record the source row count, loaded row count, start time, end time, and status. Run it twice and prove that the second run does not duplicate data.&lt;/p&gt;

&lt;p&gt;Then add incremental processing. Choose a watermark and explain why it is safe. If you use &lt;code&gt;updated_at&lt;/code&gt;, decide what happens when records arrive late or a source clock is wrong. If you use a monotonically increasing ID, decide how updates are detected. There is no magic watermark, only a trade-off you can defend.&lt;/p&gt;

&lt;p&gt;Next, break the schema on purpose. Add a nullable column. Rename another one. Change a numeric field into text. The pipeline should either handle the change or fail with enough evidence for someone to diagnose it. A silent partial load does not count as resilience (it counts as tomorrow's incident).&lt;/p&gt;

&lt;p&gt;Finally, add a backfill that can run alongside the current schedule. Keep historical and incremental state separate. Reconcile totals after the backfill, then document how to restart it.&lt;/p&gt;

&lt;p&gt;This single project teaches more useful engineering than a folder of disconnected tutorials because every new decision affects something already running.&lt;/p&gt;

&lt;h2&gt;
  
  
  Learn in layers, not brands
&lt;/h2&gt;

&lt;p&gt;The order I would use is deliberately boring.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Data correctness
&lt;/h3&gt;

&lt;p&gt;Begin with SQL, data modelling, transactions, indexes, query plans, and the difference between an operational schema and an analytical one. Learn how nulls, duplicates, time zones, and changing business definitions damage results.&lt;/p&gt;

&lt;p&gt;Python matters here, but mainly as software. Use functions, tests, type hints, logging, dependency management, and a command-line entry point. A notebook is useful for exploration. It is a poor substitute for a repeatable job.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Pipeline behaviour
&lt;/h3&gt;

&lt;p&gt;Add scheduling, retries, idempotency, checkpoints, and backfills. Learn the difference between a task completing and the data being correct. Store enough run metadata to explain what happened without reading raw logs for an hour.&lt;/p&gt;

&lt;p&gt;This is also the right time to learn an orchestrator. Pick one. The goal is to understand dependencies, scheduling, state, retries, and failure recovery. You do not need equal fluency in Airflow, Dagster, Prefect, Kestra, and every managed cloud service.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Scale and distribution
&lt;/h3&gt;

&lt;p&gt;Only after the single-node version becomes limiting would I add Spark, Kafka, or a lakehouse table format. Otherwise, it is difficult to separate the complexity of the problem from the complexity of the platform.&lt;/p&gt;

&lt;p&gt;When Spark enters the project, measure why. Is the input too large for memory? Is parallelism shortening a batch window? Does the team already run the platform? "Spark appears in many job descriptions" is a career reason to understand it, but it is not an architecture reason to deploy it.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Production ownership
&lt;/h3&gt;

&lt;p&gt;Security, cost, deployment, observability, and support are not advanced electives. They are what turns a pipeline into a service.&lt;/p&gt;

&lt;p&gt;Use workload identity instead of static credentials. Set a budget alert. Define freshness and correctness checks. Write a runbook for the two failures most likely to happen. Package the project so another engineer can run it.&lt;/p&gt;

&lt;p&gt;That final step changes the portfolio from "I followed a tutorial" to "I can own a data workload."&lt;/p&gt;

&lt;h2&gt;
  
  
  Breadth still matters, just later
&lt;/h2&gt;

&lt;p&gt;There is a reasonable counterargument. Engineers often join environments with a fixed stack, and hiring filters still look for product names. A consultant may need to compare several platforms quickly. Breadth has career value.&lt;/p&gt;

&lt;p&gt;The mistake is treating breadth and depth as competing destinations. They are a sequence.&lt;/p&gt;

&lt;p&gt;Build depth with one stack. Then map nearby tools onto concepts you already understand:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concept&lt;/th&gt;
&lt;th&gt;First implementation&lt;/th&gt;
&lt;th&gt;What to compare later&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Orchestration&lt;/td&gt;
&lt;td&gt;One scheduler&lt;/td&gt;
&lt;td&gt;State model, retries, deployment, access control&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Storage&lt;/td&gt;
&lt;td&gt;One warehouse and object store&lt;/td&gt;
&lt;td&gt;Cost, isolation, schema evolution, query patterns&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transformation&lt;/td&gt;
&lt;td&gt;SQL and one processing engine&lt;/td&gt;
&lt;td&gt;Pushdown, testing, lineage, scale&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ingestion&lt;/td&gt;
&lt;td&gt;Batch first&lt;/td&gt;
&lt;td&gt;CDC semantics, ordering, replay, source impact&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Operations&lt;/td&gt;
&lt;td&gt;Logs and run metadata&lt;/td&gt;
&lt;td&gt;Metrics, tracing, alert routing, SLOs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Now a second tool is not another syllabus. It is a comparison against known constraints.&lt;/p&gt;

&lt;h2&gt;
  
  
  A roadmap should produce decisions
&lt;/h2&gt;

&lt;p&gt;The best learning project leaves behind evidence of judgment:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;why the pipeline is batch rather than streaming;&lt;/li&gt;
&lt;li&gt;how retries avoid duplicates;&lt;/li&gt;
&lt;li&gt;what happens when the schema changes;&lt;/li&gt;
&lt;li&gt;how historical loads differ from current ingestion;&lt;/li&gt;
&lt;li&gt;which metric tells you the data is late;&lt;/li&gt;
&lt;li&gt;when the current design would stop being enough.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An interviewer can discuss those decisions. A future teammate can review them. More importantly, you can reuse the reasoning when the product names change.&lt;/p&gt;

&lt;p&gt;The long roadmap is still useful. Keep it as an atlas. Use it to notice gaps and choose the next area to explore.&lt;/p&gt;

&lt;p&gt;For the actual journey, pick one source, one destination, and one pipeline with consequences. Make it correct. Make it restartable. Make it understandable by someone else.&lt;/p&gt;

&lt;p&gt;Then add the next tool because the system needs it, not because the roadmap had another box.&lt;/p&gt;

</description>
      <category>dataengineering</category>
    </item>
  </channel>
</rss>
