Архитектура бэкенда на GCP
Браузер → Cloud Functions (2nd gen, HTTP trigger) → Firestore
↓
Cloud Logging / Cloud Monitoring
Firestore
# Создаём Firestore database (Native mode)
gcloud firestore databases create \
--location=us-central1 \
--type=firestore-native
# Начальные данные через Firebase CLI или SDK
Cloud Functions (2nd gen)
Код функции
# main.py
import functions_framework
from google.cloud import firestore
import json
import logging
db = firestore.Client()
logger = logging.getLogger(__name__)
@functions_framework.http
def visitor_counter(request):
"""HTTP Cloud Function for CRC visitor counter."""
# CORS preflight
if request.method == 'OPTIONS':
return ('', 204, {
'Access-Control-Allow-Origin': 'https://resume.ivanpetrov.com',
'Access-Control-Allow-Methods': 'GET',
'Access-Control-Allow-Headers': 'Content-Type',
'Access-Control-Max-Age': '86400'
})
headers = {
'Access-Control-Allow-Origin': 'https://resume.ivanpetrov.com',
'Content-Type': 'application/json'
}
try:
counter_ref = db.collection('counters').document('visitors')
# Atomic increment using Firestore Increment
counter_ref.set(
{'count': firestore.Increment(1)},
merge=True
)
# Read updated value
doc = counter_ref.get()
count = doc.get('count')
logger.info(f'Visitor count: {count}')
return (json.dumps({'count': count}), 200, headers)
except Exception as e:
logger.error(f'Error: {e}')
return (json.dumps({'error': 'Service unavailable'}), 503, headers)
requirements.txt
functions-framework==3.*
google-cloud-firestore==2.*
Деплой
# Деплой 2nd gen Cloud Function
gcloud functions deploy visitor-counter \
--gen2 \
--runtime=python312 \
--trigger-http \
--allow-unauthenticated \
--entry-point=visitor_counter \
--source=. \
--region=us-central1 \
--memory=128Mi \
--max-instances=10 \
--min-instances=0
# Получаем URL
gcloud functions describe visitor-counter \
--gen2 \
--region=us-central1 \
--format='get(serviceConfig.uri)'
Firestore Increment
firestore.Increment(1) -- атомарная операция, аналог DynamoDB ADD:
# Атомарный инкремент (потокобезопасный)
counter_ref.set({'count': firestore.Increment(1)}, merge=True)
# Это ЛУЧШЕ, чем read-modify-write:
doc = counter_ref.get() # Race condition!
count = doc.get('count') + 1 # Два запроса могут
counter_ref.set({'count': count}) # перезаписать друг друга
Транзакции (альтернатива)
@firestore.transactional
def increment_counter(transaction):
doc = counter_ref.get(transaction=transaction)
if doc.exists:
new_count = doc.get('count') + 1
else:
new_count = 1
transaction.set(counter_ref, {'count': new_count})
return new_count
count = increment_counter(db.transaction())
Тестирование
# Локальное тестирование
pip install functions-framework google-cloud-firestore
functions-framework --target=visitor_counter --port=8080
# В другом терминале
curl http://localhost:8080
# {"count": 1}
# Тестирование развёрнутой функции
FUNCTION_URL=$(gcloud functions describe visitor-counter --gen2 --region=us-central1 --format='get(serviceConfig.uri)')
curl $FUNCTION_URL
Сравнение с AWS и Azure
| Параметр | AWS Lambda | Azure Functions | GCP Cloud Functions |
|---|---|---|---|
| Gen | -- | v2 model | 2nd gen (Cloud Run) |
| HTTP trigger | API Gateway / Function URL | Встроенный | Встроенный |
| CORS | В API Gateway | В host.json | В коде функции |
| Cold start | 200-500 мс | 500-2000 мс | 300-800 мс |
| Min instances | Provisioned Concurrency ($) | Premium Plan ($) | --min-instances=1 ($) |
| Atomic increment | DynamoDB ADD | Cosmos DB upsert | Firestore Increment |