Proč jsou aplikace pomalé?
Většina performance problémů spadá do tří kategorií:
- Špatné datové struktury - O(n) místo O(1) operace
- Chybějící cache - Opakované výpočty a DB dotazy
- Neoptimalizované DB queries - Chybějící indexy, N+1 problém
V praxi jsme dosáhli zrychlení z 2 hodin na 3 minuty jen správnou kombinací těchto technik.
1. Datové struktury: List vs HashSet vs Dictionary
Problém: O(n) lookup v Listu
Představte si, že máte milion záznamů a pro každý potřebujete ověřit, zda existuje v jiném seznamu:
// ❌ ŠPATNĚ - O(n) pro každý lookup
List<string> existingIds = GetExistingIds(); // 100k záznamů
foreach (var record in records) // 1M záznamů
{
if (existingIds.Contains(record.Id)) // O(n) = O(100k)
{
// zpracování
}
}
// Celkem: O(n × m) = O(100 miliard operací)
Řešení: HashSet pro O(1) lookup
// ✅ SPRÁVNĚ - O(1) pro každý lookup
HashSet<string> existingIds = new HashSet<string>(GetExistingIds());
foreach (var record in records)
{
if (existingIds.Contains(record.Id)) // O(1)
{
// zpracování
}
}
// Celkem: O(n) = O(1 milion operací)
Výsledek: Zrychlení až 100 000× pro velké datasety.
Dictionary pro key-value lookup
// ❌ ŠPATNĚ - Opakované hledání v listu
foreach (var order in orders)
{
var customer = customers.FirstOrDefault(c => c.Id == order.CustomerId);
// O(n) pro každý order
}
// ✅ SPRÁVNĚ - Dictionary s O(1) lookup
var customerDict = customers.ToDictionary(c => c.Id);
foreach (var order in orders)
{
var customer = customerDict[order.CustomerId]; // O(1)
}
2. Caching: Nedělejte stejnou práci dvakrát
In-memory cache s Dictionary
private static readonly Dictionary<string, Product> _cache = new();
public Product GetProduct(string id)
{
if (_cache.TryGetValue(id, out var cached))
return cached;
var product = _db.Products.Find(id);
_cache[id] = product;
return product;
}
Distribuovaný cache s Redis
public async Task<Product> GetProductAsync(string id)
{
var cacheKey = $"product:{id}";
var cached = await _redis.GetAsync<Product>(cacheKey);
if (cached != null)
return cached;
var product = await _db.Products.FindAsync(id);
await _redis.SetAsync(cacheKey, product, TimeSpan.FromMinutes(10));
return product;
}
Kdy cachovat?
- Často čtená, zřídka měněná data
- Výpočetně náročné operace
- Externí API volání
Cache invalidation
"There are only two hard things in Computer Science: cache invalidation and naming things." - Phil Karlton
Strategie:
- TTL (Time To Live) - Automatická expirace po X minutách
- Write-through - Invalidace při každém zápisu
- Event-driven - Invalidace pomocí message queue
3. Databázové optimalizace
Indexy - základní nástroj
-- Bez indexu: Full table scan O(n)
SELECT * FROM Orders WHERE CustomerId = 123;
-- Scan: 1,000,000 rows
-- S indexem: Index seek O(log n)
CREATE INDEX IX_Orders_CustomerId ON Orders(CustomerId);
SELECT * FROM Orders WHERE CustomerId = 123;
-- Scan: 3 rows (index seek)
Execution plány
Vždy analyzujte execution plan před optimalizací:
-- SQL Server
SET STATISTICS IO ON;
SET STATISTICS TIME ON;
-- Nebo
EXPLAIN ANALYZE SELECT * FROM Orders WHERE CustomerId = 123;
Hledejte:
- Table Scan - Chybí index
- Key Lookup - Zvažte covering index
- Sort - Můžete změnit index?
N+1 problém
// ❌ N+1 problém - 1 + N dotazů
var orders = db.Orders.ToList(); // 1 dotaz
foreach (var order in orders)
{
var customer = db.Customers.Find(order.CustomerId); // N dotazů
}
// ✅ Eager loading - 1 dotaz
var orders = db.Orders
.Include(o => o.Customer)
.ToList();
Batch processing
// ❌ ŠPATNĚ - 1000 roundtripů
foreach (var item in items)
{
await db.SaveAsync(item);
}
// ✅ SPRÁVNĚ - 1 roundtrip
db.BulkInsert(items);
Reálný případ: Banka - z 2 hodin na 3 minuty
Systém generování smluv pro banku. Původně trval 2+ hodiny:
- Analýza: 95% času v opakovaném vyhledávání v Listu
- Fix 1: List → HashSet pro lookup = 10× zrychlení
- Fix 2: Dictionary cache pro opakované DB dotazy = 3× zrychlení
- Fix 3: Batch insert místo jednotlivých = 2× zrychlení
Výsledek: 2 hodiny → 3 minuty (40× zrychlení)
Checklist pro optimalizaci
- Profilujte před optimalizací (měřte, ne hádejte)
- Zkontrolujte datové struktury (List → HashSet/Dictionary)
- Přidejte cache pro opakované operace
- Analyzujte DB execution plány
- Použijte batch processing pro velké objemy
- Změřte po optimalizaci
Potřebujete pomoc s optimalizací? Ozvěte se nám - máme zkušenosti s 100× zrychlením enterprise systémů.