This guide provides step-by-step instructions to verify the backpressure queue implementation for issue #340.
The implementation includes:
- AsyncBoundedQueue: A TypeScript async bounded queue similar to Python's asyncio.Queue
- BackpressureManager: Manages queue with configurable backpressure rules
- Integration with MarketRateService: Applied to rate fetching ingestion loops
- Node.js 18+ installed
- Project dependencies installed (
npm install) - TypeScript compiler available
Execute the comprehensive test suite to verify all functionality:
npm test -- test/backpressure.test.tsAll tests should pass:
- ✅ Basic queue operations (put, get, tryPut, tryGet)
- ✅ Async operations with waiting producers/consumers
- ✅ Queue closure behavior
- ✅ Backpressure configuration
- ✅ Enqueue with backpressure rules
- ✅ Dequeue operations
- ✅ Metrics tracking (queue length, saturation, dropped packets, slowed ingestions)
- ✅ High load scenario handling
- ✅ Recovery from backpressure
Create a test file manual-test-queue.ts:
import { AsyncBoundedQueue } from './src/queue/backpressure';
async function testBasicQueue() {
console.log('=== Testing Basic Queue Operations ===');
const queue = new AsyncBoundedQueue<number>(5);
// Test tryPut
console.log('Testing tryPut...');
console.log('Queue size before:', queue.size());
queue.tryPut(1);
queue.tryPut(2);
queue.tryPut(3);
console.log('Queue size after adding 3 items:', queue.size());
// Test tryGet
console.log('\nTesting tryGet...');
console.log('Dequeued:', queue.tryGet());
console.log('Dequeued:', queue.tryGet());
console.log('Queue size after removing 2 items:', queue.size());
// Test full queue
console.log('\nTesting full queue...');
queue.tryPut(4);
queue.tryPut(5);
console.log('Queue size:', queue.size());
console.log('Is full?', queue.isFull());
console.log('Try to add to full queue:', queue.tryPut(6)); // Should return false
console.log('\n✅ Basic queue operations test completed');
}
testBasicQueue().catch(console.error);Run the manual test:
npx ts-node manual-test-queue.tsCreate a test file manual-test-backpressure.ts:
import { BackpressureManager, PacketPriority } from './src/queue/backpressure';
async function testBackpressureManager() {
console.log('=== Testing Backpressure Manager ===');
const manager = new BackpressureManager({
maxCapacity: 10,
dropThreshold: 0.8, // Drop metrics at 80%
slowDownThreshold: 0.6, // Slow down at 60%
slowDownDelay: 100, // 100ms delay
});
// Test normal enqueue
console.log('\n1. Testing normal enqueue...');
await manager.enqueue({
priority: PacketPriority.STANDARD,
data: { test: 'normal' },
timestamp: Date.now(),
});
console.log('Queue length:', manager.getQueueLength());
console.log('Metrics:', manager.getMetrics());
// Test slow down (fill to 70%)
console.log('\n2. Testing slow down (filling to 70%)...');
for (let i = 0; i < 6; i++) {
await manager.enqueue({
priority: PacketPriority.STANDARD,
data: { test: i },
timestamp: Date.now(),
});
}
console.log('Queue length:', manager.getQueueLength());
console.log('Saturation:', manager.getMetrics().saturation);
console.log('Slowed down ingestions:', manager.getMetrics().slowedDownIngestions);
// Test drop metric packets (fill to 90%)
console.log('\n3. Testing metric packet drop (filling to 90%)...');
for (let i = 0; i < 3; i++) {
await manager.enqueue({
priority: PacketPriority.STANDARD,
data: { test: i },
timestamp: Date.now(),
});
}
console.log('Queue length:', manager.getQueueLength());
const metricResult = await manager.enqueue({
priority: PacketPriority.METRIC,
data: { test: 'metric' },
timestamp: Date.now(),
});
console.log('Metric packet enqueued?', metricResult); // Should be false
console.log('Dropped packets:', manager.getMetrics().droppedPackets);
// Test dequeue
console.log('\n4. Testing dequeue...');
const dequeued = await manager.dequeue();
console.log('Dequeued packet:', dequeued);
console.log('Queue length after dequeue:', manager.getQueueLength());
console.log('\n✅ Backpressure manager test completed');
console.log('\nFinal metrics:', manager.getMetrics());
}
testBackpressureManager().catch(console.error);Run the manual test:
npx ts-node manual-test-backpressure.tsCreate a test file manual-test-integration.ts:
import { MarketRateService } from './src/services/marketRate/marketRateService';
async function testMarketRateIntegration() {
console.log('=== Testing MarketRateService Integration ===');
const service = new MarketRateService();
// Get initial backpressure metrics
console.log('\n1. Initial backpressure metrics:');
console.log(service.getBackpressureMetrics());
// Fetch a rate (this will use backpressure)
console.log('\n2. Fetching NGN rate...');
const result = await service.getRate('NGN');
console.log('Fetch result:', result.success ? 'Success' : 'Failed');
// Check metrics after fetch
console.log('\n3. Backpressure metrics after fetch:');
console.log(service.getBackpressureMetrics());
// Fetch multiple rates to test backpressure under load
console.log('\n4. Fetching multiple rates to test backpressure...');
const currencies = ['NGN', 'KES', 'GHS'];
for (let i = 0; i < 5; i++) {
for (const currency of currencies) {
await service.getRate(currency);
}
console.log(`Batch ${i + 1} completed. Queue length:`, service.getBackpressureMetrics().queueLength);
}
// Final metrics
console.log('\n5. Final backpressure metrics:');
console.log(service.getBackpressureMetrics());
console.log('\n✅ Integration test completed');
}
testMarketRateIntegration().catch(console.error);Run the integration test:
npx ts-node manual-test-integration.tsCreate a test file manual-test-load.ts:
import { BackpressureManager, PacketPriority } from './src/queue/backpressure';
async function testHighLoad() {
console.log('=== Testing High Load Scenario ===');
const manager = new BackpressureManager({
maxCapacity: 100,
dropThreshold: 0.9,
slowDownThreshold: 0.7,
slowDownDelay: 10,
});
console.log('Enqueueing 500 packets rapidly...');
const startTime = Date.now();
const promises = [];
for (let i = 0; i < 500; i++) {
const packet = {
priority: i % 3 === 0 ? PacketPriority.METRIC : PacketPriority.STANDARD,
data: { index: i },
timestamp: Date.now(),
};
promises.push(manager.enqueue(packet));
}
const results = await Promise.all(promises);
const elapsed = Date.now() - startTime;
const successCount = results.filter(r => r).length;
const failCount = results.filter(r => !r).length;
console.log('\n=== Results ===');
console.log('Total packets:', 500);
console.log('Successfully enqueued:', successCount);
console.log('Dropped:', failCount);
console.log('Time elapsed:', elapsed, 'ms');
const metrics = manager.getMetrics();
console.log('\n=== Metrics ===');
console.log('Queue length:', metrics.queueLength);
console.log('Saturation:', (metrics.saturation * 100).toFixed(2) + '%');
console.log('Dropped packets:', metrics.droppedPackets);
console.log('Slowed down ingestions:', metrics.slowedDownIngestions);
console.log('Average processing time:', metrics.averageProcessingTime.toFixed(2), 'ms');
// Process all packets
console.log('\nProcessing all packets...');
while (manager.getQueueLength() > 0) {
await manager.dequeue();
}
console.log('Final queue length:', manager.getQueueLength());
console.log('\n✅ High load test completed');
}
testHighLoad().catch(console.error);Run the high load test:
npx ts-node manual-test-load.ts-
Slow Down Threshold (70%):
- When queue reaches 70% capacity, ingestion should slow down
- Check console logs for "Slowing down ingestion" messages
- Verify
slowedDownIngestionsmetric increases
-
Drop Threshold (90%):
- When queue reaches 90% capacity, METRIC priority packets should be dropped
- Verify
droppedPacketsmetric increases - STANDARD and CRITICAL packets should still be accepted
-
Queue Capacity (1000 default):
- Queue should never exceed max capacity
- Critical packets wait for space when full
- Non-critical packets are dropped when full
-
Metrics Accuracy:
queueLengthshould match actual queue sizesaturationshould be queueLength / maxCapacitydroppedPacketsshould count actual dropsslowedDownIngestionsshould count actual slowdowns
Verify the implementation meets production requirements:
# Check TypeScript compilation
npm run build
# Run linting
npm run lint
# Run all tests
npm test
# Check for any compilation errors in the backpressure module
npx tsc --noEmit src/queue/backpressure.tsIf you want to add an API endpoint to monitor backpressure metrics:
Add to your routes (e.g., src/routes/status.ts):
import { marketRateService } from '../services/marketRate/marketRateService';
router.get('/backpressure', (req, res) => {
const metrics = marketRateService.getBackpressureMetrics();
res.json({
success: true,
data: metrics,
});
});Test the endpoint:
curl http://localhost:3000/api/v1/status/backpressureExpected response:
{
"success": true,
"data": {
"queueLength": 5,
"maxCapacity": 1000,
"saturation": 0.005,
"droppedPackets": 0,
"slowedDownIngestions": 0,
"averageProcessingTime": 2.5
}
}Solution: Increase timeout in jest.config.ts or use --testTimeout=30000
Solution: Verify slowDownThreshold is set correctly (0-1 range) and queue is filling above threshold
Solution: Verify dropThreshold is set correctly and packets have METRIC priority
Solution: Ensure MarketRateService is properly initialized and backpressure manager is instantiated
You have successfully completed the assignment if:
✅ All unit tests pass (test/backpressure.test.ts) ✅ Manual tests demonstrate correct queue behavior ✅ Backpressure slows down ingestion at 70% capacity ✅ Backpressure drops metric packets at 90% capacity ✅ Queue never exceeds max capacity (1000) ✅ Metrics accurately track queue state ✅ Integration with MarketRateService works correctly ✅ TypeScript compiles without errors ✅ No memory leaks or performance degradation under load
The backpressure queue implementation provides:
- Bounded execution buffer queue (AsyncBoundedQueue) with max capacity 1000
- Backpressure rules that slow down ingestion at 70% capacity
- Drop-tail strategy that drops metric packets at 90% capacity
- Priority-based handling where critical packets wait for space
- Comprehensive metrics for monitoring queue health
- Integration with MarketRateService ingestion loops
This implementation prevents queue overflow and out-of-memory errors during network lag on external validator endpoints, as specified in issue #340.