0Pricing
NestJS Enterprise Backend APIs · Ders

SLO'ları ve Hata Bütçelerini Tanımlama

Gecikme ve hata ölçümlerini ölçülebilir hizmet düzeyi hedeflerine ve uyarılara dönüştürün.

SLO'ları ve Hata Bütçelerini Tanımlama, CoddyKit'te ücretsiz bir NestJS Enterprise Backend APIs dersidir. Bu, 4 dersinin 4. dersidir. Aşağıdan dersin tamamını ücretsiz okuyabilir, sonra tarayıcıda yerleşik kod editörü ve 7/24 yapay zeka koçu ile uygulamalı olarak pratik yapabilirsin. Bu, NestJS Enterprise Backend APIs öğrenme yolunun bir parçasıdır ve ilerlemeniz web ve CoddyKit uygulaması arasında senkronize olur. NestJS Enterprise Backend APIs kursu toplamda 4 dersten oluşur.

Bu dersin bazı bölümleri henüz çevrilmemiş olup İngilizce olarak gösterilmektedir.

From Metrics to Promises

Your NestJS API already emits latency and error metrics. But raw numbers like p99 = 412ms mean nothing without a target. An SLO (Service-Level Objective) turns a metric into a promise: "99.9% of requests succeed within 300ms over a rolling 28 days."

  • SLI — the Service-Level Indicator: the actual measured quantity (e.g. fraction of good requests).
  • SLO — the target you commit to for that SLI (e.g. 99.9%).
  • SLA — the contractual consequence if you miss the SLO (refunds, credits).

In this lesson you translate your existing latency and error metrics into SLOs, derive an error budget, and wire up budget-burn alerting.

Defining a Good Event

Every SLI is a ratio of good events / valid events. The hard part is defining "good" precisely. For an HTTP API, a request is usually valid if it reaches your handler (exclude 404s for unknown routes and client-cancelled requests), and good if it is both fast enough and not a server error.

  • Availability SLI: good = status code not in 5xx.
  • Latency SLI: good = served under a threshold (e.g. 300ms).

Note that 4xx responses are normally not failures of your service — a 400 Bad Request means the client sent bad input. Counting them against your budget would punish you for client mistakes.

type RequestOutcome = {
  statusCode: number;
  latencyMs: number;
};

const LATENCY_THRESHOLD_MS = 300;

function isValid(o: RequestOutcome): boolean {
  // Exclude client errors from the denominator; they are not our fault.
  return o.statusCode < 400 || o.statusCode >= 500;
}

function isGood(o: RequestOutcome): boolean {
  const serverError = o.statusCode >= 500;
  const tooSlow = o.latencyMs > LATENCY_THRESHOLD_MS;
  return !serverError && !tooSlow;
}

const sample: RequestOutcome = { statusCode: 200, latencyMs: 142 };
console.log('valid:', isValid(sample), 'good:', isGood(sample));

Computing an SLI

Once you can classify each request, the SLI is just a fraction over a time window: good / valid. Multiply by 100 for a percentage. This is the same math whether you compute it from in-memory counters, Prometheus, or a data warehouse.

Here we compute an availability SLI over a batch of request outcomes. Keeping it pure and standalone makes it trivial to unit-test before wiring it into your monitoring pipeline.

type Outcome = { statusCode: number };

function availabilitySli(outcomes: Outcome[]): number {
  const valid = outcomes.filter(o => o.statusCode < 400 || o.statusCode >= 500);
  const good = valid.filter(o => o.statusCode < 500);
  if (valid.length === 0) return 1; // no traffic = perfect by convention
  return good.length / valid.length;
}

const window: Outcome[] = [
  { statusCode: 200 }, { statusCode: 200 }, { statusCode: 500 },
  { statusCode: 404 }, { statusCode: 200 }, { statusCode: 503 },
];

const sli = availabilitySli(window);
console.log('SLI:', (sli * 100).toFixed(2) + '%');

Choosing a Target and Window

An SLO is an SLI plus a target and a rolling window. Two decisions matter:

  • The number of nines. 99.9% ("three nines") allows ~43 minutes of badness per 30 days. 99.99% allows only ~4.3 minutes. Each extra nine is roughly 10x more expensive to engineer.
  • The window length. A 28- or 30-day rolling window is standard. Shorter windows react faster but are noisier; longer windows are smoother but forgive incidents slowly.

Pick targets from what users actually need, not from what you currently achieve. Don't promise 99.99% if 99.9% keeps customers happy — the extra nine costs real engineering effort.

The Error Budget

The error budget is the inverse of your SLO: the amount of failure you are allowed. If your SLO is 99.9%, your error budget is 1 - 0.999 = 0.1% of requests over the window.

This reframing is powerful: instead of "don't break anything," you get a concrete quantity to spend. As long as budget remains, the team can ship risky changes. When it's exhausted, you freeze features and focus on reliability.

function errorBudget(slo: number, totalRequests: number) {
  const allowedBadFraction = 1 - slo;
  const allowedBadRequests = Math.floor(totalRequests * allowedBadFraction);
  return { allowedBadFraction, allowedBadRequests };
}

const monthlyTraffic = 5_000_000;
const budget = errorBudget(0.999, monthlyTraffic);

console.log('Allowed bad fraction:', (budget.allowedBadFraction * 100).toFixed(2) + '%');
console.log('Allowed bad requests:', budget.allowedBadRequests.toLocaleString());

Budget as Allowable Downtime

It is often more intuitive to express an availability SLO as allowable downtime. Multiply the error-budget fraction by the window duration. For a 30-day window:

  • 99% → ~7.2 hours/month
  • 99.9% → ~43.2 minutes/month
  • 99.95% → ~21.6 minutes/month
  • 99.99% → ~4.3 minutes/month

Showing stakeholders "43 minutes" lands far better than "0.1%". The code below turns any SLO into a human-readable downtime allowance.

function allowableDowntime(slo: number, windowDays = 30): string {
  const windowMinutes = windowDays * 24 * 60;
  const downtimeMinutes = windowMinutes * (1 - slo);
  if (downtimeMinutes >= 60) {
    return (downtimeMinutes / 60).toFixed(1) + ' hours';
  }
  return downtimeMinutes.toFixed(1) + ' minutes';
}

for (const slo of [0.99, 0.999, 0.9995, 0.9999]) {
  console.log((slo * 100) + '% ->', allowableDowntime(slo));
}

Tracking Remaining Budget

During an incident the key question is: how much budget is left? Track consumed budget as a fraction of the total allowance. A value of 0 means full budget; 1.0 means fully exhausted; above 1.0 means you have already breached the SLO for the window.

This single number drives dashboards, deploy freezes, and alert routing. Expose it as a gauge so on-call engineers can see at a glance whether they can keep shipping.

function budgetConsumed(
  badRequests: number,
  totalRequests: number,
  slo: number,
): number {
  const allowedBad = totalRequests * (1 - slo);
  if (allowedBad === 0) return badRequests > 0 ? Infinity : 0;
  return badRequests / allowedBad;
}

const total = 2_000_000;
const bad = 1500;
const consumed = budgetConsumed(bad, total, 0.999);

console.log('Budget consumed:', (consumed * 100).toFixed(1) + '%');
console.log('Remaining:', ((1 - consumed) * 100).toFixed(1) + '%');
console.log('Can ship features:', consumed < 1 ? 'yes' : 'FREEZE');

Instrumenting NestJS Requests

To compute SLIs in production you need per-request data. A NestJS interceptor is the cleanest hook: it sees every request, can time it with rxjs/tap, and records the outcome to your metrics backend.

This interceptor is framework code (it depends on NestJS and a metrics service), so it is not a standalone runnable program — but it shows exactly where SLI classification belongs in a real API.

import {
  Injectable, NestInterceptor, ExecutionContext, CallHandler,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
import { SloMetrics } from './slo-metrics.service';

@Injectable()
export class SloInterceptor implements NestInterceptor {
  constructor(private readonly metrics: SloMetrics) {}

  intercept(ctx: ExecutionContext, next: CallHandler): Observable<unknown> {
    const start = process.hrtime.bigint();
    const res = ctx.switchToHttp().getResponse();

    return next.handle().pipe(
      tap({
        next: () => this.record(res.statusCode, start),
        error: () => this.record(500, start),
      }),
    );
  }

  private record(statusCode: number, start: bigint) {
    const latencyMs = Number(process.hrtime.bigint() - start) / 1e6;
    this.metrics.observe({ statusCode, latencyMs });
  }
}

Burn Rate: Spending Too Fast

A single threshold ("budget < 0%") alerts you only after the SLO is already blown. Google's SRE practice instead alerts on burn rate: how fast you are consuming budget relative to a sustainable pace.

A burn rate of 1 means you will exactly exhaust the budget by the end of the window. A burn rate of 14.4 over one hour means you'd burn the entire 30-day budget in about 2 hours — clearly page-worthy.

Formula: burnRate = (observed bad rate) / (allowed bad rate), where allowed bad rate = 1 - SLO.

function burnRate(
  badInWindow: number,
  totalInWindow: number,
  slo: number,
): number {
  const observedBadRate = totalInWindow === 0 ? 0 : badInWindow / totalInWindow;
  const allowedBadRate = 1 - slo;
  return observedBadRate / allowedBadRate;
}

// 1h window: 2% of 100k requests failed, SLO 99.9%
const rate = burnRate(2000, 100_000, 0.999);
console.log('Burn rate:', rate.toFixed(1) + 'x');
console.log('Verdict:', rate >= 14.4 ? 'PAGE NOW' : 'ok');

Multi-Window, Multi-Burn-Rate Alerts

Alerting on a single window is a trade-off: short windows catch fast burns but flap on blips; long windows are stable but slow. The SRE workbook recommends combining multiple windows with multiple burn-rate thresholds:

  • Fast page: burn rate ≥ 14.4 over 1h AND ≥ 14.4 over 5m. Burns 2% of a 30-day budget in 1h.
  • Slow ticket: burn rate ≥ 1 over 6h (and a confirming shorter window).

The short "confirming" window prevents alerting on a spike that has already recovered. Requiring both windows to fire keeps alerts both sensitive and quiet.

type Window = { badRate: number };

function shouldPage(
  long: Window, short: Window, threshold: number, slo: number,
): boolean {
  const allowed = 1 - slo;
  const longBurn = long.badRate / allowed;
  const shortBurn = short.badRate / allowed;
  return longBurn >= threshold && shortBurn >= threshold;
}

const slo = 0.999;
const fastPage = shouldPage(
  { badRate: 0.03 }, // last 1h
  { badRate: 0.025 }, // last 5m confirming
  14.4, slo,
);
console.log('Fire fast page:', fastPage);

Operationalizing the Error Budget Policy

SLOs only change behaviour if there is a written error budget policy everyone agrees to. A typical policy:

  • Budget healthy (>25% left): ship freely, take calculated risks.
  • Budget low (<25% left): require extra review, prioritise reliability work.
  • Budget exhausted (≤0): feature freeze — only reliability and bug fixes until the rolling window recovers.

Crucially, the policy must be agreed by both product and engineering before an incident. The error budget then becomes an objective arbiter, replacing arguments about "is it stable enough to ship" with a number.

type Policy = 'SHIP_FREELY' | 'EXTRA_REVIEW' | 'FREEZE';

function policyFor(remainingFraction: number): Policy {
  if (remainingFraction <= 0) return 'FREEZE';
  if (remainingFraction < 0.25) return 'EXTRA_REVIEW';
  return 'SHIP_FREELY';
}

for (const remaining of [0.8, 0.2, 0.0, -0.1]) {
  console.log((remaining * 100).toFixed(0) + '% left ->', policyFor(remaining));
}

Quick Check

Test your understanding of error budgets and burn-rate alerting.

Recap

You translated latency and error metrics into measurable SLOs:

  • SLI = good / valid events. Define "good" (no 5xx, under a latency threshold) and "valid" (exclude client 4xx) precisely before computing the ratio.
  • SLO = SLI + target + rolling window. Choose nines from user need, not current performance; 99.9% over 28-30 days is a sane default.
  • Error budget = 1 - SLO. Express it as allowable downtime (99.9% ≈ 43 min/month) to communicate with stakeholders.
  • Burn rate = observed bad rate / allowed bad rate. Use multi-window, multi-burn-rate alerts (e.g. 14.4x over 1h + 5m) to page early without flapping.
  • A written error budget policy turns the budget into an objective decision-maker for shipping vs. freezing.

In a NestJS API, an interceptor records each request outcome, feeding the SLI counters that power your dashboards and alerts.

Sıkça Sorulan Sorular

“SLO'ları ve Hata Bütçelerini Tanımlama” dersi ücretsiz mi?

Evet — “SLO'ları ve Hata Bütçelerini Tanımlama” dersin tüm metni burada web'de ücretsiz olarak okunabilir. Etkileşimli olarak pratik yapmak (yerleşik kod editörü ve 7/24 yapay zeka koçu) ve NestJS Enterprise Backend APIs kursunun geri kalanını açmak için CoddyKit PRO'ya yükselt. NestJS Enterprise Backend APIs kursu toplamda 4 dersten oluşur.

“SLO'ları ve Hata Bütçelerini Tanımlama” dersinde ne öğreneceğim?

Gecikme ve hata ölçümlerini ölçülebilir hizmet düzeyi hedeflerine ve uyarılara dönüştürün. NestJS Enterprise Backend APIs ile uygulamalı kodu tarayıcıda doğrudan çalıştırarak pratik yaparsın ve 7/24 yapay zeka koçu dersi çalışırken sorularını yanıtlar.

NestJS Enterprise Backend APIs öğrenmeye başlamak için deneyim gerekli mi?

Önceden deneyim gerekmez. CoddyKit'te NestJS Enterprise Backend APIs, başlangıçtan ileri seviyeye kadar yapılandırıldığı için buradan başlayabilir veya başından başlayıp kendi hızında ilerleme yapabilirsin. Bu, 4 dersinin 4. dersidir.

“SLO'ları ve Hata Bütçelerini Tanımlama” dersi ne kadar sürer?

Çoğu CoddyKit dersi yaklaşık 5–10 dakika sürer. Her biri kısa ve etkileşimli olduğu için sabit ilerleme yaparsın ve web ile uygulama arasında tam olarak bıraktığın yerden devam edebilirsin.

Bu NestJS Enterprise Backend APIs dersinde kod yazıp çalıştırabilir miyim?

Evet. Her NestJS Enterprise Backend APIs dersi yerleşik bir kod editörü içerir, bu sayede tarayıcıda gerçek kod yazıp çalıştırabilir ve anlık yapay zeka geri bildirimi alırsın — yerel kurulum gerekli değildir.

Bu kursun tüm dersleri

  1. Interceptor'larla Zaman Aşımları, Yeniden Denemeler ve Yalıtım
  2. Aşağı Akış Hataları için Devre Kesiciler
  3. OpenTelemetry ile Dağıtık İzleme
  4. SLO'ları ve Hata Bütçelerini Tanımlama
← NestJS Enterprise Backend APIs Sayfasına Dön