Performance Optimization Workflow
When to Use This Skill
-
Slow API response times
-
Database query optimization
-
Frontend rendering issues
-
Memory usage concerns
-
Scalability planning
Pre-Flight Checklist
-
Identify performance bottleneck
-
Gather baseline metrics
-
Determine acceptable thresholds
-
Plan measurement approach
Performance Analysis Framework
Step 1: Identify Bottleneck Type
Performance Issue ├── Database (slow queries, N+1) ├── API (serialization, processing) ├── Network (payload size, latency) └── Frontend (rendering, bundle size)
Step 2: Measure Baseline
API response time
curl -w "@curl-format.txt" -o /dev/null -s "http://api/endpoint"
Database query time (SQL Server)
SET STATISTICS TIME ON; SELECT * FROM Table WHERE ...;
Frontend bundle analysis
npm run build -- --stats-json npx webpack-bundle-analyzer stats.json
Database Optimization
N+1 Query Detection
// :x: N+1 Problem var employees = await repo.GetAllAsync(); foreach (var emp in employees) { // Each iteration queries database! Console.WriteLine(emp.Department.Name); }
// :white_check_mark: Eager Loading var employees = await repo.GetAllAsync( e => e.CompanyId == companyId, ct, e => e.Department, // Include Department e => e.Manager // Include Manager );
Query Optimization
// :x: Loading all columns var employees = await repo.GetAllAsync(); var names = employees.Select(e => e.Name);
// :white_check_mark: Projection var names = await repo.GetAllAsync( q => q.Where(e => e.IsActive) .Select(e => e.Name));
Index Recommendations
-- Frequently filtered columns CREATE INDEX IX_Employee_CompanyId ON Employees(CompanyId); CREATE INDEX IX_Employee_Status ON Employees(Status);
-- Composite index for common queries CREATE INDEX IX_Employee_Company_Status ON Employees(CompanyId, Status) INCLUDE (FullName, Email);
-- Full-text search index CREATE FULLTEXT INDEX ON Employees(FullName, Email);
Paging Patterns
// :x: Loading all then paging in memory var all = await repo.GetAllAsync(); var page = all.Skip(skip).Take(take);
// :white_check_mark: Database-level paging var page = await repo.GetAllAsync( (uow, q) => q .Where(e => e.IsActive) .OrderBy(e => e.Id) .Skip(skip) .Take(take));
API Optimization
Parallel Operations
// :x: Sequential var users = await userRepo.GetAllAsync(); var companies = await companyRepo.GetAllAsync(); var settings = await settingsRepo.GetAllAsync();
// :white_check_mark: Parallel (Tuple Await) var (users, companies, settings) = await ( userRepo.GetAllAsync(), companyRepo.GetAllAsync(), settingsRepo.GetAllAsync() );
Response Size
// :x: Returning entire entity return new Result { Employee = employee };
// :white_check_mark: Return only needed fields return new Result { Id = employee.Id, Name = employee.FullName, Status = employee.Status };
Caching
// Static data caching private static readonly ConcurrentDictionary<string, LookupData> _cache = new();
public async Task<LookupData> GetLookupAsync(string key) { if (_cache.TryGetValue(key, out var cached)) return cached;
var data = await LoadFromDbAsync(key);
_cache.TryAdd(key, data);
return data;
}
Frontend Optimization
Bundle Size
// :x: Import entire library import _ from 'lodash';
// :white_check_mark: Import specific functions import { debounce } from 'lodash-es/debounce';
Lazy Loading
// :white_check_mark: Lazy load routes const routes: Routes = [ { path: 'feature', loadChildren: () => import('./feature/feature.module') .then(m => m.FeatureModule) } ];
Change Detection
// :white_check_mark: OnPush for performance @Component({ changeDetection: ChangeDetectionStrategy.OnPush })
// :white_check_mark: Track-by for lists trackByItem = this.ngForTrackByItemProp<Item>('id');
// Template @for (item of items; track trackByItem)
Virtual Scrolling
// For large lists import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
<cdk-virtual-scroll-viewport itemSize="50"> @for (item of items; track item.id) { <div class="item">{{ item.name }}</div> } </cdk-virtual-scroll-viewport>
Background Job Optimization
Bounded Parallelism
// :x: Unbounded await items.ParallelAsync(ProcessAsync);
// :white_check_mark: Bounded await items.ParallelAsync(ProcessAsync, maxConcurrent: 5);
Batch Processing
// :x: One at a time foreach (var item in items) await repo.UpdateAsync(item);
// :white_check_mark: Batch update await repo.UpdateManyAsync(items, dismissSendEvent: true);
Performance Monitoring
Logging Slow Operations
var sw = Stopwatch.StartNew(); var result = await ExecuteOperation(); sw.Stop();
if (sw.ElapsedMilliseconds > 1000) Logger.LogWarning("Slow operation: {Ms}ms", sw.ElapsedMilliseconds);
Database Query Logging
// In DbContext configuration optionsBuilder.LogTo( Console.WriteLine, new[] { DbLoggerCategory.Database.Command.Name }, LogLevel.Information);
Performance Checklist
Database
-
Indexes on filtered columns
-
Eager loading for relations
-
Projection for partial data
-
Paging at database level
-
No N+1 queries
API
-
Parallel operations where possible
-
Response DTOs (not entities)
-
Caching for static data
-
Pagination for lists
Frontend
-
Lazy loading for routes
-
OnPush change detection
-
Track-by for lists
-
Virtual scrolling for large lists
-
Tree-shaking imports
Background Jobs
-
Bounded parallelism
-
Batch operations
-
Paged processing
-
Appropriate scheduling
Anti-Patterns to AVOID
:x: SELECT * in production
var all = await context.Table.ToListAsync();
:x: Synchronous I/O
var result = asyncOperation.Result; // Blocks thread
:x: Unbounded result sets
await repo.GetAllAsync(); // Could be millions
:x: Repeated database calls in loops
foreach (var id in ids) await repo.GetByIdAsync(id); // N queries
Verification Checklist
-
Baseline metrics recorded
-
Bottleneck identified and addressed
-
Changes measured against baseline
-
No new performance issues introduced
-
Monitoring in place