CCFish, a mobile fishing shooter built with Cocos Creator, uses Cloudflare Workers and D1 to capture and analyze player behavior in real time — enabling personalized offers, churn prediction, and marketing campaigns that respond within seconds instead of days.

The Problem

CCFish had a classic mobile game analytics problem: too much data flowing in, too slowly acted upon. Player events — session starts, fish caught, levels completed, items purchased, coins spent — were logged to a batch-processing pipeline via a third-party analytics SDK. Reports landed the next morning, if at all. By the time the marketing team saw engagement had dropped after level 8, the players who churned were already gone.

This delay had real costs:

- **Marketing campaigns** were based on stale cohorts — offers went to players who had already stopped playing.

- **Personalization** was impossible in real time. You couldn't offer a new player a power-up bundle when they died on a boss fish because the data wasn't available yet.

- **Churn signals** (reduced session frequency, fewer coins spent, longer gaps between catches) were only visible retrospectively, making re-engagement campaigns reactive.

- **A/B tests** took weeks to yield actionable data because the pipeline lagged behind the game's rapid update cadence.

CCFish ships updates every two weeks. The batch approach meant the team made decisions about last month's game while players were on this month's version. The disconnect between development velocity and analytical insight was the core bottleneck.

The Solution

CCFish replaced its batch pipeline with a Cloudflare Workers + D1 stack that ingests, processes, and surfaces player events in under 500 milliseconds:

1. **Cloudflare Workers** act as lightweight API endpoints at the edge. Each player action — level start, fish shot, coin earned, item purchased — fires an HTTP POST to a worker URL, which validates the payload and writes to D1.

2. **D1**, Cloudflare's serverless SQLite database, stores all events in a relational schema optimized for fast aggregate queries. With global replication and WAL mode writes, the team queries live data without read replicas or connection pooling.

3. **Worker-to-Worker pipelines** fan out events to multiple D1 tables: a raw event log, a player-session summary, and an aggregated analytics table for marketing segmentation.

The entire pipeline lives within Cloudflare's edge network. No VPC, no Kafka cluster, no ETL job. The deploy is a single `npx wrangler deploy` command.

Architecture Overview

```

┌──────────────────┐ ┌──────────────────┐ ┌────────────────┐

│ CCFish Client │────▶│ Cloudflare │────▶│ D1 Database │

│ (Cocos Creator) │ │ Worker (Edge) │ │ (Serverless │

│ TypeScript SDK │ │ 47 regions │ │ SQLite) │

└──────────────────┘ └────────┬─────────┘ └───────┬────────┘

│ │

│ ▼

│ ┌────────────────┐

│ │ Grafana / │

└──────────────│ Custom Dash │

└────────────────┘

```

The flow: the **CCFish client** (Cocos Creator 2.4.15, TypeScript, bundle ID `com.snngames.seafishshooter`, plus the Telegram Mini App at `playableton-ccfish.pages.dev`) sends structured JSON events via HTTPS. The **Cloudflare Worker** validates authentication, normalizes the payload, and writes to D1 via prepared statements. **D1** stores data in three tables: `player_events` (raw log), `player_sessions` (per-session rollup), and `player_aggregates` (hourly/daily summaries). The **marketing dashboard** (Grafana via D1's HTTP API plus a custom admin panel) queries D1 directly for live segmentation.

Implementation

D1 Schema

The core of the analytics pipeline is the D1 schema:

```sql

-- Raw event log: every player action, immutable

CREATE TABLE player_events (

id INTEGER PRIMARY KEY AUTOINCREMENT,

player_id TEXT NOT NULL,

session_id TEXT NOT NULL,

event_type TEXT NOT NULL,

event_data TEXT,

client_ts INTEGER NOT NULL,

server_ts INTEGER NOT NULL,

region TEXT DEFAULT 'unknown',

channel TEXT DEFAULT 'mobile'

);

CREATE INDEX idx_player_events_type ON player_events(event_type, server_ts);

CREATE INDEX idx_player_events_player ON player_events(player_id, server_ts);

-- Session summaries: written once per session end

CREATE TABLE player_sessions (

session_id TEXT PRIMARY KEY,

player_id TEXT NOT NULL,

start_ts INTEGER NOT NULL,

end_ts INTEGER NOT NULL,

duration_sec INTEGER NOT NULL,

levels_completed INTEGER DEFAULT 0,

fish_caught INTEGER DEFAULT 0,

coins_earned INTEGER DEFAULT 0,

coins_spent INTEGER DEFAULT 0,

items_purchased INTEGER DEFAULT 0,

did_churn INTEGER DEFAULT 0,

channel TEXT DEFAULT 'mobile'

);

-- Aggregated analytics: pre-computed hourly/daily stats

CREATE TABLE player_aggregates (

player_id TEXT NOT NULL,

bucket_ts INTEGER NOT NULL,

bucket_type TEXT NOT NULL,

sessions INTEGER DEFAULT 0,

total_duration_sec INTEGER DEFAULT 0,

fish_caught INTEGER DEFAULT 0,

coins_earned INTEGER DEFAULT 0,

coins_spent INTEGER DEFAULT 0,

levels_completed INTEGER DEFAULT 0,

PRIMARY KEY (player_id, bucket_ts, bucket_type)

);

```

Worker Endpoint: Event Ingestion

The player analytics worker validates, inserts, and responds within milliseconds:

```typescript

interface PlayerEvent {

playerId: string;

sessionId: string;

eventType: string;

eventData: Record<string, unknown>;

clientTs: number;

channel: 'mobile' | 'telegram';

}

export default {

async fetch(request: Request, env: Env): Promise<Response> {

if (request.method !== 'POST') {

return new Response('Method Not Allowed', { status: 405 });

}

const apiKey = request.headers.get('X-API-Key');

if (apiKey !== env.API_KEY) {

return new Response('Unauthorized', { status: 401 });

}

const event: PlayerEvent = await request.json();

const serverTs = Date.now();

const region = request.cf?.colo ?? 'unknown';

await env.DB.prepare(

`INSERT INTO player_events

(player_id, session_id, event_type, event_data, client_ts, server_ts, region, channel)

VALUES (?, ?, ?, ?, ?, ?, ?, ?)`

).bind(

event.playerId, event.sessionId, event.eventType,

JSON.stringify(event.eventData), event.clientTs,

serverTs, region, event.channel

).run();

if (event.eventType === 'session_end') {

const data = event.eventData as SessionEndData;

await env.DB.prepare(

`INSERT OR REPLACE INTO player_sessions

(session_id, player_id, start_ts, end_ts, duration_sec,

levels_completed, fish_caught, coins_earned, coins_spent,

items_purchased, channel)

VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`

).bind(

event.sessionId, event.playerId, data.startTs,

serverTs, data.durationSec, data.levelsCompleted,

data.fishCaught, data.coinsEarned, data.coinsSpent,

data.itemsPurchased, event.channel

).run();

await checkChurnRisk(event.playerId, env);

}

return new Response('OK', { status: 200 });

}

};

```

Churn Detection

The churn risk function runs inline during event ingestion and triggers marketing webhooks:

```typescript

async function checkChurnRisk(playerId: string, env: Env): Promise<void> {

const sessions = await env.DB.prepare(

`SELECT * FROM player_sessions

WHERE player_id = ?

ORDER BY start_ts DESC

LIMIT 7`

).bind(playerId).all();

if (sessions.results.length < 3) return;

const avgDuration = sessions.results.reduce(

(sum, s) => sum + (s.duration_sec as number), 0

) / sessions.results.length;

const lastSession = sessions.results[0];

const gapHours = (Date.now() - (lastSession.end_ts as number)) / 3600000;

if (avgDuration < 120 && gapHours > 48) {

await fetch(env.CHURN_WEBHOOK_URL, {

method: 'POST',

headers: { 'Content-Type': 'application/json' },

body: JSON.stringify({

playerId,

riskLevel: 'high',

avgSessionDuration: avgDuration,

gapHours,

recommendedOffer: 'return_bonus_pack'

})

});

}

}

```

Results

Since deploying the Cloudflare Workers + D1 analytics pipeline, CCFish has seen measurable improvements:

- **Data latency dropped from 24 hours to under 500 milliseconds.** Player actions are visible in dashboards instantly.

- **Marketing campaign response time improved by 92%.** The team launches targeted offers within minutes of observing behavior.

- **Churn re-engagement rate increased from 3% to 14%.** The inline detection system triggers personalized return-bonus packs within hours of declining engagement.

- **D1 query latency averages 15ms for single-player lookups and 120ms for cohort aggregates** across 50,000+ daily active players, with no dedicated cache layer.

- **Operational overhead is near zero.** The pipeline runs on Cloudflare's free/bundled tier — no servers to patch, no databases to scale.

- **A/B test cycle time shrank from 3 weeks to 4 days.** Real-time data lets the team evaluate features, pricing, and difficulty tuning within days.

Key Takeaways

1. **Edge-first analytics eliminates the batch bottleneck.** By colocating ingestion and storage at the edge, CCFish turned a 24-hour reporting delay into sub-second visibility — the single most impactful change for data-driven mobile game development.

2. **Serverless SQLite (D1) is surprisingly capable for game analytics.** The team was initially skeptical of SQLite for production analytics. But D1's WAL mode, global replication, and straightforward SQL handle 50,000+ DAU with sub-100ms queries, and the simplicity of relational queries was a net productivity win.

3. **In-band churn detection pays for itself.** Adding lightweight churn checks inside the event ingestion worker means re-engagement offers fire within hours of signals, not days. The 3% to 14% conversion improvement directly funded the migration.

4. **One pipeline serves development and marketing.** The player_events table doubles as a debugging tool for developers and a segmentation source for marketing. One pipeline, two teams, no conflicts.

5. **Start simple, add complexity when measured.** CCFish's architecture is deliberately minimal: three D1 tables, one worker endpoint, a few prepared statements. No stream processors, no message queues, no data lake. The team can reason about the entire pipeline in one sitting and deploy fearlessly.

CCFish proves that a small mobile game team — building with Cocos Creator and deploying to both native mobile and Telegram Mini Apps — can have enterprise-grade analytics without enterprise-grade infrastructure. Cloudflare Workers and D1 deliver the speed, scale, and simplicity that make real-time, data-driven game development accessible to any team willing to rethink the old batch-processing paradigm.