| name | customerio-load-scale |
| description | Implement Customer.io load testing and scaling.
Use when preparing for high traffic, load testing,
or scaling integrations for enterprise workloads.
Trigger with phrases like "customer.io load test", "customer.io scale",
"customer.io high volume", "customer.io performance test".
|
| allowed-tools | Read, Write, Edit, Bash(kubectl:*), Bash(curl:*) |
| version | 1.0.0 |
| license | MIT |
| author | Jeremy Longshore <jeremy@intentsolutions.io> |
Customer.io Load & Scale
Overview
Load testing and scaling strategies for high-volume Customer.io integrations.
Prerequisites
- Customer.io integration working
- Load testing tools (k6, Artillery)
- Staging environment with test workspace
Capacity Planning
Customer.io Rate Limits
| Endpoint | Limit | Notes |
|---|
| Track API (identify/track) | 100 req/sec | Per workspace |
| App API (transactional) | 100 req/sec | Per workspace |
| Webhooks (outbound) | Varies | Based on plan |
Scaling Targets
| Volume | Architecture | Notes |
|---|
| < 1M events/day | Single service | Direct API calls |
| 1-10M events/day | Queue-based | Message queue buffer |
| > 10M events/day | Distributed | Multiple workers |
Instructions
Step 1: Load Test Script (k6)
import http from 'k6/http';
import { check, sleep } from 'k6';
import { Rate, Trend } from 'k6/metrics';
const errorRate = new Rate('errors');
const identifyDuration = new Trend('identify_duration');
const trackDuration = new Trend('track_duration');
const BASE_URL = 'https://track.customer.io/api/v1';
const AUTH = __ENV.CUSTOMERIO_AUTH;
export const options = {
scenarios: {
identify_load: {
executor: 'ramping-rate',
startRate: 10,
timeUnit: '1s',
preAllocatedVUs: 50,
stages: [
{ target: 50, duration: '1m' },
{ target: 100, duration: '2m' },
{ : , : },
{ : , : },
],
: ,
},
: {
: ,
: ,
: ,
: ,
: [
{ : , : },
{ : , : },
{ : , : },
{ : , : },
],
: ,
},
},
: {
: [],
: [],
: [],
},
};
() {
userId = ;
payload = .({
: ,
: ,
: .(.() / ),
});
start = ();
res = http.(
,
payload,
{
: {
: ,
: ,
},
}
);
identifyDuration.( () - start);
success = (res, {
: r. === ,
});
errorRate.(!success);
();
}
() {
userId = ;
payload = .({
: ,
: {
: ,
: ().(),
},
});
start = ();
res = http.(
,
payload,
{
: {
: ,
: ,
},
}
);
trackDuration.( () - start);
success = (res, {
: r. === ,
});
errorRate.(!success);
();
}
Step 2: Horizontal Scaling
apiVersion: apps/v1
kind: Deployment
metadata:
name: customerio-worker
spec:
replicas: 3
selector:
matchLabels:
app: customerio-worker
template:
metadata:
labels:
app: customerio-worker
spec:
containers:
- name: worker
image: customerio-worker:latest
resources:
requests:
cpu: "500m"
memory: "256Mi"
limits:
cpu: "1000m"
memory: "512Mi"
env:
- name: CONCURRENCY
value: "10"
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: customerio-worker-hpa
spec:
scaleTargetRef:
Step 3: Message Queue Architecture
import { Kafka, Consumer, EachMessagePayload } from 'kafkajs';
import { TrackClient, RegionUS } from '@customerio/track';
const kafka = new Kafka({
clientId: 'customerio-worker',
brokers: process.env.KAFKA_BROKERS!.split(',')
});
const consumer = kafka.consumer({
groupId: 'customerio-workers',
sessionTimeout: 30000,
heartbeatInterval: 3000
});
const client = new TrackClient(
process.env.CUSTOMERIO_SITE_ID!,
process.env.CUSTOMERIO_API_KEY!,
{ region: RegionUS }
);
interface CustomerIOEvent {
type: 'identify' | 'track';
userId: string;
payload: any;
}
async function processMessage(: ): <> {
: = .(message..!.());
(event. === ) {
client.(event., event.);
} (event. === ) {
client.(event., {
: event..,
: event..
});
}
}
(): <> {
consumer.();
consumer.({ : , : });
consumer.({
: ,
: (payload) => {
{
(payload);
} (error) {
.(, error);
}
}
});
}
().(.);
Step 4: Rate Limiter for Fair Usage
import Bottleneck from 'bottleneck';
const limiter = new Bottleneck({
reservoir: 80,
reservoirRefreshAmount: 80,
reservoirRefreshInterval: 1000,
maxConcurrent: 20,
minTime: 10
});
limiter.on('depleted', () => {
console.warn('Rate limiter depleted, requests queued');
});
limiter.on('error', (error) => {
console.error('Rate limiter error:', error);
});
export async function rateLimitedIdentify(
client: TrackClient,
userId: string,
attributes: Record<string, any>
): Promise<> {
limiter.( client.(userId, attributes));
}
(): <> {
limiter.(
client.(userId, { : event, data })
);
}
() {
{
: limiter.(),
: limiter.(),
: limiter.(),
: limiter.
};
}
Step 5: Batch Processing
interface BatchConfig {
maxBatchSize: number;
maxWaitMs: number;
concurrency: number;
}
class BatchSender {
private batch: Array<{ userId: string; operation: 'identify' | 'track'; data: any }> = [];
private timer: NodeJS.Timer | null = null;
private processing = false;
constructor(
private client: TrackClient,
private config: BatchConfig = { maxBatchSize: 100, maxWaitMs: 1000, concurrency: 10 }
) {}
add(userId: string, operation: 'identify' | 'track', data: any): void {
this.batch.push({ userId, operation, data });
if (this.. >= ..) {
.();
} (!.) {
. = ( .(), ..);
}
}
(): <> {
(. || .. === ) ;
(.) {
(.);
. = ;
}
. = ;
items = ..(, ..);
( i = ; i < items.; i += ..) {
chunk = items.(i, i + ..);
.(chunk.( .(item)));
}
. = ;
}
(: { : ; : ; : }): <> {
(item. === ) {
..(item., item.);
} {
..(item., {
: item..,
: item..
});
}
}
}
Step 6: Load Test Execution
#!/bin/bash
export CUSTOMERIO_AUTH=$(echo -n "$CIO_SITE_ID:$CIO_API_KEY" | base64)
k6 run \
--out json=results.json \
--out influxdb=http://localhost:8086/k6 \
load-tests/customerio.js
k6 run --summary-export=summary.json load-tests/customerio.js
echo "Load test complete. Results in results.json"
Scaling Checklist
Error Handling
| Issue | Solution |
|---|
| Rate limited (429) | Reduce concurrency |
| Timeout errors | Increase timeout |
| Queue backlog | Scale workers |
Resources
Next Steps
After load testing, proceed to customerio-known-pitfalls for anti-patterns.