You are viewing a free preview of this lesson.
Subscribe to unlock all 10 lessons in this course and every other course on LearningBro.
Building reliable, performant, and cost-effective serverless applications on Google Cloud requires attention to function design, cold start mitigation, security, monitoring, and cost optimisation. This lesson consolidates best practices across Cloud Functions, Cloud Run, App Engine, and the supporting serverless services.
Each function should do one thing well. A function that fetches data, transforms it, validates it, and writes to a database is doing too much. Break it into smaller functions connected by Pub/Sub or Workflows.
Serverless triggers provide at-least-once delivery. Your function may be invoked multiple times for the same event. Design functions to produce the same result when called repeatedly with the same input.
# BAD: Not idempotent — duplicates data on retry
def process_order(event, context):
order = parse_event(event)
db.insert(order) # Duplicate row on retry
# GOOD: Idempotent — uses upsert with event ID
def process_order(event, context):
order = parse_event(event)
db.upsert(order, key=event['id']) # Same result on retry
Cold starts occur when a new instance is created to handle a request. Strategies to reduce cold start latency:
| Strategy | Platform | Impact |
|---|---|---|
| Minimum instances | Cloud Run, Cloud Functions Gen 2 | Eliminates cold starts (higher cost) |
| Startup CPU boost | Cloud Run | Faster container initialisation |
| Smaller container images | Cloud Run | Faster image pull and startup |
| Lazy initialisation | All | Defer expensive setup until first request |
| Avoid heavy frameworks | Cloud Functions | Faster function loading |
| Use compiled languages | All | Go, Rust start faster than Python, Java |
Create database connections, HTTP clients, and SDK clients outside the function handler. This allows connections to be reused across invocations on the same instance.
// GOOD: Connection created once per instance, reused across invocations
const { Pool } = require('pg');
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
exports.handler = async (req, res) => {
const result = await pool.query('SELECT * FROM users WHERE id = $1', [req.query.id]);
res.json(result.rows[0]);
};
For Cloud SQL, use the Cloud SQL Auth Proxy or the Cloud SQL connector libraries. These handle authentication, encryption, and connection pooling automatically.
Assign the minimum IAM permissions required. Create dedicated service accounts for each function with only the roles it needs.
# Create a dedicated service account
gcloud iam service-accounts create image-processor \
--display-name="Image Processor Function"
# Grant only the specific permissions needed
gcloud projects add-iam-policy-binding my-project \
--member="serviceAccount:image-processor@my-project.iam.gserviceaccount.com" \
--role="roles/storage.objectViewer"
Never store secrets in environment variables, source code, or container images. Use Secret Manager and access secrets at runtime.
# Create a secret
echo -n "my-api-key" | gcloud secrets create api-key --data-file=-
# Grant the function's service account access
gcloud secrets add-iam-policy-binding api-key \
--member="serviceAccount:image-processor@my-project.iam.gserviceaccount.com" \
--role="roles/secretmanager.secretAccessor"
By default, make all functions and services require authentication. Only allow unauthenticated access for public-facing endpoints.
Structure your log output as JSON for better filtering and analysis in Cloud Logging.
Subscribe to continue reading
Get full access to this lesson and all 10 lessons in this course.