Advanced
Object Pooling
Boost performance with MoroJS's built-in object pooling system. Reduce garbage collection pressure and improve throughput by reusing objects instead of creating new ones.
Overview
Object pooling reduces garbage collection overhead and memory allocation time by reusing objects instead of creating and destroying them repeatedly.
Without Pooling
- Requests/sec: 15,000
- Avg latency: 6.5ms
- GC time: 15% of CPU
- Memory: 250MB
With Pooling
- Requests/sec: 45,000 (+200%)
- Avg latency: 2.2ms (-66%)
- GC time: 3% of CPU (-80%)
- Memory: 180MB (-28%)
Object Pooling in Actiontypescript
1import { createApp, ObjectPoolManager } from '@morojs/moro';
2
3const app = await createApp();
4const poolManager = ObjectPoolManager.getInstance();
5
6// Pre-warm pools for better performance
7poolManager.preWarmPools({
8 params: 500,
9 query: 500,
10 headers: 200,
11 buffers: { 1024: 100, 4096: 50 }
12});
13
14app.get('/api/search', async (req, res) => {
15 // Query params are automatically pooled
16 const { q, page = 1, limit = 10 } = req.query;
17
18 // Use buffer pool for response
19 const buffer = poolManager.acquireBuffer(4096);
20
21 try {
22 const results = await search(q, page, limit);
23 const json = JSON.stringify(results);
24 buffer.write(json);
25
26 res.setHeader('Content-Type', 'application/json');
27 res.end(buffer.slice(0, json.length));
28 } finally {
29 poolManager.releaseBuffer(buffer);
30 }
31});
32
33await app.listen(3000);Built-in Pools
Automatic Pooling
MoroJS automatically manages several object pools for common use cases:
Parameter Object Pool
Reuses objects for route parameters (req.params).
typescript
// Automatically pooled
app.get('/users/:userId/posts/:postId', (req, res) => {
// req.params is from the pool
const { userId, postId } = req.params;
res.json({ userId, postId });
});Query Object Pool
Reuses objects for query string parameters (req.query).
typescript
// Automatically pooled
app.get('/search', (req, res) => {
// req.query is from the pool
const { q, page, limit } = req.query;
res.json({ results: search(q, page, limit) });
});Buffer Pools
Reuses buffers for various sizes (64, 256, 1024, 4096, 16384 bytes).
typescript
const poolManager = ObjectPoolManager.getInstance();
// Acquire a buffer
const buffer = poolManager.acquireBuffer(1024);
// Use buffer
buffer.write('Hello World');
// Release back to pool
poolManager.releaseBuffer(buffer);Route & Response Cache
Caches route lookups and complete responses with LRU eviction.
typescript
// Route lookups are automatically cached
app.get('/api/v1/users/:id', handler1);
app.get('/api/v1/posts/:id', handler2);
// Subsequent requests hit the cache
// GET /api/v1/users/123 - cache hit
// GET /api/v1/users/456 - cache hitCustom Pools
Creating Custom Object Poolstypescript
1import { ObjectPool } from '@morojs/moro';
2
3// Create a custom object pool
4class RequestContext {
5 userId?: string;
6 requestId?: string;
7 timestamp?: number;
8}
9
10const contextPool = new ObjectPool<RequestContext>(
11 // Factory function
12 () => new RequestContext(),
13 // Max pool size
14 200,
15 // Reset function
16 (ctx) => {
17 ctx.userId = undefined;
18 ctx.requestId = undefined;
19 ctx.timestamp = undefined;
20 }
21);
22
23// Use the pool
24const ctx = contextPool.acquire();
25ctx.userId = req.user.id;
26ctx.requestId = req.id;
27ctx.timestamp = Date.now();
28
29// ... use context ...
30
31contextPool.release(ctx); // Returns to poolLRU Cachetypescript
1import { LRUCache } from '@morojs/moro';
2
3// Create LRU cache
4const cache = new LRUCache<string, any>(1000); // Max 1000 items
5
6// Set values
7cache.set('user:123', { name: 'John', email: 'john@example.com' });
8
9// Get values
10const user = cache.get('user:123');
11
12// Check existence
13if (cache.has('user:123')) {
14 // User is cached
15}
16
17// Cache statistics
18const stats = cache.stats;
19console.log({
20 size: stats.size,
21 maxSize: stats.maxSize,
22 hits: stats.hits,
23 misses: stats.misses,
24 hitRate: stats.hitRate
25});Monitoring and Statistics
Global Statisticstypescript
1const poolManager = ObjectPoolManager.getInstance();
2
3// Get comprehensive statistics
4const stats = poolManager.getStats();
5
6console.log('Object Pooling Statistics:', {
7 // Parameter pool
8 params: stats.paramPool,
9
10 // Query pool
11 query: stats.queryPool,
12
13 // Header pool
14 headers: stats.headerPool,
15
16 // Buffer pools
17 buffers: stats.bufferPools,
18
19 // Caches
20 routeCache: stats.routeCache,
21 responseCache: stats.responseCache,
22
23 // Estimated memory held by pools and caches
24 totalMemory: stats.totalMemory
25});Monitoring Endpointtypescript
1app.get('/metrics/pools', (req, res) => {
2 const poolManager = ObjectPoolManager.getInstance();
3 const stats = poolManager.getStats();
4
5 res.json({
6 timestamp: Date.now(),
7 pools: {
8 params: {
9 size: stats.paramPool.poolSize,
10 utilization: `${(stats.paramPool.utilization * 100).toFixed(1)}%`,
11 acquisitions: stats.paramPool.acquireCount,
12 releases: stats.paramPool.releaseCount
13 },
14 query: {
15 size: stats.queryPool.poolSize,
16 utilization: `${(stats.queryPool.utilization * 100).toFixed(1)}%`,
17 acquisitions: stats.queryPool.acquireCount,
18 releases: stats.queryPool.releaseCount
19 },
20 routeCache: {
21 size: stats.routeCache.size,
22 hitRate: `${(stats.routeCache.hitRate * 100).toFixed(1)}%`,
23 hits: stats.routeCache.hits,
24 misses: stats.routeCache.misses
25 }
26 }
27 });
28});Adaptive Pool Sizing & Summariestypescript
1// Adaptive mode (enabled by default): usage is sampled continuously
2// and pool capacities are automatically resized to ~120% of average
3// observed usage (clamped to safe ranges), reviewed once per minute.
4poolManager.setAdaptiveMode(true);
5
6// Compact summary for dashboards
7const summary = poolManager.getPerformanceSummary();
8console.log(summary);
9// {
10// routeCacheHitRate: 98.2,
11// responseCacheHitRate: 87.5,
12// paramPoolUtilization: 42.0,
13// totalMemoryKB: 118.4
14// }
15
16// Or log it through the framework logger
17poolManager.logStats();API Reference
ObjectPoolManager Methods
typescript
ObjectPoolManager.getInstance(): ObjectPoolManager
ObjectPoolManager.reset(): void // Reset singleton (testing)
acquireParams(): Record<string, string>
releaseParams(obj): void
acquireQuery(): Record<string, string>
releaseQuery(obj): void
acquireHeaders(): Record<string, string>
releaseHeaders(obj): void
acquireBuffer(size: number): Buffer
releaseBuffer(buffer: Buffer): void
getCachedRoute(key: string): any
cacheRoute(key: string, route: any): void
hasCachedRoute(key: string): boolean
clearRouteCache(): void
getCachedResponse(key: string): CachedResponse | undefined
cacheResponse(key, buffer, headers, statusCode?): void
preWarmPools(sizes?: PreWarmSizes): void
getStats(): PoolStats
getPerformanceSummary(): PerformanceSummary
logStats(): void
setAdaptiveMode(enabled: boolean): void
clearAll(): voidObjectPool<T> Methods
typescript
constructor(factory, maxSize, reset?) // Create pool
acquire(): T // Get object from pool
release(obj: T): void // Return object to pool
clear(): void // Clear all objects
get size(): number // Current pool size
get stats() // Get pool statisticsLRUCache<K, V> Methods
typescript
constructor(maxSize: number) // Create cache
get(key: K): V | undefined // Get value
set(key: K, value: V): void // Set value
has(key: K): boolean // Check existence
delete(key: K): boolean // Delete entry
clear(): void // Clear cache
get size(): number // Current size
get stats() // Get statisticsBest Practices
Always Release Objects
typescript
// Good: Always release
const params = poolManager.acquireParams();
try {
await process(params);
} finally {
poolManager.releaseParams(params);
}Pre-warm Pools
typescript
// Pre-warm before high traffic
poolManager.preWarmPools({
params: 1000,
query: 500,
buffers: {
1024: 200,
4096: 100
}
});
await app.listen(3000);Choose Appropriate Pool Sizes
typescript
// Size based on traffic
const isProduction = process.env.NODE_ENV === 'production';
poolManager.preWarmPools({
params: isProduction ? 2000 : 100,
query: isProduction ? 1000 : 50
});Monitor Pool Utilization
typescript
setInterval(() => {
const stats = poolManager.getStats();
if (stats.paramPool.utilization > 0.9) {
console.warn('Parameter pool nearly exhausted!');
}
if (stats.paramPool.utilization < 0.1) {
console.info('Parameter pool underutilized');
}
}, 60000); // Check every minute