HardПрактика2 min

Cloud Functions и Firestore

Cloud Functions 2nd gen с HTTP trigger и Firestore для счётчика посещений CRC на GCP

Архитектура бэкенда на 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

Проверь себя

Зачем устанавливать --max-instances=10 для Cloud Function CRC?

Зачем используется firestore.Increment(1) вместо чтения и записи?

Как запустить Cloud Function локально для тестирования?

Что делает --allow-unauthenticated при деплое Cloud Function?

Что такое Cloud Functions 2nd gen и чем отличается от 1st gen?

Связанные темы