Skip to content

HeliosDB WASM Performance Tuning Guide

HeliosDB WASM Performance Tuning Guide

Overview

This guide covers performance optimization techniques for HeliosDB stored procedures, including:

  • JIT compilation optimization
  • Function result caching
  • Parallel execution
  • Memory management
  • Profiling and benchmarking

Performance Architecture

Three-Tier Caching System

L1: Instance Pool (Hot instances, <1ms access)
L2: Module Cache (Compiled modules, ~1ms access)
L3: AOT Cache (Persistent disk cache, ~10ms access)

Execution Pipeline

Request → Cache Check → Module Load → Instantiation → Execution → Result Cache
| | | | | |
<1µs <1ms ~1ms ~5ms varies <1ms

JIT Compilation Optimization

Optimization Levels

-- Fast compilation, less optimization (low latency)
ALTER FUNCTION quick_function
SET jit_mode = 'low_latency';
-- Balanced (default)
ALTER FUNCTION normal_function
SET jit_mode = 'balanced';
-- Aggressive optimization (best performance)
ALTER FUNCTION heavy_compute
SET jit_mode = 'fast';

Performance Impact

JIT ModeCompilation TimeExecution SpeedBest For
low_latency~1ms1.0xShort-lived functions
balanced~5ms1.5xGeneral purpose
fast~20ms2.0-3.0xLong-running compute

When to Use Each Mode

Low Latency:

  • Functions called frequently with short execution time
  • Request/response handlers
  • Simple data transformations

Balanced (Default):

  • General-purpose functions
  • Mixed workloads
  • Moderate execution time (10-100ms)

Fast:

  • CPU-intensive algorithms
  • Long-running computations (>100ms)
  • Batch processing

Function Result Caching

Enable Caching

ALTER FUNCTION expensive_calculation
SET cache_ttl = 300; -- Cache for 5 minutes
ALTER FUNCTION expensive_calculation
SET cache_size = 1000; -- Maximum 1000 cached entries

Cache Configuration

-- Aggressive caching
ALTER FUNCTION lookup_function
SET cache_ttl = 3600, -- 1 hour
cache_size = 10000, -- 10k entries
cache_strategy = 'lru'; -- Least Recently Used eviction
-- Minimal caching
ALTER FUNCTION volatile_function
SET cache_ttl = 10, -- 10 seconds
cache_size = 100,
cache_strategy = 'lru';

Cache Performance

// First call: ~50ms (no cache)
calculate_complex(42);
// Second call: <1ms (cache hit)
calculate_complex(42);

Cache Hit Rate

SELECT
function_name,
cache_hits,
cache_misses,
(cache_hits::float / (cache_hits + cache_misses)) * 100 as hit_rate
FROM heliosdb.function_cache_stats
ORDER BY hit_rate DESC;

When to Cache

Good Candidates:

  • Pure functions (IMMUTABLE)
  • Expensive computations
  • External API calls
  • Database aggregations
  • Complex transformations

Poor Candidates:

  • VOLATILE functions
  • Random number generation
  • Time-dependent calculations
  • User-specific data (unless partitioned)

Parallel Execution

Batch Execution

// Sequential (slow): ~100ms total
for (let i = 0; i < 10; i++) {
await db.execute('process_item', [items[i]]);
}
// Parallel (fast): ~10ms total
const results = await db.executeParallel(
items.map(item => ({
function: 'process_item',
args: [item]
}))
);

Concurrency Control

-- Set maximum concurrent executions
ALTER FUNCTION cpu_intensive
SET max_concurrency = 4;
-- Set queue size for pending executions
ALTER FUNCTION cpu_intensive
SET queue_size = 100;

Parallel Execution Patterns

Map-Reduce:

// Map phase (parallel)
const mapped = await db.executeParallel(
items.map(item => ({
function: 'map_function',
args: [item]
}))
);
// Reduce phase (sequential)
const result = await db.execute('reduce_function', [mapped]);

Pipeline:

// Stage 1: Parallel preprocessing
const preprocessed = await db.executeParallel(
items.map(item => ({ function: 'preprocess', args: [item] }))
);
// Stage 2: Parallel processing
const processed = await db.executeParallel(
preprocessed.map(item => ({ function: 'process', args: [item] }))
);
// Stage 3: Parallel postprocessing
const final = await db.executeParallel(
processed.map(item => ({ function: 'postprocess', args: [item] }))
);

Memory Management

Memory Optimization

// BAD: Creates many intermediate arrays
function process_large_array(items) {
return items
.filter(x => x > 0) // New array
.map(x => x * 2) // New array
.map(x => ({ value: x })) // New array of objects
.filter(x => x.value < 1000); // New array
}
// GOOD: Single pass, minimal allocations
function process_large_array_optimized(items) {
const result = [];
for (let x of items) {
if (x > 0) {
const doubled = x * 2;
if (doubled < 1000) {
result.push({ value: doubled });
}
}
}
return result;
}

Memory Limits

-- Increase memory limit for data-intensive function
ALTER FUNCTION process_large_dataset
SET max_memory_bytes = 67108864; -- 64MB
-- Decrease for lightweight function
ALTER FUNCTION simple_calc
SET max_memory_bytes = 4194304; -- 4MB

Streaming Results

// BAD: Load entire result set into memory
function get_all_users() {
const result = heliosdb.query('SELECT * FROM users');
return result.rows; // Could be millions of rows
}
// GOOD: Stream results
async function* stream_users() {
const BATCH_SIZE = 100;
let offset = 0;
while (true) {
const batch = heliosdb.query(
'SELECT * FROM users LIMIT ? OFFSET ?',
[BATCH_SIZE, offset]
);
if (batch.rows.length === 0) break;
for (let row of batch.rows) {
yield row;
}
offset += BATCH_SIZE;
}
}

Profiling and Benchmarking

Built-in Profiling

-- Enable profiling
ALTER FUNCTION my_function
SET profiling = true;
-- Execute function
SELECT my_function(args);
-- View profiling results
SELECT * FROM heliosdb.function_profile('my_function');

Profiling output:

function_name | my_function
total_time_ms | 47.3
compilation_time | 8.2
execution_time | 35.1
cache_time | 4.0
memory_peak_mb | 12.5
instructions | 45672134

Performance Metrics

SELECT
function_name,
call_count,
avg_execution_time_ms,
p50_execution_time_ms,
p95_execution_time_ms,
p99_execution_time_ms,
max_execution_time_ms,
avg_memory_mb,
cache_hit_rate
FROM heliosdb.function_stats
WHERE function_name = 'my_function';

Benchmarking

// Benchmark helper
function benchmark(fn, iterations = 100) {
const start = Date.now();
for (let i = 0; i < iterations; i++) {
fn();
}
const elapsed = Date.now() - start;
const avg = elapsed / iterations;
heliosdb.log(`Benchmark: ${iterations} iterations in ${elapsed}ms (avg: ${avg}ms)`);
}
// Usage
benchmark(() => expensive_calculation(42), 1000);

Optimization Techniques

1. Minimize Function Calls

// BAD: Multiple function calls
function process_items(items) {
return items.map(item => {
const validated = validate_item(item); // Function call
const transformed = transform_item(validated); // Function call
return formatted_item(transformed); // Function call
});
}
// GOOD: Inline processing
function process_items_optimized(items) {
return items.map(item => {
// Inline validation
if (!item || !item.id) return null;
// Inline transformation
const transformed = {
id: item.id,
name: item.name.toUpperCase()
};
// Inline formatting
return `${transformed.id}: ${transformed.name}`;
}).filter(x => x !== null);
}

2. Use Appropriate Data Structures

// BAD: O(n) lookup for each item
function find_users(user_ids) {
const all_users = heliosdb.query('SELECT * FROM users').rows;
return user_ids.map(id =>
all_users.find(u => u.id === id) // O(n) for each
);
}
// GOOD: O(1) lookup with Map
function find_users_optimized(user_ids) {
const all_users = heliosdb.query('SELECT * FROM users').rows;
const user_map = new Map(all_users.map(u => [u.id, u]));
return user_ids.map(id => user_map.get(id));
}

3. Batch Database Operations

// BAD: Multiple queries (N+1 problem)
function get_user_orders(user_ids) {
return user_ids.map(user_id => {
const orders = heliosdb.query(
'SELECT * FROM orders WHERE user_id = ?',
[user_id]
).rows;
return { user_id, orders };
});
}
// GOOD: Single query
function get_user_orders_optimized(user_ids) {
const placeholders = user_ids.map(() => '?').join(',');
const all_orders = heliosdb.query(
`SELECT * FROM orders WHERE user_id IN (${placeholders})`,
user_ids
).rows;
// Group by user_id
const grouped = new Map();
for (let order of all_orders) {
if (!grouped.has(order.user_id)) {
grouped.set(order.user_id, []);
}
grouped.get(order.user_id).push(order);
}
return user_ids.map(user_id => ({
user_id,
orders: grouped.get(user_id) || []
}));
}

4. Lazy Evaluation

// GOOD: Only compute what's needed
function find_first_match(items, predicate) {
for (let item of items) {
if (predicate(item)) {
return item; // Stop as soon as match found
}
}
return null;
}
// BAD: Compute everything
function find_first_match_bad(items, predicate) {
const all_matches = items.filter(predicate);
return all_matches[0] || null;
}

Performance Checklist

Before deploying to production:

  • JIT mode configured appropriately
  • Caching enabled for expensive functions
  • Cache TTL and size tuned
  • Resource limits configured
  • Parallel execution used for batch operations
  • Memory allocations minimized
  • Database operations batched
  • Appropriate data structures used
  • Profiling data reviewed
  • Benchmarks meet SLA requirements

Performance Targets

Function TypeTarget LatencyTarget Throughput
Simple<1ms>10,000 req/s
Medium<10ms>1,000 req/s
Complex<100ms>100 req/s
Batch<1s>10 batches/s

Troubleshooting

High Latency

  1. Check cache hit rate
  2. Review JIT optimization level
  3. Profile execution time breakdown
  4. Check for N+1 database queries
  5. Review memory allocations

Low Throughput

  1. Enable parallel execution
  2. Increase max_concurrency
  3. Pre-warm instance pool
  4. Batch requests
  5. Optimize hot paths

High Memory Usage

  1. Check for memory leaks
  2. Reduce data structure size
  3. Use streaming for large datasets
  4. Decrease memory limits
  5. Profile memory allocations

Conclusion

Performance optimization is an iterative process. Start with profiling to identify bottlenecks, apply appropriate optimizations, and measure the results. The three-tier caching system, JIT compilation options, and parallel execution capabilities provide powerful tools for achieving high performance while maintaining security and isolation.