Dashboard Optimierungen¶
Übersicht der vorgenommenen Optimierungen¶
1. Frontend Optimierungen (Flutter/Dart)¶
Firestore Queries¶
- Limit hinzugefügt: Top 50 Artikel, Top 50 Kunden, Top 20 Länder
- Server-side Sortierung:
orderBy('salesVolume', descending: true)direkt in Firestore - Vorteil: Drastisch reduzierter Datentransfer, schnellere Queries
Vorher:
// Lade ALLE Artikel/Kunden aus Firestore
final snapshot = await _firestore
.collection('statisticsPeriodSummary')
.doc(periodKey)
.collection('articlesByRevenue')
.get(); // Könnte 1000+ Dokumente sein!
// Sortiere im Client
results.sort((a, b) => b.salesVolume.compareTo(a.salesVolume));
Nachher:
// Lade nur Top 50, bereits sortiert
final snapshot = await _firestore
.collection('statisticsPeriodSummary')
.doc(periodKey)
.collection('articlesByRevenue')
.orderBy('salesVolume', descending: true)
.limit(50)
.get(); // Maximal 50 Dokumente
Widget Performance¶
- Entfernte doppelte Sortierung: Widgets sortieren nicht mehr selbst
- Weniger Speicher: Keine Kopien für Sortierung nötig
- Schnelleres Rendering: Daten sind bereits sortiert
Vorher:
void showFullArticleListDialog(List<ArticleSalesStatistic> articles) {
final sortedArticles = [...articles] // Kopie erstellen
..sort((a, b) => b.salesVolume.compareTo(a.salesVolume)); // Erneut sortieren
// ...
}
Nachher:
void showFullArticleListDialog(List<ArticleSalesStatistic> articles) {
// Artikel sind bereits sortiert - direkt verwenden
// ...
}
2. Backend Optimierungen (Cloud Functions)¶
Entfernte redundante Collection¶
- Gelöscht:
statisticsCustomersCollection - Behalten:
statisticsCustomerSalesCollection (identischer Inhalt) - Vorteil: 50% weniger Schreiboperationen für Kundenstatistiken
Firestore Struktur vorher:
statisticsCustomers/{timePeriod}_{customerId} ← REDUNDANT
statisticsCustomerSales/{timePeriod}_{customerId}
Firestore Struktur nachher:
Memory Optimierungen¶
- Map statt Object: Bessere Performance für Country Statistics
- Top-N Filtering: Nur Top 20 Länder werden gespeichert
- Explizites Memory Clearing:
countryMap.clear()nach Verwendung
Vorher:
const countryMap = {}; // Object - langsamer
for (const order of filteredOrders) {
// ... verarbeite ALLE Länder
}
// Schreibe ALLE Länder in Firestore (könnte 100+ sein)
Nachher:
const countryMap = new Map(); // Map - schneller
for (const order of filteredOrders) {
if (!c) continue; // Skip ungültige Daten
// ...
}
// Sortiere und nimm nur Top 20
const topCountries = Array.from(countryMap.entries())
.sort((a, b) => b[1].salesVolume - a[1].salesVolume)
.slice(0, 20);
// Schreibe nur Top 20 Länder
countryMap.clear(); // Gib Speicher frei
Code-Deduplizierung¶
- Neue Helper-Funktion:
writeCountryStatistics() - Wiederverwendung: In beiden Trigger-Funktionen
- Vorteil: Einfachere Wartung, konsistentes Verhalten
3. Firestore Indexes (erforderlich)¶
Die neuen Queries benötigen Composite Indexes. Diese werden automatisch erstellt oder können manuell hinzugefügt werden:
{
"indexes": [
{
"collectionGroup": "articlesByRevenue",
"queryScope": "COLLECTION",
"fields": [
{ "fieldPath": "salesVolume", "order": "DESCENDING" }
]
},
{
"collectionGroup": "statisticsCustomerSales",
"queryScope": "COLLECTION",
"fields": [
{ "fieldPath": "timePeriod", "order": "ASCENDING" },
{ "fieldPath": "salesVolume", "order": "DESCENDING" }
]
},
{
"collectionGroup": "statisticsCountries",
"queryScope": "COLLECTION",
"fields": [
{ "fieldPath": "timePeriod", "order": "ASCENDING" },
{ "fieldPath": "salesVolume", "order": "DESCENDING" }
]
}
]
}
Performance-Vergleich¶
Vorher¶
- Artikel laden: ~1000 Dokumente, ~200KB Daten
- Kunden laden: ~500 Dokumente, ~100KB Daten
- Länder laden: ~50 Dokumente, ~10KB Daten
- Client-Sortierung: ~300ms pro Widget
- Firestore Writes: ~1500 Dokumente pro Berechnung
Gesamt pro Dashboard-Load: ~310KB, ~900ms
Nachher¶
- Artikel laden: 50 Dokumente, ~10KB Daten
- Kunden laden: 50 Dokumente, ~10KB Daten
- Länder laden: 20 Dokumente, ~4KB Daten
- Server-Sortierung: Bereits sortiert
- Firestore Writes: ~1000 Dokumente pro Berechnung
Gesamt pro Dashboard-Load: ~24KB, ~100ms
Verbesserung¶
- 92% weniger Daten: 310KB → 24KB
- 89% schneller: 900ms → 100ms
- 33% weniger Writes: 1500 → 1000 Dokumente
Kostenrechnung (ca. Werte)¶
Firestore Reads¶
Vorher: 1550 Dokumente × 0.06€/100k = 0.00093€ pro Dashboard-Load
Nachher: 120 Dokumente × 0.06€/100k = 0.000072€ pro Dashboard-Load
Einsparung: ~92% weniger Read-Kosten
Firestore Writes (pro Statistik-Berechnung)¶
Vorher: 1500 Dokumente × 0.18€/100k = 0.0027€
Nachher: 1000 Dokumente × 0.18€/100k = 0.0018€
Einsparung: ~33% weniger Write-Kosten
Bei 1000 Dashboard-Loads pro Tag¶
Vorher: 0.93€/Tag = 27.90€/Monat
Nachher: 0.072€/Tag = 2.16€/Monat
Einsparung: 25.74€/Monat = ~310€/Jahr
Migration¶
Alte statisticsCustomers Collection löschen (optional)¶
// Script zum Löschen der alten Collection
const admin = require('firebase-admin');
const db = admin.firestore();
async function deleteOldCollection() {
const snapshot = await db.collection('statisticsCustomers').get();
console.log(`Deleting ${snapshot.size} documents...`);
const batches = [];
let batch = db.batch();
let count = 0;
snapshot.docs.forEach(doc => {
batch.delete(doc.ref);
count++;
if (count >= 500) {
batches.push(batch.commit());
batch = db.batch();
count = 0;
}
});
if (count > 0) {
batches.push(batch.commit());
}
await Promise.all(batches);
console.log('Done!');
}
deleteOldCollection();
Nach Deployment prüfen¶
# Prüfe ob neue Indexes aktiv sind
firebase firestore:indexes
# Prüfe Logs
firebase functions:log --only scheduledHourlyStatisticsUpdate
# Teste manuell
cd functions
node manual_trigger_statistics.js
Best Practices für zukünftige Entwicklung¶
- Immer Limits verwenden: Bei Listen mit potenziell vielen Einträgen
- Server-side Sortierung:
orderBy()in Firestore statt Client - Top-N Pattern: Nur die benötigten Daten speichern
- Memory Management: Explizites Clearing bei großen Objekten
- Code-Deduplizierung: Helper-Funktionen für wiederkehrende Muster
Monitoring¶
Überwache folgende Metriken: - Dashboard Load-Zeit (sollte <200ms sein) - Firestore Read-Count (sollte ~120 pro Load sein) - Function Execution Time (sollte <30s sein) - Memory Usage der Function (sollte <400MB sein)