Category: Developer Productivity

Practical guides, workflows, and tool breakdowns for developers who want to ship faster and work smarter. No fluff — just what actually moves the needle.

  • Beyond Full Refreshes: How I Redesigned a High-Volume Analytics Pipeline Around Incremental Processing

    Beyond Full Refreshes: How I Redesigned a High-Volume Analytics Pipeline Around Incremental Processing

    Full data refreshes are attractive because they are simple.

    Extract the source data, replace the analytical dataset, refresh downstream reporting, and repeat. When datasets are relatively small, this architecture can be perfectly reasonable. It is straightforward to understand, troubleshoot, and maintain.

    The problem emerges as historical data grows.

    In a mature analytics environment, most historical records may remain unchanged between processing cycles. Yet a full-refresh architecture continues moving and processing those records repeatedly. Over time, such an approach can increase processing time, expand the scope of failures, and make recovery pricier.

    The architectural question eventually changes from

    “How can we process all of the data faster?”

    to:

    “What actually changed since the last successful processing cycle?”

    That question became the foundation for a more change-oriented approach to analytics processing.

    When Full Refreshes Stop Scaling

    A conventional full-refresh pipeline can be represented simply:

    Source → Full Extraction → Staging → Replace Analytical Data → Reporting

    Every execution processes the historical dataset again, regardless of whether individual records have changed.

    This isn’t automatically a suboptimal architecture. For relatively small datasets, a full refresh can actually be preferable because its simplicity reduces operational complexity.

    At a larger scale, however, repeatedly processing unchanged history creates work that does not necessarily add analytical value.

    The challenge is therefore not merely optimizing a full refresh. It is determining whether the full refresh is still necessary.

    Figure 1. Full refresh versus incremental processing. A full refresh repeatedly processes historical data, while incremental processing focuses routine work on records that changed.

    The alternative is to establish a historical baseline and then focus routine processing on changes.

    Moving From Full Refreshes to Change Detection

    If a source provides a reliable modification timestamp, a basic incremental extraction pattern might look like this:

    SELECT
        record_id,
        event_date,
        updated_at,
        sttus
    FROM source_records
    WHERE updated_at > @LastSuccessfulRun;

    Instead of retrieving the entire historical dataset, the pipeline retrieves records that require attention after the previous successful processing point.

    That sounds simple, but the phrase “successful processing point” matters.

    The watermark should not advance merely because extraction started. If processing fails after the marker advances, the next execution could skip records that were extracted but never successfully processed.

    A safer sequence is

    Extract → Process → Validate → Confirm Success → Advance Watermark

    Timestamp boundaries also require care. Multiple records can share the same modification timestamp, and late-arriving data can fall around processing boundaries. Depending on the source system, an overlap window or secondary key may be needed to prevent records from being missed.

    Incremental processing, therefore, is not simply adding a WHERE updated_at > … condition. It requires designing the state of the pipeline around successful processing.

    Processing Large Workloads in Bounded Windows

    Incremental processing doesn’t eliminate every large operation.

    Historical initialization, migrations, reconciliation, backfills, and recovery can still require substantial processing.

    Instead of treating years of historical data as one enormous unit of work, I prefer dividing these workloads into logical windows.

    Conceptually:

    SELECT
        record_id,
        event_date,
        updated_at,
        status
    FROM source_records
    WHERE event_date >= @WindowStart
      AND event_date < @WindowEnd;

    A window could represent a day, week, month, quarter, or another interval appropriate for the dataset.

    The key property is bounded work.

    If ten years of data are processed as a single operation, a failure creates a huge recovery boundary. With windowed processing, each completed interval becomes a checkpoint.

    If one window fails, only that portion needs to be retried, so the entire historical workload does not need to be restarted.

    Window-level processing also makes performance problems easier to isolate. If one interval repeatedly behaves differently from others, the investigation can focus on that subset rather than the entire dataset.

    A Practical Retry Pattern

    For a backfill or recovery run, each completed window should be treated as a checkpoint rather than as an all-or-nothing job. The pipeline can record the window start, window end, processing status, and completion time. If one interval fails validation, only that interval needs to be rerun after the issue is corrected. This keeps the retry scope small and makes it easier to distinguish a localized data problem from a pipeline-wide failure. The same principle also improves operational monitoring because teams can see exactly which processing window is incomplete.

    Figure 2. Change-oriented analytics pipeline. Changes are identified, processed in bounded units, validated, and then propagated to the analytics store and reporting layer.

    Incremental Processing Means More Than Finding New Records

    A common mistake is treating incremental processing as synonymous with finding newly created records.

    Operational data is rarely that simple.

    An analytical synchronization process may need to account for three categories:

    New records—records that did not previously exist.

    Updated records—existing records whose attributes or status changed.

    Missing records—records that exist in the analytical environment but are no longer present in the source.

    Creation and modification timestamps can help with the first two categories.

    Missing records are different.

    If a source record disappears, there may be no new updated_at timestamp announcing that deletion. Detecting the difference may require reconciliation.For example:

    SELECT target.record_id
    FROM analytics_records AS target
    LEFT JOIN source_keys AS source
        ON target.record_id = source.record_id
    WHERE source.record_id IS NULL;

    This identifies analytical records that no longer have a corresponding source key.

    For giant datasets, reconciliation itself can become expensive, so it may also need to be partitioned into manageable units.

    The broader principle is

    Incremental architecture requires both change detection and reconciliation.

    Figure 3. New, updated, and missing records in incremental processing. Reliable synchronization must account for inserts, updates, and source-to-target reconciliation.

    Separate Operational and Analytical Workloads

    Moving less data solves only part of the scalability problem.

    Operational databases are designed to support detailed transactions. Analytical environments support aggregation, business metrics, trend analysis, and reporting.

    If a BI application repeatedly scans detailed operational-grain records to calculate the same metrics, work that could be performed upstream is being repeated downstream.

    For example, an analytical structure could pre-aggregate commonly required information:

    SELECT
        event_date,
        organization_id,
        status,
        COUNT(*) AS record_count
    FROM analytics_records
    GROUP BY
        event_date,
        organization_id,
        status;

    A reporting application can query this smaller analytical structure instead of repeatedly recalculating the same aggregation from transaction-level data.

    This doesn’t mean eliminating detailed data. Detailed grain may still be necessary for auditing, drill-through analysis, and investigation.

    The objective is to distinguish between the grain required for accurate storage and the grain required for routine reporting.

    As data volume grows, that distinction becomes increasingly important.

    Validate Before Destructive Operations

    Performance isn’t the only concern in pipeline architecture.

    Reliability matters just as much.

    Consider an extraction that unexpectedly returns no data because an upstream connection temporarily fails.

    If the pipeline truncates or deletes known-good analytical data before confirming that the new extraction succeeded, a temporary upstream problem can become a downstream reporting incident.

    A safer sequence is

    Extract → Validate → Load → Confirm → Mark Successful

    Validation can include:

    • expected record-count ranges
    • minimum-volume thresholds
    • expected date coverage
    • control totals
    • comparisons with previous processing cycles
    • source extraction status

    The exact rules depend on the dataset.

    The principle is much simpler:

    Don’t destroy known-good analytical data until the replacement input has been validated.

    A Four-Question Framework for Change-Oriented Processing

    These techniques become more useful when they operate as parts of one architecture rather than isolated optimizations.

    When evaluating a pipeline, I use four questions:

    1. What changed?
    Change detection determines which records actually require processing.

    2. How much should be processed at once?
    Windowing establishes manageable and recoverable units of work.

    3. Is the result credible?
    Validation determines whether incoming data is safe to propagate downstream.

    4. What level of detail does reporting actually require?
    Analytical modeling determines how downstream consumption should be structured.

    Figure 4. Four-question framework for change-oriented analytics pipelines. The framework connects change detection, processing scope, validation, and analytical modeling.

    Together, these questions shift the optimization problem away from simply making full refreshes faster.

    Instead, the architecture asks whether particular data needs to be processed again at all.

    When Not to Use Incremental Processing

    Incremental architecture introduces its own complexity.

    A full refresh may remain appropriate when:

    • datasets are relatively small;
    • Refresh times are already acceptable;
    • Source systems don’t provide reliable change indicators;
    • Reconciliation would be more complicated than reprocessing the dataset;
    • Pipeline simplicity is more valuable than incremental performance improvements.

    Incremental processing should therefore be an architectural decision, not an automatic rule.

    The objective isn’t to eliminate full refreshes everywhere.

    It is to recognize when their simplicity has stopped outweighing their processing cost.

    Frequently Asked Questions

    How much data volume justifies incremental processing?

    There isn’t a universal record-count threshold. The better signals are processing time, resource consumption, recovery cost, growth rate, and the proportion of data that actually changes between cycles.

    How should late-arriving updates be handled?

    Depending on the source, pipelines can use overlap windows, modification timestamps, secondary keys, or periodic reconciliation. The appropriate strategy depends on how reliably the source represents changes.

    How do you detect deleted records?

    When deletions aren’t represented through change timestamps or explicit deletion indicators, source-to-target key reconciliation can identify analytical records that no longer exist upstream.

    When is a full refresh actually safer?

    When datasets are small and change detection is unreliable or unnecessarily complicated, rebuilding a dataset from a trusted source may provide a simpler and more predictable recovery model.

    Move Less Data, More Intelligently

    Full refreshes remain useful because they simplify synchronization: rebuild the analytical state from the source and start again.

    But simplicity at one scale can become inefficiency at another.

    A scalable analytics pipeline can establish its historical baseline once and concentrate routine processing on what actually changes. Windowed processing can reduce recovery boundaries. Reconciliation can account for records that timestamps alone cannot identify. Analytical models can reduce repeated reporting workloads, while validation protects known-good data from temporary source failures.

    The goal isn’t simply to make a full refresh faster.

    It is to determine whether the full refresh needs to happen in the first place.

    For scalable analytics pipelines, sometimes the fastest record to process is the one that never needed to be processed again.

  • The Real Reason Data Engineers Fail Technical Interviews

    The Real Reason Data Engineers Fail Technical Interviews

    You can be good at SQL and still struggle in a Data Engineer interview.

    You can work with Snowflake every day.

    You can build production pipelines, write dbt models, debug data issues and understand your company’s entire data platform.

    Then the interviewer gives you a problem you’ve technically seen before:

    “Find users who have logged in for seven consecutive days.”

    You know window functions.

    You know ROW_NUMBER().

    You’ve probably solved a similar problem before.

    But instead of immediately recognizing Gaps & Islands, you start thinking about joins.

    Then the interviewer adds:

    “What if the user logs in multiple times on the same day?”

    You adjust your query.

    Then:

    “Now find the users whose current streak is still active.”

    Now you’re rebuilding the logic.

    Five minutes have disappeared.

    This is where many otherwise strong engineers struggle.

    The problem isn’t always knowledge. It’s recognition under pressure.


    Knowing SQL isn’t the same as knowing how to solve interview SQL

    In your day-to-day job, you rarely receive a completely blank editor and a timer.

    You have documentation.

    You have existing models.

    You have previous queries.

    You can search.

    You can test your logic.

    You can ask another engineer.

    An interview removes most of those advantages.

    You’re given a problem you’ve never seen in exactly that form and expected to identify the solution path quickly.

    That’s a different skill.

    Consider these questions:

    • Find the longest consecutive activity period.
    • Find customers with purchases in three consecutive months.
    • Find the longest period of successful pipeline runs.
    • Find missing dates in an event stream.

    The business scenarios are different.

    The underlying SQL pattern can be the same.

    That’s why memorizing individual questions has diminishing returns.

    What you really want to build is pattern recognition.


    The 60-second recognition reflex

    A useful way to prepare is to train yourself to identify the pattern before writing SQL.

    For example:

    If the interviewer says…Think about…
    “Consecutive” / “streak”Gaps & Islands
    “Top N for each…”Window functions
    “Previous / next event”LAG() / LEAD()
    “Running total”Window aggregation
    “Latest record”Ranking
    “Session” / “session boundary”Event gaps + cumulative grouping
    “Missing records”Anti-joins / NOT EXISTS
    “History of changes”SCD Type 2
    “Safe to rerun”Idempotency
    “Process only new data”Incremental processing
    “Large join is slow”Distribution, skew, join strategy

    The objective isn’t to memorize the table.

    It’s to make the recognition automatic.

    You hear the problem.

    You identify the family of solutions.

    Then you start solving.


    The first question is often the easy part

    A common mistake in interview preparation is practicing only the initial question.

    But experienced interviewers rarely stop there.

    Imagine you’ve just solved:

    Find the latest order for every customer.

    The next questions could be:

    What if two orders have the same timestamp?

    Then:

    What if the source sends duplicate records?

    Then:

    What if the data arrives two days late?

    Then:

    How would you make this incremental?

    Then:

    How would you make the pipeline safe to rerun?

    Notice what’s happening.

    The interview has moved from a SQL question into data engineering thinking.

    The interviewer is testing whether you understand the system around the query.

    That’s why a good preparation strategy needs more than solutions.

    It needs traps and follow-ups.


    Data Engineer interviews aren’t just SQL

    SQL is often the entry point, but a Data Engineer interview can cover a much larger surface area.

    Data modeling

    You may be asked to design a model for:

    • E-commerce
    • Payments
    • Subscriptions
    • Logistics
    • Customer analytics

    And explain:

    • Fact vs dimension
    • Grain
    • Keys
    • Relationships
    • Star schema
    • SCD Type 2
    • Normalization

    The interviewer is looking for your reasoning, not just whether you can draw a few tables.


    Data pipelines

    You might be asked:

    Design a pipeline processing hundreds of millions of events.

    Now you’re dealing with:

    • Batch vs streaming
    • CDC
    • Incremental loads
    • Idempotency
    • Watermarks
    • Late-arriving data
    • Retries
    • Backfills
    • Data quality
    • Monitoring

    Again, there isn’t one magic answer.

    You need to recognize the relevant engineering concerns and explain your trade-offs.


    Cloud and warehouse systems

    Depending on the role, you may need to discuss:

    • Snowflake
    • AWS
    • Storage vs compute
    • Partitioning
    • Clustering
    • Query performance
    • Caching
    • Cost optimization
    • Security

    The interviewer may start with a simple question and progressively introduce scale or cost constraints.


    PySpark

    A PySpark question can quickly move from:

    “Write this transformation.”

    to:

    “Why is it slow?”

    Now you’re discussing:

    • Shuffles
    • Partitions
    • Data skew
    • Join strategies
    • Lazy evaluation
    • UDFs
    • Memory

    The ability to recognize the underlying performance problem becomes more important than remembering another piece of syntax.


    dbt

    You might be asked about:

    • Incremental models
    • Snapshots
    • Tests
    • Macros
    • Sources
    • Source freshness
    • Jobs
    • Data quality
    • Deployment

    Again, the interview rarely stays at the definition level.

    The follow-up is usually where the real test begins.


    This is why I built the Data Engineer’s Interview Playbook

    I wanted a preparation resource that felt closer to an actual interview.

    Not another collection of questions where you read:

    Question: Find the second-highest salary.

    Answer: DENSE_RANK().

    Then move on.

    Instead, each interview simulation follows a structure:

    Scenario → Recognition → Solution → Trap → Follow-up

    The goal is to train the reflex that happens between hearing the question and writing the first line of code.


    What’s inside?

    The playbook contains 511 interview simulations covering the broader Data Engineering interview loop.

    Part I — Core SQL

    306 drills across 17 modules, covering areas such as:

    • Gaps & Islands
    • Top-N per group
    • Running totals
    • Event → State
    • Rolling windows
    • Cohorts & retention
    • Self-joins
    • Date spines
    • Percentiles
    • Overlapping ranges
    • Deduplication
    • Idempotency
    • Incremental loads
    • SCD Type 2
    • Sessionization
    • Funnels
    • Data-quality assertions
    • Median edge cases

    Part II — Advanced SQL

    120 drills across 8 modules, including:

    • Recursive CTEs
    • Pivots
    • Time zones and DST
    • Query plans
    • Data skew and salting
    • Approximate counts
    • Multi-source reconciliation
    • Set operations
    • SQL anti-patterns

    Part III — The rest of the interview

    85 additional items, including:

    • 20 data-modeling scenarios
    • 10 pipeline system-design cases
    • 25 PySpark drills
    • 15 dbt interview questions
    • 15 behavioral questions with STAR outlines

    Every item is designed like an interview

    Each simulation includes:

    Difficulty

    Foundational → Intermediate → Senior

    Recognition keyword

    The wording that should trigger the relevant pattern.

    Core technique

    The approach you should consider first.

    Full solution

    Runnable SQL or a complete engineering answer.

    The trap

    The mistake or edge case that commonly catches candidates.

    Chained follow-up

    The next question an interviewer can ask after you’ve solved the first one.

    That last piece is particularly important.

    Because you don’t just want to know how to solve a question.

    You want to know what happens after you’ve solved it.


    It also includes a study system

    The playbook includes a 12-week preparation schedule, a timed-drill approach and a master progress checklist.

    A simple rule drives the practice:

    Try the problem before looking at the answer.

    Give yourself five minutes.

    If you can’t identify the underlying pattern within roughly 60 seconds, that’s useful feedback.

    You now know exactly what you need to practice.


    Who is it for?

    The playbook is designed for:

    • Data Engineers preparing for interviews
    • Analytics Engineers
    • Engineers moving into Data Engineering
    • Candidates who are comfortable with SQL but struggle with interview-style problems
    • Engineers preparing for product companies, consultancies and startups

    It’s particularly useful if you’re comfortable doing your normal job but find whiteboard or timed interview questions much harder.


    Who is it not for?

    This isn’t designed for absolute SQL beginners.

    If you’re still learning basic joins, aggregations and window functions, build those fundamentals first.

    And it isn’t intended to be a collection of abstract programming puzzles.

    The scenarios are designed around Data Engineering problems:

    events, orders, pipelines, staging tables, incremental loads, late-arriving data, data quality and production behavior.


    The goal isn’t to memorize 511 answers

    This is the most important part.

    You shouldn’t finish the playbook knowing 511 answers by heart.

    You should finish it recognizing patterns.

    So when someone asks:

    “Find the longest consecutive period…”

    your brain starts looking for Gaps & Islands.

    When they ask:

    “Find the top 3 for every region…”

    you think Top-N per group.

    When they ask:

    “Make this pipeline safe to run twice…”

    you think Idempotency.

    When they ask:

    “Why did this Spark job suddenly become extremely slow?”

    you start thinking about skew, shuffles, partitions and joins.

    That’s the difference between remembering interview questions and being prepared for interviews.


    Get the Data Engineer’s Interview Playbook

    If you’re preparing for a Data Engineer interview, you can explore the complete playbook here:

    The Data Engineer’s Interview Playbook — 511 Interview Simulations

    It includes the full SQL curriculum, advanced SQL, data modeling, system design, PySpark, dbt and behavioral preparation, along with the 12-week study system.

    There’s also a 30-day guarantee if you work through the playbook and don’t feel measurably sharper at SQL and Data Engineer interviews.


    One final thought

    The interviewer isn’t going to give you the exact question you practiced last night.

    They’re going to change the wording.

    Add a constraint.

    Introduce duplicates.

    Add late-arriving data.

    Change the scale.

    Ask what happens when the pipeline fails.

    Then ask you to explain why your solution works.

    So don’t prepare only for questions.

    Prepare for patterns.

    Because the real interview skill isn’t:

    “I’ve seen this exact question before.”

    It’s:

    “I haven’t seen this exact question before, but I recognize what it’s testing.”

  • 2extract for Web Scraping: Python, Proxies & MCP Guide

    2extract for Web Scraping: Python, Proxies & MCP Guide

    This post is for data engineers who need geo-targeted scraping through residential or mobile proxies and want to see how 2extract works in practice. It covers the 2extract MCP server driven by an AI agent, then the same account driven by a Python package (twextract) that builds three datasets: geo/address, eCommerce pricing and market research. The code, output and failure modes are from real runs against a live account.

    Why the exit point matters

    A ten-thousand-row scraping job died two hours in, wallet already charged for the first six thousand rows, when a regional storefront started quietly returning empty product grids instead of a clean 403 I could at least catch. A request from a cloud IP gets fingerprinted as automation by anything that matters (pricing pages, ad verification targets, review aggregators) before retry logic even notices something is wrong. The fix isn’t a longer timeout. It’s a different exit point.

    That is the problem 2extract solves, and it is worth being precise about what changes and what doesn’t. It is a residential and mobile proxy network, billed pay-as-you-go, where every targeting decision (country, city, ZIP, carrier, how sticky a session should be) is a parameter you build into the proxy username rather than a checkbox in a vendor portal. That is a different operating model from the flat “buy a country, get an IP list” products many data teams have tried and half-abandoned. The same account is also reachable through an MCP (Model Context Protocol) server at mcp.2extract.com, so an agent such as Claude, Cursor or Codex can check the balance, look up a geo slug, create a proxy and cap its spend without anyone hand-writing the client shown below.

    I found the sharp edge the expensive way, twenty minutes into my first real run. I’d copied a proxy username from the dashboard, then pasted -country-us onto the end of it for a quick test. The copied string already carried a targeting suffix from an earlier session I’d forgotten to clean up. Two suffixes, one string, and an instant 407 that read exactly like a wrong password. It wasn’t. It was a syntax problem wearing an authentication error’s clothes, and it is the first thing this article will save you from repeating.

    What 2extract actually is — and the one thing it isn’t

    2extract does not scrape websites. It doesn’t parse HTML, doesn’t decide which fields belong in a product catalog, and doesn’t store anything you collect. It sits between your program and the public internet and makes each outbound request look like it came from a real residential connection (or, on the mobile product, a real carrier connection), optionally narrowed to a country, state, city or ZIP you choose.

    Every stack built on it has the same three layers:

    • Collector: an agent calling MCP tools, or Python code with requests and BeautifulSoup. It chooses URLs, parses responses, dedupes and writes files.
    • Gateway: proxy.2extract.net:5555. It authenticates the request, applies targeting, picks an exit IP and tunnels the traffic through.
    • Target: whatever site or API holds the data.

    Mobile proxies push the residential idea further. The exit is a carrier IP (T-Mobile US shows up under ISP code 310260 in the docs), priced separately, and targeted with -isp instead of -country. The two targeting families are mutually exclusive on a single username, and the gateway rejects a request that tries to mix them.

    None of this is a license to do anything you couldn’t already justify without a proxy. The Acceptable Use Policy rules out unauthorized access, account farming, scalping, spam and ad fraud in plain terms, and a residential exit doesn’t override robots.txt, a site’s terms of service or copyright law.

    The proxy username is the actual control plane

    Most proxy vendors let you pick a country from a dashboard dropdown or set it in an HTTP header. 2extract does neither. After you create a proxy, the dashboard gives you a base username shaped like 2xt-customer-[CLIENT_ID]-proxy-[PROXY_NAME], and that base is immutable for the life of the proxy. Everything situational is appended afterward as a hyphenated suffix, built fresh by your code on every request.

    This also shapes the bill. Most proxy vendors sell each country as a separate product. 2extract sells one residential proxy and treats geography as a parameter on the request, so the cost doesn’t multiply with the number of markets you target.

    The Targeting dataclass below is how the Python package enforces those rules before a request ever leaves your machine. validate() refuses to mix geographic and network targeting, and refuses city, state or ZIP without a country. build_username() appends the suffix parts to the clean base username, which is exactly what prevents the double-suffix mistake above. (normalized() and suffix_parts() live in the full source on GitHub.)

    @dataclass(frozen=True)
    class Targeting:
        country: str | None = None
        state: str | None = None
        city: str | None = None
        zip_code: str | None = None
        asn: str | None = None
        isp: str | None = None
        session: str | None = None
        time_minutes: int | None = None
        const: bool = False
    
        def validate(self) -> None:
            t = self.normalized()
            geo = any([t.country, t.state, t.city, t.zip_code])
            net = any([t.asn, t.isp])
            if geo and net:
                raise TargetingError(
                    "Geographic targeting (country/state/city/zip) and network "
                    "targeting (asn/isp) are mutually exclusive. Use one group only."
                )
            if (t.state or t.city or t.zip_code) and not t.country:
                raise TargetingError("city, state and zip require country in the same username.")
    
    
    def build_username(base_username: str, targeting: Targeting | None = None) -> str:
        base = base_username.strip()
        if targeting is None:
            return base
        parts = targeting.suffix_parts()
        return f"{base}-{'-'.join(parts)}" if parts else base

    What the residential product is used for

    Use caseWhy residential matters hereSession strategy
    eCommerce & price monitoringStores change currency, tax and stock by country; a datacenter IP gets a generic or blocked pageSticky for the length of a category walk
    Market researchDiscussion APIs and open product databases still rate-limit aggressively at volumeRotating, one exit per batch
    Geo / localizationRegional prices, SERPs and ads only render correctly to an exit that looks localRotating, unique IP per market sample
    Ad verification & SEO / SERPSame mechanics underneath, but SERPs are heavily JavaScript-drivenUsually needs a real browser, not this stack

    How a request travels, and how it fails

    Every request makes the same trip: collector to gateway to target, and the response back along the same path. Three things go wrong along the way. A residential peer can simply go offline mid-session. A proxy left in Inactive status returns a 407 even with a correct password. And gateway-level errors surface in an X-2extract-Error or X-Proxy-Error response header, except that the header goes dark over HTTPS CONNECT tunneling.

    On this stack a 407 narrows to one of three causes: the proxy is Inactive, the wallet is empty, or the username carries malformed or conflicting targeting. The client turns that into a readable error instead of a generic failure:

    def _gateway_407(self, targeting, exc):
        header = self._read_gateway_header_if_needed(targeting)
        hint = header or (
            "the proxy is Inactive, the password is wrong, or the wallet is empty. "
            "Open My Proxies and set Status to Active, then rerun."
        )
        if header and "inactive" in header.lower():
            hint = f"{header}. Open My Proxies and set Status to Active (Actions menu)."
        return GatewayError(f"2extract rejected the proxy login (407): {hint}", status_code=407)

    Setting up an account

    No library can do this part for you.

    1. Create an account and open the dashboard. Unverified accounts carry a $50 lifetime deposit cap.
    2. Top up the Pay-As-You-Go wallet. The minimum is $10, and residential and mobile traffic bill from the same balance.
    3. If the job will ever need more than $50 deposited, verify identity first.
    4. Create an API key scoped to at least geo:read, proxies:read, proxies:write and balance:read. It is shown exactly once.
    5. Create one residential proxy, and a second dedicated proxy only if you need mobile ISP targeting.
    6. Set a monthly spend cap on each proxy.
    7. Copy the base username and password somewhere safe: a .env file if you’re driving this with Python, the agent’s own credential store if it’s MCP. Never store the targeted version with a suffix already attached.

    Running the same account through MCP

    Everything above assumes a person writing Python. 2extract also ships an MCP server at mcp.2extract.com that exposes the same account (balance, proxies, geo catalog, traffic history) as tools any MCP-capable agent can call. The product doesn’t change: it is still one residential proxy, still billed pay-as-you-go, still targeted through a username suffix. What changes is who assembles the username. The agent does it from a plain-language request, instead of you doing it with the Targeting dataclass.

    fig. 4 — the round trip behind every tool call: agent → MCP server → account, and back

    I connected it as a custom MCP server inside an agent’s tool panel and had 19 tools and 19 resources enabled with nothing more than the server URL and an API key. There was no separate SDK and no client to install.

    The six steps below ran in one continuous session against the same account.

    fig. 5 — the six steps below, in one continuous session against the same account

    Step 1: check the balance in plain language

    The simplest sanity check skips the dashboard tab entirely. Asking the agent “What is my 2extract balance?” triggers one tool call, getAccountBalance, and comes back in about 22 seconds with the wallet figure to six decimal places, not just the rounded number the UI shows:

    {
      "balance_display": "$4.75",
      "balance_exact": "$4.746951",
      "note": "enough to create a new proxy"
    }

    📷 getAccountBalance answering a plain-language balance check — the agent surfaces both the rounded and the exact figure

    Step 2: look up geo slugs without a docs tab

    Every geographic suffix has to come from 2extract’s own catalog, never guessed. A state name that is off by one word doesn’t error; it is silently ignored as a targeting parameter. searchGeoRegions puts that catalog in the agent’s hands. Asking for Germany’s regions returns the exact slugs the proxy username expects:

    RegionUsername param
    Baden-Württembergbadenwurttemberg
    Bavariabavaria
    North Rhine-Westphalianorthrhinewestphalia
    Saxonysaxony
    Thuringiathuringia

    The agent added, unprompted, “do not guess them.” That is the same warning this article gives about the username, now enforced by the tool call instead of a paragraph the reader has to remember.

    Step 3: create the proxy the lookup was for

    With the slug in hand, the same session calls createProxyResource with targeting baked into the proxy itself rather than appended per request:

    {
      "tool": "createProxyResource",
      "arguments": {
        "name": "de-baden-agent-demo",
        "type": "residential",
        "country": "DE",
        "state": "badenwurttemberg"
      }
    }

    The response is the same base-username shape described earlier, 2xt-customer-[CLIENT_ID]-proxy-de-baden-agent-demo, except this proxy carries Baden-Württemberg targeting as its default. A plain request with no suffix at all still exits from that region.

    Step 4: set a spend cap without leaving the chat

    The setup checklist says to cap spend on every proxy from the dashboard. Over MCP that is a second tool call on the proxy you just created:

    {
      "tool": "setProxyLimit",
      "arguments": {"proxy": "de-baden-agent-demo", "monthly_cap_usd": 5}
    }

    At a $4.75 balance that cap does little for a demo account, but it is the same guardrail every production proxy behind this article’s three pipelines runs under, set through the agent instead of the dashboard.

    I deactivated and deleted this demo proxy right after testing it, using deactivateProxyResource and deleteProxyResource from the same tools panel, so a five-minute demo didn’t inflate the account’s proxy count. That is why the dashboard in the next step shows one active proxy, not two.

    Step 5: check the result against the dashboard

    MCP doesn’t read from a separate ledger. The balance, the proxy and its cap show up identically in 2extract’s web dashboard, because both surfaces read and write the same account state. Asking the agent for a 30-day traffic-and-spend rollup returns the same wallet history the dashboard’s usage tab shows, broken down by day:

    The dashboard, checked separately right after, agrees:

    Balance, active-proxy count and traffic line up across both views. The $4.75 agent-side versus $4.74 on the dashboard is rounding, not a discrepancy, and both show one active proxy because the demo proxy was deleted after use. MCP is a second way into the same account, not a shortcut around the dashboard.

    Step 6: route one request through the result

    Everything so far provisions a proxy; it doesn’t prove a byte moved through it. Closing the loop takes one ordinary HTTP request through the username MCP just created:

    username = "2xt-customer-[CLIENT_ID]-proxy-de-baden-agent-demo"
    proxies = {"http": f"http://{username}:{password}@proxy.2extract.net:5555",
               "https": f"http://{username}:{password}@proxy.2extract.net:5555"}
    response = requests.get("https://httpbin.org/ip", proxies=proxies, timeout=30)
    print(response.json())
    {"origin": "89.204.xx.xx"}

    That IP resolves to a Baden-Württemberg ISP block, which confirms that the region MCP looked up, and the proxy MCP created, is where the request exits. The Python pipelines below log the same exit.* field on every row for exactly this reason.

    Three pipelines, three different datasets

    The rest of this post uses the same account, driven by code instead of an agent: three pipelines built on one client, for anyone who wants to own the collection logic.

    Geo and address: proof of exit location

    _rotating_geo_targeting generates a fresh session ID per hop, which forces 2extract to assign a new exit IP. scrape_steam_price fetches a Steam store page for a given country and pulls the displayed price, falling back to a second selector if the first doesn’t match.

    def _rotating_geo_targeting(country: str) -> Targeting:
        """A fresh -session id forces 2extract to assign a new exit IP for this hop."""
        return Targeting(country=country, session=uuid.uuid4().hex[:12], time_minutes=1)
    
    def scrape_steam_price(client, *, country, app_id="730", targeting=None):
        url = f"https://store.steampowered.com/app/{app_id}/"
        response = client.get(url, targeting=targeting or Targeting(country=country),
                               params={"cc": country}, timeout=45)
        soup = BeautifulSoup(response.text, "lxml")
        price = soup.select_one("div.game_purchase_price") or soup.select_one("[data-price-final]")
        return {"app_id": app_id, "price": price.get_text(strip=True) if price else None,
                "http_status": response.status_code}

    A row from an actual checkpoint file. Note that the exit landed in Frankfurt (Hesse) for a country-level de request:

    {
      "row_number": 41,
      "collected_at": "2026-08-14T09:12:03Z",
      "label": "DE",
      "targeting": {"country": "de", "session": "a91fe2c7b1d4"},
      "exit": {"ip": "89.204.xx.xx", "city": "Frankfurt", "region": "Hesse"},
      "ok": true
    }

    eCommerce and price monitoring

    def parse_price(text):
        raw = " ".join(str(text).split())
        currency = "GBP" if "£" in raw else "EUR" if "€" in raw else "USD" if "$" in raw else None
        match = re.search(r"[\d]+\.[\d]+|[\d]+", raw.replace(",", ""))
        value = float(match.group(0)) if match else None
        return raw, value, currency

    Output lands in data/output/eCommerce.xlsx. Sessions stay sticky for the length of a category walk.

    Market research

    This pipeline pulls from Hacker News through the Algolia API and from Open Food Facts. Its “sentiment” field is an engagement heuristic derived from points and comment counts. It is not a human-coded label and not an LLM classifier.

    From collected rows to a spreadsheet

    Every pipeline writes a JSON checkpoint every hundred rows before touching Excel. That habit has saved a multi-hour run from a crashed openpyxl write more than once. The JSON-to-Excel step deliberately does no new computation; it only formats data that has already been collected and validated.

    How the package is organized

    None of the three pipeline scripts talk to requests directly. They all go through a single ExtractClient that owns authentication, retry behavior and username construction. That boundary is the reason adding the market research pipeline took an afternoon instead of a week.

    Residential, mobile or datacenter: the tradeoffs

    DatacenterResidentialMobile
    Exit looks likeCloud ASN (AWS, GCP)Home ISP connectionCarrier IP
    Block / CAPTCHA rateHighestLowLowest
    Typical costCheapestMidHighest
    Targeting granularityData center location onlyCountry / state / city / ZIPCarrier (ISP code) only
    Best fitLow-stakes internal jobseCommerce, geo, market researchMobile-specific SERPs, app traffic

    Where this breaks

    Country targeting is a request to the gateway, not a guarantee that a specific city lands on every hop. The only trustworthy record of where a request exited is the exit.* data that comes back, and it belongs in every row you store.

    JSON APIs like DummyJSON and Open Food Facts still return a bare 403 without a properly set User-Agent, proxy or not.

    Matching “the same product” across two catalogs by name and brand is a heuristic, not an identity.

    And a proxy changes your odds of getting a response, not your obligation to respect what a site’s terms of service say.

    How this was tested

    Every pipeline described above was run end to end against a live 2extract account: real wallet balance, real proxy status states, real 407s along the way. The MCP walkthrough ran against the same account in the same session, and the balance, geo slugs and traffic figures shown are what the account returned, not stand-ins. The full source is on GitHub under an MIT-style layout you can clone and rerun against your own account.rerun against their own account.

    Questions worth asking before production

    Does 2extract scrape websites for me?

    No. It is a proxy network that routes requests through residential or mobile exit IPs. Parsing, deduplication and storage stay the collector’s job.

    Does 2extract have an MCP server for agents like Claude, Cursor or Codex?

    Yes. mcp.2extract.com exposes the same account (balance, proxies, geo catalog, traffic history) as tools any MCP-capable agent can call, as shown in the walkthrough above. It is the same wallet and the same billing as the Python package; MCP hands the control plane to the agent instead of a human writing the client.

    Why does a request get a 407 when the password is definitely right?

    In practice it almost always means the proxy is set to Inactive in the dashboard, the wallet balance has run out, or the username carries malformed or conflicting targeting parameters.

    Can country targeting and mobile ISP targeting live on the same proxy?

    No. Geographic targeting and network targeting are mutually exclusive on a single username, and mobile ISP targeting needs its own dedicated proxy credential as well.

    Do I need a separate proxy for every country?

    No. One residential proxy covers every supported country. Targeting is a parameter appended per request, not a separate product bought per market.

    Does a residential proxy make scraping any site legally safe?

    No. A proxy changes which IP a target sees; it has no bearing on robots.txt, a site’s terms of service or copyright law.

    Further reading

    Pricing, account limits and interface details in this article reflect 2extract’s dashboard as of September 2026, captured while writing and testing the code above against a live account. Treat this as a guide to the architecture and integration pattern rather than a live pricing reference. Vendors update rates and UI labels independently of when an article is published, so confirm current numbers on the dashboard before budgeting a production run.

  • Building Data Pipelines That Feed AI Features Without Breaking the Bill

    Building Data Pipelines That Feed AI Features Without Breaking the Bill

    When the consumer at the end of a pipeline is a language model rather than a dashboard, the expensive step moves from the transform to the last hop, and every row you push through it costs money. This article covers the pipeline shape we keep returning to, a four-question test for choosing batch, event-driven or request-path inference, and the three cost levers that matter, in the order they matter.

    Photo: Derrick Coetzee, “Front of server racks at NERSC”, Wikimedia Commons, CC0 1.0 public
    domain dedication

    Most teams adding an AI feature to an existing product start by asking which model to use, or whether they need a streaming platform. Both matter far less to the bill than a duller question.

    How many model calls does one business event cause, and how many of them could have waited, or not happened at all?

    A pipeline built around that question stays predictable. A pipeline that treats the model as one more sink, like a reporting table, tends to produce the invoice that gets the feature switched off.

    The Consumer Changed, the Pipeline Did Not

    A classic analytics pipeline ends in a dashboard. The transform is the heavy step, the output is an aggregate, and reprocessing a day of data is cheap because warehouse compute is cheap per row. Staleness of a few hours is usually fine.

    A pipeline that feeds an AI feature inverts most of that:

    • The expensive step is the last one. Warehouse SQL over a few hundred thousand rows costs little. Sending those same rows to a model provider is billed per token, per row.
    • The output is per record, not aggregated. A product description, a ticket category, a summary for one report. There is no “roll it up” shortcut.
    • Reprocessing is no longer free. A backfill that is harmless for a dashboard can be the most expensive job of the month when a model sits at the end of it.
    • Idempotency becomes a cost control, not just a correctness property. A retry that re-sends a row is a second charge.

    The practical consequence: design the pipeline so the model sees as few rows as possible, each as small as possible, and never the same unchanged row twice.

    The Pipeline Shape We Keep Coming Back To

    Across the AI retrofits we have shipped into products that were already in production, from ticket triage on a marketplace to drafting product copy and summarising reports for internal reviewers, the architecture has settled into four layers.

    The operational store stays the write path, the warehouse prepares a model input contract, and a worker only calls the model for rows whose input actually changed.

    Figure 1: The pipeline shape. The operational store stays the write path, the warehouse prepares a model input contract, and a worker only calls the model for rows whose input actually changed.

    Our examples use BigQuery and a Node.js worker, because that is where most of this work runs. The shape maps directly onto AWS: object storage plus Athena or Redshift for the warehouse layer, and a scheduled container task for the worker.

    Layer 1: Ingestion, Kept Deliberately Boring

    The operational database stays the write path. The relevant tables are exported, by a managed streaming export or a scheduled one, into append-only raw tables in the warehouse.

    This is the same additive pattern we use for any workload that outgrows the operational database: keep writes where they are and build the new read path elsewhere. The AI feature can then be removed without touching the product’s core data.

    Layer 2: Transformation Produces a Model Input Contract

    The most useful artifact in the whole pipeline is a single view that defines exactly what the model is allowed to see, in exactly the shape the prompt reads. Nothing else is sent.

    -- One row per record the model may see, in the shape the prompt reads.
    CREATE OR REPLACE VIEW ai.product_copy_input AS
    SELECT
      p.product_id,
      p.title,
      p.category,
      p.origin,
      p.unit,
      -- Hash only the fields the prompt uses. Price and stock are excluded on purpose.
      TO_HEX(SHA256(TO_JSON_STRING(STRUCT(p.title, p.category, p.origin, p.unit)))) AS input_hash
    FROM raw.products_latest AS p
    WHERE p.status = 'unpublished';

    Three things happen in that view. Fields are trimmed to the handful the prompt needs. Anything that must not leave your infrastructure is removed or replaced with a placeholder here, in SQL you can review, not in application code scattered across services. And input_hash gives every row a content-based identity the next layer uses to skip work.

    On a feature that read health-related customer records, replacing a full record with a hand-picked field set cut prompt tokens by more than half, and reduced what left our infrastructure at all. That one change beat every model-pricing decision on the same feature.

    Layer 3: The Inference Worker Is a Pipeline Stage

    The model call belongs in a worker that behaves like any other pipeline stage: it selects its inputs, processes them in batches, validates its outputs, and records what it did.

    interface ModelInput { productId: string; inputHash: string; prompt: string }
    
    async function runNightlyDrafts(cap: SpendCap): Promise<void> {
      // Only rows whose input hash has never been sent to the model
      const rows: ModelInput[] = await warehouse.query(`
        SELECT i.* FROM ai.product_copy_input AS i
        LEFT JOIN ai.processed_inputs AS d USING (product_id, input_hash)
        WHERE d.product_id IS NULL`);
    
      for (const batch of chunk(rows, 50)) {
        if (!cap.allows(estimateTokens(batch))) break; // stop quietly, the app keeps its fallback
        const results = await provider.generate(batch);
        const valid = results.filter(isValidDraft); // schema check, invalid output is dropped
        await sidecar.upsert(valid); // keyed by product_id and input_hash
        await warehouse.insert('ai.processed_inputs', results.map(toLedgerRow)); // failures too, or they are re-sent nightly
        cap.record(results);
      }
    }

    The output never overwrites the product’s own fields. It lands in a sidecar table keyed by record ID and input hash, and the application reads it when a valid row exists.

    When none exists, because the worker has not run, the cap was hit or validation failed, the application renders what it rendered before the feature existed. Removing the feature means deleting a table, not reversing a migration.

    Batch, Event-Driven or Request Path: A Four-Question Test

    Most “batch versus streaming” debates about AI features are really a question about who is waiting. We run every feature through four questions, in order, and stop at the first one that gives a clear answer.

    Figure 2: The four-question test. Each question is an exit; most features leave at question one or two.

    1. Can the Output Be Computed Before Anyone Asks for It?

    If yes, it is a scheduled batch job, full stop. Latency is free, batch pricing from providers applies, and the input hash means the nightly run only touches rows that changed.

    Product description drafting is our clearest example: retailers submit products during the day, drafts appear overnight in a draft field, and a person approves before anything is published.

    2. Does a Person Wait on Screen for the Result?

    If nobody is watching, it is event-triggered and asynchronous: a post-write trigger puts the record on a queue, a worker calls the model, and the result lands later.

    Support ticket triage works this way. The ticket is saved first, the customer sees no added latency, and the suggested category appears for the ops team a few seconds later. If the call fails or exceeds its timeout, the ticket goes to the default queue as it always did.

    For most AI features, this is what “streaming” means: a trigger and a queue. A dedicated event streaming platform earns its place when many independent consumers need the same event history, which one AI feature rarely justifies.

    3. Did the Person Explicitly Ask for It?

    If a user clicked “tidy up this description”, it is a user-triggered call. Seconds are acceptable because they asked and are watching a loading state. It still needs a cancel path and the original content preserved.

    4. Can the Page Render Acceptably If the Call Is Skipped?

    Only now do we consider the request path, and only with a hard timeout well under the page’s existing latency budget and a deterministic fallback that renders the pre-AI experience.

    If the page cannot render without the model’s answer, the feature is not ready for that path. Precompute it, or do not ship it there.

    Where the Bill Actually Comes From

    The monthly cost of an AI feature is roughly:

    calls per business event × tokens per call × event volume

    The price per token is the factor people argue about, and it is the one we touch last.

    Across our retrofits, per-call cost varied by two orders of magnitude between features, and the levers that moved it were, in order of impact:

    1. Trim the input. Covered in layer 2. Fewer fields, fewer tokens, less data leaving your systems.
    2. Key the cache on content, not identity. See below.
    3. Switch models, but only with an eval set. A smaller model is cheaper per call, but without a fixed set of real inputs and assertions to prove quality held, a model switch is a guess with a saving attached.

    Key the Cache on Content, Not Identity

    An expensive mistake we have made ourselves is deciding whether to regenerate based on the record: its ID plus an updated_at timestamp. Records change constantly for reasons the model does not care about.

    Figure 3: An illustrative sequence of changes to one product. Keyed on identity, every change triggers a model call. Keyed on a hash of the fields the prompt reads, only the changes the model would notice do.

    Keyed on identity, every change triggers a model call. Keyed on a hash of the fields the prompt reads, only the changes the model would notice do.

    On a marketplace feature that generates copy once per product version and serves it thousands of times, moving the cache key from the product ID to a hash of the normalised attribute set meant regeneration only happened when attributes actually changed.

    Monthly spend on that feature dropped to a fraction of its launch figure, with no change to the model or the prompt.

    Put the Spend Cap in the Pipeline

    Provider dashboards can alert you, but they cannot make your product degrade gracefully.

    We write a hard ceiling into the worker itself, as in the snippet above. When it is reached, the worker stops and the application falls back, so an overspend becomes a quiet degradation someone reviews in the morning rather than an invoice discovered at month end.

    Where Managed Services Stop Being Worth It

    The pitch for a managed ETL connector, a distributed compute cluster, or a dedicated vector database is usually made as if the data volume were the hard part. For most AI features inside an existing product, it is not.

    The volume that matters is bounded by business events: products listed, tickets opened, reports produced.

    Our rules of thumb:

    • Managed connectors earn their fee for SaaS sources you do not control and whose APIs change under you. For your own operational database, a native export into the warehouse is usually simpler and cheaper.
    • Distributed compute such as Spark earns its place when the transform itself is the heavy step: preparing very large corpora, non-SQL processing at scale, or feeding self-hosted models. When the transform fits in warehouse SQL and the expensive step is a rate-limited API call, a cluster adds operational weight without shortening the part that is slow.
    • Serverless functions suit short triggers. Long batch runs belong in a container runtime with no execution time ceiling.
    • A dedicated vector store is worth it once the corpus and query volume outgrow what your existing database or warehouse can serve. For a staff-only internal search over a modest document set, it is often one more system to secure and keep in sync.

    The trade-off we accept is a pipeline that looks unimpressive on an architecture slide. In return, fewer systems hold copies of the data, which matters when a deletion request has to reach every copy.

    When a Pipeline Is the Wrong Answer

    Not every AI feature needs one.

    If the feature is user-triggered, operates on content already on screen, and runs a few times a day per user, a direct call with a timeout and a preserved original is simpler and cheaper than any pipeline.

    A pipeline also cannot fix missing rules. We once scoped automatic routing of support messages against categories that existed in a dropdown, while the real routing logic lived in one person’s head and contradicted it.

    No amount of data engineering helps there. If the rules cannot be written down before work starts, write them down first.

    What to Do on Monday

    Pick your most expensive AI feature and write down three numbers:

    1. Model calls per business event.
    2. Average input tokens per call.
    3. How many of last month’s calls processed an input identical to one already processed.

    Then add an input hash to the view that feeds it.

    If that third number is not close to zero, the hash will pay for itself before you touch the model.

  • From ETL Pipelines to Data Products: Designing Reusable Data Infrastructure for Enterprise AI

    From ETL Pipelines to Data Products: Designing Reusable Data Infrastructure for Enterprise AI

    Enterprise data platforms often begin with a simple objective: move data from operational systems into a place where it can be analyzed. Over time, however, the number of data sources, consumers, and business requirements grows. A pipeline originally created for one dashboard becomes useful to another team. A transformation developed for a reporting workload is recreated for an application. An AI team builds yet another pipeline because the existing data was not structured for its requirements.

    The problem is not that organizations have too few pipelines. In many mature environments, they have too many pipelines performing overlapping work.

    This creates a different challenge for data engineering: how do we build data infrastructure that can be reused across analytics, applications, and AI without turning every new requirement into another independent pipeline?

    One answer is to move from thinking primarily about ETL pipelines toward thinking about data products.

    A data product is not simply a table in a warehouse or a dataset stored in a lake. It is a reusable data asset with defined meaning, ownership, quality expectations, metadata, lineage, and consumers. The objective is to make the data useful beyond the specific pipeline that originally produced it.

    The Problem with Use-Case-Specific Pipelines

    Traditional ETL architectures are often organized around downstream requirements. A team receives a request for a report, builds an extraction and transformation process, and produces the required dataset. Another team later needs similar information and creates another pipeline because its requirements are slightly different.

    At first, this approach is reasonable. The system is small, the requirements are clear, and the fastest solution is often to build exactly what is needed.

    The difficulty appears as the organization grows.

    Multiple pipelines may independently extract the same source data, apply similar business rules, and create slightly different versions of the same business entity. One pipeline may define an active customer differently from another. One dashboard may calculate revenue using one transformation while another application uses a different version.

    Eventually, the organization has a collection of pipelines that individually work but collectively create a difficult data environment.

    The goal of a data product approach is not to eliminate pipelines. Pipelines remain essential. The change is in what the pipeline is designed to produce.

    Instead of building a pipeline exclusively for one downstream consumer, the pipeline can contribute to a reusable data asset with clearly defined characteristics.

    Figure 1. The evolution from use-case-specific ETL pipelines toward reusable data products.

    From Pipelines to Data Products

    The distinction is subtle but important.

    A pipeline describes how data moves and changes.

    A data product describes what trusted data is made available for others to use.

    For example, an organization may have customer, order, inventory, or product information arriving from multiple operational systems. Instead of creating separate transformations for every consumer, the platform can produce a curated data product representing a well defined business concept.

    That product should answer basic questions before another team consumes it:

    • What does this data represent?
    • Who owns it?
    • How frequently is it updated?
    • What quality expectations does it have?
    • What transformations have been applied?
    • Where did the data originate?
    • Which downstream systems depend on it?
    • How should consumers interpret important fields?

    This turns the dataset from an anonymous technical output into something that other teams can confidently build upon.

    The distinction becomes particularly important when AI systems enter the architecture. AI applications need access to enterprise information, but simply exposing more raw data does not necessarily produce better results. The data must have consistent meaning, appropriate granularity, and enough context for the consuming system to use it correctly.

    Designing the Architecture for Reuse

    A reusable data architecture does not require one enormous centralized pipeline. Instead, it separates concerns while establishing clear interfaces between layers.

    A typical architecture can begin with operational databases, APIs, files, event streams, and other enterprise sources. An ingestion layer brings that information into the platform, where raw data can be preserved before transformation.

    Transformation and quality processes then produce curated datasets. The important difference is that these curated datasets are designed as reusable products rather than temporary outputs for one report.

    Figure 2. A reusable data product architecture separates ingestion, transformation, quality, governance, and consumption.

    The architecture can support multiple consumers from the same trusted data product.

    Analytics teams may use it for dashboards and reporting. Applications may consume it through APIs or services. Data scientists may use it for machine learning workflows. AI systems may use it as part of retrieval, contextualization, or decision support workflows.

    This does not mean every consumer receives exactly the same representation. Different consumers may require different interfaces or derived views. The important principle is that core business logic should not be unnecessarily duplicated.

    Data Contracts Make Reuse Possible

    Reusability becomes difficult when consumers do not know what they can rely on.

    A data contract provides an explicit agreement between data producers and consumers about the expected characteristics of a data asset. At the simplest level, this can include schema and data types. In a mature environment, the contract can go further.

    It can define expected semantics, ownership, freshness, acceptable values, compatibility expectations, and changes that require communication.

    Consider a field called status.

    From a technical perspective, a string is a perfectly valid datatype. But what does the string mean?

    Does active mean an account is currently usable? Does it mean the customer has purchased something recently? Does it mean a subscription is paid?

    Schema validation cannot answer that question.

    For reusable data products, semantic consistency is as important as structural consistency.

    A contract therefore becomes a mechanism for protecting consumers from unexpected changes while giving producers a clear responsibility for maintaining the data they publish.

    Quality Is Part of the Product

    Data quality should not be treated as a final step performed after a pipeline has been built.

    If a dataset is intended to become a reusable data product, quality is part of the product itself.

    Different products will require different checks, but common considerations include completeness, validity, uniqueness, consistency, and freshness.

    For example, a product containing transactional information might need to detect duplicate records. A product supporting operational decisions might require strict freshness expectations. A product used for historical analysis may tolerate delayed updates but require strong consistency over time.

    The important point is that quality expectations should be explicit and measurable.

    This also changes how data engineers think about failures. Instead of asking only whether a pipeline completed successfully, engineers can ask whether the resulting data product continues to meet its defined expectations.

    Metadata and Lineage Are Not Optional Extras

    When organizations have hundreds of datasets, discovering what a dataset means can become as difficult as producing it.

    Metadata helps answer questions such as where a dataset came from, what its fields represent, how frequently it changes, and who is responsible for it.

    Lineage provides another important dimension: understanding how data moved through the system and which upstream sources contributed to the final product.

    This becomes especially valuable when something changes.

    If a source field is modified, engineers should be able to determine which transformations and downstream consumers may be affected. Without lineage, that investigation can become a manual search across pipelines and documentation.

    For data products to remain reusable, discoverability and explainability need to be designed alongside the data itself.

    One Data Product, Multiple Consumers

    A major advantage of this approach is that the same trusted data foundation can support different types of workloads.

    Figure 3. A reusable data product can support analytics, applications, and AI workloads without duplicating core transformation logic.

    Consider a curated product representing a business entity such as a product, customer, transaction, or inventory position.

    An analytics team might use it to create operational dashboards. An application might use the same information to support a workflow. An AI system might use it to provide context to an agent or model.

    The consumers are different, but the underlying business definitions do not need to be reinvented each time.

    This is where the concept becomes particularly powerful for enterprise AI.

    Making Data Products Useful for AI

    AI systems introduce a new category of data consumer.

    Traditional analytical workloads often operate through structured queries and predefined metrics. AI applications may need to retrieve information dynamically, combine multiple pieces of context, interpret relationships, and use that information as part of an inference or action.

    That places additional demands on the underlying data.

    AI systems benefit from data that is:

    • semantically consistent
    • sufficiently granular
    • appropriately contextualized
    • discoverable
    • governed
    • fresh enough for the intended use case
    • accessible through reliable interfaces

    This does not mean every data product needs to be redesigned specifically for AI.

    Instead, organizations should build reusable data foundations that can support AI as one of several consumers.

    That distinction helps prevent a common architectural mistake: creating an entirely separate data ecosystem every time a new AI initiative appears.

    Avoiding the “One Pipeline Per Use Case” Trap

    The answer is not to centralize every transformation into one massive pipeline.

    Over centralization can create its own problems. A change made for one consumer can unexpectedly affect many others. Teams may also become dependent on a central group for every modification.

    A better approach is to identify which data assets and transformations are genuinely reusable.

    Common business entities and shared definitions are strong candidates for reusable products. Highly specialized analytical logic may remain closer to the consuming workload.

    The architectural question should therefore be:

    What should be shared, and what should remain specific to the consumer?

    Good data engineering is not about maximizing reuse at any cost. It is about finding the right boundaries.

    Practical Principles for Building Reusable Data Infrastructure

    Organizations beginning this transition can start with a few practical principles.

    Build once, consume many times.
    When multiple teams repeatedly implement the same business logic, investigate whether the underlying data should become a reusable product.

    Define ownership early.
    A reusable dataset without clear ownership eventually becomes nobody’s responsibility.

    Treat metadata as part of the product.
    Documentation, definitions, lineage, and discoverability are not administrative additions. They determine whether another team can actually use the data.

    Make quality measurable.
    Define expectations around freshness, completeness, validity, and other characteristics that matter to the product’s consumers.

    Design for change.
    Schemas, business rules, and upstream systems will evolve. Data products should have clear compatibility and change management practices.

    Separate shared data from consumer specific logic.
    Not every transformation needs to be centralized. Reuse the parts that represent stable, broadly useful business concepts while allowing downstream teams to build specialized views.

    Conclusion

    The evolution from ETL pipelines to data products is not about replacing one technology with another. It is a shift in how organizations think about the outputs of data engineering.

    A pipeline can successfully move data from one system to another and still create little long term value if every downstream consumer must interpret, validate, and transform that data independently.

    A data product takes a different approach. It treats trusted data as a reusable enterprise capability with defined meaning, quality expectations, ownership, metadata, and lineage.

    That approach becomes increasingly important as organizations add AI systems to their technology landscape. AI does not eliminate the need for sound data infrastructure. It increases the number of ways that trusted enterprise data can be consumed.

    The mature data platform, therefore, is not simply a collection of pipelines.

    It is an ecosystem of reliable data products that allows analytics, applications, and AI systems to build on the same trusted foundation.

  • ETL vs. ELT vs. Reverse ETL: A Practitioner’s Framework for Choosing the Right Pattern for a Given Workload

    ETL vs. ELT vs. Reverse ETL: A Practitioner’s Framework for Choosing the Right Pattern for a Given Workload

    As a data engineer, you’ve likely been in a design review where someone says at the very end, “We should just do ELT for everything!”. You are a data engineer, and you’ve probably been in a design review where you heard someone say at the end, “We should just do ELT for everything!”. Or you’ve inherited a package in an old 10-year-old version of SSIS that you would have to pick apart row-by-row in a painful manner, and the business is wondering why the “modern” warehouse team can’t simply replace it overnight. The industry has morphed ETL, ELT and now Reverse ETL into tribal identities: pick one, fight about it in slack threads, ship. But that’s backwards. The pattern is not the personality, it is a means to an engineering end.

    The reality, though, is that ETL, ELT, and Reverse ETL are not mutually exclusive approaches — they are three different kinds of pipelines that are tackling three different problems and most production data pipelines require all three to be running concurrently. Don’t identify a winner. It’s to align the pattern with the limitations of the workload: data sensitivity, complexity of transformation, latency requirements, and the most cost-efficient place to execute the workload.

    The Problem Deep Dive

    All three patterns use the same three verbs—extract, load, transform—but in different orders: that’s why it’s a bit of a muddle.

    ETL (Extract, Transform, Load): ETL is the process of moving data into a staging area, where it is transformed in some way outside of the target system before it is loaded into the clean data in the target system. This was the standard practice for decades as target systems or data warehouses, particularly transactional databases, would be costly to compute on and required to be buffered from raw and dirty data. This pattern has become the backbone of careers such as those of tools like SSIS, Informatica, and Talend.

    ELT (Extract, Load, Transform) first loads raw data into the destination, and then transforms it-while-stored using compute functions on the destination. It really only became dominant when cloud warehouses (Snowflake, BigQuery, Redshift, Azure Synapse) separated storage from compute and started to offer cheap ways to store raw data and transform it later, incrementally, using tools like dbt.

    Reverse ETL moves the already transformed, already modeled data out of the warehouse and into operational systems for business teams to take action: Salesforce, HubSpot, Braze (an ad platform). It’s more of a third point on the same spectrum; it’s the vehicle for the other two, which is a problem that neither ETL nor ELT were intended to solve—getting warehouse truth into the tools where other things and humans are happening.

    The sticking point is that teams find themselves using these as “short cuts” rather than as decisions based on the workload:

    ·       Failure of compliance due to ELT by-default. A healthcare team loads raw PII into a warehouse without masking or tokenization as that is the way modern data stacks work.A healthcare or fintech team places the raw PII in a warehouse without masking or tokenization first. Now, without the mask, SSNs or PHI are stored in raw schemas that are accessible to half the analytics org and the cost of the compliance retrofit outweighs the transform step. This is the same for ETL’s pre-load transformation; scrub before, don’t scrub after it lands.

    ·       Runaway warehouse costs due to misusing ELT. A team pushes a full nightly extract of a 500-million-row transactional table into Snowflake, and uses dbt models to re-scan the data on every run, rather than incremental models. The warehouse bill expands due to the compute moving from a dedicated ETL server to the meterized cloud credits, without any one looking at the meter.

    ·       Operational data that has been stale due to the lack of Reverse ETL. A customer lifetime value model is created in the marketing team’s warehouse, but not piped back to the CRM. The warehouse model has no way to get back out of Salesforce, so the Sales reps still see the raw purchase counts. The insight is there, but it isn’t there where the person needs it at decision time.

    ·       ETL (Row-by-row) instead of ELT (Set-based). Some classic SSIS or legacy on-prem pipelines where they were changing records one by one inside the pipeline, and the same transformation can be written as a simple set-based SQL statement in the target warehouse and run in a fraction of the time.

    All these are the correct tools for the wrong jobs — but not bad tools!

    The Solution: A Decision Framework

    Ask 4 questions about each workload, instead of “which pattern do we standardize on?”. In reality, all three patterns are implemented together on various pipelines on most platforms.

    1. Is the data required to be scrubbed, masked, or filtered before it reaches any place it can be queried? If yes — PII, PHI, cardholder data, anything under GDPR/HIPAA/PCI scope — transform before load. This remains ETL’s main reason to be, regardless of the ELT fashions. Mask/tokenize in the extraction layer (Azure Data Factory data flows, or a simple Python/SQL Server SSIS step), meaning that any raw sensitive values never reach the raw schema of the warehouse. For those on the Microsoft stack, this is the typical best case scenario for maintaining ADF or SSIS in the mix in an otherwise ELT-focused Azure Synapse or Fabric pipeline, instead of removing it from the mix because dbt is cool.

    2. Does the transformation require a lot of steps and iterations and needs to be versioned, tested, re-run by analysts? If yes, use ELT. Bring in raw (or lightly scrubbed) data to the warehouse and leave the work of in-place modeling to dbt, stored procedures or Synapse/Databricks notebooks. You have version-controlled transformation logic, built-in automated testing, lineage graphs, and can rebuild history without re-extracting from a fragile source system when someone discovers a bug in the transformation.

    The most important decision when creating a model that causes cost blowouts is whether to run it incrementally (only the new or changed rows since the last run) or not. The key for teams transitioning from SQL Server/SSIS to a dbt-style ELT mindset is to get this concept in their heads early: a transformation is no longer “a step in a pipeline,” it’s a “materialized view” that needs to be refreshed, and it’s that refresh strategy where most of the warehouse bill lives or dies.

    3. What is the latency requirement and is it variable with each hop? Batch analytics (nightly board reporting) does not require ELT’s load then transform lag. Extracting and initial transforming typically occur within a streaming layer (Kafka/Event Hubs + stream processing) before anything enters a warehouse, or in the context of ETL, transform early.

    4. Is there a need for this insight to act upon outside of the warehouse for a human or downstream system(s)? Whether the data pipeline was ETL or ELT, without Reverse ETL the value of the data is lost once it’s returned. Tools such as Census, Hightouch, or a scheduled Azure Function or Logic App that read from a warehouse view and write to an API fill in the gap. Model it once, sync it wherever it needs to act it out. What this typically involves in practice is creating a single, well-governed model in the warehouse, e.g. a “customer health” or “customer segment” view, which categorizes each customer as “at-risk”, “high-value” or “standard” according to recency and lifetime spend, and then letting a Reverse ETL tool match that segment field with a custom field in the CRM, following a set schedule. Never again will any engineer have to create a CSV to Salesforce, and a sales rep’s view of the segment is always up to date with the warehouse model that powers it.

    In reality: streaming or batch extraction with PII scrubbed at the source (ETL) → raw but safe data deposited in the warehouse (ELT) → curated marts synced back to operational tools (Reverse ETL). Three patterns, one pipeline, for each one of them it is really good at doing.

    Proof: What This Looks Like in Practice

    The initial design on a retail insight app I was working on was ELT only – raw order and customer data was coming straight from Salesforce and the transactional database into Snowflake and all masking and transformation was being done downstream in dbt. Until a compliance audit brought up the fact that raw customer PII was accessible to any analytics use case with access to the warehouse, which also accessed some fields that never saw use in any analytics use case.

    But it wasn’t about giving up on ELT. It was putting in a thin ETL step at extraction: an Azure Data Factory data flow that was stripping out PII fields before putting data into the raw schema, and everything else flowed directly through to dbt for modeling. Access to detokenized values was restricted to a few service accounts.

    The measurable outcome: compliance gap was closed without any action on the 40+ dbt models that were already deployed, as the business logic that needed to be changed didn’t need to be moved. Compute cost was not impacted — the masking step did not cost anything in the warehouse compute, it did on the extraction layer. The team then integrated an hour-by-hour (via Hightouch) Reverse ETL sync to move the customer health segment view from the marketing platform into the CRM – removing a weekly manual CSV export performed by a marketing analyst every Monday. None of these three required giving up the other changes, and each pattern was used precisely where the compromises were warranted.

    The Close

    ETL, ELT, and Reverse ETL are not competing architectures for your loyalty, but three tools to address three different questions: what must be cleaned before it is put into the warehouse, what is less expensive to transform when compute resides there, and what must come out of the warehouse to have an impact? The ones who are burned are those who choose one pattern and apply all of their workloads through it.

    The next time you are thinking of setting up a pipeline, don’t ask “are we an ETL shop or an ELT shop?”. For each workload: Does it need scrubbing before it lands? Is the transform complex enough to benefit from version control and incremental materialization? Does it need to be materialized at extraction time (latency) given that output needs to walk back out of the warehouse to do any good? When answering the four questions truthfully for each data flow, the correct pattern — typically multiple patterns — emerges spontaneously.

  • Data Engineering ROI: How to Justify Your Data Platform Investment to the Board

    Data Engineering ROI: How to Justify Your Data Platform Investment to the Board

    Every data engineering leader has sat through the same meeting: the platform work is done, the pipelines are stable, but defending its value to the board is difficult because it rarely appears on a single P&L line. Data platform budgets are often treated as discretionary IT spend, meaning they get cut first during a downturn. Getting ROI measurement right is what keeps your next initiative fundable.

    Why Data Engineering ROI Is Hard to Measure (and Why It Matters)

    Boards fund outcomes. Data engineering delivers infrastructure. That mismatch is the root of the problem.

    A board can evaluate a sales tool by pipeline generated, or a marketing spend by cost-per-acquisition. Data platform investments don’t map that cleanly pipeline reliability, schema governance, and data quality improvements are foundational, meaning they enable other initiatives rather than generating value on their own. An Al model that improves fraud detection accuracy gets the credit; the governed, clean, well-lineaged data pipeline underneath it, without which the model wouldn’t have worked, gets none.

    This isn’t just a communication problem. Left unaddressed, it becomes a funding problem. Data platform budgets get treated as discretionary IT spend, get cut first in a downturn, and then get blamed when the next Al initiative underperforms because the data underneath it was never solid. Getting ROI measurement right isn’t an exercise in optics, it’s what keeps the next initiative fundable.

    Metrics That Actually Translate to Business Impact

    The fix starts with picking metrics a board member without a data engineering background can actually interpret. A few that consistently translate well:

    Data downtime cost avoided: Every hour a critical pipeline is down or serving bad data has a real cost delayed reporting, blocked decisions, or in regulated industries, compliance exposure. Tracking incidents avoided (or their reduced frequency after a platform investment) turns an abstract reliability improvement into a dollar figure.

    Time-to-insight reduction: How long does it take from “we need this data” to “here’s the answer”? If that cycle shrinks from days to hours after a platform investment, that’s a directly measurable efficiency gain that maps to faster business decisions.

    Engineering hours reclaimed from firefighting: A mature platform investment shows up as a shift in how engineers spend their time less time patching broken pipelines and chasing data quality issues, more time building new capabilities. That ratio, tracked before and after, is one of the cleanest ROI signals available.

    Data quality incident rate: Fewer downstream errors caused by bad data, wrong numbers in a report, a broken dashboard, a flawed model input is a leading indicator of platform health that’s easy to track and easy to explain.

    Cost-per-query or compute efficiency: For teams on modern cloud data stacks, tracking compute spend against query volume or data processed shows whether platform investments are actually improving unit economics, not just adding capability.

    None of these require exotic instrumentation. Most are extractable from existing observability and cost-monitoring tools already in place. The work is in deciding which ones matter for a given business and tracking them consistently.

    Connecting Data Initiatives to Business Outcomes

    Metrics alone don’t make the case they need to be tied to a specific business decision or outcome of the platform investment enabled or unblocked.

    The strongest version of this argument doesn’t say “we modernized our data stack.” It says: “faster, more reliable data pipelines cut our fraud review time from four hours to forty minutes,” or “consolidating our data sources let underwriting make decisions same-day instead of next-day.” Specific, traceable, and tied to something the board already understands the value of.

    This only works if a baseline exists before the investment. Teams that skip measuring the “before” state lose the ability to prove improvement later, a gap worth closing at the start of any platform initiative, not after the fact when the board asks for numbers. A structured data-readiness assessment before a major platform investment is one of the more reliable ways to establish that baseline, since it forces a documented starting point across data quality, infrastructure, and governance maturity that the post-investment numbers can be measured against.

    Framing matters too. An investment task built around “we need to modernize our data infrastructure” competes with every other infrastructure request in the budget cycle. An investment task built around “this unblocks same-day underwriting decisions” competes on the same terms as revenue-generating initiatives and tends to win more often.

    Making the Case to the Board

    When it’s time to present, resist the instinct to show everything. A board conversation isn’t the place for a full metrics dashboard, it’s the place for three or four numbers, chosen because they answer the two questions every board member is actually asking: why now, and what happens if we don’t.

    “Why now” is answered by connecting the investment to a business pressure the board already recognizes regulatory deadlines, a competitor’s faster decision cycles, or a growth plan that the current data infrastructure can’t support. “What happens if we don’t” is answered by quantifying the cost of inaction: the downtime already being absorbed, the compliance exposure already being carried out, the engineering hours already being spent on maintenance instead of building.

    This is a distinction we see play out constantly at Samta.ai, working with BFSI and regulated clients across Singapore. The teams that get board sign-off aren’t necessarily running the most technically impressive platforms, they’re the ones who walked into the room with a baseline, a business outcome, and a dollar figure attached to inaction.

    A recent IDC-backed business value study on enterprise data platform investments found that organizations with mature data discovery and governance infrastructure consistently recovered platform costs through reduced analyst search time and fewer duplicate data efforts alone before counting any downstream Al or analytics gains. That’s the kind of framing that resonates with a board: cost recovery that doesn’t depend on a speculative future win.

  • Building AI Agents: What Actually Works in Production

    Building AI Agents: What Actually Works in Production

    Ask ten teams if they’ve built an AI agent and nine will say yes. Look at what they actually shipped and most of it is a workflow with one LLM call in the middle — a fixed pipeline where a model fills in one step, not an autonomous system deciding its own path. That’s not a criticism. It’s the most important thing nobody says out loud about building AI products in 2026: the stuff that actually works in production is far less “agentic” than the demos, and the teams getting value are the ones who figured out which 20% of true agency is worth the risk and hard-coded the other 80%.

    There’s a clean mental model underneath the hype, though, and it’s worth having whether you’re building a real agent or an honest workflow. An agent is just a language model wrapped in five things: tools (what it can do), knowledge (what it knows), memory (what it remembers), a loop (how it acts), and guardrails (what it must not do). Answer “what tools, what knowledge, what memory” for your use case and the architecture mostly writes itself. This is that model, told from a data engineer’s chair — where “tools” means governed access to your warehouse, “knowledge” means your RAG and your data quality, and every one of these five parts fails in ways you’ve seen before under different names.

    TL;DR

    • → An AI agent is an LLM plus five components: tools, knowledge, memory, a reasoning loop, and guardrails — design those and the architecture follows.
    • → Most production “agents” are really workflows with one smart step, and that’s usually the correct, cheaper, safer choice — reach for true agency only when the path genuinely can’t be fixed in advance.
    • → What works: narrow single-purpose agents, curated tool sets, humans approving risky actions, and evals gating every release. What’s still hype: fully autonomous do-anything agents acting unsupervised.
    • → For a data engineer, “give the agent tools” means governed, least-privilege access to your systems — an agent with write access is a service account that makes its own decisions.
    • → “Knowledge” is a RAG-and-data-quality problem, not a model problem; most agent failures trace to bad retrieval or stale data, not a weak LLM.
    • → The reasoning loop needs a hard step cap, and the whole thing needs evals — an agent without evaluation is an untested pipeline with a mind of its own.

    The five parts of an agent

    Strip away the branding and every agent, simple or complex, is a model surrounded by the same five components. The value of the model is that it forces you to answer concrete questions before you write code — and each question maps onto infrastructure you already own.

    The whole model on one page: an LLM is just the reasoner. Tools, knowledge, memory, the loop, and guardrails are the parts you actually engineer — and the parts that actually break.

    Tools — what it can do. The functions the agent can call: query a table, hit an API, write a file. For a data engineer this is the highest-stakes component, because “give the agent a tool” means “grant a non-deterministic system access to your infrastructure.” A read-only tool against account-usage views is low risk; a tool that can suspend a warehouse or write to a table is a service account that makes its own decisions. Least privilege isn’t a nice-to-have here — it’s the whole safety model, which is why I’ve written separately on giving agents access without opening security holes. The emerging standard for exposing these tools cleanly is MCP, which I broke down in MCP explained in 3 levels.

    Knowledge — what it knows. The facts the agent needs that aren’t in the model’s training: your schemas, your docs, your current data. This is a retrieval problem, and it’s where most agent projects actually fail — not because the model is weak, but because the retrieval is bad or the underlying data is stale. If you’re wiring this up on your own data, the mechanics are in the Cortex Search RAG guide. The uncomfortable truth: your agent is only as good as the data platform underneath it.

    Memory — what it remembers. State that persists across steps or sessions, so a follow-up like “now do the same for last quarter” doesn’t start from zero. In practice this is checkpointing — the same durable-state thinking you apply to any pipeline that has to resume after a failure rather than restart.

    The loop — how it acts. The reason-act-observe cycle that separates an agent from a single call: the model reasons, calls a tool, reads the result, and decides what to do next. This loop is the source of an agent’s power and its danger, which is why it always needs a hard cap on steps.

    Guardrails — what it must not do. Input validation, output filtering, step limits, and human approval for risky actions. These are the AI equivalent of the constraints and tests you’d never ship a pipeline without — the difference between a demo and something you can leave running.

    The distinction that matters most: workflow vs agent

    Before you build anything, answer one question honestly: does the task need the model to decide the path, or just to do one hard step along a path you already know? Getting this wrong is the most common and most expensive mistake in the space.

    A workflow runs a path you defined; an agent decides the path at runtime. The runtime freedom is exactly what makes agents powerful — and exactly what makes them harder to test, secure, and cost-control.

    A workflow is a fixed sequence you designed, with a model doing the intelligent part of one or more steps — classify this ticket, extract these fields, draft this summary. You control the path; the model fills in the reasoning. An agent hands the model the wheel: it decides which tools to call and in what order, looping until the task is done. The agent is more flexible and can handle open-ended tasks a rigid pipeline can’t — but that same freedom is what makes it harder to test (the path changes every run), harder to secure (it can call tools in combinations you didn’t anticipate), and harder to cost-control (each loop is a billed call). The senior move is to default to a workflow and escalate to agency only where the task genuinely can’t be pre-planned — which is the same restraint I argued for in choosing tools over subagents.

    What actually works (and what’s still theater)

    Here’s the honest cut from teams actually running this in production, stripped of vendor optimism. The pattern is consistent: narrow beats broad, supervised beats autonomous, and boring beats clever.

    The consistent signal from production: narrow beats broad, supervised beats autonomous, curated beats sprawling. The right column is where budgets and pilots go to die.

    The single most reliable pattern is the narrow, single-purpose agent: one job, a small curated set of tools, and a human in the approval path for anything consequential. The fantasy that keeps failing is the autonomous do-anything agent with dozens of tools and no supervision — it demos beautifully and falls apart on the long tail of real inputs. Multi-agent “swarms” are especially over-applied; most problems that get a swarm proposal are better served by one well-scoped agent or, more often, a workflow. And underneath all of it: without evals gating releases, you have no idea whether a change helped or hurt, which means “it looked good in the demo” becomes your entire QA process. This connects to a deeper reliability problem I covered in why larger models give confidently wrong answers in production — more autonomy multiplies the blast radius of that failure mode.

    A pragmatic way to start

    If you’re building your first AI product, resist starting with “let’s build an agent.” Start with the three founding questions — what tools, what knowledge, what memory — and answer them for the narrowest useful version of the task. Then build the simplest thing that could work, which is almost always a workflow: a fixed path with one or two LLM-powered steps, real retrieval behind the knowledge step, and a guardrail on anything that touches production. Add evals before you add capability. Only when you hit a task where you genuinely can’t predetermine the path — where the model really does need to choose among tools dynamically — do you graduate to a true agent, and even then you keep the tool set tight and a human on the risky actions. The teams shipping working AI products aren’t the ones who built the most autonomous system. They’re the ones who were honest about how little autonomy the job actually required.

    The gotchas nobody warns you about

    Most “agents” should be workflows. If you can draw the path in advance, you don’t need an agent — you need a pipeline with a smart step. Building an agent for a workflow problem buys you cost, unpredictability, and a security surface you didn’t need.

    Every tool is an attack surface and a cost center. More tools means more ways for the agent to do something surprising, and more tokens spent deciding among them. Curate ruthlessly; a tight tool set outperforms a sprawling one.

    Your agent’s ceiling is your data platform. Bad retrieval, stale data, or missing metadata will sink a great model. Agent quality is a data-quality problem wearing a trench coat.

    The loop will run away if you let it. A missing step cap turns a confused agent into a runaway bill. Set a hard recursion limit before you set anything else.

    No evals means no engineering. If you can’t measure whether a change improved the agent, you’re not building a product, you’re tuning a slot machine. Evals — including credit for the agent saying “I can’t do this” — come before more features.

    The one principle

    An agent is a language model wrapped in tools, knowledge, memory, a loop, and guardrails — and “what actually works” is deciding, for each of those five, how little you can get away with. The instinct that ships working AI products isn’t reaching for maximum autonomy; it’s the engineer’s instinct to build the simplest system that solves the problem, grant the least access that does the job, and measure everything. You already have those instincts. Building agents is mostly the discipline of not abandoning them because the technology is exciting.


    Related reading: MCP: how agents get their tools · Giving agents access safely · Tools vs subagents: don’t over-build · Build the knowledge layer (RAG) · OpenAI: a practical guide to building agents · AI agent systems: architectures & evaluation

  • 20 AI Concepts Every Data Engineer Actually Needs

    20 AI Concepts Every Data Engineer Actually Needs

    There are a hundred “AI concepts explained for beginners” listicles, and most of them are written for people who will never build anything. This one isn’t. If you’re a data engineer, you already understand pipelines, storage, and cost better than most ML tutorials assume — what you actually need is a map of the AI vocabulary that keeps showing up in your Slack, your architecture reviews, and your on-call, with a straight answer to the only question that matters: what does this mean for the systems I build?

    So this is the 20-concept tour, but curated and framed for practitioners. No math derivations, no “imagine a neuron is like a brain cell” hand-waving. Each concept gets a plain definition and a one-line reason it matters in a data pipeline. They’re grouped into four tiers — foundations, language models, grounding, and production — because that’s roughly the order the ideas build on each other, and roughly the order you’ll hit them in real work.

    TL;DR

    • → For data engineers, the AI stack reduces to four tiers: foundations (how models learn), language models (how LLMs behave), grounding (how you make them use your data), and production (how you ship them safely).
    • → The three concepts you’ll argue about most are RAG, fine-tuning, and MCP — RAG is what the model knows, fine-tuning is what it is, MCP is what it can do.
    • → The 2026 default escalation is prompt → RAG → fine-tune → distill; you move right only when the cheaper option provably hits a wall.
    • → Most AI failures in production are data problems, not model problems — Gartner projected at least 30% of generative-AI projects would be abandoned after proof of concept, with poor data quality a leading cause.
    • → Embeddings and vector databases are just a new index type over your data; you already have the instincts to reason about them.
    • → Tokens and context windows are cost and correctness levers, not trivia — they decide your bill and your accuracy.
    • → Evals and guardrails are the AI equivalent of tests and constraints; a model without them is an untested pipeline.

    Tier 1: Foundations — how models learn

    1. Neural network. A stack of simple math functions with tunable weights that, together, learn to map inputs to outputs. For your purposes it’s a black box that turns numbers into numbers; the engineering interest is that it’s just a big parameterized function, not magic.

    2. Training. The process of adjusting those weights by showing the network examples and nudging it toward less wrong. Relevance: training is a batch job with a colossal input dataset — the same data-quality-in, garbage-out rule you already live by applies, at scale.

    3. Parameters (weights). The learned numbers inside the model; “7B” or “70B” refers to how many. More parameters means more capacity and more cost to run — model size is a compute-budget decision, not just an accuracy one.

    4. Tokens. The chunks (roughly word-pieces) that models read and write. This is the one every engineer underestimates: tokens are the unit you’re billed in and the unit context limits are measured in. A pipeline that sends 40,000 tokens per call has a cost and latency profile you must design for.

    5. Embeddings. A way to turn text (or images, or rows) into a fixed-length vector of numbers where “similar things are near each other.” Relevance: this is just a new kind of index over your data. If you can reason about a hash index, you can reason about an embedding — it’s a lookup keyed on meaning instead of exact match.

    Tier 2: Language models — how LLMs behave

    6. Large Language Model (LLM). A very large neural network trained to predict the next token over enormous text corpora, which turns out to be enough to answer questions, write code, and summarize. Treat it as a stochastic function from prompt to text — powerful, useful, and never guaranteed correct.

    7. Context window. The maximum tokens a model can consider at once — prompt plus retrieved data plus its own output. This is a hard architectural constraint: it caps how much of your data a single call can see, and, as I’ve written about in why larger models give wrong answers in production, accuracy often degrades long before you actually fill it.

    8. Temperature. A knob for randomness in the output. Low means consistent and predictable; high means varied and creative. For data pipelines you almost always want it low — but even at zero, output isn’t fully deterministic, which trips up a lot of teams.

    9. Prompting. The instructions you give the model. It’s the cheapest, fastest way to change behavior, and the first rung of the escalation ladder below. Underrated skill: a precise prompt often beats a fancier technique.

    10. Non-determinism. The same prompt can produce different answers on different runs. This breaks the mental model engineers bring from deterministic systems, and it’s why you can’t validate an LLM feature with a single spot-check — I dug into the mechanics in why LLMs give different answers to the same question.

    Tier 3: Grounding — making models use your data

    This is the tier that turns a generic model into something useful on your organization’s data, and it’s where the three most-argued-about concepts live. The distinction is worth getting exactly right, because teams routinely pick the wrong one and waste weeks.

    The distinction teams get wrong most often: RAG, fine-tuning, and MCP solve three different problems and combine freely — they’re not competing choices.

    11. RAG (Retrieval-Augmented Generation). At query time, you retrieve relevant documents from your own data and feed them into the prompt, so the model answers from current, private context instead of only its training. It’s the knowledge layer, the default starting point for most enterprise use cases, and the reason answers can carry citations.

    12. Vector database. The store that holds embeddings and answers “find the N most similar chunks to this query.” It’s the retrieval engine underneath RAG — conceptually, a similarity index you query with meaning. If you want the hands-on version, I walked through building this on Snowflake in the Cortex Search RAG guide.

    13. Fine-tuning. Actually retraining the model’s weights on your examples to change its behavior — tone, format, domain reasoning. It’s the behavior layer, not a way to teach the model new facts (RAG does that better). In 2026, small-model fine-tunes via LoRA/QLoRA are cheap; full fine-tuning rarely makes sense for a product team.

    14. Hallucination. When a model produces confident, fluent, wrong output. This is the single most important failure mode for anyone putting AI on data, because it fails silently — the answer looks right. RAG with citations is the most reliable mitigation; blind trust is the most common mistake.

    15. Context engineering. The umbrella discipline (formalized by Anthropic in its 2025 write-up on effective context engineering) of deliberately designing everything that goes into the model’s context — retrieved docs, tools, history, instructions. The 2026 reframing is that “RAG vs long-context vs MCP” is the wrong debate; they’re all tools inside context engineering.

    Tier 4: Production — shipping models safely

    16. Agents. An LLM in a loop that can reason, call tools, observe results, and repeat until a task is done. This is the agency layer — the shift from “answers questions” to “takes actions.” It’s also where the risk jumps, which is why guardrails below matter.

    17. MCP (Model Context Protocol). A standard way to expose typed tools — APIs, databases, systems — that an agent can invoke. If RAG is declarative memory, MCP is agency: the capacity to act. It’s fast becoming the interface layer between agents and your platform; I explained it at three depths in MCP explained in 3 levels.

    18. Evals. Systematic tests that measure whether a model’s output is good enough — accuracy, format, abstention, safety. Evals are to AI what unit and integration tests are to code: without them you’re shipping an untested pipeline and hoping. Critically, a good eval rewards a model for saying “I don’t know” instead of guessing.

    19. Guardrails. The bounds around a model in production — input validation, output filtering, step/recursion limits, human approval for risky actions. They’re the difference between a demo and something you can leave running, and they belong in the release gate, not as an afterthought.

    20. Distillation. Training a smaller, cheaper model to mimic a bigger one, to cut cost and latency once a use case is proven. It’s the last rung of the escalation ladder — reached rarely, and only after cheaper options are exhausted.

    The canonical 2026 sequence. Start at the bottom-left; move up and right only when the cheaper option provably fails — not because the next rung sounds more serious.

    How these fit together

    The concepts aren’t a flat glossary; they stack. Foundations explain why a model behaves like a probabilistic function. The language-model tier explains the levers you actually touch — tokens, context, temperature. Grounding is where you inject your data (RAG), reshape behavior (fine-tuning), and grant agency (MCP). Production is where evals and guardrails keep the whole thing honest. When someone proposes “let’s fine-tune a model on our data so it answers support questions,” you now have the vocabulary to catch the mistake: they want RAG (new facts), not fine-tuning (new behavior), and the actual bottleneck will be data quality — which is where Gartner expected many generative-AI efforts to stall: data problems dressed up as model problems.

    The gotchas nobody warns you about

    Fine-tuning is not how you add knowledge. The most common expensive mistake is fine-tuning to teach facts. Fine-tuning changes behavior; RAG adds knowledge. Reach for RAG first, almost always.

    Tokens are a cost and correctness lever, not trivia. Underestimating token usage blows budgets and, via context degradation, quietly lowers accuracy. Treat token count as a first-class number in any AI pipeline design.

    Your AI project is a data project. The model is rarely the bottleneck — retrieval quality, freshness, lineage, and governance are. If your data platform is shaky, no model choice will save the feature.

    “Agent” is often overkill. A single prompt or a RAG call solves many problems a full agentic loop is proposed for. Agents add capability and cost and risk; use them when the task genuinely needs multi-step tool use.

    No evals means no idea. Without evaluation you cannot tell whether a model change helped or hurt. Ship evals with the feature, and make abstention (“I don’t know”) a passing answer, not a failing one.

    The one principle

    Every one of these twenty concepts is, for a data engineer, a variation on problems you already know — indexing, cost, testing, data quality, and access — wearing new vocabulary. You don’t need to become an ML researcher to build serious AI systems. You need to map the new terms onto the engineering instincts you already have, know which layer a given concept lives in, and remember that in production the model is almost never the hard part — your data is.


    Related reading: MCP explained in 3 levels · Build RAG on your own data · Why bigger models still get it wrong · Why LLMs give different answers · It’s not AI — it’s automation

  • Why Larger LLMs Give Incorrect Answers in Production

    Why Larger LLMs Give Incorrect Answers in Production

    A team I worked with upgraded their support-ticket triage pipeline to a bigger, newer model expecting fewer mistakes. Benchmarks said it was smarter across the board. Two weeks in, the error rate on a specific ticket category had gotten worse, not better — and worse in a particular way: the new model was wrong just as often as the old one, but its wrong answers now came with confident, detailed justifications instead of the old model’s hedgy “I’m not entirely sure, but…” The support team started trusting the wrong answers more, because they sounded more certain. Nobody had budgeted for that.

    That’s the part the “AI hallucinates” conversation usually skips. Bigger models are not simply less wrong — in several measured respects they’re differently wrong, and the difference that matters most in production is confidence, not accuracy. There’s real, recent research behind this, and it points at three separate mechanisms: how models are trained to answer instead of abstain, how their reliability quietly degrades as the input grows even inside benchmarks-passing context windows, and how production conditions differ from the clean single-turn evals every leaderboard measures. None of that means larger models are worse. It means “larger” doesn’t buy you the thing production actually needs, which is knowing when to say “I don’t know.”

    TL;DR

    • → OpenAI’s 2025 research reframes hallucination as an incentive problem: next-token training and standard evals reward confident guessing over calibrated “I don’t know,” so models learn to bluff.
    • → Pretrained models are reasonably well-calibrated; RLHF alignment measurably degrades that calibration, making models more confident without making them more correct — an effect researchers call the alignment tax.
    • → Scaling model size doesn’t eliminate this; multiple studies describe larger models producing “confident nonsense” at a similar or greater rate, just more fluently.
    • → Chroma’s 2025 “Context Rot” study tested 18 frontier models and found every one degrades as input length grows — well before the context window is full, even on simple tasks.
    • → Distractors (content that’s topically close but doesn’t answer the question) hurt accuracy more than irrelevant filler, and the effect compounds as input grows.
    • → In Chroma’s tests, Claude models abstained more under ambiguity while GPT models more often produced confident, incorrect answers — model families differ meaningfully in this failure mode.
    • → Production adds failure modes benchmarks don’t test: retrieval quality in RAG, long accumulated agent context, and non-deterministic sampling — so a benchmark-topping model can still underperform in your actual pipeline.

    The training incentive: models are rewarded for guessing

    Start with the most fundamental mechanism, because it explains why this doesn’t go away as models get bigger. OpenAI’s 2025 paper, Why Language Models Hallucinate, argues that the standard training and evaluation setup structurally rewards confident guessing over calibrated uncertainty. A model is trained to predict the next token, and it’s evaluated on benchmarks that score right-or-wrong with no separate credit for a correct “I don’t know.” A model that always guesses will, on average, score higher than one that abstains when unsure — even if the abstaining model is more trustworthy. The behavior isn’t a bug that better data fixes; it’s the predictable output of an incentive structure, and scaling the model up scales the same incentive with it.

    This gets compounded at the alignment stage. Research tracing model calibration through the training pipeline — from Kadavath et al.’s foundational 2022 work through more recent studies on the “alignment tax” — has found that base, pretrained models are reasonably well-calibrated: when a pretrained model says it’s 80% confident, it’s right roughly 80% of the time. RLHF, the fine-tuning stage that makes a model helpful and fluent, measurably degrades that calibration. The model comes out more confident and more polished, but not more accurate — confidence and correctness get pulled apart at exactly the stage designed to make the model pleasant to use.

    Each stage of the standard training pipeline independently pushes toward confident answers. None of them individually looks like a bug — the compounding is the problem.

    Scaling doesn’t fix this because scaling doesn’t change the incentive. A survey published in Frontiers in AI in 2025 makes the point directly: larger models remain capable of “confident nonsense,” and model scaling alone amplifies rather than eliminates hallucination in certain contexts. A bigger model has more capacity to construct a fluent, internally consistent, wrong answer — which is a worse failure mode for a downstream system to catch than an obviously garbled one.

    Context rot: accuracy degrades long before the window fills

    The second mechanism is specific to production because it’s about what happens once you start feeding a model real, long inputs — RAG context, chat history, tool outputs, agent state — rather than the short, clean prompts most benchmarks use. A 2025 Chroma technical report, deliberately titled Context Rot, tested 18 frontier models including GPT-4.1, Claude 4, Gemini 2.5, and Qwen3, and found that every one of them degrades as input length increases — often well before the context window is close to full, and even on tasks as simple as finding one fact in a document or exactly repeating a string back.

    Chroma’s 2025 study found this pattern held across all 18 models tested — accuracy declines with input length even when the task itself doesn’t get harder.

    Three details from that report matter for anyone building production RAG or agent systems. First, ambiguity compounds the effect: when the needed fact doesn’t closely match the wording of the question — the realistic case, since users rarely phrase things the way a document does — performance degrades faster as input grows. Second, distractors hurt more than plain irrelevant filler; content that’s topically related but doesn’t actually answer the question pulls accuracy down non-uniformly, and the effect gets worse, not better, as more distractors are added. Third — and this is the one worth sitting with — the researchers found that structurally coherent context (well-organized, logically flowing text) degraded model performance more than shuffled, incoherent text did. That’s the opposite of the intuitive assumption that cleaner input is always easier for a model to use.

    There’s also a genuinely useful, model-specific finding buried in that report: under ambiguity, Claude models more often abstained — explicitly stating that an answer couldn’t be determined from the given context — while GPT models more often produced a confident, incorrect answer instead. That’s not a claim that one vendor is unconditionally more accurate; it’s evidence that how a model handles uncertainty under long, messy context is a real, measurable, model-specific property worth testing before you pick what runs in production, not something you can assume from a benchmark leaderboard alone.

    Why this specifically bites in production and not in your evaluation

    Put those two mechanisms together and the production gap makes sense. Most public benchmarks are short, clean, single-turn, and score right-or-wrong with no credit for calibrated abstention — which is exactly the setup that rewards confident guessing and doesn’t test context rot at all. Production is the opposite on every count: it’s long (RAG context, chat history, tool outputs), ambiguous (real user phrasing rarely matches document wording), and cumulative (an agent’s context grows with every tool call it makes). A model can genuinely top the leaderboard and still be the wrong choice for a pipeline that hands it 40,000 tokens of retrieved documents and expects a single correct fact back.

    This is also where architecture choices you already know matter start to compound the problem instead of solving it. Retrieving too many chunks “to be safe” in a RAG pipeline doesn’t just cost more — per the context rot findings, it actively degrades accuracy, especially when the extra chunks are distractors rather than clean irrelevant filler. An agent that accumulates tool results across a long-running task is accumulating exactly the kind of long, structurally coherent context Chroma found hurts performance most. And a model under uncertainty defaulting to a confident guess rather than an abstention is the last-mile version of the training-incentive problem — it’s not a separate bug, it’s the same one showing up downstream.

    What actually helps

    None of this is a reason to avoid larger models — it’s a reason to design around known failure modes instead of assuming scale solves them. A few things follow directly from the research above. Keep retrieved context tight rather than generous: fewer, more relevant chunks measurably outperform “retrieve broadly and let the model sort it out,” because more chunks means more distractors and distractors compound with length. Test for abstention behavior specifically, not just accuracy — a model that says “I can’t determine this from the given context” on an ambiguous case is behaving correctly even though a naive eval scores it as a miss; the more dangerous system is the one that never abstains. And treat long-running agent context as a liability to actively manage — summarizing or pruning accumulated state periodically rather than letting it grow unbounded, since the Chroma findings suggest that coherent, well-organized accumulated context degrades performance rather than protecting it.

    It’s also worth remembering that non-determinism sits underneath all of this: the same prompt can produce a different answer on a different run, for reasons rooted in how these models sample tokens — I’ve written separately about why LLMs give different answers to the same question, and that variance means a single spot-check of a prompt tells you less than it feels like it does. If you’re using AI to generate or review structured output like SQL, the same discipline applies: never trust a green run as proof of correctness, a point I’ve made about why passing tests still ship bad data. And if agents are reading your pipeline’s metadata to answer questions, the context rot findings are a direct argument for curating what reaches them rather than dumping everything and hoping — the same principle behind giving agents metadata access deliberately rather than broadly.

    The gotchas nobody warns you about

    A benchmark win doesn’t transfer to your pipeline. Public leaderboards are short, clean, single-turn tests. If your production input is long, ambiguous, or accumulated over many turns, the benchmark isn’t measuring the failure mode you’ll actually hit.

    More retrieved context is not a safety margin. The instinct to retrieve generously “in case the model needs it” directly works against the research: more chunks means more distractors, and distractors degrade accuracy faster as volume grows.

    A model that never says “I don’t know” is a red flag, not a feature. If your eval only scores right/wrong, you can’t distinguish a model correctly abstaining from one confidently guessing wrong — and the second one is more dangerous precisely because it’s harder to catch downstream.

    Well-organized context can hurt more than messy context. Counterintuitively, Chroma found coherent, logically flowing input degraded performance more than shuffled text with the same content. Don’t assume tidy prompt construction is automatically safer.

    Model families differ on this specific behavior. Whether a model abstains or guesses under ambiguity is a measurable, model-specific property. If reliability under uncertainty matters for your use case, test for it directly rather than assuming it from general capability scores.

    The one principle

    A larger model gives you more capability, not more honesty about its limits — those are trained in separately, and mostly not trained in at all. The fix isn’t a bigger model or a longer context window; it’s designing the system around the specific, now well-documented ways models fail — tight retrieval instead of generous, explicit testing for abstention, and active management of anything that accumulates context over time. Production doesn’t need a model that’s never wrong. It needs one — and a system around it — that’s honest about when it doesn’t know.


    Related reading: Why LLMs give different answers to the same question · Why passing tests still ship bad data · Giving agents metadata access safely · It’s not AI you should worry about — it’s automation · Chroma: Context Rot research report · Why Language Models Hallucinate (OpenAI, 2025)