Skip to content

Performance Characteristics

This document explains the performance characteristics of Tabula Lens, including benchmarks, typical performance metrics, and factors that affect performance.

Tabula Lens is designed for high performance with efficient query execution, minimal overhead, and scalable architecture. The performance characteristics vary based on database size, query complexity, network latency, and configuration.

Typical Query Performance:

Database Size Query Type Latency Throughput
Small (< 1K rows) Simple SELECT 1-5ms 1000+ queries/sec
Medium (1K-100K rows) Simple SELECT 5-20ms 500-1000 queries/sec
Large (100K-1M rows) Simple SELECT 20-100ms 100-500 queries/sec
Large (1M+ rows) Simple SELECT 100-500ms 20-100 queries/sec

Query Performance by Type:

Query Type Typical Latency Notes
Simple SELECT 1-50ms Single table, no joins
SELECT with filter 5-100ms Depends on filter selectivity
SELECT with sort 10-200ms Depends on sort column indexing
SELECT with pagination 5-150ms Depends on offset size
Complex queries 50-500ms Joins, aggregations, subqueries

Backend API Performance:

Operation Latency Notes
Authentication check 1-10ms JWT verification
Authorization check 1-5ms Permission validation
Query execution 5-500ms Database query time
Response serialization 1-10ms JSON conversion
Total API response 10-1000ms Includes all operations

Typical API Response Times:

  • Fast: < 50ms (small datasets, indexed queries)
  • Normal: 50-200ms (medium datasets, typical queries)
  • Slow: 200-1000ms (large datasets, complex queries)
  • Very Slow: > 1000ms (requires optimization)

Component Rendering Performance:

Operation Latency Notes
Initial render 10-50ms Component mount
Data fetch 50-1000ms API call + processing
Data rendering 10-100ms Table rendering
State update 1-10ms React state change
Re-render 1-20ms With React.memo

Page Load Performance:

Metric Target Notes
First Contentful Paint (FCP) < 1.8s Initial content visible
Largest Contentful Paint (LCP) < 2.5s Main content loaded
First Input Delay (FID) < 100ms Interactivity
Cumulative Layout Shift (CLS) < 0.1 Visual stability
Time to Interactive (TTI) < 3.8s Fully interactive

1. Database Size

-- Small database: Fast queries
SELECT * FROM small_table; -- 1-5ms
-- Large database: Slower queries
SELECT * FROM large_table; -- 100-500ms

Impact:

  • Query time increases with database size
  • Indexing becomes more critical
  • Pagination helps manage large datasets

2. Indexing

-- Without index: Slow
SELECT * FROM users WHERE email = '[email protected]'; -- 500ms
-- With index: Fast
CREATE INDEX idx_users_email ON users(email);
SELECT * FROM users WHERE email = '[email protected]'; -- 5ms

Impact:

  • Proper indexing can improve query performance 10-100x
  • Indexes add overhead to writes
  • Composite indexes for multi-column queries

3. Query Complexity

-- Simple query: Fast
SELECT * FROM users LIMIT 10; -- 5ms
-- Complex query: Slow
SELECT * FROM users
JOIN orders ON users.id = orders.user_id
WHERE users.status = 'active'
GROUP BY users.id
HAVING COUNT(orders.id) > 5
ORDER BY users.created_at DESC
LIMIT 10; -- 500ms

Impact:

  • Joins add significant overhead
  • Aggregations (GROUP BY, HAVING) are expensive
  • Subqueries impact performance

1. Network Latency

Local network: < 1ms
Same region: 10-50ms
Cross-region: 100-300ms
Cross-continent: 200-500ms

Impact:

  • Network latency adds to total response time
  • CDN can reduce latency for static assets
  • Database proximity matters

2. Bandwidth

Small result set (< 1KB): < 10ms transfer
Medium result set (1-10KB): 10-50ms transfer
Large result set (10-100KB): 50-500ms transfer
Very large result set (> 100KB): 500ms+ transfer

Impact:

  • Large result sets increase transfer time
  • Compression can reduce transfer size
  • Pagination reduces transfer size

1. Connection Pooling

// Without pooling: Slow
const tabulaLens = new TabulaLens(DATABASE_URL);
// Each query creates new connection: 100-500ms overhead
// With pooling: Fast
const pool = new Pool({ max: 20 });
const tabulaLens = new TabulaLens(pool);
// Reuses connections: 1-10ms overhead

Impact:

  • Connection pooling reduces overhead 10-50x
  • Pool size affects concurrent performance
  • Proper pool sizing is critical

2. Caching

// Without caching: Each query hits database
Query 1: 100ms (database)
Query 2: 100ms (database)
Query 3: 100ms (database)
Total: 300ms
// With caching: Subsequent queries use cache
Query 1: 100ms (database)
Query 2: 1ms (cache)
Query 3: 1ms (cache)
Total: 102ms

Impact:

  • Caching can improve performance 10-100x
  • Cache hit ratio affects overall performance
  • Cache invalidation strategy matters

3. Response Compression

// Without compression: Large responses
Response size: 100KB
Transfer time: 500ms
// With compression: Small responses
Response size: 10KB (compressed)
Transfer time: 50ms

Impact:

  • Compression reduces transfer size 5-10x
  • Adds CPU overhead for compression/decompression
  • Most effective for text-based responses

Test Environment:

  • Database: PostgreSQL 15
  • Database size: 100,000 rows
  • Hardware: 4 CPU cores, 8GB RAM
  • Network: Local (latency < 1ms)

Query Performance:

Query Type Avg Latency Min Max P95 P99
Simple SELECT 8ms 2ms 25ms 15ms 20ms
SELECT with filter 15ms 5ms 50ms 30ms 40ms
SELECT with sort 25ms 10ms 75ms 50ms 60ms
SELECT with pagination 20ms 8ms 60ms 40ms 50ms
Complex query 150ms 50ms 500ms 300ms 400ms

API Performance:

Operation Avg Latency Min Max P95 P99
GET /api/tabula-lens (small) 25ms 15ms 50ms 40ms 45ms
GET /api/tabula-lens (medium) 75ms 50ms 150ms 120ms 140ms
GET /api/tabula-lens (large) 200ms 100ms 500ms 350ms 450ms

Frontend Performance:

Operation Avg Latency Min Max P95 P99
Initial render 30ms 20ms 50ms 45ms 48ms
Data fetch 100ms 50ms 300ms 200ms 250ms
Data rendering 50ms 30ms 100ms 80ms 90ms
Total page load 180ms 100ms 450ms 325ms 388ms

1. Indexing

-- Before: 500ms average query time
-- After adding index: 5ms average query time
-- Improvement: 100x faster

2. Connection Pooling

// Before: 200ms per query (new connection each time)
// After connection pooling: 20ms per query
// Improvement: 10x faster

3. Caching

// Before: 100ms per query (database hit)
// After caching: 1ms per query (cache hit)
// Improvement: 100x faster (for cached queries)

4. Response Compression

// Before: 500ms transfer time (100KB)
// After compression: 50ms transfer time (10KB)
// Improvement: 10x faster transfer

5. Pagination

// Before: 1000ms (returning 10,000 rows)
// After pagination: 50ms (returning 50 rows)
// Improvement: 20x faster

Database KPIs:

  • Query latency (P50, P95, P99)
  • Query throughput (queries per second)
  • Connection pool utilization
  • Database CPU usage
  • Database memory usage

API KPIs:

  • Response time (P50, P95, P99)
  • Request throughput (requests per second)
  • Error rate
  • Authentication latency
  • Authorization latency

Frontend KPIs:

  • Page load time
  • Time to interactive
  • First contentful paint
  • Largest contentful paint
  • Cumulative layout shift

Database Monitoring:

-- Enable query logging
ALTER DATABASE your_database SET log_min_duration_statement = 100;
-- Monitor slow queries
SELECT * FROM pg_stat_statements
ORDER BY mean_exec_time DESC
LIMIT 10;
-- Monitor connection count
SELECT count(*) FROM pg_stat_activity;

API Monitoring:

// Use Prometheus for metrics
const promClient = require('prom-client');
const httpRequestDuration = new promClient.Histogram({
name: 'http_request_duration_seconds',
help: 'Duration of HTTP requests in seconds',
labelNames: ['method', 'route', 'status_code']
});
// Record metrics
app.use((req, res, next) => {
const start = Date.now();
res.on('finish', () => {
const duration = (Date.now() - start) / 1000;
httpRequestDuration.observe(
{ method: req.method, route: req.path, status_code: res.statusCode },
duration
);
});
next();
});

Frontend Monitoring:

// Use Web Vitals
import { getCLS, getFID, getLCP } from 'web-vitals';
getCLS(console.log);
getFID(console.log);
getLCP(console.log);
// Send to analytics
export function reportWebVitals(metric) {
fetch('/api/analytics', {
method: 'POST',
body: JSON.stringify(metric)
});
}

1. Index Optimization

-- Analyze query patterns
SELECT * FROM pg_stat_statations
WHERE query LIKE '%users%';
-- Add appropriate indexes
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_users_status_created ON users(status, created_at);
-- Remove unused indexes
DROP INDEX IF EXISTS idx_unused;

2. Query Optimization

-- Use EXPLAIN ANALYZE to analyze queries
EXPLAIN ANALYZE SELECT * FROM users WHERE email = '[email protected]';
-- Optimize based on analysis
-- - Add missing indexes
-- - Rewrite inefficient queries
-- - Use appropriate data types

3. Configuration Tuning

-- Increase shared_buffers
ALTER SYSTEM SET shared_buffers = '256MB';
-- Increase work_mem for complex queries
ALTER SYSTEM SET work_mem = '16MB';
-- Increase effective_cache_size
ALTER SYSTEM SET effective_cache_size = '1GB';
-- Reload configuration
SELECT pg_reload_conf();

1. Connection Pool Tuning

// Optimize pool size based on workload
const pool = new Pool({
max: 20, // Increase for high concurrency
min: 5, // Keep some connections ready
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000,
});

2. Caching Strategy

// Implement multi-level caching
const cache = new NodeCache({
stdTTL: 300, // 5 minutes
checkperiod: 60,
maxKeys: 1000 // Limit cache size
});

3. Response Optimization

// Enable compression
app.use(compression({
threshold: 1024, // Only compress > 1KB
level: 6 // Compression level (1-9)
}));
// Implement streaming for large responses
app.get('/api/tabula-lens/stream', async (req, res) => {
res.setHeader('Content-Type', 'application/json');
res.setHeader('Transfer-Encoding', 'chunked');
// Stream results
for await (const row of stream) {
res.write(JSON.stringify(row) + '\n');
}
res.end();
});

1. Component Optimization

// Use React.memo to prevent unnecessary re-renders
const OptimizedComponent = React.memo(({ data }) => {
return <DatabaseViewer data={data} />;
});

2. Data Fetching Optimization

// Use built-in debounced filtering from useTableState
const { filter, setFilter, debouncedFilter } = useTableState({
filterDebounceMs: 300
});
// Use pagination
const [page, setPage] = useState(1);
const pageSize = 50;
// Prefetch data
useEffect(() => {
queryClient.prefetchQuery(['data', page + 1]);
}, [page]);

3. Bundle Optimization

// Code splitting
const DatabaseViewer = lazy(() => import('@tabula-lens/react'));
// Tree shaking
import { DatabaseViewer } from '@tabula-lens/react';
  • ✅ Use appropriate indexes on frequently queried columns
  • ✅ Implement connection pooling
  • ✅ Use LIMIT to prevent large result sets
  • ✅ Monitor query performance regularly
  • ❌ Avoid SELECT * in production
  • ❌ Don’t create indexes on low-cardinality columns
  • ❌ Avoid N+1 query patterns
  • ✅ Implement response compression
  • ✅ Use caching for expensive operations
  • ✅ Implement rate limiting
  • ✅ Monitor and log performance metrics
  • ❌ Avoid synchronous operations in request handlers
  • ❌ Don’t return more data than needed
  • ❌ Avoid blocking the event loop
  • ✅ Use React.memo for expensive components
  • ✅ Implement debouncing for user input
  • ✅ Use virtual scrolling for large lists
  • ✅ Implement code splitting
  • ❌ Avoid unnecessary re-renders
  • ❌ Don’t fetch data on every keystroke
  • ❌ Avoid rendering large DOM trees
  • Monitor your application’s performance metrics
  • Implement performance optimizations based on your specific use case
  • Set up performance monitoring and alerting
  • Check Caching Strategies for advanced caching patterns