21 / 36
Π§ΡΠΎ ΡΠ°ΠΊΠΎΠ΅ Time to First Byte (TTFB) ΠΈ ΠΊΠ°ΠΊ Π΅Π³ΠΎ ΠΎΠΏΡΠΈΠΌΠΈΠ·ΠΈΡΠΎΠ²Π°ΡΡ?
ΠΠΎΠ»Π½ΡΠΉ ΠΎΡΠ²Π΅Ρ
TTFB ΡΠΊΠ»Π°Π΄ΡΠ²Π°Π΅ΡΡΡ ΠΈΠ· Π½Π΅ΡΠΊΠΎΠ»ΡΠΊΠΈΡ ΡΡΠ°ΠΏΠΎΠ²:
JavaScript
ΠΡΠ°ΡΠ·Π΅Ρ Π‘Π΅ΡΠ²Π΅Ρ
| |
| --- DNS Lookup ---------------------------> |
| --- TCP Handshake ------------------------> |
| --- TLS Handshake (Π΅ΡΠ»ΠΈ HTTPS) ----------> |
| --- ΠΡΠΏΡΠ°Π²ΠΊΠ° HTTP-Π·Π°ΠΏΡΠΎΡΠ° ----------------> |
| [ΠΠ±ΡΠ°Π±ΠΎΡΠΊΠ° Π½Π° ΡΠ΅ΡΠ²Π΅ΡΠ΅] |
| <-- ΠΠ΅ΡΠ²ΡΠΉ Π±Π°ΠΉΡ ΠΎΡΠ²Π΅ΡΠ° ------------------- |
| |
| <====== ΠΡΡ ΡΡΠΎ TTFB ===============> |
ΠΠ·ΠΌΠ΅ΡΠ΅Π½ΠΈΠ΅ TTFB ΡΠ΅ΡΠ΅Π· Performance API:
JavaScript
// Π§Π΅ΡΠ΅Π· Navigation Timing API
const [entry] = performance.getEntriesByType('navigation');
const ttfb = entry.responseStart - entry.requestStart;
console.log(`TTFB: ${ttfb.toFixed(2)} ms`);
// ΠΠΎΠ»Π΅Π΅ Π΄Π΅ΡΠ°Π»ΡΠ½Π°Ρ ΡΠ°Π·Π±ΠΈΠ²ΠΊΠ°
const dns = entry.domainLookupEnd - entry.domainLookupStart;
const tcp = entry.connectEnd - entry.connectStart;
const tls = entry.secureConnectionStart > 0
? entry.connectEnd - entry.secureConnectionStart
: 0;
const serverTime = entry.responseStart - entry.requestStart;
console.log(`DNS: ${dns.toFixed(2)} ms`);
console.log(`TCP: ${tcp.toFixed(2)} ms`);
console.log(`TLS: ${tls.toFixed(2)} ms`);
console.log(`Server processing: ${serverTime.toFixed(2)} ms`);
Π‘ΠΏΠΎΡΠΎΠ±Ρ ΠΎΠΏΡΠΈΠΌΠΈΠ·Π°ΡΠΈΠΈ:
JavaScript
// 1. Π‘Π΅ΡΠ²Π΅ΡΠ½ΡΠΉ ΠΊΠ΅Ρ -- ΡΠ°ΠΌΡΠΉ Π±ΠΎΠ»ΡΡΠΎΠΉ Π²ΡΠΈΠ³ΡΡΡ
// Express + node-cache
import NodeCache from 'node-cache';
const cache = new NodeCache({ stdTTL: 300 }); // 5 ΠΌΠΈΠ½ΡΡ
app.get('/api/products', (req, res) => {
const cached = cache.get('products');
if (cached) {
return res.json(cached); // ΠΌΠ³Π½ΠΎΠ²Π΅Π½Π½ΡΠΉ ΠΎΡΠ²Π΅Ρ ΠΈΠ· ΠΊΠ΅ΡΠ°
}
const products = db.query('SELECT * FROM products');
cache.set('products', products);
res.json(products);
});
JavaScript
// 2. ΠΠΏΡΠΈΠΌΠΈΠ·Π°ΡΠΈΡ ΠΠ-Π·Π°ΠΏΡΠΎΡΠΎΠ²
// β N+1 Π·Π°ΠΏΡΠΎΡΠΎΠ² -- ΠΊΠ°ΠΆΠ΄ΡΠΉ ΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΠ΅Π»Ρ ΠΏΠΎΡΠΎΠΆΠ΄Π°Π΅Ρ ΠΎΡΠ΄Π΅Π»ΡΠ½ΡΠΉ Π·Π°ΠΏΡΠΎΡ
app.get('/api/posts', async (req, res) => {
const posts = await db.query('SELECT * FROM posts LIMIT 20');
for (const post of posts) {
post.author = await db.query(
'SELECT * FROM users WHERE id = ?', [post.authorId]
);
}
res.json(posts);
});
// β
ΠΠ΄ΠΈΠ½ Π·Π°ΠΏΡΠΎΡ Ρ JOIN
app.get('/api/posts', async (req, res) => {
const posts = await db.query(`
SELECT p.*, u.name as authorName, u.avatar as authorAvatar
FROM posts p
JOIN users u ON p.authorId = u.id
LIMIT 20
`);
res.json(posts);
});
HTML
<!-- 3. Π Π΅ΡΡΡΡΠ½ΡΠ΅ ΠΏΠΎΠ΄ΡΠΊΠ°Π·ΠΊΠΈ Π² HTML -->
<!-- DNS prefetch Π΄Π»Ρ ΡΡΠΎΡΠΎΠ½Π½ΠΈΡ
Π΄ΠΎΠΌΠ΅Π½ΠΎΠ² -->
<link rel="dns-prefetch" href="https://api.example.com">
<!-- Preconnect -- DNS + TCP + TLS Π·Π°ΡΠ°Π½Π΅Π΅ -->
<link rel="preconnect" href="https://api.example.com" crossorigin>
<!-- 103 Early Hints -- ΡΠ΅ΡΠ²Π΅Ρ ΠΎΡΠΏΡΠ°Π²Π»ΡΠ΅Ρ ΠΏΠΎΠ΄ΡΠΊΠ°Π·ΠΊΠΈ Π΄ΠΎ ΠΎΡΠ½ΠΎΠ²Π½ΠΎΠ³ΠΎ ΠΎΡΠ²Π΅ΡΠ° -->
Nginx
# 4. ΠΠ°ΡΡΡΠΎΠΉΠΊΠ° CDN / nginx Π΄Π»Ρ ΠΊΠ΅ΡΠΈΡΠΎΠ²Π°Π½ΠΈΡ
# nginx.conf
proxy_cache_path /var/cache/nginx levels=1:2
keys_zone=app_cache:10m max_size=1g inactive=60m;
server {
location /api/ {
proxy_cache app_cache;
proxy_cache_valid 200 10m;
proxy_cache_use_stale error timeout updating;
proxy_pass http://backend;
}
}
π Π Π΅Π°Π»ΡΠ½ΡΠ΅ ΠΊΠ΅ΠΉΡΡ
ΠΠΈΠ°Π³Π½ΠΎΡΡΠΈΠΊΠ° ΠΌΠ΅Π΄Π»Π΅Π½Π½ΠΎΠ³ΠΎ TTFB:
JavaScript
// Π‘ΠΊΡΠΈΠΏΡ Π΄Π»Ρ ΠΌΠΎΠ½ΠΈΡΠΎΡΠΈΠ½Π³Π° TTFB Π² production
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.entryType === 'navigation') {
const ttfb = entry.responseStart - entry.requestStart;
// ΠΡΠΏΡΠ°Π²Π»ΡΠ΅ΠΌ ΠΌΠ΅ΡΡΠΈΠΊΡ Π² Π°Π½Π°Π»ΠΈΡΠΈΠΊΡ
if (ttfb > 800) {
sendToAnalytics('slow_ttfb', {
ttfb,
url: entry.name,
transferSize: entry.transferSize,
serverTiming: entry.serverTiming,
});
}
}
}
});
observer.observe({ type: 'navigation', buffered: true });
Server-Timing Π·Π°Π³ΠΎΠ»ΠΎΠ²ΠΎΠΊ Π΄Π»Ρ ΠΎΡΠ»Π°Π΄ΠΊΠΈ ΡΠ΅ΡΠ²Π΅ΡΠ½ΠΎΠΉ ΡΠ°ΡΡΠΈ:
JavaScript
// Π‘Π΅ΡΠ²Π΅Ρ ΠΏΠΎΠΊΠ°Π·ΡΠ²Π°Π΅Ρ, Π½Π° ΡΡΠΎ ΡΡΠ»ΠΎ Π²ΡΠ΅ΠΌΡ
app.get('/api/data', async (req, res) => {
const start = performance.now();
const dbStart = performance.now();
const data = await db.query('SELECT ...');
const dbTime = performance.now() - dbStart;
const cacheStart = performance.now();
await redis.set('key', JSON.stringify(data));
const cacheTime = performance.now() - cacheStart;
res.set('Server-Timing', [
`db;dur=${dbTime.toFixed(1)};desc="Database"`,
`cache;dur=${cacheTime.toFixed(1)};desc="Cache write"`,
`total;dur=${(performance.now() - start).toFixed(1)}`,
].join(', '));
res.json(data);
});
// ΠΠΈΠ΄Π½ΠΎ Π² DevTools Π½Π° Π²ΠΊΠ»Π°Π΄ΠΊΠ΅ Network (Timing)
Π Π΅Π·ΡΠΌΠ΅
TTFB -- ΠΏΠ΅ΡΠ²Π°Ρ ΠΌΠ΅ΡΡΠΈΠΊΠ° Π² ΡΠ΅ΠΏΠΎΡΠΊΠ΅ Π·Π°Π³ΡΡΠ·ΠΊΠΈ ΡΡΡΠ°Π½ΠΈΡΡ. ΠΡΠ»ΠΈ TTFB ΠΌΠ΅Π΄Π»Π΅Π½Π½ΡΠΉ, Π²ΡΠ΅ ΠΎΡΡΠ°Π»ΡΠ½ΡΠ΅ ΠΌΠ΅ΡΡΠΈΠΊΠΈ (FCP, LCP) Π±ΡΠ΄ΡΡ ΠΏΠ»ΠΎΡ ΠΈΠΌΠΈ Π°Π²ΡΠΎΠΌΠ°ΡΠΈΡΠ΅ΡΠΊΠΈ. ΠΡΠ½ΠΎΠ²Π½ΡΠ΅ ΡΠΎΡΠΊΠΈ ΠΎΠΏΡΠΈΠΌΠΈΠ·Π°ΡΠΈΠΈ: ΡΠ΅ΡΠ²Π΅ΡΠ½ΡΠΉ ΠΊΠ΅Ρ, CDN, ΠΎΠΏΡΠΈΠΌΠΈΠ·Π°ΡΠΈΡ Π·Π°ΠΏΡΠΎΡΠΎΠ² ΠΊ ΠΠ, preconnect/dns-prefetch, HTTP/2 ΠΈ 103 Early Hints.