Skip to main content

Google Cloud Firestore

File: firestore.py

Purpose: Serverless NoSQL database for Google Cloud deployments

Implementation Details

class FirestoreService(DatabaseService):
def __init__(self, project_id: str = None):
firebase_admin.initialize_app()
self.db = firestore.client()

Characteristics

  • Storage: Cloud-native distributed database
  • Persistence: Automatic with multi-region replication
  • Performance: Low-latency worldwide access
  • Revision Tracking: Placeholder "firestore-rev" (uses server-side versioning)
  • Auto-initialization: Collections created on first write

Configuration

# .env or environment variables
DATABASE_PROVIDER=firestore

# Authentication via Firebase Admin SDK:
# - Set GOOGLE_APPLICATION_CREDENTIALS pointing to service account JSON
# - Or use Application Default Credentials in GCP

Authentication Setup

1. Create a Firebase Project

  1. Go to Firebase Console
  2. Create a new project or select an existing one
  3. Enable Firestore Database (Native mode recommended)

2. Generate Service Account Key

  1. Go to Project Settings > Service Accounts
  2. Click "Generate New Private Key"
  3. Download the JSON key file
  4. Store it securely (DO NOT commit to version control)

3. Set Environment Variable

export GOOGLE_APPLICATION_CREDENTIALS="/path/to/service-account-key.json"

4. Alternative: Application Default Credentials

When running on Google Cloud Platform (GCP), you can use Application Default Credentials:

# No GOOGLE_APPLICATION_CREDENTIALS needed
# The SDK automatically uses the service account of the GCP resource

Special Considerations

1. Collection Auto-Creation

Collections are created implicitly on first document write:

  • No need to pre-create collections
  • No "create database" equivalent

2. Document Structure

Firestore uses collections and documents:

  • db_name parameter maps to collection name
  • doc_id parameter is the document ID within that collection

3. ID Field

Implementation adds _id to returned documents:

def get(self, db_name: str, doc_id: str) -> Dict[str, Any]:
doc_ref = self.db.collection(db_name).document(doc_id)
doc = doc_ref.get()
data = doc.to_dict()
data["_id"] = doc.id # Add ID to document
return data

4. Listing Documents

Streams all documents in collection:

docs = self.db.collection(db_name).stream()
for doc in docs:
data = doc.to_dict()
data["_id"] = doc.id
documents.append(data)

5. Revision Information

Returns placeholder revision:

  • Firestore handles versioning server-side
  • Application doesn't need to track revisions

Production Recommendations

  • Use security rules to restrict access
  • Enable audit logging for compliance
  • Use composite indexes for complex queries (if needed)
  • Monitor quota usage (reads/writes/deletes)
  • Consider Firestore Native mode over Datastore mode
  • Use multi-region for high availability

Cost Considerations

  • Charged per document read/write/delete
  • Free tier: 50K reads, 20K writes, 20K deletes per day
  • Storage: $0.18/GB/month
  • Network egress charges apply

Cost Optimization Tips

  1. Minimize document reads by caching
  2. Use batch operations when possible
  3. Structure data to reduce document count
  4. Monitor usage in Firebase Console

Example Usage

from services.database_service import get_database_service
from config import Settings
import os

# Set credentials
os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = "/path/to/key.json"

settings = Settings(DATABASE_PROVIDER="firestore")
db = get_database_service(settings)

# Save a document
result = db.save("agents", "agent-1", {"name": "My Agent", "code": "..."})
print(result) # {"id": "agent-1", "rev": "firestore-rev"}

# Retrieve the document
agent = db.get("agents", "agent-1")
print(agent) # {"_id": "agent-1", "name": "My Agent", "code": "..."}

# List all agents
all_agents = db.list_all("agents")

# Delete the agent
db.delete("agents", "agent-1")

Common Operations

Setting Up Firestore Security Rules

// firestore.rules
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Allow authenticated users to read/write their own data
match /users/{userId} {
allow read, write: if request.auth.uid == userId;
}

// Allow service account full access (for backend)
match /{document=**} {
allow read, write: if request.auth.token.email == "service-account@project.iam.gserviceaccount.com";
}
}
}

Deploy rules:

firebase deploy --only firestore:rules

Creating Composite Indexes

If you need complex queries, create indexes in Firebase Console or via CLI:

# firestore.indexes.json
{
"indexes": [
{
"collectionGroup": "agents",
"queryScope": "COLLECTION",
"fields": [
{"fieldPath": "created_at", "order": "DESCENDING"},
{"fieldPath": "name", "order": "ASCENDING"}
]
}
]
}

Deploy indexes:

firebase deploy --only firestore:indexes

Monitoring Usage

View usage in Firebase Console:

  1. Go to Firestore Database
  2. Click "Usage" tab
  3. Monitor reads, writes, deletes, and storage

Troubleshooting

Authentication Failed

Error: DefaultCredentialsError: Could not automatically determine credentials

Solutions:

  • Set GOOGLE_APPLICATION_CREDENTIALS environment variable
  • Verify the service account key file exists and is valid
  • Check file permissions: ls -l /path/to/key.json
  • Ensure the service account has Firestore permissions

Permission Denied

Error: PermissionDenied: Missing or insufficient permissions

Solutions:

  • Verify service account has "Cloud Datastore User" role
  • Check Firestore security rules
  • Ensure Firestore is enabled in Firebase Console
  • Grant appropriate IAM permissions in GCP Console

Document Not Found

Error: HTTPException: Document not found

Solutions:

  • Verify the document exists in Firebase Console
  • Check collection and document ID spelling
  • Ensure you're using the correct Firebase project

Quota Exceeded

Error: ResourceExhausted: Quota exceeded

Solutions:

  • Check usage in Firebase Console
  • Upgrade to Blaze plan for higher quotas
  • Optimize code to reduce reads/writes
  • Implement caching to reduce database calls

Performance Tuning

Batch Operations

Use batch writes for multiple documents (up to 500 operations):

batch = self.db.batch()

# Add multiple operations
for i in range(100):
doc_ref = self.db.collection("agents").document(f"agent-{i}")
batch.set(doc_ref, {"name": f"Agent {i}"})

# Commit all at once
batch.commit()

Pagination

Implement pagination for large collections:

# Get first page
query = self.db.collection("agents").limit(100)
docs = query.stream()

# Get next page using last document as cursor
last_doc = None
for doc in docs:
last_doc = doc
# Process document

# Next page
if last_doc:
next_query = self.db.collection("agents").start_after(last_doc).limit(100)
next_docs = next_query.stream()

Server Timestamps

Use server timestamps instead of client timestamps:

from google.cloud.firestore import SERVER_TIMESTAMP

doc_ref.set({
"name": "Agent",
"created_at": SERVER_TIMESTAMP
})

Security Best Practices

  1. Service Account Security: Store credentials securely, never in version control
  2. IAM Roles: Use least privilege principle for service accounts
  3. Security Rules: Implement granular access control
  4. Audit Logging: Enable Cloud Audit Logs
  5. VPC Service Controls: Restrict Firestore access to specific networks
  6. Data Encryption: Firestore encrypts data at rest by default

Migration from Another Database

from services.database_service import get_database_service
from config import Settings
import os

# Source: CouchDB
source_settings = Settings(
DATABASE_PROVIDER="couchdb",
COUCHDB_URL="http://localhost:5984",
COUCHDB_USER="admin",
COUCHDB_PASSWORD="password"
)
source_db = get_database_service(source_settings)

# Target: Firestore
os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = "/path/to/key.json"
target_settings = Settings(DATABASE_PROVIDER="firestore")
target_db = get_database_service(target_settings)

# Migrate all collections
collections = ["agents", "deployments", "users", "sessions", "tickets", "demos"]
for collection in collections:
print(f"Migrating {collection}...")
documents = source_db.list_all(collection)

for doc in documents:
doc_id = doc.pop("_id")
# Remove source-specific fields
doc.pop("_rev", None)

# Save to Firestore
target_db.save(collection, doc_id, doc)
print(f" Migrated {doc_id}")

print(f"Completed {collection}: {len(documents)} documents")

Last Updated: 2026-01-11