Every analytics product starts with the same architecture: a tracking pixel, a data ingestion pipeline, a message queue, a database, and a dashboard. By the time a pageview becomes a chart, the data has crossed three continents, two cloud providers, and a Kafka cluster someone forgot to secure.
We wanted something different. When we sat down to design Glinto, we wrote three constraints on a whiteboard:
- Data must never leave the EU, ever.
- Every request must complete in under 10ms at the edge.
- No cold starts — a visitor should never wait for infrastructure to boot.
That list pointed in exactly one direction: Cloudflare Workers, with D1 for storage and Durable Objects for state.
The Architecture at a Glance
Glinto’s backend lives entirely on Cloudflare’s edge network. There’s no traditional server, no container orchestration, and no database running in a Frankfurt data center. Instead:
- Tracking ingestion runs as a Cloudflare Worker, deployed globally but with jurisdiction-bound routing that keeps payloads within EU data centers.
- Analytics queries hit D1 (Cloudflare’s distributed SQLite-in-the-edge database). Each account’s data lives in a D1 database that’s automatically replicated across Europe — read replicas in Amsterdam, Frankfurt, Madrid, and Paris.
- Session aggregation uses Durable Objects for in-memory counters and real-time deduplication. No Redis, no external cache.
- The Monday summary (our weekly AI-powered overview) runs as a scheduled Worker that reads the last 7 days of data, generates a plain-English summary, and drops it in the user’s dashboard.
- Dashboards are pre-rendered and cached at the edge. When you open Glinto, you’re reading static fragments that get hydrated with real-time counts from Durable Objects.
Why Workers, Not Lambdas
We evaluated three platforms: AWS Lambda, Fly.io, and Cloudflare Workers. The decision came down to latency and data locality.
Lambda’s cold start problem isn’t just an inconvenience — it’s disqualifying for real-time analytics. When a Lambda function hasn’t been invoked recently, AWS needs 200ms–2s to provision a sandbox and load your runtime. For a tracking endpoint that should respond in under 5ms, that’s a non-starter. Provisioned concurrency helps, at significant cost and complexity.
Fly.io was our runner-up. The promise of running VMs close to users with Anycast networking is compelling. But Fly’s EU coverage is limited compared to Cloudflare’s footprint, and their distributed SQLite story (LiteFS) requires careful conflict resolution logic that we’d rather not maintain.
Cloudflare Workers solve the cold start problem at the platform level. Workers aren’t containers — they’re V8 isolates running in the same process. A Worker can start in under 5ms because there’s no OS-level sandbox to provision. And with Smart Placement, frequently invoked Workers automatically migrate closer to their traffic patterns.
The result: our tracking endpoint consistently responds in under 5ms from any European location, and most query endpoints complete in 10–30ms.
D1: SQLite at the Edge
D1 deserves its own section. It’s Cloudflare’s managed SQLite database that runs at the edge. Every database has a primary located in a specific region (ours is Frankfurt), with automatically managed read replicas distributed across Cloudflare’s network.
This is different from traditional database scaling. You’re not managing a cluster, running failover scripts, or worrying about read-your-writes consistency. D1 handles replication internally, and your Workers talk to the nearest replica automatically.
For analytics workloads, this architecture is nearly ideal:
- Pageview writes are low-latency (the primary is in the same region as most European traffic).
- Dashboard reads are spread across replicas, so a busy Monday morning doesn’t create a bottleneck.
- Writes are SQLite-compatible, which means we can use the same query patterns we’d use in development — no translation layer between SQLite and some proprietary edge SQL dialect.
The trade-off is that D1 is not PostgreSQL. No extensions, no stored procedures, no triggers. For our use case, that’s fine — we do aggregation in Worker code and only use D1 for storage and simple queries.
Durable Objects for Real-Time Counting
Here’s a problem every analytics tool faces: you want to show a live visitor count on the dashboard, but you can’t query the database for every page load. That’s too slow and too expensive.
The traditional answer is Redis — an in-memory store that holds ephemeral counters. But Redis at the edge means managing a Redis-compatible service (Upstash, perhaps) and accepting a few milliseconds of network latency for every increment.
Durable Objects are Cloudflare’s answer to stateful edge computing. A Durable Object is a JavaScript/TypeScript class that lives in a specific location, persists durable state (stored as key-value pairs), and can handle WebSocket connections. Objects are globally unique by ID, so every request to the same Object ID is routed to the same physical instance.
We use Durable Objects for:
- Per-site visitor counters that aggregate pageviews in real time. The Object holds an in-memory counter that increments on every tracking call and flushes to D1 every 60 seconds.
- Session deduplication — the Object keeps a Bloom filter of recent session hashes so we don’t double-count page refreshes.
- WebSocket push — when a dashboard is open, it connects to the site’s Durable Object via WebSocket and receives live counter updates without polling.
This gives us real-time analytics with no external dependencies beyond Cloudflare’s stack.
What We Learned Building This
1. SQLite at the edge is surprisingly capable. We expected to push aggregation logic into Worker code to compensate for SQLite’s limited feature set. In practice, D1 handles complex queries faster than we expected. A dashboard query that joins pageviews, sessions, and referrers across a 30-day window with GROUP BY runs in 20–50ms on the read replica. That’s fast enough to serve synchronously.
2. Durable Objects require careful state management. The in-memory counter pattern works great until a Durable Object gets evicted (due to inactivity) and the counter is lost. We learned to flush counters to D1 on every increment, not on a timer. Overkill? Maybe, but it means we never lose data.
3. EU data residency requires constant vigilance. Cloudflare’s jurisdiction-bound Workers can restrict execution to EU data centers, but you have to configure this explicitly. We wrote a pre-deployment check that verifies every Worker, D1 database, and Durable Object namespace is pinned to the EU jurisdiction. This runs in CI and blocks deployments that accidentally target global execution.
4. Observability at the edge is hard. Traditional APM tools (Datadog, New Relic) don’t work well with Workers because there’s no persistent agent. We built our own lightweight instrumentation that logs to Cloudflare’s analytics engine, with custom metrics for tracking endpoint latency, D1 query times, and Durable Object eviction rates.
The Business Case for Edge-Native
Building on Workers wasn’t just a technical choice — it had business implications:
- No server provisioning. We don’t maintain EC2 instances, Kubernetes clusters, or autoscaling groups. The platform handles scaling automatically.
- Predictable pricing. Workers pricing is per-request and per-duration, with a generous free tier. For a SaaS product with usage-based pricing, we can predict our infrastructure costs from our customer metrics.
- European hosting, verified. Cloudflare’s compliance certifications (ISO 27001, SOC 2, and the EU Cloud Code of Conduct) give our customers confidence that their data stays where we say it stays. For European enterprises, this matters more than any feature we could build.
If you’re building a data-intensive product for the European market and architectural simplicity matters to you, Workers + D1 + Durable Objects is worth a serious look. It’s not the right choice for every workload, but for analytics — where low latency, data locality, and predictable scaling are table stakes — it’s the best foundation we’ve found.