Udržitelné architektonické vzory: úložiště, mezipaměť a dávka
Architektonická rozhodnutí, která děláme každý den – jak strukturujeme úložiště, jak úložiště spravujeme cache, jak organizujeme dávkové úlohy – určete uhlíková stopa softwaru při měření 10-100krát vyšší ve srovnání s mikrooptimalizacemi kódu. Špatně navržená architektura který přistupuje k databázi při každém požadavku, nepoužívá cachování a zpracovává data v reálném čase, kdy může dělat to v dávce, může spotřebovat řádově více energie než dobře navržená architektura.
Podle analýzy od Green Software Foundation Impact Framework, architektonické volby související s vrstvením úložiště, mezipamětí a plánováním dávek představují 40-60 % potenciálu snížení emisí podnikového softwarového systému. Není to jen o optimalizaci Technická: Je to profesionální odpovědnost, kterou musí zvážit každý architekt a developer nedílnou součástí své profese.
V tomto devátém článku série Green Software prozkoumáme udržitelné architektonické vzory efektivnější: od inteligentního vrstvení úložiště po mezipaměť využívající uhlík, od plánování dávek až po zelená energetická okna v API udržitelném designu. Se skutečnými příklady kódu a kompletní případovou studií což ukazuje 45% snížení uhlíkové stopy webu elektronického obchodu s 1 milionem denních návštěv.
Co se naučíte
- Víceúrovňové úložiště (horký/teplý/studený/archiv) a zásady životního cyklu dat pro snížení spotřeby energie
- Vzor ukládání do mezipaměti s ohledem na uhlík: geograficky inteligentní CDN, víceúrovňové ukládání do mezipaměti, optimalizované zrušení platnosti
- Dávkové zpracování v oknech zelené energie s Carbon Aware SDK
- Správná velikost a automatické zmenšování: jak se vyhnout plýtvání energií při nečinnosti.
- Vzory udržitelné databáze: optimalizace dotazů, materializované pohledy, repliky čtení
- Efektivita sítě: komprese, HTTP/3, edge computing
- Udržitelné vzory frontendu: líné načítání, optimalizace obrazu, energie tmavého režimu
- Udržitelný design API: stránkování, výběr polí, GraphQL vs REST
- Monitorování uhlíku pomocí skóre Prometheus, Grafana a SCI za službu
- Případová studie elektronického obchodu: -45% uhlíková stopa při použití všech vzorů
Green Software Series – 10 článků
| # | Titul | Soustředit | Stát |
|---|---|---|---|
| 1 | Principy zeleného softwarového inženýrství | GSF, SCI, 8 základních principů | Publikováno |
| 2 | Měření emisí pomocí CodeCarbon | CodeCarbon, energetické profilování Pythonu | Publikováno |
| 3 | Carbon Aware SDK: Inteligentní plánování | Časový a geografický pohyb pracovních zátěží | Publikováno |
| 4 | Climatiq API: Údaje o emisích v reálném čase | Emise API, konverzní faktory | Publikováno |
| 5 | GreenOps: Udržitelná infrastruktura jako kód | Terraformní zelená, bodové instance, automatické škálování | Publikováno |
| 6 | Umělá inteligence a uhlíková stopa: Odpovědné školení | Efektivní modely, LoRA, kvantování | Publikováno |
| 7 | Rozsah 3 v Software Pipelines | Emise proti proudu/po proudu, dodavatelský řetězec | Publikováno |
| 8 | Scope Modeling: Simulace dopadu | Simulace, analýza what-if, zelený plán | Publikováno |
| 9 | Udržitelné architektonické vzory | Úložiště, mezipaměť, dávka, návrh API | Tento článek |
| 10 | ESG, CSRD a dodržování předpisů pro softwarové týmy | Povinný reporting, audit trail, ESG metriky | Další |
Tiering úložiště: správná data na správném místě
První velké plýtvání energií v moderních systémech je úložiště není optimalizováno: zřídka přístupná data uložená na vysoce výkonných NVMe SSD, nebo v horším případě v paměti RAM. SSD podnik spotřebuje až 6-10 Wattů při volnoběhu a 25 Wattů při zátěži. Ukládání na HDD spotřeba 5-8W. Páskové nebo studené úložiště spotřebuje méně než 0,01 wattu na TB při nečinnosti.
Strategie vrstvené úložiště sestává z klasifikace dat na základě četnosti získat přístup a automaticky je přesunout do energeticky vhodné vrstvy úložiště. Organizace Média, která správně implementují vrstvené úložiště, snižují náklady na úložiště 40–70 % a související uhlíková stopa v podobném poměru.
4vrstvá architektura úložiště
| Úroveň | Typ | Latence | Cena/TB/měsíc | Energie/TB | Typické použití |
|---|---|---|---|---|---|
| Horký | NVMe SSD / Redis | < 1 ms | 200–500 USD | Vysoká (25 W) | Aktivní data za posledních 30 dní |
| Teplý | SSD Standard / S3 Standard | 1-10 ms | 20-50 $ | Střední (8W) | Data za 1–12 měsíců, přístupná týdně |
| Studený | HDD/S3 IA/GCS Nearline | 50-250 ms | 4-10 dolarů | Nízká (3W) | Data 1-7 let, měsíční přístup |
| Archiv | Glacier / Archiv GCS / Páska | 1-12 hodin | 0,40-1 USD | Minimální (<0,01W) | Data >7 let, shoda, záloha |
Implementujte zásady automatického životního cyklu dat
Srdcem vrstveného úložiště je automatizace zásad životního cyklu. Místo řízení ručním přesunem dat definujeme pravidla, která systém automaticky aplikuje věk a četnost přístupu k datům.
from dataclasses import dataclass
from datetime import datetime, timedelta
from enum import Enum
from typing import Optional
import boto3
import logging
logger = logging.getLogger(__name__)
class StorageTier(Enum):
HOT = "hot"
WARM = "warm"
COLD = "cold"
ARCHIVE = "archive"
# Energia media per operazione per tier (Wh per GB spostato/acceduto)
TIER_ENERGY_PROFILE = {
StorageTier.HOT: {"idle_w_per_tb": 25, "access_wh_per_gb": 0.002},
StorageTier.WARM: {"idle_w_per_tb": 8, "access_wh_per_gb": 0.0008},
StorageTier.COLD: {"idle_w_per_tb": 3, "access_wh_per_gb": 0.0003},
StorageTier.ARCHIVE: {"idle_w_per_tb": 0.01, "access_wh_per_gb": 0.00001},
}
@dataclass(frozen=True)
class LifecyclePolicy:
"""Immutabile: definisce le soglie di transizione tra tier."""
hot_to_warm_days: int = 30
warm_to_cold_days: int = 90
cold_to_archive_days: int = 365
delete_after_days: Optional[int] = None # None = mantieni forever
@dataclass(frozen=True)
class DataAsset:
"""Rappresenta un asset dati con le sue metriche."""
key: str
size_gb: float
created_at: datetime
last_accessed: datetime
current_tier: StorageTier
access_count_30d: int
class DataLifecycleManager:
"""Gestisce il lifecycle dei dati verso tier energeticamente ottimali."""
def __init__(self, policy: LifecyclePolicy, s3_client=None):
self._policy = policy
self._s3 = s3_client or boto3.client("s3")
def evaluate_tier_transition(self, asset: DataAsset) -> Optional[StorageTier]:
"""
Determina se un asset deve essere spostato.
Ritorna il nuovo tier o None se nessun cambio necessario.
"""
age_days = (datetime.utcnow() - asset.created_at).days
days_since_access = (datetime.utcnow() - asset.last_accessed).days
# Regola 1: archivio per dati molto vecchi
if age_days > self._policy.cold_to_archive_days:
if asset.current_tier != StorageTier.ARCHIVE:
return StorageTier.ARCHIVE
# Regola 2: cold per dati accessati raramente
elif age_days > self._policy.warm_to_cold_days and asset.access_count_30d < 2:
if asset.current_tier in (StorageTier.HOT, StorageTier.WARM):
return StorageTier.COLD
# Regola 3: warm per dati maturi con accesso moderato
elif age_days > self._policy.hot_to_warm_days and asset.access_count_30d < 10:
if asset.current_tier == StorageTier.HOT:
return StorageTier.WARM
return None # Nessun cambio necessario
def calculate_carbon_savings(
self,
asset: DataAsset,
target_tier: StorageTier,
carbon_intensity_g_kwh: float = 350 # Media europea 2025
) -> dict:
"""Stima il risparmio CO2 mensile dal cambio di tier."""
current_profile = TIER_ENERGY_PROFILE[asset.current_tier]
target_profile = TIER_ENERGY_PROFILE[target_tier]
# Energia idle mensile (30 giorni * 24 ore)
current_idle_wh = (current_profile["idle_w_per_tb"] * asset.size_gb / 1000) * 720
target_idle_wh = (target_profile["idle_w_per_tb"] * asset.size_gb / 1000) * 720
savings_wh = current_idle_wh - target_idle_wh
savings_kwh = savings_wh / 1000
savings_co2_g = savings_kwh * carbon_intensity_g_kwh
return {
"monthly_savings_kwh": round(savings_kwh, 4),
"monthly_savings_co2_g": round(savings_co2_g, 2),
"annual_savings_co2_kg": round(savings_co2_g * 12 / 1000, 3),
}
def execute_transition(self, asset: DataAsset, target_tier: StorageTier, bucket: str) -> dict:
"""Sposta l'asset al nuovo tier su S3 con storage class appropriata."""
storage_class_map = {
StorageTier.WARM: "STANDARD_IA",
StorageTier.COLD: "GLACIER_IR",
StorageTier.ARCHIVE: "DEEP_ARCHIVE",
}
storage_class = storage_class_map.get(target_tier, "STANDARD")
savings = self.calculate_carbon_savings(asset, target_tier)
try:
# Copia con nuova storage class (immutabile: crea nuovo oggetto S3)
self._s3.copy_object(
CopySource={"Bucket": bucket, "Key": asset.key},
Bucket=bucket,
Key=asset.key,
StorageClass=storage_class,
MetadataDirective="COPY",
)
logger.info(
"Tier transition: %s -> %s | CO2 saved: %.2fg/month",
asset.current_tier.value, target_tier.value,
savings["monthly_savings_co2_g"]
)
return {"success": True, "savings": savings}
except Exception as e:
logger.error("Transition failed for %s: %s", asset.key, e)
return {"success": False, "error": str(e)}
# AWS S3 Lifecycle Policy come JSON (alternativa Infrastructure-as-Code)
S3_LIFECYCLE_POLICY = {
"Rules": [
{
"ID": "GreenDataLifecycle",
"Status": "Enabled",
"Transitions": [
{"Days": 30, "StorageClass": "STANDARD_IA"},
{"Days": 90, "StorageClass": "GLACIER_IR"},
{"Days": 365, "StorageClass": "DEEP_ARCHIVE"},
],
"Expiration": {"Days": 2555}, # 7 anni poi elimina
}
]
}
Komprese: Nejvíce podceňovaný vzor
Komprese dat současně snižuje spotřebu úložiště (méně bajtů pro údržbu) a síťový provoz (méně bajtů k přenosu). Poměr ušetřená energie / vynaložená energie komprese je typicky 10:1 až 100:1, což z něj dělá jeden z nejoblíbenějších vzorů energeticky výhodné.
import zlib
import lz4.frame
import zstandard as zstd
import time
from dataclasses import dataclass
from typing import Callable
@dataclass(frozen=True)
class CompressionProfile:
"""Profilo immutabile di un algoritmo di compressione."""
name: str
compress_fn: Callable[[bytes], bytes]
decompress_fn: Callable[[bytes], bytes]
cpu_intensity: float # 1.0 = baseline, <1 = meno CPU, >1 = più CPU
best_for: str
def benchmark_compression(data: bytes, profile: CompressionProfile) -> dict:
"""Misura efficienza energetica di un algoritmo su dati reali."""
# Compressione
start = time.perf_counter()
compressed = profile.compress_fn(data)
compress_ms = (time.perf_counter() - start) * 1000
# Decompressione
start = time.perf_counter()
profile.decompress_fn(compressed)
decompress_ms = (time.perf_counter() - start) * 1000
ratio = len(data) / len(compressed)
# Energia stimata: CPU_time * intensità * 0.001 Wh per ms di CPU
compress_energy_mwh = compress_ms * profile.cpu_intensity * 0.001
storage_savings_pct = (1 - 1/ratio) * 100
return {
"algorithm": profile.name,
"ratio": round(ratio, 2),
"storage_savings_pct": round(storage_savings_pct, 1),
"compress_ms": round(compress_ms, 2),
"decompress_ms": round(decompress_ms, 2),
"compress_energy_mwh": round(compress_energy_mwh, 4),
"best_for": profile.best_for,
}
# Profili degli algoritmi principali
COMPRESSION_PROFILES = [
CompressionProfile(
name="zlib-6",
compress_fn=lambda d: zlib.compress(d, level=6),
decompress_fn=zlib.decompress,
cpu_intensity=1.0,
best_for="Compatibilità universale, HTTP responses"
),
CompressionProfile(
name="lz4",
compress_fn=lz4.frame.compress,
decompress_fn=lz4.frame.decompress,
cpu_intensity=0.15, # Molto veloce, meno CPU
best_for="Stream real-time, alta frequenza di accesso"
),
CompressionProfile(
name="zstd-3",
compress_fn=lambda d: zstd.ZstdCompressor(level=3).compress(d),
decompress_fn=lambda d: zstd.ZstdDecompressor().decompress(d),
cpu_intensity=0.4,
best_for="Bilanciamento ottimale ratio/velocità (raccomandato)"
),
CompressionProfile(
name="zstd-19",
compress_fn=lambda d: zstd.ZstdCompressor(level=19).compress(d),
decompress_fn=lambda d: zstd.ZstdDecompressor().decompress(d),
cpu_intensity=2.5, # Alta CPU per compressione massima
best_for="Archivio cold/archive, dati rari, batch notturno"
),
]
# Regola pratica per scelta del livello di compressione:
# HOT tier -> lz4 (latenza minima, decompress velocissimo)
# WARM tier -> zstd-3 (bilanciamento ottimale)
# COLD tier -> zstd-9 (ratio migliore, latenza tollerabile)
# ARCHIVE -> zstd-19 o brotli-11 (massimo risparmio storage)
Ukládání do mezipaměti Carbon-Aware: Podávejte více tím, že budete méně počítat
Ukládání do mezipaměti je pravděpodobně nejúčinnějším vzorem pro snížení emisí softwaru: každý cache hit zcela eliminuje spotřebu energie příslušného výpočtu. Systém s úspěšnost mezipaměti 90 % provádí pouze 1/10 výpočtů ve srovnání s výpočtem bez mezipaměti, s úměrnou úsporou energie.
Ale „ukládání do mezipaměti s ohledem na uhlík“ přesahuje jednoduchou optimalizaci výkonu: zvažte auhlíková intenzita sítě rozhodnout, co předem načíst a za kolik čas uchovat data v mezipaměti a kdy provádět operace zahřívání mezipaměti.
Víceúrovňová architektura mezipaměti
Úrovně mezipaměti a energetický dopad
| Úroveň | Technologie | Latence | Energie pro Hit | Úspora energie vs DB |
|---|---|---|---|---|
| L1: Probíhá | HashMap, LRU v RAM | < 0,1 ms | ~0,001 mWh | Úspora 99,9 %. |
| L2: Distribuováno | Redis, Memcached | 0,1-1 ms | ~0,01 mWh | 99% úspora |
| L3: CDN Edge | CloudFront, Fastly, CF | 1-20 ms | ~0,05 mWh | 95% úspora |
| DB dotaz | PostgreSQL, MySQL | 5-100 ms | ~1-10 mWh | — základní linie |
import Redis from "ioredis";
interface CacheEntry<T> {
readonly data: T;
readonly cachedAt: number;
readonly ttlMs: number;
readonly carbonIntensityAtCache: number; // gCO2/kWh quando cacheato
}
interface CarbonAwareCacheConfig {
readonly l1MaxEntries: number;
readonly l1DefaultTtlMs: number;
readonly l2DefaultTtlMs: number;
readonly lowCarbonThreshold: number; // gCO2/kWh
readonly highCarbonTtlMultiplier: number; // TTL più lungo quando carbon e alto
}
const DEFAULT_CONFIG: CarbonAwareCacheConfig = {
l1MaxEntries: 1000,
l1DefaultTtlMs: 60_000, // 1 minuto L1
l2DefaultTtlMs: 300_000, // 5 minuti L2
lowCarbonThreshold: 200, // <200 gCO2/kWh = energia verde
highCarbonTtlMultiplier: 3, // TTL 3x più lungo su energia sporca
};
class CarbonAwareMultiLevelCache<T> {
private readonly l1 = new Map<string, CacheEntry<T>>();
private readonly config: CarbonAwareCacheConfig;
private readonly redis: Redis;
private currentCarbonIntensity = 350; // Default, aggiornato periodicamente
// Metriche immutabili per reporting
private readonly metrics = {
l1Hits: 0,
l2Hits: 0,
misses: 0,
carbonSavedGrams: 0,
};
constructor(redisClient: Redis, config: Partial<CarbonAwareCacheConfig> = {}) {
this.redis = redisClient;
this.config = { ...DEFAULT_CONFIG, ...config };
}
async get(key: string): Promise<T | null> {
// L1: memoria locale (nessuna I/O, minima energia)
const l1Entry = this.l1.get(key);
if (l1Entry && !this.isExpired(l1Entry)) {
this.metrics.l1Hits++;
this.metrics.carbonSavedGrams += 0.005; // ~5mg CO2 risparmiati vs DB
return l1Entry.data;
}
// L2: Redis distribuito
try {
const raw = await this.redis.get(key);
if (raw) {
const entry: CacheEntry<T> = JSON.parse(raw);
if (!this.isExpired(entry)) {
// Promuovi in L1
this.setL1(key, entry.data, this.config.l1DefaultTtlMs);
this.metrics.l2Hits++;
this.metrics.carbonSavedGrams += 0.003; // ~3mg CO2 vs DB
return entry.data;
}
}
} catch (err) {
console.warn("L2 cache read failed, fallback to source:", err);
}
this.metrics.misses++;
return null;
}
async set(key: string, data: T): Promise<void> {
// TTL adattivo in base all'intensità carbonica corrente
// Quando l'energia e verde (low carbon), TTL più breve e accettabile
// Quando l'energia e "sporca" (high carbon), TTL più lungo per ridurre ricalcoli
const isHighCarbon = this.currentCarbonIntensity > this.config.lowCarbonThreshold;
const ttlMultiplier = isHighCarbon ? this.config.highCarbonTtlMultiplier : 1;
const l1TtlMs = this.config.l1DefaultTtlMs * ttlMultiplier;
const l2TtlMs = this.config.l2DefaultTtlMs * ttlMultiplier;
this.setL1(key, data, l1TtlMs);
// Scrivi in Redis in modo non bloccante
const entry: CacheEntry<T> = {
data,
cachedAt: Date.now(),
ttlMs: l2TtlMs,
carbonIntensityAtCache: this.currentCarbonIntensity,
};
await this.redis.set(key, JSON.stringify(entry), "PX", l2TtlMs);
}
private setL1(key: string, data: T, ttlMs: number): void {
// Evict LRU se pieno
if (this.l1.size >= this.config.l1MaxEntries) {
const firstKey = this.l1.keys().next().value;
if (firstKey) this.l1.delete(firstKey);
}
this.l1.set(key, {
data,
cachedAt: Date.now(),
ttlMs,
carbonIntensityAtCache: this.currentCarbonIntensity,
});
}
private isExpired(entry: CacheEntry<T>): boolean {
return Date.now() - entry.cachedAt > entry.ttlMs;
}
updateCarbonIntensity(intensityGCO2PerKWh: number): void {
this.currentCarbonIntensity = intensityGCO2PerKWh;
}
getMetrics(): Readonly<typeof this.metrics> {
const total = this.metrics.l1Hits + this.metrics.l2Hits + this.metrics.misses;
return {
...this.metrics,
hitRate: total ? ((this.metrics.l1Hits + this.metrics.l2Hits) / total * 100).toFixed(1) + "%" : "N/A",
} as any;
}
}
Carbon-Aware CDN: Serving from the Green Edge
CDN jako Cloudflare nebo Fastly má okrajové uzly v desítkách regionů velmi náročných na uhlík jiný. Směrujte provoz na okraje s zelenější energií, když to latence dovolí, může snížit provozní emise 20–40 %.
// Cache invalidation: uno dei problemi più difficili del software
// Pattern carbon-aware: invalida in batch durante finestre di bassa carbon intensity
interface InvalidationJob {
readonly tags: readonly string[];
readonly priority: "immediate" | "carbon-optimal" | "batch-night";
readonly scheduledAt: Date;
readonly maxDelayMs: number;
}
class CarbonAwareCacheInvalidator {
private readonly pendingJobs: InvalidationJob[] = [];
private readonly carbonAwareSdk: any; // Carbon Aware SDK instance
async scheduleInvalidation(
tags: string[],
priority: InvalidationJob["priority"] = "carbon-optimal",
maxDelayMs: number = 3_600_000 // 1 ora di tolleranza
): Promise<{ jobId: string; scheduledFor: Date }> {
if (priority === "immediate") {
await this.executePurge(tags);
return { jobId: crypto.randomUUID(), scheduledFor: new Date() };
}
// Trova la finestra con minore carbon intensity nelle prossime maxDelayMs
const optimalTime = await this.findGreenWindow(maxDelayMs);
const job: InvalidationJob = {
tags: Object.freeze(tags),
priority,
scheduledAt: optimalTime,
maxDelayMs,
};
// Immutabile: non mutiamo pendingJobs esistenti, aggiungiamo nuovo
this.pendingJobs.push(job);
return {
jobId: crypto.randomUUID(),
scheduledFor: optimalTime,
};
}
private async findGreenWindow(maxDelayMs: number): Promise<Date> {
const windowEnd = new Date(Date.now() + maxDelayMs);
try {
// Carbon Aware SDK: trova il momento con minore intensità carbonica
const forecast = await this.carbonAwareSdk.getForecast({
location: "westeurope",
start: new Date(),
end: windowEnd,
duration: 15, // Job richiede ~15 minuti
});
return new Date(forecast.optimalWindow.start);
} catch {
// Fallback: esegui immediatamente se il forecast non e disponibile
return new Date();
}
}
private async executePurge(tags: string[]): Promise<void> {
// Cloudflare Cache Tag Purge API
await fetch("https://api.cloudflare.com/client/v4/zones/ZONE_ID/purge_cache", {
method: "POST",
headers: {
"Authorization": "Bearer CF_TOKEN",
"Content-Type": "application/json",
},
body: JSON.stringify({ tags }),
});
}
}
Dávkové zpracování s ohledem na uhlík: Pracujte, když je energie zelená
Dávkové zpracování je ideálním kandidátem pro časový posun, princip který spočívá v přesunu flexibilních pracovních zátěží v dobách uhlíkové intenzity elektrické sítě je nižší. Systém, který spouští své dávky každou noc přebytek energie z větru a slunce může snížit související emise 30–70 % ve srovnání s provedením na dobu určitou.
The Batch Paradox ve 02:00
Mnoho systémů plánuje dávky na 02:00 „protože je menší provoz“. Ale v Evropě tam noc není vždy dobou s nižší intenzitou uhlíku: v mnoha regionech sluneční a v noci chybí a vítr se mění. V některých oblastech 10:00–14:00 (špička slunečního záření) nebo 2:00–6:00 (stálý vítr) mají mnohem nižší uhlíkovou intenzitu. Použijte skutečná data z Sada Carbon Aware SDK namísto dočasného pravidla palce.
from celery import Celery
from celery.schedules import crontab
from datetime import datetime, timedelta
from typing import Optional, NamedTuple
import httpx
import asyncio
import logging
logger = logging.getLogger(__name__)
app = Celery("green_batch", broker="redis://localhost:6379/0")
class GreenWindow(NamedTuple):
"""Finestra di esecuzione ottimale per carbon footprint."""
start: datetime
end: datetime
carbon_intensity_g_kwh: float
savings_pct_vs_now: float
class CarbonAwareBatchScheduler:
"""Schedula batch job nelle finestre a minore carbon intensity."""
BASE_CARBON_API = "https://api.electricitymap.org/v3"
def __init__(self, api_token: str, location: str = "IT"):
self._token = api_token
self._location = location
async def get_optimal_window(
self,
job_duration_minutes: int,
max_delay_hours: int = 12,
min_savings_pct: float = 15.0
) -> Optional[GreenWindow]:
"""
Trova la finestra ottimale per eseguire un batch job.
Ritorna None se nessuna finestra con risparmio sufficiente trovata.
"""
headers = {"auth-token": self._token}
async with httpx.AsyncClient() as client:
# Intensità carbonica attuale
current_resp = await client.get(
f"{self.BASE_CARBON_API}/carbon-intensity/latest",
params={"zone": self._location},
headers=headers
)
current_data = current_resp.json()
current_intensity = current_data["carbonIntensity"]
# Forecast delle prossime ore
forecast_resp = await client.get(
f"{self.BASE_CARBON_API}/carbon-intensity/forecast",
params={"zone": self._location},
headers=headers
)
forecast = forecast_resp.json()
now = datetime.utcnow()
deadline = now + timedelta(hours=max_delay_hours)
best_window: Optional[GreenWindow] = None
min_intensity = current_intensity
for slot in forecast["forecast"]:
slot_time = datetime.fromisoformat(slot["datetime"].replace("Z", "+00:00"))
slot_time = slot_time.replace(tzinfo=None)
if slot_time < now or slot_time > deadline:
continue
intensity = slot["carbonIntensity"]
if intensity < min_intensity:
min_intensity = intensity
savings_pct = (current_intensity - intensity) / current_intensity * 100
if savings_pct >= min_savings_pct:
best_window = GreenWindow(
start=slot_time,
end=slot_time + timedelta(minutes=job_duration_minutes),
carbon_intensity_g_kwh=intensity,
savings_pct_vs_now=round(savings_pct, 1),
)
return best_window
async def schedule_green(
self,
task_name: str,
job_duration_minutes: int,
task_kwargs: dict,
max_delay_hours: int = 12
) -> dict:
"""Schedula un Celery task nella finestra più verde."""
window = await self.get_optimal_window(
job_duration_minutes=job_duration_minutes,
max_delay_hours=max_delay_hours
)
if window:
delay_seconds = (window.start - datetime.utcnow()).total_seconds()
task = app.send_task(
task_name,
kwargs=task_kwargs,
countdown=max(0, int(delay_seconds))
)
logger.info(
"Scheduled '%s' for %s (%.1f%% CO2 savings, %.0fg/kWh)",
task_name, window.start.isoformat(),
window.savings_pct_vs_now, window.carbon_intensity_g_kwh
)
return {
"task_id": task.id,
"scheduled_for": window.start.isoformat(),
"carbon_intensity": window.carbon_intensity_g_kwh,
"co2_savings_pct": window.savings_pct_vs_now,
}
else:
# Nessuna finestra verde disponibile: esegui ora
task = app.send_task(task_name, kwargs=task_kwargs)
logger.warning("No green window found for '%s', executing immediately", task_name)
return {"task_id": task.id, "scheduled_for": "now", "co2_savings_pct": 0}
# Definizione dei task Celery con metriche carbon
@app.task(name="batch.nightly_report", bind=True)
def nightly_report_batch(self, report_date: str) -> dict:
"""
Genera report notturni. Non time-critical: ideale per temporal shifting.
Carbon savings tipici: 20-60% spostando dalle 02:00 alla finestra verde.
"""
logger.info("Generating report for %s (carbon-optimal execution)", report_date)
# ... logica report
return {"status": "completed", "report_date": report_date}
@app.task(name="batch.data_sync", bind=True)
def data_sync_batch(self, source: str) -> dict:
"""
Sincronizzazione dati tra sistemi. Tollerante a ritardi di alcune ore.
"""
# ... logica sync
return {"status": "synced", "source": source}
# Scheduler che usa carbon-awareness invece di crontab fisso
async def schedule_nightly_jobs():
scheduler = CarbonAwareBatchScheduler(
api_token="your_electricity_maps_token",
location="IT" # Italia
)
# Report: accetta fino a 12h di ritardo
await scheduler.schedule_green(
task_name="batch.nightly_report",
job_duration_minutes=45,
task_kwargs={"report_date": datetime.now().strftime("%Y-%m-%d")},
max_delay_hours=12
)
# Data sync: accetta fino a 6h di ritardo
await scheduler.schedule_green(
task_name="batch.data_sync",
job_duration_minutes=20,
task_kwargs={"source": "salesforce"},
max_delay_hours=6
)
Nastavení správné velikosti a automatické zmenšení: Zastavení neviditelného odpadu
Studie společnosti Gartner z roku 2025 odhaduje, že 35–40 % podnikových cloudových zdrojů tvoří nadměrně rezervované: servery běžící na 10–15 % CPU, databáze s 90 % nevyužité paměti, Lambda funguje s přidělenými 3 GB RAM, když stačí 256 MB. Toto "nečinné počítání" je čisté plýtvání energií.
Systematické určování správné velikosti — snížení zdrojů na minimum nutné ke splnění požadavků výkonu – často jde o jediný zásah s nejlepší návratnost investic do energie. Nevyžaduje přepisování kódu – je to záležitost konfigurace a monitorování.
Strategie správné velikosti pro redukci uhlíku
| Strategie | Typické úspory | Složitost | Riziko |
|---|---|---|---|
| Snižte velikost nadměrně zajišťovaných instancí | 20–40 % | Nízký | Nízká (snadné vrácení zpět) |
| Agresivní automatické zmenšování | 30–60 % | Průměrný | Střední (latence studeného startu) |
| Bez serveru pro občasné pracovní zatížení | 50–90 % | Vysoký | Střední (studený start, uzamčení dodavatele) |
| Spot/preemptable instance na dávku | 60–80 % | Vysoký | Vysoká (přerušení) |
| Naplánujte vypnutí po hodinách (vývoj/staging) | 40–70 % | Nízký | Null (neproduktová prostředí) |
import boto3
from datetime import datetime
from dataclasses import dataclass
from typing import Sequence
@dataclass(frozen=True)
class ScalingPolicy:
"""Politica di scaling immutabile con carbon awareness."""
min_capacity: int
max_capacity: int
target_cpu_pct: float
scale_in_cooldown_sec: int
green_hour_min_capacity: int # capacità minima durante ore verdi
low_traffic_scale_in_factor: float # Fattore aggressivo in ore basse traffico
def create_carbon_aware_scaling_policies(
asg_name: str,
policy: ScalingPolicy,
region: str = "eu-west-1"
) -> dict:
"""
Configura Auto Scaling Group con politiche carbon-aware.
Scale-in più aggressivo durante ore di basso traffico (di notte)
dove l'energia potrebbe essere più verde E il traffico e basso.
"""
autoscaling = boto3.client("autoscaling", region_name=region)
# Policy principale: target tracking su CPU
main_policy = autoscaling.put_scaling_policy(
AutoScalingGroupName=asg_name,
PolicyName=f"{asg_name}-carbon-aware-cpu",
PolicyType="TargetTrackingScaling",
TargetTrackingConfiguration={
"PredefinedMetricSpecification": {
"PredefinedMetricType": "ASGAverageCPUUtilization"
},
"TargetValue": policy.target_cpu_pct,
"ScaleInCooldown": policy.scale_in_cooldown_sec,
"ScaleOutCooldown": 120,
"DisableScaleIn": False,
}
)
# Scheduled action: scale down notturno aggressivo
# Combina basso traffico + potenziale energia verde
autoscaling.put_scheduled_update_group_action(
AutoScalingGroupName=asg_name,
ScheduledActionName=f"{asg_name}-night-scaledown",
Recurrence="0 22 * * *", # Ogni giorno alle 22:00 UTC
MinSize=policy.green_hour_min_capacity,
MaxSize=policy.max_capacity,
DesiredCapacity=policy.green_hour_min_capacity,
)
# Scheduled action: scale up mattutino prima del traffico
autoscaling.put_scheduled_update_group_action(
AutoScalingGroupName=asg_name,
ScheduledActionName=f"{asg_name}-morning-scaleup",
Recurrence="0 7 * * MON-FRI", # Lun-Ven alle 07:00 UTC
MinSize=policy.min_capacity,
MaxSize=policy.max_capacity,
DesiredCapacity=policy.min_capacity + 2, # Pre-warm prima del traffico
)
return {
"asg_name": asg_name,
"main_policy_arn": main_policy["PolicyARN"],
"estimated_monthly_co2_reduction_pct": 35, # Tipico per questo pattern
}
# Lambda Right-sizing: trova la memoria ottimale
# Principio: RAM in eccesso = costo carbonio inutile
def optimize_lambda_memory(function_name: str, region: str = "eu-west-1") -> dict:
"""
Analizza l'utilizzo memoria di una Lambda e suggerisce il right-size.
AWS Lambda Power Tuning (tool open source) automatizza questo processo.
"""
lambda_client = boto3.client("lambda", region_name=region)
cloudwatch = boto3.client("cloudwatch", region_name=region)
# Recupera configurazione attuale
config = lambda_client.get_function_configuration(FunctionName=function_name)
current_memory_mb = config["MemorySize"]
# Recupera metriche CloudWatch: max memory used negli ultimi 7 giorni
# (in produzione usare AWS Lambda Power Tuning per analisi completa)
metrics = cloudwatch.get_metric_statistics(
Namespace="AWS/Lambda",
MetricName="MaxMemoryUsed",
Dimensions=[{"Name": "FunctionName", "Value": function_name}],
StartTime=datetime.utcnow().replace(hour=0, minute=0) - __import__("datetime").timedelta(days=7),
EndTime=datetime.utcnow(),
Period=86400,
Statistics=["Maximum"],
)
if not metrics["Datapoints"]:
return {"status": "insufficient_data"}
max_used_mb = max(dp["Maximum"] for dp in metrics["Datapoints"])
# Buffer di sicurezza: 30% sopra il massimo osservato
recommended_mb = min(int(max_used_mb * 1.3 / 64 + 1) * 64, 10240)
co2_reduction_pct = max(0, (current_memory_mb - recommended_mb) / current_memory_mb * 100)
return {
"current_memory_mb": current_memory_mb,
"max_observed_mb": int(max_used_mb),
"recommended_mb": recommended_mb,
"potential_co2_reduction_pct": round(co2_reduction_pct, 1),
"annual_cost_savings_usd": (current_memory_mb - recommended_mb) / 1024 * 0.0000166667 * 3_600_000 * 12,
}
Vzorce udržitelné databáze: Méně dotazů, méně uhlíku
Databáze je často součástí s větší spotřeba energie systému podnik. Každý dotaz zahrnuje I/O disku, alokaci vyrovnávací paměti RAM, cykly CPU pro analýzu, plánování a provádění. Optimalizace dotazů není jen o výkonu: je přímé snížení emisí.
Vzor 1: Materializované pohledy pro snížení přepočtů
Materializované pohledy jsou nejúčinnějším vzorem pro eliminaci drahých agregovaných dotazů, které jsou průběžně znovu prováděny. Místo přepočítávání složitých SUM, COUNT, JOIN s každým požadavkem, výsledek je předem vypočítán a aktualizován pravidelně nebo prostřednictvím spouštěčů.
-- PROBLEMA: Query aggregata pesante eseguita 1000x al giorno
-- Ogni esecuzione: 2-5 secondi, 100-500ms CPU, I/O intensivo
-- Stima: ~500mWh/giorno solo per questa query
-- Query pesante PRIMA (eseguita ad ogni richiesta)
SELECT
c.category_id,
c.name AS category_name,
COUNT(DISTINCT o.order_id) AS total_orders,
SUM(oi.quantity * oi.unit_price) AS total_revenue,
AVG(oi.quantity * oi.unit_price) AS avg_order_value,
COUNT(DISTINCT o.customer_id) AS unique_customers
FROM categories c
JOIN products p ON p.category_id = c.category_id
JOIN order_items oi ON oi.product_id = p.product_id
JOIN orders o ON o.order_id = oi.order_id
WHERE o.created_at >= NOW() - INTERVAL '30 days'
GROUP BY c.category_id, c.name;
-- SOLUZIONE: Materialized view con refresh in finestra verde
CREATE MATERIALIZED VIEW mv_category_metrics_30d AS
SELECT
c.category_id,
c.name AS category_name,
COUNT(DISTINCT o.order_id) AS total_orders,
SUM(oi.quantity * oi.unit_price) AS total_revenue,
AVG(oi.quantity * oi.unit_price) AS avg_order_value,
COUNT(DISTINCT o.customer_id) AS unique_customers,
NOW() AS last_refreshed
FROM categories c
JOIN products p ON p.category_id = c.category_id
JOIN order_items oi ON oi.product_id = p.product_id
JOIN orders o ON o.order_id = oi.order_id
WHERE o.created_at >= NOW() - INTERVAL '30 days'
GROUP BY c.category_id, c.name
WITH DATA;
-- Indice per query O(1)
CREATE UNIQUE INDEX idx_mv_category_metrics ON mv_category_metrics_30d (category_id);
-- Refresh schedulato: CONCURRENT permette letture durante il refresh
-- Schedulare nelle finestre verdi (es. con pg_cron + carbon intensity check)
REFRESH MATERIALIZED VIEW CONCURRENTLY mv_category_metrics_30d;
-- Con pg_cron (schedulare refresh ogni ora nelle ore diurne con solare)
SELECT cron.schedule(
'refresh-category-metrics',
'0 * * * *', -- Ogni ora; logica carbon-aware nell'applicazione
'REFRESH MATERIALIZED VIEW CONCURRENTLY mv_category_metrics_30d'
);
-- Query dopo: O(1) sul materialized view
-- Stima risparmio: 99% della computazione originale
SELECT * FROM mv_category_metrics_30d ORDER BY total_revenue DESC;
-- Pattern 2: Read Replica per separare carichi
-- Letture analitiche (intensive) -> read replica
-- Scritture -> primary (minimo carico)
-- Pattern 3: Partial Indexes per ridurre I/O
-- INVECE DI: indice su tutti i 50M ordini
CREATE INDEX idx_orders_status_all ON orders(status, created_at);
-- MEGLIO: indice solo sui 500K ordini attivi (1% del totale)
-- 99% meno I/O, 99% meno spazio, maintenance molto più veloce
CREATE INDEX idx_orders_status_active ON orders(status, created_at)
WHERE status IN ('pending', 'processing', 'shipped');
-- Pattern 4: Connection Pooling per ridurre overhead
-- PgBouncer: max_client_conn=1000, pool_size=20
-- Riduce: TCP handshakes, SSL negotiation, process fork overhead
-- Stima risparmio: 30-50% CPU PostgreSQL su workload high-concurrency
Optimalizace dotazu: Problém N+1 a rychlé načítání
Problém N+1 je jedním z nejběžnějších a nejnákladnějších anti-vzorců, pokud jde o emise: místo jediného dotazu, který načte všechna potřebná data, spustíte N+1 samostatných dotazů. Při N=1000 objednávkách se vygeneruje 1001 dotazů místo 1, čímž se uhlíková stopa vynásobí 1000 operace.
from sqlalchemy import select
from sqlalchemy.orm import selectinload, joinedload, Session
from typing import Sequence
# ANTI-PATTERN: N+1 queries - EVITARE
def get_orders_naive(session: Session, limit: int = 100) -> list:
"""
PROBLEMATICO: per 100 ordini genera 101 query.
100 ordini -> 1 query
100 customer -> 100 query separate
Stima: ~2mWh per 100 ordini. Su 10.000 richieste/giorno = 20Wh/giorno.
"""
orders = session.execute(select(Order).limit(limit)).scalars().all()
# Ogni accesso a order.customer triggersa una nuova query! (lazy loading)
return [{"id": o.id, "customer": o.customer.email} for o in orders]
# PATTERN GREEN: Eager loading con selectin
def get_orders_green(session: Session, limit: int = 100) -> list:
"""
OTTIMIZZATO: 2 query totali invece di N+1.
Query 1: tutti gli ordini
Query 2: tutti i customer in una sola query IN
Stima: 0.02mWh per 100 ordini. Risparmio: 99%.
"""
stmt = (
select(Order)
.options(selectinload(Order.customer)) # 2 query totali
.limit(limit)
)
orders = session.execute(stmt).scalars().all()
return [{"id": o.id, "customer": o.customer.email} for o in orders]
# Ancora meglio: joinedload per 1 sola query
def get_orders_single_query(session: Session, limit: int = 100) -> list:
"""
ULTRA-OTTIMIZZATO: 1 sola query con JOIN.
Ideale quando il numero di relazioni e basso.
Stima: 0.01mWh per 100 ordini. Risparmio: 99.5%.
"""
stmt = (
select(Order)
.options(joinedload(Order.customer)) # JOIN: 1 sola query
.limit(limit)
)
orders = session.execute(stmt).unique().scalars().all()
return [{"id": o.id, "customer": o.customer.email} for o in orders]
# Pattern: Projection - seleziona solo i campi necessari
def get_order_summary(session: Session, order_ids: list[int]) -> list[dict]:
"""
Seleziona SOLO i campi necessari, non SELECT *.
Su tabelle con 50+ colonne, SELECT * trasferisce 10-20x più dati.
"""
stmt = (
select(
Order.id,
Order.total_amount,
Order.status,
# Solo 3 campi invece di 50+
)
.where(Order.id.in_(order_ids))
)
rows = session.execute(stmt).all()
return [{"id": r.id, "total": float(r.total_amount), "status": r.status} for r in rows]
Efektivita sítě: Každý přenesený bajt má uhlíkovou cenu
Přenos dat má skutečné energetické náklady: 0,06-0,1 kWh na GB pro provoz internet (páteř + poslední míle). Aplikace, která přenese 10 TB nekomprimovaných dat za den spotřebuje přibližně 600-1000 kWh pouze na přenos, což odpovídá 200-400 kg CO₂ za den (s průměrnou evropskou intenzitou 350 gCO₂/kWh).
HTTP/3 a QUIC: Účinnost na úrovni protokolu
HTTP/3 s QUIC eliminuje blokování head-of-line a snižuje počet potřebných zpátečních cest navázat spojení. U aplikací s mnoha souběžnými požadavky může HTTP/3 snížit latence 15–30 % a následně aktivní CPU čas serverů.
import express, { Request, Response, NextFunction } from "express";
import compression from "compression";
import { createBrotliCompress, createGzip } from "zlib";
import { pipeline } from "stream/promises";
const app = express();
// Compressione intelligente: scegli il miglior algoritmo
// Brotli: migliore ratio (15-25% superiore a gzip), supportato da tutti i browser moderni
// Gzip: fallback per client vecchi
app.use(compression({
// Comprimi solo se il risparmio e significativo
threshold: 1024, // Min 1KB per comprimere
level: 6, // Bilanciamento CPU/ratio
filter: (req, res) => {
// Non comprimere immagini già compresse (jpeg, webp, png)
const contentType = res.getHeader("Content-Type") as string || "";
if (contentType.includes("image/")) return false;
return compression.filter(req, res);
},
}));
// Middleware: risposta minima con field selection
// Invece di returnare tutto l'oggetto, risponde solo con i campi richiesti
function fieldSelectionMiddleware(req: Request, res: Response, next: NextFunction): void {
const originalJson = res.json.bind(res);
res.json = (body: any) => {
const fields = req.query["fields"];
if (!fields || typeof fields !== "string" || !body) {
return originalJson(body);
}
const requestedFields = fields.split(",").map(f => f.trim());
// Filtra solo i campi richiesti (non muta body originale)
const filtered = Array.isArray(body)
? body.map(item => pickFields(item, requestedFields))
: pickFields(body, requestedFields);
return originalJson(filtered);
};
next();
}
function pickFields(obj: Record<string, unknown>, fields: string[]): Record<string, unknown> {
return fields.reduce<Record<string, unknown>>((acc, field) => {
if (field in obj) {
return { ...acc, [field]: obj[field] };
}
return acc;
}, {});
}
app.use(fieldSelectionMiddleware);
// Esempio: GET /api/users?fields=id,name,email
// Invece di returnare 50 campi, restituisce solo 3
// Riduzione payload tipica: 60-90%
// Cache-Control headers: riduce richieste ripetute
function addCacheHeaders(res: Response, maxAgeSeconds: number): void {
res.setHeader("Cache-Control", `public, max-age={maxAgeSeconds}, stale-while-revalidate=60`);
res.setHeader("Vary", "Accept-Encoding, Accept");
}
app.get("/api/products/:id", async (req: Request, res: Response) => {
const product = await getProduct(req.params["id"]);
// Dati statici: cache aggressiva
if (product?.isStatic) {
addCacheHeaders(res, 86400); // 1 giorno
} else {
addCacheHeaders(res, 300); // 5 minuti
}
res.json(product);
});
// ETag per validazione cache efficiente
// Invece di re-scaricare, il client verifica se il dato e cambiato
app.get("/api/catalog", async (req: Request, res: Response) => {
const catalog = await getCatalog();
const etag = require("crypto")
.createHash("md5")
.update(JSON.stringify(catalog))
.digest("hex");
// Se ETag non cambiato: risponde 304 (0 bytes di payload!)
if (req.headers["if-none-match"] === etag) {
res.status(304).end();
return;
}
res.setHeader("ETag", etag);
res.json(catalog);
});
async function getProduct(id: string): Promise<any> {
return { id, isStatic: true, name: "Product" };
}
async function getCatalog(): Promise<any[]> {
return [];
}
Udržitelné vzory frontend: Klient je součástí problému
Frontend je často nejvíce přehlíženou součástí v analýze emisí softwaru, ale provozování náročného JavaScriptu na milionech zařízení má obrovský souhrnný dopad. Skript 500KB JS běžící na 1 milionu zařízení trvá cca 50 GWh roční energie na uživatelských zařízeních – nikoli na našich serverech.
Optimalizace obrazu: Nejrychlejší výdělky
Obrázky představují v průměru 50-70% hmotnosti webové stránky. Projeďte kolem JPEG to WebP/AVIF zmenšuje velikost o 25-50 %, s ekvivalentním snížením přenosu dat a v době dekódování na zařízení uživatele.
// angular.json - abilita image optimization built-in di Angular
// Angular 17+ ha NgOptimizedImage integrato
import { NgOptimizedImage } from '@angular/common';
import { Component, ChangeDetectionStrategy } from '@angular/core';
@Component({
selector: 'app-product-card',
standalone: true,
imports: [NgOptimizedImage],
changeDetection: ChangeDetectionStrategy.OnPush, // Riduce change detection cycles
template: `
<!-- NgOptimizedImage: lazy loading + srcset automatico + WebP -->
<img
ngSrc="products/laptop-pro.jpg"
[width]="400"
[height]="300"
[priority]="isAboveFold"
loading="lazy"
decoding="async"
alt="Laptop Pro - Vista frontale"
/>
`
})
export class ProductCardComponent {
isAboveFold = false;
}
// build pipeline: conversione automatica a WebP/AVIF con sharp
// scripts/optimize-images.mjs
import sharp from 'sharp';
import { readdir, stat } from 'fs/promises';
import path from 'path';
async function optimizeImages(inputDir: string, outputDir: string): Promise<void> {
const files = await readdir(inputDir);
const imageFiles = files.filter(f => /\.(jpg|jpeg|png)$/i.test(f));
const results = await Promise.all(imageFiles.map(async (file) => {
const inputPath = path.join(inputDir, file);
const baseName = path.basename(file, path.extname(file));
// Genera WebP (supporto universale 2025)
const webpPath = path.join(outputDir, `{baseName}.webp`);
await sharp(inputPath)
.webp({ quality: 80, effort: 6 })
.toFile(webpPath);
// Genera AVIF (compressione superiore, browser moderni)
const avifPath = path.join(outputDir, `{baseName}.avif`);
await sharp(inputPath)
.avif({ quality: 65, effort: 7 })
.toFile(avifPath);
const [origSize, webpSize, avifSize] = await Promise.all([
stat(inputPath).then(s => s.size),
stat(webpPath).then(s => s.size),
stat(avifPath).then(s => s.size),
]);
return {
file,
origKB: (origSize / 1024).toFixed(1),
webpKB: (webpSize / 1024).toFixed(1),
avifKB: (avifSize / 1024).toFixed(1),
webpSaving: ((1 - webpSize/origSize) * 100).toFixed(1) + '%',
avifSaving: ((1 - avifSize/origSize) * 100).toFixed(1) + '%',
};
}));
console.table(results);
}
Tmavý režim a úspora energie na OLED
Tmavý režim: Skutečný dopad na OLED displeje
Na OLED displejích (přítomném ve všech prémiových smartphonech a mnoha noteboocích od roku 2024) černé pixely doslova konzumují 0 energie (pixel OLED nevyzařuje světlo, pokud je vypnutý). Rozhraní s čistě černým pozadím (#000000) na OLED může spotřebovat až O 60-80% méně energie displeje proti bílému pozadí (#FFFFFF). S miliardami OLED zařízení je nabídka dobře implementovaného tmavého režimu zmenšením významné emise na straně uživatele.
/* Dark mode: usa true black (#000000) per massimizzare risparmio OLED */
/* Anche #0d0d0d e sufficiente e più gradevole esteticamente */
:root {
--bg-primary: #ffffff;
--bg-secondary: #f5f5f5;
--text-primary: #1a1a1a;
--surface: #ffffff;
}
/* Auto dark mode: si attiva in base alla preferenza di sistema */
@media (prefers-color-scheme: dark) {
:root {
--bg-primary: #000000; /* True black per OLED */
--bg-secondary: #0d0d0d; /* Quasi-nero, più comfort visivo */
--text-primary: #e8e8e8;
--surface: #111111;
}
}
/* Classe manuale per toggle */
.dark-theme {
--bg-primary: #000000;
--bg-secondary: #0d0d0d;
--text-primary: #e8e8e8;
--surface: #111111;
}
/* Ridurre motion: riduce animazioni = meno GPU = meno energia */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
/* Font system: evita Google Fonts quando possibile */
/* Font di sistema = 0 download, rendering istantaneo */
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
}
/* Se font custom e necessario: usa font-display: swap per evitare blocking */
@font-face {
font-family: "CustomFont";
src: url("/fonts/custom.woff2") format("woff2");
font-display: swap; /* Non blocca rendering mentre scarica */
font-weight: 400 700; /* Variable font: 1 file per tutti i pesi */
}
Udržitelný design API: Méně zpáteční cesty, méně uhlíku
Design API má přímý dopad na emise: špatně navržená API vyžadují více zpátečních cest, přenést více dat, než je nutné, a donutit klienty, aby tak učinili více žádostí o získání informací, které potřebují.
Efektivní stránkování: Kurzor versus posun
Stránkování s OFFSET je energeticky nákladné: načíst stránku 100 s 20 prvky, databáze musí iterovat a zahodit 2000 řádků (OFFSET 2000). Pomocí kurzorového stránkování databáze skočí přímo do správného bodu pomocí indexu, přičemž spotřebuje energii úměrnou pouze číslu vrácených řádků, nikoli těch vynechaných.
import { Pool } from "pg";
interface PaginationResult<T> {
readonly data: readonly T[];
readonly nextCursor: string | null;
readonly totalCount?: number;
}
// ANTI-PATTERN: Offset pagination
// Costo query: O(offset + limit) - cresce con la profondità di paginazione
async function getProductsOffset(
db: Pool,
page: number,
pageSize: number = 20
): Promise<PaginationResult<any>> {
const offset = page * pageSize;
// COSTOSO: il DB scorre 'offset' righe per poi scartarle
const result = await db.query(
"SELECT * FROM products ORDER BY id LIMIT $1 OFFSET $2",
[pageSize, offset]
);
// Esempio pagina 1000 con 20 items: DB scorre 20.020 righe
// Stima: 50x più lenta e 50x più dispendiosa di cursor pagination
return { data: result.rows, nextCursor: null };
}
// PATTERN GREEN: Cursor-based pagination
// Costo query: O(limit) - costante, usa indice
async function getProductsCursor(
db: Pool,
cursor: string | null,
pageSize: number = 20
): Promise<PaginationResult<any>> {
let query: string;
let params: any[];
if (cursor) {
// Decode cursor: contiene l'ID dell'ultimo elemento visto
const lastId = parseInt(Buffer.from(cursor, "base64").toString("utf-8"));
query = `
SELECT id, name, price, category_id
FROM products
WHERE id > $1
ORDER BY id ASC
LIMIT $2
`;
params = [lastId, pageSize + 1]; // +1 per sapere se c'è una pagina successiva
} else {
query = `
SELECT id, name, price, category_id
FROM products
ORDER BY id ASC
LIMIT $1
`;
params = [pageSize + 1];
}
const result = await db.query(query, params);
const rows = result.rows;
const hasMore = rows.length > pageSize;
const data = hasMore ? rows.slice(0, pageSize) : rows;
const lastItem = data[data.length - 1];
// Encode next cursor
const nextCursor = hasMore && lastItem
? Buffer.from(String(lastItem.id)).toString("base64")
: null;
return {
data,
nextCursor,
// Non calcola totalCount (costoso): usa solo se necessario con estimate
};
}
// GraphQL vs REST: quando ognuno e più efficiente
// REST: efficiente per risorse semplici e ben definite, cache HTTP nativa
// GraphQL: efficiente per UI complesse con molti componenti che richiedono dati diversi
// Pattern GraphQL carbon-aware: DataLoader per batch N+1
import DataLoader from "dataloader";
import { GraphQLResolveInfo } from "graphql";
// Senza DataLoader: 1000 resolver -> 1000 query SQL separate
// Con DataLoader: 1000 resolver -> 1 query SQL con WHERE IN
const productLoader = new DataLoader<number, any>(
async (productIds: readonly number[]) => {
const result = await db.query(
"SELECT * FROM products WHERE id = ANY($1)",
[[...productIds]]
);
// Mappa per mantenere l'ordine corretto
const productMap = new Map(result.rows.map(p => [p.id, p]));
return productIds.map(id => productMap.get(id) || null);
},
{ batch: true, maxBatchSize: 100 } // Max 100 per query per sicurezza
);
declare const db: Pool;
Monitorování uhlíku: opatření ke zlepšení
"Co nelze změřit, nelze zlepšit." Monitoring uhlíku na úrovni služba vám umožňuje identifikovat nejemisivnější komponenty, sledovat průběh v čase, a vypočítat SCI skóre (Software Carbon Intensity) jako společnost KPI.
from prometheus_client import Counter, Histogram, Gauge, start_http_server
from functools import wraps
import time
import httpx
import logging
from typing import Callable, Any
logger = logging.getLogger(__name__)
# Metriche Prometheus custom per carbon monitoring
CARBON_INTENSITY_GAUGE = Gauge(
"carbon_intensity_g_co2_per_kwh",
"Current grid carbon intensity in gCO2/kWh",
["region"]
)
ENERGY_CONSUMED_COUNTER = Counter(
"energy_consumed_wh_total",
"Total energy consumed in Wh",
["service", "endpoint", "method"]
)
CARBON_EMITTED_COUNTER = Counter(
"carbon_emitted_gco2_total",
"Total carbon emitted in gCO2",
["service", "endpoint", "method"]
)
REQUEST_DURATION_HISTOGRAM = Histogram(
"http_request_duration_seconds",
"HTTP request duration",
["service", "endpoint", "method"],
buckets=[0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5]
)
SCI_SCORE_GAUGE = Gauge(
"sci_score_mgco2_per_request",
"Software Carbon Intensity score in mgCO2 per functional unit",
["service"]
)
# Stima consumo energetico per tipo di risorsa
# Basato su benchmark empirici per server tipici (TDP ~200W, utilization 20%)
ENERGY_ESTIMATES_WH = {
"cpu_second": 0.011, # ~11 mWh per secondo di CPU @ 200W TDP, 20% util
"memory_gb_second": 0.000375, # ~0.375 mWh per GB*s RAM
"ssd_read_gb": 0.0002, # ~0.2 mWh per GB letto da SSD
"ssd_write_gb": 0.0004, # ~0.4 mWh per GB scritto su SSD
"network_gb": 0.1, # ~100 mWh per GB trasferito (rete)
}
class CarbonMetricsCollector:
"""Raccoglie metriche carbon per Prometheus + Grafana."""
def __init__(self, service_name: str, region: str = "IT"):
self._service = service_name
self._region = region
self._current_intensity = 350.0 # Default gCO2/kWh
async def update_carbon_intensity(self) -> None:
"""Aggiorna l'intensità carbonica dalla grid in tempo reale."""
try:
async with httpx.AsyncClient(timeout=5) as client:
resp = await client.get(
"https://api.electricitymap.org/v3/carbon-intensity/latest",
params={"zone": self._region},
headers={"auth-token": "YOUR_TOKEN"}
)
data = resp.json()
self._current_intensity = data["carbonIntensity"]
CARBON_INTENSITY_GAUGE.labels(region=self._region).set(self._current_intensity)
except Exception as e:
logger.warning("Carbon intensity fetch failed: %s", e)
def track_request(self, endpoint: str, method: str):
"""Decorator per tracciare carbon footprint di ogni endpoint."""
def decorator(func: Callable) -> Callable:
@wraps(func)
async def wrapper(*args: Any, **kwargs: Any) -> Any:
start = time.perf_counter()
result = await func(*args, **kwargs)
duration_s = time.perf_counter() - start
# Stima energia: basata sulla durata (proxy per CPU+I/O)
estimated_energy_wh = duration_s * ENERGY_ESTIMATES_WH["cpu_second"]
# Carbon emessa
carbon_g = estimated_energy_wh * self._current_intensity / 1000
# Aggiorna metriche Prometheus
labels = {"service": self._service, "endpoint": endpoint, "method": method}
ENERGY_CONSUMED_COUNTER.labels(**labels).inc(estimated_energy_wh * 1000) # in mWh
CARBON_EMITTED_COUNTER.labels(**labels).inc(carbon_g * 1000) # in mgCO2
REQUEST_DURATION_HISTOGRAM.labels(**labels).observe(duration_s)
return result
return wrapper
return decorator
def update_sci_score(self, total_carbon_mgco2: float, functional_units: int) -> None:
"""
Aggiorna il SCI score: mgCO2 per functional unit (es. per request).
Formula SCI: (E * I + M) / R
E = energia, I = intensità, M = embodied carbon, R = functional units
"""
if functional_units > 0:
sci = total_carbon_mgco2 / functional_units
SCI_SCORE_GAUGE.labels(service=self._service).set(sci)
# Grafana dashboard query examples (PromQL):
#
# Carbon footprint per endpoint (top 10 più emissivi):
# topk(10, sum by (endpoint) (rate(carbon_emitted_gco2_total[5m])))
#
# SCI score nel tempo:
# sci_score_mgco2_per_request{service="api-gateway"}
#
# Risparmio carbon da cache hits:
# rate(cache_hits_total[5m]) * 0.005 # 5mg CO2 per hit risparmiato
#
# Carbon intensity corrente vs soglia verde:
# carbon_intensity_g_co2_per_kwh > 300 # Alert se supera soglia
Případová studie: Elektronický obchod s 1 milionem návštěv/den – -45% uhlíková stopa
Podívejme se, jak se všechny tyto vzory integrují do konkrétního případu. Elektronický obchod s 1 milionem denní návštěvy, 50 000 objednávek/den a neoptimalizovaná starší architektura. Tým má implementoval vzory popsané v tomto článku v 6měsíčním projektu. Výsledky hovoří sám.
Základní linie architektury před optimalizací
| Komponent | Nejprve konfigurace | Problém | Uhlík/měsíc (odhad) |
|---|---|---|---|
| webové servery | 20x c5.2xlarge always-on | Průměr CPU 8%, 92% nečinnost | 450 kg CO₂ |
| Databáze | db.r5.4xlarge, SELECT * všude | N+1 dotazů, žádný index | 280 kg CO₂ |
| S3 úložiště | Vše ve standardní úrovni | 5 let protokolů nikdy nezpřístupněných v horkém úložišti | 90 kg CO₂ |
| Dávkové úlohy | Opraveno ve 02:00 UTC | Žádné povědomí o uhlíku | 120 kg CO₂ |
| CDN/Cache | Úspěšnost mezipaměti 35 % | TTL jsou příliš krátké, žádné ukládání do mezipaměti okrajů | 180 kg CO₂ |
| Frontend | Balíček JS 2,8 MB, JPEG | Žádné dělení kódu, neoptimalizované obrázky | 200 kg CO₂ |
| Celkový | 1 320 kg CO₂/měsíc |
Výsledky po optimalizaci (6 měsíců)
| Zásah | Vzor použit | Redukce uhlíku | Doba realizace |
|---|---|---|---|
| Automatické škálování + správná velikost EC2 | Snižte množství agresivních instancí | -195 kg CO₂/měsíc (43 %) | 2 týdny |
| Optimalizace dotazu + materializované pohledy | N+1 fix, projekce, dílčí indexy | -140 kg CO₂/měsíc (50 %) | 4 týdny |
| Zásady životního cyklu S3 | Víceúrovňové úložiště: horké/teplé/studené/archiv | -72 kg CO₂/měsíc (80 %) | 1 týden |
| Plánování šarží s ohledem na uhlík | Carbon Aware SDK, zelená okna | -48 kg CO₂/měsíc (40 %) | 3 týdny |
| Víceúrovňová mezipaměť (Redis + CDN) | Míra přístupu do mezipaměti: 35 % -> 87 % | -126 kg CO₂/měsíc (70 %) | 6 týdnů |
| Frontend: WebP/AVIF + dělení kódu | Balíček 2,8 MB -> 380 KB, obrázky WebP | -110 kg CO₂/měsíc (55 %) | 3 týdny |
| Celkem uloženo | -691 kg CO₂/měsíc (-52 %) | 6 měsíců celkem |
Konečným výsledkem je snížení 52 % měsíční uhlíkové stopy (od 1 320 kg do 629 kg CO₂), s roční úsporou cca 8,3 tuny CO₂ ekvivalentní. To se rovná vyřazení asi 4 aut ze silnic na rok. A není Bylo nutné přepsat aplikaci od začátku: všechny zásahy byly inkrementální.
Klíčová lekce z případové studie
Dva zásahy s nejlepší návratností investic (ušetřený uhlík / úsilí) byly:
- Zásady životního cyklu S3 (80% snížení, 1 týden práce): stačí pár Konfigurační linky Infrastructure-as-Code. Je to vzor s poměrem dopad/úsilí vůbec nejvyšší.
- Automatické škálování a správná velikost (43% snížení, 2 týdny): ohromující Většina systémů je přeprovozována. Snižte zajišťování na nezbytné minimum s automatickým škálováním má okamžitý a měřitelný dopad.
Morálka: vždy začínejte s nejjednoduššími vzory. 80 % výdělku pochází z 20 %. zásahy.
Anti-vzory, kterým je třeba se vyhnout: 5 nejčastějších nástrah
Anti-Pattern #1: Agresivní Off-Time Cache Warming Green
Během špiček uhlíkové intenzity (obvykle večer, když solární energie chybí a plyn pokrývá poptávku) spotřebuje více energie než ušetřit nějaké. Zahřívání mezipaměti musí být naplánováno v zelených oknech identifikovaných sadou Carbon Aware SDK.
Anti-Pattern #2: Podrobné protokoly v horkém úložišti
Uchovávat roky protokolů ladění v S3 Standard (horká vrstva) je plýtvání penězi a energií. K protokolům starším než 30 dnů se přistupuje jen zřídka; po 90 dnech téměř nikdy. Implementovat Politika životního cyklu je první akcí, kterou je třeba provést v jakémkoli starším systému.
Anti-Pattern #3: Vždy zapnuto pro občasné pracovní zatížení
Služba, která přijímá 10 požadavků za den, nemusí běžet 24/7 na vyhrazené instanci. Lambda, Cloud Run nebo Fargate s škálováním na nulu jsou zelenou volbou pro přerušované pracovní zatížení. Stále zapnutý EC2 t3.small instance emituje ~15 kg CO₂/měsíc; Lambda pro 10 požadavků/den emituje <0,001 kg CO₂/měsíc. 15 000krát méně.
Anti-Pattern #4: SELECT * ve výrobě
SELECT * přenese všechna pole tabulky, i když je aplikace používá
pouze 2-3. U tabulek s více než 50 sloupci a miliony řádků se tím data vynásobí 10–20
přenesené z databáze s ekvivalentním energetickým dopadem. Vždy používejte explicitní projekci.
Anti-Vzor #5: Monolitický balíček JavaScriptu
Balíček JS o velikosti 3 MB, který si stáhne a zhlédne 1 milion uživatelů denně, trvá cca 2 000 kWh energie CPU na uživatelských zařízeních. S rozdělením kódu a líným načítáním, Počáteční balíček může klesnout na 100-200 kB, což snižuje spotřebu energie 15-30x klientská strana. Energie spotřebovaná na uživatelských zařízeních je součástí rozsahu 3 softwaru.
Kontrolní seznam: Udržitelné vzory pro váš příští sprint
Vysoká priorita (maximální dopad, nízká složitost)
- Implementujte zásady životního cyklu S3/GCS pro automatický přesun dat do studených vrstev
- Analyzujte a omezte poskytování instancí EC2/GCE (AWS Compute Optimizer, GCP Recommender)
- Přidejte plán vypnutí pro vývojová/stagingová prostředí mimo pracovní dobu
- Povolit kompresi gzip/brotli na všech koncových bodech HTTP (pokud již není povoleno)
- Převeďte obrázky na WebP/AVIF v procesu sestavení
- Odebrat vše
SELECT *nahradit je explicitní projekcí
Střední priorita (vysoký dopad, střední složitost)
- Implementujte víceúrovňové ukládání do mezipaměti (L1 v procesu + L2 Redis) pro nejvíce dostupné zdroje
- Vytvářejte materializované pohledy pro často prováděné souhrnné dotazy
- Migrujte časově flexibilní dávkové úlohy na plánování sady Carbon Aware SDK
- Vyřešte problémy N+1 s dychtivým načítáním nebo DataLoaderem (GraphQL)
- Implementuje stránkování založené na kurzoru jako náhradu za odsazené stránkování
- Přidejte k odpovědím API příslušné ETags a Cache-Controls
Nízká priorita (postupná vylepšení)
- Zvažte migraci na HTTP/3 pro aplikace s vysokou latencí
- Implementujte výběr pole v koncových bodech REST (param
?fields=) - Přidejte tmavý režim se skutečnou černou, abyste snížili spotřebu energie na OLED zařízeních uživatelů
- Nakonfigurujte monitorování uhlíku pomocí řídicího panelu Prometheus + Grafana pro skóre SCI
- Zaveďte dílčí indexy pro filtrované dotazy na malé podmnožiny dat
- Vyhodnoťte bezserverové (lambda, cloudové funkce) pro pracovní zátěže s <100 požadavky/hodinu
Závěry: Udržitelná architektura jako standardní praxe
Udržitelné architektonické vzory nejsou luxusem vyhrazeným pro velké technologické společnosti s rozpočtem pro udržitelnost: jsou to dobré inženýrské postupy, které se zlepšují současně výkon, náklady a dopad na životní prostředí. Vrstvené úložiště, efektivní ukládání do mezipaměti, dávka Plánování s ohledem na uhlík, správná velikost a optimalizace dotazů vytvářejí efektivnější systémy pod každou dimenzí.
Případová studie ukazuje, že je možné snížit uhlíkovou stopu starého systému 50 %+ za 6 měsíců s postupnými zásahy, aniž byste museli přepisovat architekturu od začátku. Klíč je začít s intervencemi s vysokým dopadem a nízkou složitostí (politiky životního cyklu, správná velikost, optimalizace dotazu), změřte výsledky pomocí metrik SCI a poté pokračujte směrem sofistikovanější optimalizace.
La směrnice CSRD (Corporate Sustainability Reporting Directive), platná pro velké evropské společnosti od roku 2025 a pro středně velké společnosti od roku 2026, bude vyžadovat mnoho organizace hlásit emise softwaru jako součást emisí rozsahu 3 udržitelná architektura již není jen etickou volbou: stává se požadavkem regulační a konkurenční.
Další článek v seriálu
Desátý a poslední článek doplní sérii Green Software ESG, CSRD a dodržování předpisů pro softwarové týmy: jak strukturovat reporting povinné verze softwaru, vytvářet auditní záznamy pro regulační orgány a integrovat je Metriky SCI v procesech podnikového řízení. Probereme také praktické nástroje pro podávání zpráv: Protokol GHG, ESRS E1 a rámec dopadu GSF.
Další zdroje
- Carbon Aware SDK (Green Software Foundation): github.com/Green-Software-Foundation/carbon-aware-sdk
- Electricity Maps API: Údaje o uhlíkové intenzitě v reálném čase pro 50+ regionů
- AWS Compute Optimizer: Automatická doporučení správné velikosti
- Impact Framework (GSF): Výpočet SCI podle složek
- Web Almanach 2024 (Archiv HTTP): Skutečné statistiky výkonu a velikosti webu
- Cloudová uhlíková stopa: Open source nástroj pro měření cloudových emisí s více poskytovateli







