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
| Scale | Users | Operations/Day | Monthly Cost | Notes |
|---|---|---|---|---|
| Pilot | <1K | 50K-100K | $0 (free tier) | 5-10 merchants, light usage |
| Early Growth | 1K-10K | 500K-1M | $50-150 | Typical SaaS startup |
| Scaling | 10K-100K | 5M-10M | $200-800 | Regional platform |
| Enterprise | 100K-1M | 50M-100M | $2K-5K | Multi-region, heavy traffic |
| Breaking Point | 1M+ | 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
| Feature | Firestore ✅ (Recommended) | Realtime Database |
|---|---|---|
| Querying | Rich (compound indexes, range queries, ordering) | Limited (shallow queries only) |
| Scalability | Unlimited horizontal scale | ~200K concurrent connections/instance |
| Data Model | Document/collection (structured) | JSON tree (denormalized) |
| Offline Support | Excellent (IndexedDB) | Good (local cache) |
| Pricing | Per-operation | Per-GB-downloaded |
| Real-time | Document listeners | Full tree sync |
| Best for | Complex queries, structured data | Simple 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
| Scale | Users | Maps Loads/Month | Places Lookups | Monthly Cost | % of Total Infra |
|---|---|---|---|---|---|
| Pilot | 100 | 500-1K | 100-200 | $5-10 | <10% |
| Early Growth | 1K | 5K-10K | 500-1K | $50-100 | ~30% |
| Scaling | 10K | 50K-100K | 5K-10K | $500-1K | ~30% |
| Enterprise | 100K | 500K-1M | 50K-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:
- Cache Place Details: Store merchant coordinates in Firestore (don't re-query)
- Lazy load maps: Only load JavaScript API when user taps "Map View"
- Static maps for previews: Use Static Maps API ($2/1K loads) for thumbnails
- 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:
- DevTools modification — User opens browser DevTools, modifies JavaScript to skip distance check
- Direct Firestore writes — User uses Firebase SDK directly to write lantern document, bypassing app entirely
- Request interception — User intercepts network request and modifies coordinates before sending
- 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:
- Tamper-proof — Distance calculation happens on Google's servers, not user's browser
- No bypass possible — User can send fake coordinates, but server fetches real venue location and validates
- Audit trail — Server can log suspicious patterns (e.g., user "teleporting" between distant venues)
- 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
lightLanternSecureCloud Function → server fetches venue → calculates distance → validates proximity → writes lantern to Firestore - Implementation:
lightLanternSecureandextinguishLanternSecureCloud 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
// 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
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)
| Trigger | Symptom | Solution | Timeline |
|---|---|---|---|
| Query latency >500ms | Complex joins, heavy aggregations | Add Cloud SQL (Postgres) for relational data | Month 18-24 |
| Analytics queries slow Firestore | Merchant dashboards timeout | Stream to BigQuery for analytics | Month 12-18 |
| >1M ops/day | Firestore costs >$1K/month | Add Redis cache for hot data | Month 24-36 |
| Multi-region compliance | GDPR, data residency laws | Cloud Spanner or regional Firestore | Year 3+ |
| Sub-10ms query latency | Real-time Wave interactions | Memorystore (Redis) + CDN edge caching | Year 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:
- Year 1-2: Firestore only (current)
- Year 2-3: Firestore + BigQuery (analytics offload)
- Year 3-4: Firestore + BigQuery + Redis (hot cache)
- Year 4-5: Firestore + BigQuery + Redis + Cloud SQL (relational data)
Cost Comparison at 1M Active Users:
| Architecture | Monthly Cost | Use Case |
|---|---|---|
| Firestore alone | $500-1,500 | Simple apps, <10M ops/day |
| Firestore + BigQuery | $800-2,000 | Analytics-heavy |
| Firestore + Redis | $1,200-2,500 | Low-latency reads |
| Full GCP stack (SQL + Datastore + Redis + BigQuery) | $2,500-5,000 | Enterprise multi-region |
Cost Bottlenecks (In Order of Impact)
Pilot → Year 3 Cost Breakdown
| Cost Category | Pilot (100 users) | Year 1 (20K users) | Year 3 (100K users) | % of Total |
|---|---|---|---|---|
| Payment Processing (Stripe) | $240/month | $1,500/month | $15,000/month | 85-90% |
| Google Maps API | $5-10 | $50-100 | $500-1,000 | 3-5% |
| Firestore | $0 (free) | $50-150 | $500-1,000 | 3-5% |
| Cloud Functions | $0 (free) | $10-20 | $100-200 | 1-2% |
| Cloudflare Hosting | $0 (free) | $0 (free) | $0 (free) | 0% |
| Total Infrastructure | ~$245 | ~$1,610 | ~$16,600 | 100% |
Key Insight: Payment processing dominates costs (85-90%). Database optimization is <5% of total spend.
Priority order for cost optimization:
- 🔥 Negotiate Stripe rates (2.9% → 2.5% saves $1K/month at scale)
- 📍 Cache Google Maps data (saves $200-500/month)
- 💾 Optimize Firestore queries (saves $50-100/month)
- ⚡ 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-commonlibrary - Create composite index:
offerscollection →geohashfield - Document in Firestore rules
- Use
- [ ] Set Firebase budget alerts
- Firebase Console → Usage & Billing → Budget Alerts
- Alert threshold: $50/month (10x expected pilot cost)
- [ ] Create Firestore composite indexes
offerscollection:[geohash, redemptionCount]redemptionscollection:[userId, timestamp]- Run
firebase deploy --only firestore:indexes
- [ ] Document data model
- Create
docs/engineering/architecture/DATA_MODEL.md - Define collections, fields, security rules, indexes
- Create
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
Related Documentation
- Tech Stack — Full technology overview
- Economics — Cost projections & unit economics
- Security Architecture — Encryption & privacy model
- Pilot Strategy — Business timeline & financial model
- API Documentation — Backend API design
Changelog
| Date | Change | Rationale |
|---|---|---|
| 2026-01-11 | Initial version | Document scaling strategy & migration triggers |
Next Review: 2026-07-01 (after pilot completion, 6 months)