Skip to content

Database & Scaling Strategy

Last Updated: 2026-01-18
Status: Active — guiding pilot through Year 3 infrastructure decisions


Overview

This document outlines Lantern's database selection rationale, scaling thresholds, migration triggers, and cost considerations. It also addresses how location-based features (Google Maps API, geofencing) impact infrastructure decisions.

TL;DR: Firestore handles our needs for 3-5 years. Focus on merchant adoption, not premature optimization.


Current Architecture

Firestore (Primary Database)

  • What we use it for:

    • User profiles (encrypted location, preferences, Wave history)
    • Merchant data (venue info, offers, analytics)
    • Offer redemptions & check-in logs
    • Encrypted notification queues
  • Why Firestore:

    • Rich querying: Compound indexes for "nearby offers user hasn't redeemed"
    • Offline-first: IndexedDB persistence for PWA (enabled in firebase.js)
    • Real-time: Document listeners for live offer updates
    • Security: Granular Firestore Security Rules
    • Scalability: Horizontal scaling built-in (no sharding needed)

Implementation Details

  • Persistence: IndexedDB with memory fallback (see firebase.js)
  • Location Privacy: Ephemeral location checks only (never store raw coordinates)
  • Encryption: Client-side encryption before Firestore writes (see encryption.js)

Scale Limits & Thresholds

Firestore Capacity (What You Actually Get)

Free Tier (Spark Plan)

  • 50K document reads/day
  • 20K document writes/day
  • 20K document deletes/day
  • 1 GiB storage
  • Use case: Pilot phase (5-10 merchants, <1K users)

Blaze Plan (Pay-as-you-go)

  • Reads: $0.06 per 100K documents
  • Writes: $0.18 per 100K documents
  • Deletes: $0.02 per 100K documents
  • Storage: $0.18/GB/month
  • No hard limits — scales to millions of ops/day

Real-World Benchmarks

ScaleUsersOperations/DayMonthly CostNotes
Pilot<1K50K-100K$0 (free tier)5-10 merchants, light usage
Early Growth1K-10K500K-1M$50-150Typical SaaS startup
Scaling10K-100K5M-10M$200-800Regional platform
Enterprise100K-1M50M-100M$2K-5KMulti-region, heavy traffic
Breaking Point1M+100M+$5K-15K+Consider hybrid architecture

Lantern Year 1 projection: 40 merchants × ~500 users/merchant = 20K users
~1M operations/day = $50-100/month (0.02% of Firestore's comfort zone)


Firebase Realtime Database vs Firestore

FeatureFirestore ✅ (Recommended)Realtime Database
QueryingRich (compound indexes, range queries, ordering)Limited (shallow queries only)
ScalabilityUnlimited horizontal scale~200K concurrent connections/instance
Data ModelDocument/collection (structured)JSON tree (denormalized)
Offline SupportExcellent (IndexedDB)Good (local cache)
PricingPer-operationPer-GB-downloaded
Real-timeDocument listenersFull tree sync
Best forComplex queries, structured dataSimple chat, live scoreboards

Decision Matrix for Lantern

Firestore wins because we need:

  • Compound queries: "offers within 5km of user who hasn't redeemed yet"
  • Structured data: Users, merchants, offers, redemptions as separate collections
  • Offline-first: PWA requires IndexedDB persistence
  • Security rules: Fine-grained access control per document

Realtime DB would be better for:

  • Simple key-value stores
  • Global state sync (all users see same data)
  • <50ms latency for every update (e.g., multiplayer games, typing indicators)

Verdict: Stick with Firestore. Our use case is location-based discovery, not real-time chat.


Google Maps API Impact

API Integration Points

1. Places API (Venue Search & Discovery)

  • Use case: Merchant onboarding (verify business exists, get coordinates)
  • Cost: $32 per 1K requests (Place Details)
  • Pilot usage: ~100-200 requests/month (manual merchant onboarding)
  • Year 1 cost: <$10/month

2. Geolocation API (Browser-based)

  • Use case: Get user's current location for "nearby offers"
  • Cost: Free (browser API, no Google quota)
  • Implementation: navigator.geolocation.getCurrentPosition()

3. Maps JavaScript API (Optional: Display map UI)

  • Use case: Show merchant locations on map view
  • Cost: $7 per 1K map loads (Dynamic Maps)
  • Pilot cost: <$10/month (most users just see list view)
  • Optimization: Lazy load map only when user taps "Map View"

4. Geocoding API (Optional)

  • Use case: Convert addresses to coordinates
  • Cost: $5 per 1K requests
  • When needed: Merchant address input (if not using Places API)

Cost Projections with Google Maps

ScaleUsersMaps Loads/MonthPlaces LookupsMonthly Cost% of Total Infra
Pilot100500-1K100-200$5-10<10%
Early Growth1K5K-10K500-1K$50-100~30%
Scaling10K50K-100K5K-10K$500-1K~30%
Enterprise100K500K-1M50K-100K$5K-10K~20%

Key Insight: Google Maps costs scale linearly with user activity but remain <30% of total infrastructure costs (Firebase + Cloudflare + Maps). Payment processing fees dwarf all API costs.

Optimization Strategies:

  1. Cache Place Details: Store merchant coordinates in Firestore (don't re-query)
  2. Lazy load maps: Only load JavaScript API when user taps "Map View"
  3. Static maps for previews: Use Static Maps API ($2/1K loads) for thumbnails
  4. Batch geocoding: Geocode all merchant addresses once during onboarding

Database Impact: Minimal

  • Google Maps responses are cached in Firestore (merchant collection)
  • User location is ephemeral (never stored, only used for proximity checks)
  • No additional database load from Maps integration

Geofencing Implementation & Impact

Last Updated: 2026-01-18 (Issue #28 implementation)

Why Server-Side Validation Matters (Fraud Prevention)

Client-side validation alone is not sufficient for security-critical features like geofencing. Here's why:

Client-Side Only (Vulnerable)

User's Browser                          Firestore
     │                                      │
     ├─ calculateDistance() ───────────────►│
     │  (JavaScript check)                  │
     │                                      │
     │  ✅ Pass? ──► Write lantern ─────────►│ Saved!

Attack vectors:

  1. DevTools modification — User opens browser DevTools, modifies JavaScript to skip distance check
  2. Direct Firestore writes — User uses Firebase SDK directly to write lantern document, bypassing app entirely
  3. Request interception — User intercepts network request and modifies coordinates before sending
  4. Location spoofing apps — GPS spoofing apps on rooted/jailbroken devices report fake coordinates

Server-Side Validation (Secure) ✅ Implemented

User's Browser              Cloud Function              Firestore
     │                           │                          │
     ├─ lightLanternSecure() ───►│                          │
     │  (sends coordinates)      │                          │
     │                           ├─ Fetch venue ───────────►│
     │                           │◄─────────────────────────┤
     │                           │                          │
     │                           ├─ calculateDistance()     │
     │                           │  (SERVER validates!)     │
     │                           │                          │
     │                           │  ✅ Pass? Write lantern ─►│
     │◄── Success ───────────────┤                          │

Why this is secure:

  1. Tamper-proof — Distance calculation happens on Google's servers, not user's browser
  2. No bypass possible — User can send fake coordinates, but server fetches real venue location and validates
  3. Audit trail — Server can log suspicious patterns (e.g., user "teleporting" between distant venues)
  4. Firestore rules — Can restrict direct writes, requiring all lanterns to be created via Cloud Function

Business impact:

  • Merchant trust — Merchants can trust that users redeeming offers were actually at the venue
  • Fraud detection — Server can flag velocity violations (checking into venues 50km apart within 5 minutes)
  • Legal compliance — Location verification for age-restricted venues or regulated promotions

Geofencing Strategy

1. Client-Side Haversine Distance ✅ Implemented

  • How it works: Browser gets GPS → calculates distance to venue → blocks "Light Lantern" if >100m (or custom venue.radius)
  • Implementation: calculateDistance() using Haversine formula
  • Firestore query: Geohash-based filtering for nearby venue discovery
  • Database impact: Low — 1 read per location check (~5-10K reads/day for 1K users)
  • Cost: <$5/month

2. Server-Side Verification (Fraud Prevention) ✅ Implemented

  • How it works: Client calls lightLanternSecure Cloud Function → server fetches venue → calculates distance → validates proximity → writes lantern to Firestore
  • Implementation: lightLanternSecure and extinguishLanternSecure Cloud Functions
  • Database impact: Medium — 1 venue read + 1 lantern query + 1 lantern write + 1 venue update per lighting
  • Cost: Cloud Functions $0.40/million invocations + Firestore writes ($0.18/100K)
  • Total cost: <$10/month for pilot
  • Fallback: Client-side validation if Cloud Function unavailable

3. QR Code Verification (High-Value Offers)

  • How it works: Merchant shows QR code → user scans → server validates merchant signature
  • Database impact: Low — 1 read to verify QR signature
  • Use case: BOGO offers, >$20 value redemptions
  • Cost: Negligible (<$1/month)

Geofencing Data Model

Firestore Structure

javascript
// merchants/{merchantId}
{
  name: "Coffee Shop",
  location: {
    lat: 32.7157,
    lng: -117.1611,
    geohash: "9mudgbe0c",  // For proximity queries
    radius: 200  // meters
  },
  offers: [...]
}

// offers/{offerId}
{
  merchantId: "abc123",
  geohash: "9mudgbe0c",  // Indexed for "nearby offers" query
  redemptionCount: 42,
  maxRedemptions: 100,
  perUserLimit: 1
}

// redemptions/{redemptionId}
{
  userId: "xyz789",
  offerId: "offer456",
  timestamp: "2026-01-11T10:30:00Z",
  verifiedLocation: true,  // Server-side validation flag
  verificationMethod: "geofence" | "qr-code"
}

Geohash Indexing for Proximity Queries

Implementation: Use geofire-common library

javascript
import { geohashForLocation, geohashQueryBounds } from 'geofire-common'

// 1. When merchant creates offer, store geohash
const geohash = geohashForLocation([lat, lng])
await setDoc(doc(db, 'offers', offerId), {
  ...offerData,
  geohash,
  location: { lat, lng, radius: 200 }
})

// 2. Query offers within 5km of user
const center = [userLat, userLng]
const radiusInM = 5000
const bounds = geohashQueryBounds(center, radiusInM)

const promises = bounds.map((b) => {
  const q = query(
    collection(db, 'offers'),
    orderBy('geohash'),
    startAt(b[0]),
    endAt(b[1])
  )
  return getDocs(q)
})

const snapshots = await Promise.all(promises)
// Filter results by actual distance (geohash is approximate)

Database Operations:

  • Writes: 1 geohash calculation per offer creation (negligible cost)
  • Reads: 2-4 Firestore queries per "nearby offers" check (~10-20 reads)
  • Index: Composite index on geohash (auto-created by Firestore)

Cost Impact:

  • 1K users checking location 5x/day = 50K reads/day = 1.5M reads/month
  • Cost: 1.5M reads × $0.06/100K = $0.90/month 🎯

GCP Migration Path (When Needed)

Firebase is GCP under the hood. Migration is gradual, not binary.

Migration Triggers (Not Before Year 3-5)

TriggerSymptomSolutionTimeline
Query latency >500msComplex joins, heavy aggregationsAdd Cloud SQL (Postgres) for relational dataMonth 18-24
Analytics queries slow FirestoreMerchant dashboards timeoutStream to BigQuery for analyticsMonth 12-18
>1M ops/dayFirestore costs >$1K/monthAdd Redis cache for hot dataMonth 24-36
Multi-region complianceGDPR, data residency lawsCloud Spanner or regional FirestoreYear 3+
Sub-10ms query latencyReal-time Wave interactionsMemorystore (Redis) + CDN edge cachingYear 2-3

Hybrid Architecture (Long-Term Vision)

┌─────────────────────────────────────────────────────────┐
│                      Client (PWA)                       │
│  - IndexedDB offline cache                              │
│  - Encrypted local storage                              │
└─────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────┐
│                   Cloud Load Balancer                   │
└─────────────────────────────────────────────────────────┘

        ┌──────────────────┼──────────────────┐
        ▼                  ▼                  ▼
┌──────────────┐  ┌─────────────────┐  ┌─────────────┐
│   Firestore  │  │  Cloud Functions │  │ Memorystore │
│ (user data,  │  │  (business logic,│  │   (Redis)   │
│  offers,     │  │   auth, webhooks)│  │ (hot cache) │
│  redemptions)│  │                  │  │             │
└──────────────┘  └─────────────────┘  └─────────────┘
        │                  │
        ▼                  ▼
┌──────────────┐  ┌─────────────────┐
│  BigQuery    │  │   Cloud SQL     │
│ (analytics,  │  │  (relational    │
│  reporting)  │  │   merchant data)│
└──────────────┘  └─────────────────┘

Migration sequence:

  1. Year 1-2: Firestore only (current)
  2. Year 2-3: Firestore + BigQuery (analytics offload)
  3. Year 3-4: Firestore + BigQuery + Redis (hot cache)
  4. Year 4-5: Firestore + BigQuery + Redis + Cloud SQL (relational data)

Cost Comparison at 1M Active Users:

ArchitectureMonthly CostUse Case
Firestore alone$500-1,500Simple apps, <10M ops/day
Firestore + BigQuery$800-2,000Analytics-heavy
Firestore + Redis$1,200-2,500Low-latency reads
Full GCP stack (SQL + Datastore + Redis + BigQuery)$2,500-5,000Enterprise multi-region

Cost Bottlenecks (In Order of Impact)

Pilot → Year 3 Cost Breakdown

Cost CategoryPilot (100 users)Year 1 (20K users)Year 3 (100K users)% of Total
Payment Processing (Stripe)$240/month$1,500/month$15,000/month85-90%
Google Maps API$5-10$50-100$500-1,0003-5%
Firestore$0 (free)$50-150$500-1,0003-5%
Cloud Functions$0 (free)$10-20$100-2001-2%
Cloudflare Hosting$0 (free)$0 (free)$0 (free)0%
Total Infrastructure~$245~$1,610~$16,600100%

Key Insight: Payment processing dominates costs (85-90%). Database optimization is <5% of total spend.

Priority order for cost optimization:

  1. 🔥 Negotiate Stripe rates (2.9% → 2.5% saves $1K/month at scale)
  2. 📍 Cache Google Maps data (saves $200-500/month)
  3. 💾 Optimize Firestore queries (saves $50-100/month)
  4. Add Redis cache (costs $100/month but improves UX)

Action Items

Immediate (Pilot Phase)

  • [x] Enable Firestore offline persistence ✅ (already enabled in firebase.js)
  • [ ] Implement geohash indexing for proximity queries
    • Use geofire-common library
    • Create composite index: offers collection → geohash field
    • Document in Firestore rules
  • [ ] Set Firebase budget alerts
    • Firebase Console → Usage & Billing → Budget Alerts
    • Alert threshold: $50/month (10x expected pilot cost)
  • [ ] Create Firestore composite indexes
    • offers collection: [geohash, redemptionCount]
    • redemptions collection: [userId, timestamp]
    • Run firebase deploy --only firestore:indexes
  • [ ] Document data model
    • Create docs/engineering/architecture/DATA_MODEL.md
    • Define collections, fields, security rules, indexes

Year 1 (Growth Phase)

  • [ ] Upgrade to Blaze plan when free tier limits hit (~1K users)
  • [ ] Implement geofence server-side validation (Cloud Function)
    • Verify user location matches redemption location
    • Flag suspicious redemptions (>5km from venue)
    • Log fraud metrics to Firestore
  • [ ] Add Google Maps caching
    • Store Place Details in Firestore merchants collection
    • Cache for 90 days, refresh on merchant update
  • [ ] Monitor query performance
    • Enable Firestore performance monitoring
    • Set up Cloud Logging alerts for >500ms queries

Year 2-3 (Scaling Phase)

  • [ ] Add BigQuery analytics pipeline
    • Stream Firestore writes to BigQuery
    • Build merchant analytics dashboards
    • Offload reporting queries from Firestore
  • [ ] Evaluate Redis cache if query latency >300ms
    • Cache hot merchant offers (updated hourly)
    • Cache user profiles (invalidate on update)
    • Use Memorystore (managed Redis on GCP)
  • [ ] Review migration triggers (quarterly)
    • Query latency P95
    • Monthly Firestore costs
    • User-reported performance issues


Changelog

DateChangeRationale
2026-01-11Initial versionDocument scaling strategy & migration triggers

Next Review: 2026-07-01 (after pilot completion, 6 months)

Built with VitePress