DEV Community

Cover image for Processing Millions of Records Through Shopify: Lessons From High-Volume Integrations
Muhammad Masad Ashraf
Muhammad Masad Ashraf

Posted on • Originally published at kolachitech.com

Processing Millions of Records Through Shopify: Lessons From High-Volume Integrations

Your Shopify integration works great. Until it doesn't.

At 1,000 records a day, a simple loop with API calls is fine. At 500,000 records a day, that same loop becomes an eight-hour backlog, a wall of 429 errors, and inventory numbers nobody trusts.

I work on enterprise Shopify integrations, and the pattern is always the same. Teams don't fail because their code is bad. They fail because they scale a small-volume design instead of switching to a high-volume one. These are two different architectures.

Here's what actually changes at scale.

Rule 1: Stop Paginating. Use Bulk Operations.

The biggest mistake I see: teams exporting a 500k product catalog by paginating through GraphQL, 250 records per page.

That's 2,000+ sequential requests. Each one burns rate limit budget. Any one of them can fail mid-run, and now you're writing resume logic.

Shopify's Bulk Operations API exists for exactly this. You submit one query, Shopify runs it asynchronously on their side, and you download the full result as JSONL:

mutation {
  bulkOperationRunQuery(
    query: """
    {
      products {
        edges {
          node {
            id
            title
            variants { edges { node { id sku inventoryQuantity } } }
          }
        }
      }
    """
  ) {
    bulkOperation { id status }
    userErrors { field message }
  }
}
Enter fullscreen mode Exit fullscreen mode

One submission. One download. Nearly zero rate limit cost. Bulk mutations do the same thing for writes via staged uploads.

The catch: only one bulk operation per type runs per shop at a time. So you need a small scheduler that queues bulk jobs and runs them sequentially. That's a cheap price for the throughput.

Rule 2: Treat Rate Limits as a Budget, Not an Obstacle

For real-time work that can't wait for a bulk job, you're back on the standard GraphQL API with its calculated query cost model.

The teams that run smoothly at scale do three things:

  1. Query only what they need. Every field adds cost. That description field you never use? It's costing you throughput.
  2. Throttle dynamically. Every response includes throttleStatus with your remaining budget. Read it and adjust pacing in real time instead of hardcoding sleep timers.
  3. Shift non-urgent work off-peak. Price updates at 3 AM don't compete with order processing at noon.
const { currentlyAvailable, restoreRate } =
  response.extensions.cost.throttleStatus;

if (currentlyAvailable < NEXT_QUERY_COST) {
  const waitMs =
    ((NEXT_QUERY_COST - currentlyAvailable) / restoreRate) * 1000;
  await sleep(waitMs);
}
Enter fullscreen mode Exit fullscreen mode

Simple, boring, and it eliminates 429s almost entirely.

Rule 3: Put a Queue Between Everything

Synchronous webhook processing dies at scale. A webhook arrives, your handler does all the work inline, it times out, Shopify retries, and now you have duplicates AND delays.

The fix is old and reliable: your webhook endpoint validates the HMAC, drops the payload on a queue, and returns 200. Done in milliseconds. Workers pull from the queue and process at whatever pace downstream systems can handle.

Queues buy you three things you cannot live without at volume:

  • Buffering. A flash sale fills the queue instead of killing your servers
  • Backpressure. Workers slow down when your ERP struggles, and nothing is lost
  • Retry isolation. One failed event goes back to the queue without blocking the rest

Add a dead letter queue for poison messages. Some events will fail forever (malformed payload, deleted resource, mapping bug). After N attempts, shunt them aside for human review instead of letting them clog the pipeline.

Rule 4: Idempotency Is Not Optional

At high volume, duplicate delivery is a certainty, not an edge case. Shopify delivers webhooks at-least-once. Your own retries resubmit operations that actually succeeded. This happens thousands of times a day.

If processing the same event twice creates two orders, decrements inventory twice, or emails a customer twice, you have a data corruption machine.

Make every operation idempotent:

  • Track processed webhook IDs in a dedup table with a TTL
  • Use upserts instead of inserts
  • Attach unique operation keys to outbound mutations

Boring code. Saves your weekend.

Rule 5: Accept Eventual Consistency, Then Manage It

Once data flows through queues and async jobs, systems will briefly disagree. Your WMS says 90 units, Shopify says 100. For 45 seconds, both are "right."

Fighting this is futile. Managing it works:

  • Define one system of record per data type. Inventory lives in the WMS. Pricing lives in the ERP. Orders originate in Shopify. No exceptions.
  • Set freshness targets per type. Inventory: under 60 seconds. Product descriptions: an hour is fine.
  • Reconcile on a schedule. A nightly job diffs the systems and flags drift before customers find it for you.

Also: webhooks arrive out of order. An orders/updated can land before its orders/create. Rare at low volume, constant at high volume. Version checks or event buffering in your consumers handle it.

The Architecture That Falls Out of All This

Webhooks/Pollers → Queue → Worker Fleet → Middleware → ERP/WMS/CRM
                     ↓
              Dead Letter Queue

Bulk Scheduler → Bulk Operations API   (heavy jobs, separate lane)

Reconciliation Jobs → drift detection (scheduled)
Enter fullscreen mode Exit fullscreen mode

The key insight is the two-lane design. Urgent events (orders, payments) flow through queues with tight latency targets. Heavy jobs (nightly catalog syncs, migrations) run through bulk operations without competing for the same rate limit budget.

Monitor These Five Things

You cannot manage what you cannot see:

  1. Queue depth and age - rising depth means workers are falling behind
  2. Processing lag - event creation to sync completion
  3. Error rate by type - transient vs. systematic
  4. Rate limit utilization - know how close you run to the ceiling
  5. Reconciliation drift - how many records disagreed at last check

Alert on trends, not just failures. A queue growing steadily for two hours is a problem long before it overflows.

TL;DR

  • Bulk Operations API for anything touching thousands of records
  • Dynamic, cost-aware throttling for real-time calls
  • Queues between every producer and consumer, with DLQs
  • Idempotency everywhere, no exceptions
  • One system of record per data type, plus scheduled reconciliation
  • Observability from day one

High-volume Shopify integration is a distributed systems problem wearing an e-commerce costume. Treat it like one and it behaves.


This is a condensed version of a longer guide I wrote on high-volume integration processing. Questions about your own setup? Drop them in the comments.

Top comments (0)