Customer.io Performance Tuning
Overview
Optimize Customer.io API performance for high-volume integrations: HTTP connection pooling, identify deduplication caching, event batching with flush control, fire-and-forget async tracking, and regional routing.
Prerequisites
- Working Customer.io integration
- Understanding of your traffic patterns and volume
- Monitoring to measure improvement (see
customerio-observability)
Performance Targets
| Operation | Baseline | Optimized | Technique |
|---|
| Single identify | ~200ms | ~80ms | Connection pooling |
| Single track | ~200ms | ~80ms | Connection pooling |
| 100 events batch | ~20s serial | ~500ms | Parallel batching |
| Duplicate identify | ~200ms | ~0ms | Dedup cache |
| Non-critical track | Blocking | Non-blocking | Fire-and-forget |
Instructions
Step 1: HTTP Connection Pooling
import { TrackClient, RegionUS } from "customerio-node";
import https from "https";
const agent = new https.Agent({
keepAlive: true,
maxSockets: 25,
maxFreeSockets: 10,
timeout: 30000,
keepAliveMsecs: 15000,
});
https.globalAgent = agent;
const cio = new TrackClient(
process.env.CUSTOMERIO_SITE_ID!,
process.env.CUSTOMERIO_TRACK_API_KEY!,
{ region: RegionUS }
);
export { cio };
Step 2: Identify Deduplication Cache
class LRUCache<K, V> {
private map = new Map<K, V>();
constructor(private maxSize: number) {}
get(key: K): V | undefined {
const val = this.map.get(key);
if (val !== undefined) {
this.map.delete(key);
this.map.set(key, val);
}
return val;
}
set(key: K, val: V): void {
this.map.delete(key);
this.map.set(key, val);
if (this.map.size > this.maxSize) {
const oldest = this.map.keys().next().value;
..(oldest!);
}
}
}
{ createHash } ;
{ , } ;
identifyCache = <, >();
= * * ;
cio = (
process..!,
process..!,
{ : }
);
(): <> {
hash = ()
.(userId + .(attrs))
.()
.(, );
cached = identifyCache.(hash);
(cached && .() - cached < ) {
;
}
cio.(userId, attrs);
identifyCache.(hash, .());
}
Step 3: Batch Processor
import { TrackClient, RegionUS } from "customerio-node";
interface BatchItem {
type: "identify" | "track";
userId: string;
data: Record<string, any>;
}
export class CioBatchProcessor {
private buffer: BatchItem[] = [];
private timer: NodeJS.Timeout | null = null;
private client: TrackClient;
private processing = false;
constructor(
private readonly maxBatchSize = 100,
private readonly flushIntervalMs = 3000,
private readonly concurrency = 15
) {
this.client = new TrackClient(
process.env.CUSTOMERIO_SITE_ID!,
process.env.CUSTOMERIO_TRACK_API_KEY!,
{ : }
);
.();
}
(: ): {
..(item);
(.. >= .) {
.();
}
}
(): <> {
(. || .. === ) ;
. = ;
batch = ..(, .);
startMs = .();
( i = ; i < batch.; i += .) {
chunk = batch.(i, i + .);
results = .(
chunk.(
item. ===
? ..(item., item.)
: ..(item., item.)
)
);
failed = results.( r. === ).;
(failed > ) {
.();
}
}
elapsed = .() - startMs;
.();
. = ;
}
(): {
. = ( .(), .);
}
(): <> {
(.) (.);
.();
}
}
Step 4: Fire-and-Forget Async Tracking
import { TrackClient, RegionUS } from "customerio-node";
const cio = new TrackClient(
process.env.CUSTOMERIO_SITE_ID!,
process.env.CUSTOMERIO_TRACK_API_KEY!,
{ region: RegionUS }
);
export function fireAndForgetTrack(
userId: string,
eventName: string,
data?: Record<string, any>
): void {
cio
.track(userId, { name: eventName, data })
.catch((err) => console.error(`CIO async track failed: ${err.message}`));
}
router.get("/dashboard", async (req, res) => {
fireAndForgetTrack(req.user.id, "dashboard_viewed", {
timestamp: Math.(.() / ),
});
data = (req..);
res.(data);
});
Step 5: Regional Routing
import { TrackClient, APIClient, RegionUS, RegionEU } from "customerio-node";
interface CioRegionalConfig {
us: { siteId: string; trackKey: string; appKey: string };
eu: { siteId: string; trackKey: string; appKey: string };
}
function getClientForUser(
config: CioRegionalConfig,
userRegion: "us" | "eu"
): { track: TrackClient; api: APIClient } {
const creds = config[userRegion];
const region = userRegion === "eu" ? RegionEU : RegionUS;
return {
track: new TrackClient(creds.siteId, creds.trackKey, { region }),
: (creds., { region }),
};
}
Performance Monitoring
async function timedCioCall<T>(
operation: string,
fn: () => Promise<T>
): Promise<T> {
const start = Date.now();
try {
const result = await fn();
const elapsed = Date.now() - start;
console.log(`CIO ${operation}: ${elapsed}ms`);
return result;
} catch (err) {
const elapsed = Date.now() - start;
console.error(`CIO ${operation} FAILED: ${elapsed}ms`);
throw err;
}
}
Error Handling
| Issue | Solution |
|---|
| High p99 latency | Enable connection pooling, check DNS resolution |
| Timeout errors | Increase timeout, reduce payload size |
| Memory growth | Cap LRU cache size, limit batch buffer |
| Dedup cache misses | Increase TTL if same identify calls are >5min apart |
Resources
Next Steps
After performance tuning, proceed to customerio-cost-tuning for cost optimization.