Skip to content

Performance Best Practices ​

Learn how to optimize your BoardAPI integration for maximum performance and scalability.

Overview ​

BoardAPI is designed to handle large-scale collaborative boards with thousands of objects and hundreds of concurrent users. This guide covers best practices for optimal performance.

Key Performance Metrics ​

MetricTargetProduction Results
HTTP API Latency (p95)<300msβœ… 245ms
Object Creation<50msβœ… 38ms
Cursor Updates<20msβœ… 12ms
Initial Load Time<1sβœ… 0.8s
Max Concurrent Users200+βœ… 1000+

Board Size Limits & Recommendations ​

ResourceRecommendedHard LimitPerformance Impact
Objects per board1,00010,000Rendering slowdown beyond 1K
Concurrent users50200Network overhead increases linearly
Board dimensions5000x5000pxUnlimitedMemory usage grows with size
File uploads10MB each50MBAffects transfer time
WebSocket messages/sec100500CPU usage increases

Performance Tiers ​

Small Boards (0-100 objects)

  • No optimization needed
  • Full real-time sync
  • Instant loading

Medium Boards (100-1,000 objects)

  • Lazy loading recommended
  • Viewport-based rendering
  • ~2-3 second load time

Large Boards (1,000-10,000 objects)

  • Lazy loading required
  • Chunking system active
  • Consider pagination or frames

Extra Large Boards (10,000+ objects)

  • Split into multiple boards
  • Use Frames for organization
  • Implement custom pagination

Object Count Optimization ​

Reducing Object Overhead ​

1. Combine Small Objects

Instead of creating separate objects for each element, group related items:

javascript
// ❌ Bad: 100 individual text objects
for (let i = 0; i < 100; i++) {
  await createObject({ type: 'text', text: `Item ${i}` });
}

// βœ… Good: Single rich text object
await createObject({
  type: 'text',
  text: 'Item 1\nItem 2\n...\nItem 100'
});

2. Use Frames for Organization

Split large boards into logical sections using Frames:

javascript
// Create frames for different sections
const lectureFrame = await createFrame({
  name: 'Lecture Content',
  width: 1920,
  height: 1080
});

const exerciseFrame = await createFrame({
  name: 'Practice Exercises',
  width: 1920,
  height: 1080
});

3. Archive Inactive Objects

Remove objects that are no longer needed:

javascript
// Delete old objects before creating new ones
const oldObjects = await getObjectsByTag('session-2024-11-01');
await deleteObjects(oldObjects.map(o => o.id));

Lazy Loading Implementation ​

BoardAPI automatically implements viewport-based lazy loading for boards with 100+ objects. The system only renders objects visible in the current viewport plus a 20% buffer.

How it works:

  • Objects outside viewport: Not rendered (zero DOM nodes)
  • Objects entering viewport: Rendered with 200ms delay
  • Objects leaving viewport: Cleaned up after 5s

Client-side optimization:

javascript
// Use pagination for object lists
const { objects, total, page } = await getObjects({
  boardId: 'your-board-uuid',
  page: 1,
  limit: 50  // Fetch 50 at a time
});

// Only render visible objects
const visibleObjects = objects.filter(obj =>
  isInViewport(obj.position, currentViewport)
);

WebSocket Connection Management ​

Optimizing Real-Time Updates ​

1. Throttle Cursor Updates

Don't send cursor position on every mouse move:

javascript
// ❌ Bad: Sends 60+ events/sec
canvas.addEventListener('mousemove', (e) => {
  socket.emit('cursor:move', { x: e.clientX, y: e.clientY });
});

// βœ… Good: Throttled to 20 events/sec
let lastCursorUpdate = 0;
const THROTTLE_MS = 50; // 20 updates/sec

canvas.addEventListener('mousemove', (e) => {
  const now = Date.now();
  if (now - lastCursorUpdate < THROTTLE_MS) return;

  lastCursorUpdate = now;
  socket.emit('cursor:move', { x: e.clientX, y: e.clientY });
});

2. Batch Object Updates

Combine multiple changes into a single WebSocket message:

javascript
// ❌ Bad: 3 separate events
socket.emit('object:update', { id: 1, x: 100 });
socket.emit('object:update', { id: 1, y: 200 });
socket.emit('object:update', { id: 1, rotation: 45 });

// βœ… Good: Single batched update
socket.emit('object:update', {
  id: 1,
  x: 100,
  y: 200,
  rotation: 45
});

3. Use Compression

BoardAPI automatically compresses WebSocket messages larger than 1KB using perMessageDeflate. This reduces bandwidth by ~30% for large object updates.

To verify compression is working:

javascript
// Check message size in browser DevTools Network tab
// Look for "Sec-WebSocket-Extensions: permessage-deflate"

Connection Pooling ​

For high-traffic applications, reuse WebSocket connections:

javascript
// βœ… Single connection for multiple boards
const socket = io('https://api.boardapi.io/whiteboard', {
  query: { token: yourJWT }
});

// Join multiple boards over same connection
socket.emit('board:join', { boardUuid: 'board-1' });
socket.emit('board:join', { boardUuid: 'board-2' });

Caching Strategies ​

HTTP Caching ​

BoardAPI implements Redis caching with the following TTLs:

ResourceCache TTLInvalidation
Board state10 minutesOn any update
Board list1 minuteOn create/delete
Templates1 hourManual invalidation
User metadata5 minutesOn profile update

Client-side caching:

javascript
// Cache board metadata in localStorage
const cachedBoard = localStorage.getItem(`board:${boardUuid}`);
if (cachedBoard) {
  const { data, timestamp } = JSON.parse(cachedBoard);

  // Use cache if less than 5 minutes old
  if (Date.now() - timestamp < 5 * 60 * 1000) {
    return data;
  }
}

// Fetch fresh data
const board = await fetchBoard(boardUuid);
localStorage.setItem(`board:${boardUuid}`, JSON.stringify({
  data: board,
  timestamp: Date.now()
}));

Database Query Optimization ​

BoardAPI uses chunking for large boards, dividing the canvas into 512x512px tiles:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Canvas (2000x2000px)  β”‚
β”‚  β”Œβ”€β”€β”€β”€β”¬β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”     β”‚
β”‚  β”‚0,0 β”‚1,0 β”‚2,0 β”‚     β”‚  Each chunk:
β”‚  β”œβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€     β”‚  512x512px
β”‚  β”‚0,1 β”‚1,1 β”‚2,1 β”‚     β”‚  Stored separately
β”‚  β”œβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€     β”‚  in database
β”‚  β”‚0,2 β”‚1,2 β”‚2,2 β”‚     β”‚
β”‚  β””β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”˜     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Benefits:

  • Only load visible chunks (80% faster queries)
  • Smaller JSONB documents (faster inserts)
  • Parallel chunk loading

Monitoring Integration ​

Real-Time Performance Monitoring ​

BoardAPI exposes monitoring endpoints for observability:

bash
# Get performance statistics
GET /monitoring/performance/stats

# Response
{
  "timestamp": "2025-11-28T10:00:00Z",
  "stats": {
    "totalRequests": 1523,
    "successRate": 99.3,
    "averageDuration": 45,
    "p50": 32,
    "p95": 120,
    "p99": 280
  }
}

Integration with monitoring services:

javascript
// Poll performance stats
setInterval(async () => {
  const stats = await fetch('https://api.boardapi.io/monitoring/performance/stats');
  const data = await stats.json();

  // Send to your monitoring service (Datadog, New Relic, etc.)
  if (data.stats.p95 > 500) {
    alert('High latency detected!');
  }
}, 60000); // Check every minute

Custom Metrics ​

Track your own performance metrics:

javascript
// Measure board load time
const startTime = Date.now();

await loadBoard(boardUuid);

const loadTime = Date.now() - startTime;
console.log(`Board loaded in ${loadTime}ms`);

// Log to your analytics
analytics.track('board_load_time', {
  boardUuid,
  duration: loadTime,
  objectCount: board.objects.length
});

Troubleshooting Slow Boards ​

Common Issues & Solutions ​

Issue: Board loads slowly (>3 seconds)

Solutions:

  1. Check object count: GET /boards/:uuid/stats
  2. Enable lazy loading (automatic for 100+ objects)
  3. Split into multiple frames
  4. Consider archiving old content

Issue: Real-time updates lag

Solutions:

  1. Verify WebSocket compression is enabled
  2. Reduce cursor update frequency (20/sec max)
  3. Batch object updates
  4. Check network latency: socket.io.engine.ping
javascript
// Check WebSocket ping
socket.io.engine.on('ping', () => {
  console.log('Ping:', socket.io.engine.pingInterval);
});

// Typical values:
// <50ms: Excellent
// 50-150ms: Good
// 150-300ms: Acceptable
// >300ms: Poor (consider CDN/edge deployment)

Issue: High memory usage

Solutions:

  1. Clear old event listeners
  2. Dispose of unused canvases
  3. Implement pagination for participant lists
  4. Use virtual scrolling for long lists
javascript
// Clean up event listeners when leaving board
socket.off('object:created');
socket.off('object:updated');
socket.off('object:deleted');

// Dispose of canvas
excalidrawAPI.resetScene();

Issue: Slow API responses

Solutions:

  1. Use pagination: GET /boards?page=1&limit=50
  2. Fetch only needed columns: GET /boards?fields=id,title,status
  3. Enable HTTP caching headers
  4. Consider GraphQL for selective field fetching (roadmap)

Performance Debugging Checklist ​

β–‘ Check object count (target: <1,000)
β–‘ Verify WebSocket ping (<150ms)
β–‘ Monitor HTTP request duration (p95 <300ms)
β–‘ Check browser DevTools Performance tab
β–‘ Review Network tab for large payloads
β–‘ Verify lazy loading is active (100+ objects)
β–‘ Check Redis cache hit rate (target: >70%)
β–‘ Monitor database query time (<50ms)
β–‘ Review error rate (target: <1%)
β–‘ Check frame rate (target: 60fps)

Production Deployment Tips ​

Infrastructure Recommendations ​

1. Use CDN for Static Assets

Serve static files from a CDN:

bash
# Configure CDN origin
https://cdn.yourdomain.com/boards/*

# BoardAPI serves assets with cache headers
Cache-Control: public, max-age=31536000, immutable

2. Database Connection Pooling

Configure PostgreSQL connection pool:

env
# .env
DB_POOL_SIZE=20
DB_POOL_MAX=50
DB_POOL_MIN=5

3. Redis for Caching

Enable Redis for production:

env
# .env
REDIS_HOST=your-redis-host
REDIS_PORT=6379
REDIS_PASSWORD=your-password
CACHE_TTL=600  # 10 minutes

4. Load Balancing

Distribute WebSocket connections across multiple servers:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Client  β”‚
β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜
     β”‚
     β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Load Balancerβ”‚  (Sticky sessions for WebSocket)
β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜
     β”‚    β”‚
     β–Ό    β–Ό
β”Œβ”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”
β”‚WS-1 β”‚ β”‚WS-2 β”‚
β””β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”˜

Scaling Strategies ​

Horizontal Scaling

  • Add more BoardAPI instances
  • Use sticky sessions for WebSocket
  • Share Redis cache across instances

Vertical Scaling

  • Increase CPU for real-time processing
  • Add RAM for caching
  • Use SSD for database

Database Scaling

  • Enable read replicas for queries
  • Partition boards by organization
  • Archive old boards regularly

Best Practices Summary ​

Do's βœ… ​

  • Throttle cursor updates to 20/sec
  • Batch object updates when possible
  • Use Frames to organize large boards
  • Enable lazy loading for 100+ objects
  • Monitor performance metrics
  • Cache board metadata client-side
  • Use compression for large payloads
  • Archive inactive content
  • Implement pagination for lists
  • Test with production-like data

Don'ts ❌ ​

  • Don't send events on every mousemove
  • Don't load all objects upfront
  • Don't create 10,000+ objects on one board
  • Don't skip connection cleanup
  • Don't ignore performance metrics
  • Don't fetch full board state for lists
  • Don't disable WebSocket compression
  • Don't use long-polling when WebSocket available
  • Don't hardcode viewport dimensions
  • Don't skip load testing

Next Steps ​

Performance Audit Service ​

Need help optimizing your BoardAPI integration? Our team can audit your implementation and provide tailored recommendations.

Contact: performance@boardapi.io