Zum Inhalt

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: statisticsCustomers Collection
  • Behalten: statisticsCustomerSales Collection (identischer Inhalt)
  • Vorteil: 50% weniger Schreiboperationen für Kundenstatistiken

Firestore Struktur vorher:

statisticsCustomers/{timePeriod}_{customerId}  ← REDUNDANT
statisticsCustomerSales/{timePeriod}_{customerId}

Firestore Struktur nachher:

statisticsCustomerSales/{timePeriod}_{customerId}  ← Einzige Quelle

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

  1. Immer Limits verwenden: Bei Listen mit potenziell vielen Einträgen
  2. Server-side Sortierung: orderBy() in Firestore statt Client
  3. Top-N Pattern: Nur die benötigten Daten speichern
  4. Memory Management: Explizites Clearing bei großen Objekten
  5. 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)

# Cloud Function Metrics anzeigen
firebase functions:log --only scheduledHourlyStatisticsUpdate --limit 10