Skip to main content

Database Performance Guide

Optimization strategies, indexing patterns, and query best practices for Axiom Genesis databases.

Overview

Axiom Genesis databases are designed for multi-tenant, high-concurrency workloads. This guide covers performance optimization across both MSSQL and PostgreSQL.

Index Strategy

Multi-Tenant Index Pattern

Every table should have indexes that include tenant_id as the leading column:

-- Primary lookup pattern: tenant + primary key
CREATE INDEX idx_users_tenant_id
ON app_users(usr_tenant_id, usr_id)
WHERE usr_is_deleted = 0;

-- Secondary lookup pattern: tenant + frequently queried column
CREATE INDEX idx_users_tenant_email
ON app_users(usr_tenant_id, usr_email_id)
WHERE usr_is_deleted = 0;

Filtered Indexes

Use filtered indexes to exclude soft-deleted records:

-- MSSQL - Filtered index
CREATE INDEX idx_users_active
ON app_users(usr_tenant_id, usr_status)
WHERE usr_is_deleted = 0;

-- PostgreSQL - Partial index
CREATE INDEX idx_users_active
ON app_users(usr_tenant_id, usr_status)
WHERE usr_is_deleted = 0;

JSON Indexes

For frequently queried JSON paths:

-- MSSQL 2022+ - Computed column + index
ALTER TABLE app_page_configs
ADD pgc_page_type AS JSON_VALUE(pgc_config, '$.pageType');

CREATE INDEX idx_page_type
ON app_page_configs(pgc_tenant_id, pgc_page_type)
WHERE pgc_is_deleted = 0;

-- PostgreSQL - GIN index on JSONB
CREATE INDEX idx_page_config_gin
ON app_page_configs USING GIN (pgc_config);

-- PostgreSQL - Expression index for specific path
CREATE INDEX idx_page_type
ON app_page_configs((pgc_config->>'pageType'))
WHERE pgc_is_deleted = 0;

Query Optimization

Always Include Tenant Filter

-- ✅ Good - Tenant filter enables index usage
SELECT * FROM app_users
WHERE usr_tenant_id = @tenantId
AND usr_status = 1
AND usr_is_deleted = 0;

-- ❌ Bad - Full table scan
SELECT * FROM app_users
WHERE usr_status = 1;

Use Appropriate Data Types

-- ✅ Good - Use BIGINT for ID parameters
.input('userId', sql.BigInt, userId)

-- ❌ Bad - Implicit conversion causes index scan
.input('userId', sql.VarChar, userId.toString())

Limit Result Sets

-- ✅ Good - Paginated query
SELECT TOP 50 * FROM app_users
WHERE usr_tenant_id = @tenantId
AND usr_is_deleted = 0
ORDER BY usr_id
OFFSET @offset ROWS;

-- ❌ Bad - Unbounded query
SELECT * FROM app_users
WHERE usr_tenant_id = @tenantId;

Avoid SELECT *

-- ✅ Good - Explicit columns
SELECT usr_id, usr_email_id, usr_first_name, usr_last_name
FROM app_users
WHERE usr_tenant_id = @tenantId;

-- ❌ Bad - Returns unnecessary data
SELECT * FROM app_users;

Connection Pool Optimization

MSSQL Configuration

// config/db/db_connections.js
const poolConfig = {
min: 10, // Minimum connections
max: 100, // Maximum connections
idleTimeoutMillis: 30000, // Close idle connections after 30s
acquireTimeoutMillis: 60000, // Wait 60s for available connection
createTimeoutMillis: 30000, // Connection creation timeout
destroyTimeoutMillis: 5000, // Connection destruction timeout
reapIntervalMillis: 1000, // Check for idle connections every 1s
};

PostgreSQL Configuration

const poolConfig = {
min: 10,
max: 100,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 60000,
statement_timeout: 30000, // Query timeout
};

Caching Strategy

Redis Cache Patterns

// Entity registry caching (24-hour TTL)
const CACHE_TTL = 86400;

async function getEntityInfo(entityCode) {
const cacheKey = `obj_registry:${entityCode}`;

// Try cache first
let cached = await redis.get(cacheKey);
if (cached) return JSON.parse(cached);

// Fall back to database
const result = await db.query(`
SELECT * FROM app_object_registries
WHERE obj_code = @entityCode
`);

// Cache result
await redis.setex(cacheKey, CACHE_TTL, JSON.stringify(result));
return result;
}

Cache Invalidation

// Invalidate on update
async function updateEntityRegistry(entityCode, updates) {
await db.query('UPDATE app_object_registries SET ...');

// Clear cache
await redis.del(`obj_registry:${entityCode}`);
}

Query Analysis

MSSQL Query Plan

-- Enable execution plan
SET STATISTICS IO ON;
SET STATISTICS TIME ON;

-- Your query here
SELECT * FROM app_users WHERE usr_tenant_id = 1;

-- Disable
SET STATISTICS IO OFF;
SET STATISTICS TIME OFF;

PostgreSQL Query Plan

-- Analyze query
EXPLAIN ANALYZE
SELECT * FROM app_users WHERE usr_tenant_id = 1;

-- With buffer information
EXPLAIN (ANALYZE, BUFFERS)
SELECT * FROM app_users WHERE usr_tenant_id = 1;

Common Issues

IssueSymptomSolution
Missing indexTable scan in planAdd appropriate index
Parameter sniffingInconsistent performanceUse OPTIMIZE FOR or RECOMPILE
Implicit conversionIndex not usedMatch parameter types
Large result setHigh memory usageAdd pagination
Lock contentionQuery timeoutsOptimize transactions

Bulk Operations

Batch Inserts

// ✅ Good - Batched insert
const batch = records.map(r => `(@tenantId, '${r.code}', '${r.name}')`);
await db.query(`
INSERT INTO app_items (itm_tenant_id, itm_code, itm_name)
VALUES ${batch.join(',')}
`);

// ❌ Bad - Individual inserts
for (const record of records) {
await db.query(`INSERT INTO app_items VALUES ...`);
}

Table-Valued Parameters (MSSQL)

-- Create type
CREATE TYPE dbo.ItemTableType AS TABLE (
itm_code NVARCHAR(100),
itm_name NVARCHAR(200)
);

-- Use in procedure
CREATE PROCEDURE InsertItems
@TenantId BIGINT,
@Items dbo.ItemTableType READONLY
AS
BEGIN
INSERT INTO app_items (itm_tenant_id, itm_code, itm_name)
SELECT @TenantId, itm_code, itm_name FROM @Items;
END

Monitoring

Key Metrics

MetricThresholdAction
Query duration> 1 secondOptimize query
Connection pool usage> 80%Increase pool size
Cache hit ratio< 80%Review cache strategy
Lock wait time> 5 secondsInvestigate blocking
DeadlocksAnyReview transaction design

MSSQL DMVs

-- Top queries by execution count
SELECT TOP 10
qs.execution_count,
qs.total_elapsed_time / qs.execution_count AS avg_time,
SUBSTRING(qt.text, 1, 100) AS query_text
FROM sys.dm_exec_query_stats qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) qt
ORDER BY qs.execution_count DESC;

-- Missing indexes
SELECT * FROM sys.dm_db_missing_index_details;

PostgreSQL Statistics

-- Slow queries
SELECT query, calls, mean_time, total_time
FROM pg_stat_statements
ORDER BY mean_time DESC
LIMIT 10;

-- Index usage
SELECT schemaname, tablename, indexname, idx_scan
FROM pg_stat_user_indexes
ORDER BY idx_scan ASC;

Best Practices Checklist

Query Design

  • Include tenant_id in WHERE clause
  • Use parameterized queries
  • Limit result sets with TOP/LIMIT
  • Select only needed columns
  • Use appropriate data types

Index Design

  • Leading column is tenant_id
  • Filtered for is_deleted = 0
  • Cover frequently queried columns
  • Consider JSON computed columns

Connection Management

  • Use connection pooling
  • Close connections properly
  • Set appropriate timeouts
  • Monitor pool utilization

Caching

  • Cache entity registries
  • Cache configuration data
  • Implement cache invalidation
  • Monitor cache hit ratio

See Also