DevOps & verktyg
Prestanda & optimering
Mät, felsök och optimera prestanda för frontend, backend och databas.
01 · Kod
Optimeringsexempel
Konkreta mönster för att mäta och optimera på varje lager: webben, databasen, Node.js och frontend.
Web Performance Metrics
Mät och analysera viktiga prestandamått för webbapplikationer.
JavaScript
// Performance Observer API
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log({
name: entry.name,
type: entry.entryType,
duration: entry.duration,
startTime: entry.startTime
});
}
});
// Observera olika typer av prestanda
observer.observe({
entryTypes: ['navigation', 'resource', 'paint', 'layout-shift', 'largest-contentful-paint']
});
// Core Web Vitals
function getCoreWebVitals() {
// Largest Contentful Paint (LCP)
new PerformanceObserver((entryList) => {
const entries = entryList.getEntries();
const lastEntry = entries[entries.length - 1];
console.log('LCP:', lastEntry.renderTime || lastEntry.loadTime);
}).observe({ entryTypes: ['largest-contentful-paint'] });
// First Input Delay (FID)
new PerformanceObserver((entryList) => {
const firstInput = entryList.getEntries()[0];
const fid = firstInput.processingStart - firstInput.startTime;
console.log('FID:', fid);
}).observe({ type: 'first-input', buffered: true });
// Cumulative Layout Shift (CLS)
let clsValue = 0;
new PerformanceObserver((entryList) => {
for (const entry of entryList.getEntries()) {
if (!entry.hadRecentInput) {
clsValue += entry.value;
}
}
console.log('CLS:', clsValue);
}).observe({ type: 'layout-shift', buffered: true });
}
// Custom performance marks
performance.mark('myFunction-start');
// ... function code ...
performance.mark('myFunction-end');
performance.measure('myFunction', 'myFunction-start', 'myFunction-end');
const measure = performance.getEntriesByName('myFunction')[0];
console.log(`Function took ${measure.duration}ms`);Database Query Optimization
Optimera databasprestanda med indexering och query-analys.
SQL
-- PostgreSQL Query Optimization
-- Analysera query performance
EXPLAIN ANALYZE
SELECT u.*, p.title, p.created_at
FROM users u
LEFT JOIN posts p ON p.user_id = u.id
WHERE u.created_at > '2024-01-01'
ORDER BY u.created_at DESC
LIMIT 100;
-- Skapa index för vanliga queries
CREATE INDEX idx_users_created_at ON users(created_at DESC);
CREATE INDEX idx_posts_user_id ON posts(user_id);
CREATE INDEX idx_posts_created_at ON posts(created_at DESC);
-- Composite index för WHERE och ORDER BY
CREATE INDEX idx_users_status_created ON users(status, created_at DESC);
-- Partial index för filtrerade queries
CREATE INDEX idx_active_users ON users(created_at DESC)
WHERE status = 'active';
-- Query för att hitta saknade index
SELECT
schemaname,
tablename,
attname,
n_distinct,
most_common_vals
FROM pg_stats
WHERE schemaname = 'public'
AND n_distinct > 100
AND NOT EXISTS (
SELECT 1
FROM pg_indexes
WHERE tablename = pg_stats.tablename
AND indexdef LIKE '%' || attname || '%'
);
-- Hitta långsamma queries
SELECT
query,
calls,
mean_exec_time,
total_exec_time,
min_exec_time,
max_exec_time
FROM pg_stat_statements
WHERE mean_exec_time > 100
ORDER BY mean_exec_time DESC
LIMIT 20;
-- Connection pooling med PgBouncer config
[databases]
mydb = host=localhost port=5432 dbname=mydb
[pgbouncer]
pool_mode = transaction
max_client_conn = 1000
default_pool_size = 25
reserve_pool_size = 5
reserve_pool_timeout = 3
server_idle_timeout = 600Node.js Performance Optimization
Optimera Node.js applikationer för maximal prestanda.
JavaScript
// Cluster mode för multi-core användning
const cluster = require('cluster');
const os = require('os');
if (cluster.isMaster) {
const numCPUs = os.cpus().length;
console.log(`Master ${process.pid} is running`);
// Fork workers
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on('exit', (worker, code, signal) => {
console.log(`Worker ${worker.process.pid} died`);
cluster.fork(); // Restart worker
});
} else {
// Worker process
require('./server.js');
}
// Memory optimization
const v8 = require('v8');
// Increase heap size
require('v8').setFlagsFromString('--max-old-space-size=4096');
// Monitor memory usage
setInterval(() => {
const usage = process.memoryUsage();
console.log({
rss: `${Math.round(usage.rss / 1024 / 1024)}MB`,
heapTotal: `${Math.round(usage.heapTotal / 1024 / 1024)}MB`,
heapUsed: `${Math.round(usage.heapUsed / 1024 / 1024)}MB`,
external: `${Math.round(usage.external / 1024 / 1024)}MB`
});
}, 30000);
// Async performance patterns
const pLimit = require('p-limit');
const limit = pLimit(10); // Max 10 concurrent operations
async function processItems(items) {
const promises = items.map(item =>
limit(() => processItem(item))
);
return Promise.all(promises);
}
// Cache frequently accessed data
const NodeCache = require('node-cache');
const cache = new NodeCache({
stdTTL: 600, // 10 minutes
checkperiod: 120 // Check every 2 minutes
});
async function getCachedData(key, fetchFunction) {
const cached = cache.get(key);
if (cached) return cached;
const data = await fetchFunction();
cache.set(key, data);
return data;
}
// Stream processing for large data
const { pipeline } = require('stream/promises');
const { Transform } = require('stream');
const processStream = new Transform({
objectMode: true,
async transform(chunk, encoding, callback) {
try {
const processed = await processChunk(chunk);
callback(null, processed);
} catch (error) {
callback(error);
}
}
});
await pipeline(
readStream,
processStream,
writeStream
);Frontend Performance Optimization
Optimera frontend-prestanda med moderna tekniker.
JavaScript
// Webpack optimization config
module.exports = {
optimization: {
usedExports: true,
minimize: true,
sideEffects: false,
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
priority: 10,
reuseExistingChunk: true
},
common: {
minChunks: 2,
priority: 5,
reuseExistingChunk: true
}
}
}
}
};
// Lazy loading with React
import { lazy, Suspense } from 'react';
const HeavyComponent = lazy(() =>
import(/* webpackChunkName: "heavy" */ './HeavyComponent')
);
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<HeavyComponent />
</Suspense>
);
}
// Image optimization
const ImageOptimizer = ({ src, alt, ...props }) => {
const [isIntersecting, setIsIntersecting] = useState(false);
const imgRef = useRef();
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setIsIntersecting(true);
observer.disconnect();
}
},
{ threshold: 0.1 }
);
if (imgRef.current) {
observer.observe(imgRef.current);
}
return () => observer.disconnect();
}, []);
return (
<div ref={imgRef}>
{isIntersecting ? (
<img
src={src}
alt={alt}
loading="lazy"
decoding="async"
{...props}
/>
) : (
<div style={{ backgroundColor: '#f0f0f0', ...props.style }} />
)}
</div>
);
};
// Service Worker for caching
// sw.js
const CACHE_NAME = 'app-v1';
const urlsToCache = [
'/',
'/styles/main.css',
'/scripts/main.js'
];
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => cache.addAll(urlsToCache))
);
});
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(response => {
// Cache hit - return response
if (response) {
return response;
}
return fetch(event.request).then(response => {
// Check if valid response
if (!response || response.status !== 200 || response.type !== 'basic') {
return response;
}
// Clone and cache the response
const responseToCache = response.clone();
caches.open(CACHE_NAME)
.then(cache => {
cache.put(event.request, responseToCache);
});
return response;
});
})
);
});02 · Mätning
Verktyg för mätning & analys
Automatisera prestandatester och bevaka applikationen i drift.
Lighthouse CI
Automatisera prestandatester i CI/CD pipeline.
JavaScript
# .lighthouserc.js
module.exports = {
ci: {
collect: {
url: ['http://localhost:3000/', 'http://localhost:3000/about'],
numberOfRuns: 3,
settings: {
preset: 'desktop'
}
},
assert: {
preset: 'lighthouse:recommended',
assertions: {
'categories:performance': ['warn', { minScore: 0.9 }],
'categories:accessibility': ['error', { minScore: 0.95 }],
'categories:seo': ['warn', { minScore: 0.9 }],
'first-contentful-paint': ['warn', { maxNumericValue: 2000 }],
'largest-contentful-paint': ['warn', { maxNumericValue: 2500 }],
'interactive': ['warn', { maxNumericValue: 3500 }],
'cumulative-layout-shift': ['warn', { maxNumericValue: 0.1 }]
}
},
upload: {
target: 'temporary-public-storage'
}
}
};
# GitHub Action
name: Lighthouse CI
on: [push, pull_request]
jobs:
lighthouse:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
- run: npm install
- run: npm run build
- run: npm install -g @lhci/cli@0.9.x
- run: lhci autorunAPM with New Relic
Application Performance Monitoring setup.
JavaScript
// newrelic.js
exports.config = {
app_name: ['My Application'],
license_key: process.env.NEW_RELIC_LICENSE_KEY,
logging: {
level: 'info'
},
distributed_tracing: {
enabled: true
},
transaction_tracer: {
enabled: true,
transaction_threshold: 'apdex_f',
record_sql: 'obfuscated',
explain_threshold: 500
},
error_collector: {
enabled: true,
ignore_status_codes: [404]
}
};
// Custom instrumentation
const newrelic = require('newrelic');
// Transaction tracking
app.get('/api/users/:id', (req, res) => {
newrelic.startSegment('fetchUser', true, async () => {
const user = await db.getUser(req.params.id);
// Custom attributes
newrelic.addCustomAttribute('userId', req.params.id);
newrelic.addCustomAttribute('userType', user.type);
res.json(user);
});
});
// Background job monitoring
function processJob(job) {
return newrelic.startBackgroundTransaction('processJob', () => {
return processJobLogic(job);
});
}Redis Caching Strategy
Implementera effektiv caching med Redis.
JavaScript
const redis = require('redis');
const { promisify } = require('util');
const client = redis.createClient({
host: process.env.REDIS_HOST,
port: process.env.REDIS_PORT,
password: process.env.REDIS_PASSWORD,
retry_strategy: (options) => {
if (options.error && options.error.code === 'ECONNREFUSED') {
return new Error('Redis connection refused');
}
if (options.total_retry_time > 1000 * 60 * 60) {
return new Error('Redis retry time exhausted');
}
if (options.attempt > 10) {
return undefined;
}
return Math.min(options.attempt * 100, 3000);
}
});
// Promisify Redis methods
const getAsync = promisify(client.get).bind(client);
const setAsync = promisify(client.set).bind(client);
const delAsync = promisify(client.del).bind(client);
const existsAsync = promisify(client.exists).bind(client);
// Cache middleware
const cache = (keyPattern, ttl = 300) => {
return async (req, res, next) => {
const key = keyPattern.replace(':id', req.params.id);
try {
const cached = await getAsync(key);
if (cached) {
return res.json(JSON.parse(cached));
}
} catch (error) {
console.error('Redis get error:', error);
}
// Store original send
const originalSend = res.json;
// Override send method
res.json = async function(data) {
try {
await setAsync(key, JSON.stringify(data), 'EX', ttl);
} catch (error) {
console.error('Redis set error:', error);
}
originalSend.call(this, data);
};
next();
};
};
// Usage
app.get('/api/users/:id', cache('user::id', 600), getUserHandler);
// Cache invalidation
async function invalidateUserCache(userId) {
const keys = [
`user:${userId}`,
`user:${userId}:posts`,
`user:${userId}:profile`
];
await Promise.all(keys.map(key => delAsync(key)));
}
// Batch operations
const pipeline = client.pipeline();
users.forEach(user => {
pipeline.set(`user:${user.id}`, JSON.stringify(user), 'EX', 3600);
});
await pipeline.exec();03 · Tekniker
Optimeringstekniker
- Bundle Size AnalysisAnalysera och minska bundle-storlekLösning: Använd webpack-bundle-analyzer för att visualisera dependencies
- Tree ShakingTa bort oanvänd kod automatisktLösning: Aktivera production mode och använd ES6 modules
- HTTP/2 PushFörladdning av kritiska resurserLösning: Konfigurera server push för CSS och kritisk JS
- Brotli CompressionBättre komprimering än gzipLösning: Aktivera Brotli på server-nivå för 15-20% mindre filer
04 · Mätvärden
Viktiga prestandamått
| Mått | Beskrivning | Mål |
|---|---|---|
| Response Time | Tid från request till response | < 200ms för API-anrop |
| Time to First Byte (TTFB) | Tid till första byte från servern | < 600ms |
| First Contentful Paint (FCP) | När användaren ser första innehållet | < 1.8s |
| Time to Interactive (TTI) | När sidan blir interaktiv | < 3.8s |
05 · Arbetssätt
Så jobbar du med prestanda
- Mät innan du optimerar
- Använd profiling tools för att identifiera flaskhalsar, gissa inte.
- Optimera kritiska vägar först
- Fokusera på de delar som påverkar användarupplevelsen mest.
- Cacha på alla nivåer
- Implementera caching från CDN till databas.
- Automatisera prestandatester
- Integrera Lighthouse och liknande verktyg i CI/CD för att fånga regressioner.