Building Atchoo Analytics: Multi-Tenant Architecture for Shopify

Published:

Reading Time:8 Minutes

Image for Building Atchoo Analytics: Multi-Tenant Architecture for Shopify

Here's something that constantly frustrates Shopify merchants: they have no idea what's happening on their product pages before the checkout. Shopify's built-in analytics tell you what sold, but not the journey that led there. Which product template converts better? Are people even clicking Add to Cart, or just bouncing? What happens when you tweak the layout?

Most merchants are flying blind. That's why we built Atchoo Analytics.

The Problem We're Solving

Shopify gives merchants conversion rates, sales data, and traffic sources. All useful stuff, but it's missing the crucial middle bit. You know someone arrived at your store and you know if they bought something. What you don't know is what happened on that product page. (If you're curious about how conversion rates impact revenue, check out our Conversion Rate Calculator.)

Did they view multiple products? Did they add to cart and then abandon? Which of your product templates actually performs better? If you're running a store with custom templates (and most serious merchants are), you're making decisions based on gut feeling rather than data.

Atchoo tracks the actions that matter: page views and add-to-cart events, broken down by template. Want to know if your "premium-product" template converts better than your standard one? Now you can actually find out.

The Stack

We built this on Remix because it handles server-side rendering beautifully and plays nicely with Shopify's ecosystem. The syntax is clean, the data loading patterns make sense, and having API routes built in meant we didn't need to cobble together separate backend services.

For the database, we went with Turso - SQLite in the cloud with edge distribution. Combined with Drizzle ORM for type safety, it gives us the performance we need without the complexity of managing a traditional database. The whole thing deploys on Vercel.

Key parts of the stack:

  • Remix for the full-stack framework
  • Shopify Polaris for the UI (because consistency with Shopify's admin matters)
  • Drizzle ORM for type-safe database queries
  • Turso for edge-distributed SQLite databases
  • Recharts for the analytics visualizations
  • Resend for transactional emails (GDPR compliance stuff)

Everything's TypeScript because we've learned the hard way that catching type errors at compile time beats debugging them in production.

The Architecture That Makes It Work

Here's where it gets interesting. When you're building a multi-tenant SaaS app, you've got a fundamental decision to make: do you throw everyone into one big database with a shop_id column, or do you give each customer their own isolated space?

We went with option two. Every Shopify store that installs Atchoo gets their own dedicated Turso database.

Main Database (credentials storage)
         │
    ┌────┴────┬──────────┬──────────┐
    ▼         ▼          ▼          ▼
 Shop A    Shop B     Shop C     Shop N
Database  Database   Database   Database

Why do it this way? A few reasons:

Data isolation is absolute. There's zero chance of one merchant accidentally seeing another's data. We're not relying on query logic to enforce boundaries - the boundaries are physical.

Cleanup is trivial. When a merchant uninstalls, we drop their database. Gone. No carefully crafted DELETE queries, no orphaned records, no foreign key headaches. One command and their data is completely removed from our infrastructure. This matters more than you'd think - apps churn, and you don't want dead data accumulating forever.

GDPR compliance becomes simpler. Related to the above, but worth calling out separately. When someone exercises their right to deletion, we can prove their data is gone because the entire container is gone. No auditing row-level deletions across shared tables.

Performance scales linearly. One shop with a million events doesn't slow down queries for everyone else.

It's actually cheaper at scale. Turso's pricing model works in our favour here. We're not paying for one massive database instance - we're distributing load across many small ones.

The trade-off is complexity in managing database connections. We handle this with a credential lookup system: the main database stores encrypted connection details for each shop's database. When a request comes in, we look up the shop, decrypt their database URL, and connect. Connections are cached in memory to avoid repeating this dance on every request.

Tracking Without Killing Performance

The tracking endpoint is the heart of the system. It needs to be fast (users shouldn't notice any delay), reliable (we can't lose data), and handle volume (popular stores generate a lot of events).

The endpoint accepts POST requests from the storefront tracking script:

{
  "eventData": {
    "type": "view",
    "template": {
      "name": "product",
      "suffix": "premium"
    },
    "product": {
      "id": "gid://shopify/Product/123",
      "title": "Blue Widget",
      "price": 2999
    }
  }
}

We enrich this with server-side data (timestamp, user agent, referrer) and persist it. But here's the thing about raw event storage: it doesn't scale for analytics queries. When you've got a million rows and you want to know "how many page views did the premium template get last month?", you're going to wait.

So we aggregate.

When an event comes in, we update two summary tables: daily metrics and hourly metrics. Instead of counting millions of rows, we're looking up a handful of pre-computed totals.

Raw Event → INSERT into trackingevent table
         → UPSERT into template_daily_metrics
         → UPSERT into template_hourly_metrics

The aggregation happens synchronously with the event insert. It adds a few milliseconds but transforms analytics from "please wait while we crunch your data" to "here's your dashboard, instantly."

Query times went from seconds (scanning raw events) to milliseconds (index lookups on aggregated tables). The difference is dramatic.

A/B Testing That Actually Works

The Pro tier includes A/B testing. You pick two templates, split your traffic, and let statistical significance tell you which performs better.

We're using Bayesian analysis rather than traditional p-values. Without getting too deep into statistics, the benefit is that you get a "probability that variant B beats variant A" rather than a binary significant/not-significant result. Merchants find this more intuitive.

The flow works like this:

  1. Merchant creates a test, selecting products and templates
  2. Our tracking script assigns visitors randomly to control or treatment
  3. Events are tagged with the test ID and variant
  4. Daily snapshots capture the running statistics
  5. When confidence is high enough, we declare a winner

The A/B system respects the statistical reality that you need sufficient sample size. We don't let merchants read too much into results from 50 visits - we show confidence levels clearly and warn when data is insufficient.

Handling Shopify's Billing

Shopify has a managed pricing system for apps. You define your plans in their partner dashboard, redirect users to their billing page to subscribe, and receive webhooks when things change. Sounds straightforward, right?

The complexity is in the edge cases. What happens when a payment fails? What about free trials expiring? Development stores that don't actually pay?

We implemented a grace period system:

Payment fails
  → Set grace period (3 days)
  → User keeps access
  → If payment succeeds: clear grace period
  → If grace period expires: downgrade to free

This handles the common case where someone's card expires and they don't immediately notice. Rather than abruptly cutting off access (and generating support tickets), we give them time to sort it out.

For development stores, we detect them via the subscription webhook data and handle them appropriately. They get full access for testing purposes, but we track them separately from paying customers.

The Onboarding Problem

Shopify Theme App Extensions are powerful - they let us inject tracking code into any store without touching their theme files. But they require the merchant to actually enable them.

This creates an onboarding challenge. The app installs successfully, but tracking won't work until they've opened their theme editor and enabled our app block. Many merchants don't realize this.

We built a three-step onboarding flow:

  1. Enable the extension - we explain where to find it and what to click
  2. Test the tracking - visit your store, add something to cart, verify it shows up
  3. Select your industry - for benchmarking purposes

We track completion of each step. If someone's been using the app for a while but step 2 never completed, we know their tracking probably isn't working and can prompt them accordingly.

Compliance By Design

GDPR and similar regulations aren't something you bolt on later. We built compliance into the architecture from day one.

When Shopify sends us a customers/data_request webhook, we:

  1. Query that customer's tracking events
  2. Generate a CSV export
  3. Upload it to secure temporary storage
  4. Email the merchant a download link
  5. Auto-delete after 7 days

For shop/redact (app uninstall), we delete the shop's credentials from our main database and drop their entire shop database. Clean slate.

The per-shop database architecture actually makes this easier. There's no surgical deletion of rows from shared tables - when the data needs to go, the whole database goes.

What We Learned

Multi-tenancy decisions matter early. We committed to per-shop databases from the start. Retrofitting this would have been painful.

Aggregation is non-negotiable at scale. We should have built the aggregated metrics tables from day one. The migration from raw queries to aggregated data was tedious.

Shopify's ecosystem has opinions. Working within their session management, billing, and webhook patterns is easier than fighting them. We use their libraries where available rather than rolling our own.

Grace periods save support tickets. Being lenient with billing failures while still protecting the business is worth the implementation effort.

Onboarding is product. If users can't get value from your app because setup is confusing, that's a product failure, not a user failure.

The Actual Value

Merchants using Atchoo can now answer questions they couldn't before:

  • Which product templates drive the most add-to-carts?
  • Is my new premium template worth keeping?
  • How does my conversion rate compare to industry benchmarks?
  • Does changing my product page layout actually improve performance?

It's the data that was always there in user behaviour, just never captured.

What's Next

We're actively developing based on merchant feedback. The A/B testing system is getting more sophisticated. We're looking at extending tracking to collection pages. And there's interesting potential in correlating template performance with traffic sources.

The goal isn't feature bloat - it's making the data that matters accessible to merchants who don't have data science teams. Explore all our free eCommerce tools in Labs.

Got a Shopify app idea that needs building? Or an existing store that could use better analytics infrastructure? We specialize in turning complex e-commerce challenges into elegant solutions. Let's talk about what you're trying to solve.

If you're a Shopify merchant flying blind on product page performance, give Atchoo Analytics a try. There's a free tier to get started, and you might be surprised what you learn about your customers' behaviour.

About the author

Ryan Carter

Ryan is the Director of Studio Future, a Dubai-based eCommerce agency specializing in Shopify development, digital marketing, and systems integration. With over a decade of experience building high-converting commerce solutions for brands from startups to FTSE 100 companies, Ryan combines technical expertise with strategic growth focus to solve complex eCommerce challenges.