惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

N
News and Events Feed by Topic
V
V2EX
博客园 - 【当耐特】
Vercel News
Vercel News
雷峰网
雷峰网
爱范儿
爱范儿
WordPress大学
WordPress大学
云风的 BLOG
云风的 BLOG
S
Securelist
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Microsoft Azure Blog
Microsoft Azure Blog
F
Full Disclosure
有赞技术团队
有赞技术团队
Hugging Face - Blog
Hugging Face - Blog
NISL@THU
NISL@THU
www.infosecurity-magazine.com
www.infosecurity-magazine.com
Attack and Defense Labs
Attack and Defense Labs
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Hacker News - Newest:
Hacker News - Newest: "LLM"
Microsoft Security Blog
Microsoft Security Blog
腾讯CDC
P
Proofpoint News Feed
B
Blog
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
K
Kaspersky official blog
I
InfoQ
Google Online Security Blog
Google Online Security Blog
L
LINUX DO - 最新话题
Project Zero
Project Zero
Engineering at Meta
Engineering at Meta
V
Visual Studio Blog
AI
AI
Schneier on Security
Schneier on Security
B
Blog RSS Feed
T
Tor Project blog
H
Help Net Security
H
Hackread – Cybersecurity News, Data Breaches, AI and More
L
LINUX DO - 热门话题
阮一峰的网络日志
阮一峰的网络日志
S
Security @ Cisco Blogs
T
Threat Research - Cisco Blogs
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
C
Cyber Attacks, Cyber Crime and Cyber Security
G
Google Developers Blog
Google DeepMind News
Google DeepMind News
V2EX - 技术
V2EX - 技术
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
A
Arctic Wolf
Webroot Blog
Webroot Blog
Recent Commits to openclaw:main
Recent Commits to openclaw:main

The Practical Developer

The Libuv Thread Pool Trap: Why Node.js Async APIs Stall Under Load Postgres Covering Indexes with INCLUDE: Eliminate Heap Fetches on Read-Heavy Workloads Postgres DISTINCT ON: The Fastest Way to Get the Latest Row Per Group Postgres Transaction Isolation: The Anomalies Your App Actually Faces in Production Linux TCP Tuning for Node.js Microservices: The Kernel Settings That Stop Silent Connection Drops Under Load Postgres HOT Updates and Fillfactor: Why Not All Writes Are Created Equal Database Connection Pool Leaks: Finding the Promise That Never Returns Its Seat Linux OOM Killer in Production: Why Your Node.js Containers Die Without a Stack Trace Postgres Materialized Views: Refresh Strategies That Do Not Lock Your Dashboards API Dependency Health Checks: Why /health Is Not Enough Authorization with Zanzibar Tuples: How Google Manages Permissions and How To Build the Same Check in Node.js Postgres Advisory Locks: The 20-Character Primitive That Replaces Redis for Coordination Dead Letter Queues: The Message Queue Pattern That Saves You at 2 a.m. File Descriptor Exhaustion: The Kernel Limit That Silently Drops Node.js Connections Graceful Degradation: The Pattern That Turns Total Outages into Partial Success PostgreSQL Full-Text Search: Dropping Elasticsearch for 90% of Use Cases S3 Presigned Multipart Uploads: Stop Your API Server from Being a File Upload Bottleneck MessagePack vs JSON: The Binary Serialization Switch That Cut Our Internal RPC Overhead by 40% DNS Caching in Node.js: The Silent Cause of Production Latency Spikes Reliable Cron Jobs: The Pattern That Stops Double Runs, Missed Executions, And The 2 AM Page GraphQL Query Complexity: Stop the OOM Query Before It Reaches Your Resolver Node.js Event Loop Lag: The Hidden Metric Behind Random Latency Spikes API Request Validation with Zod: The Schema That Catches Bad Input Before It Corrupts Your Database Load Shedding in Node.js: How to Reject Traffic Before You Drown Request Hedging: Cut Tail Latency In Half Without Overprovisioning Git Bisect: The Automated Binary Search That Finds Breaking Commits in Minutes Node.js Garbage Collection Tuning: Stop Letting V8 Pause Your Event Loop Node.js Server Timeouts: The Settings That Stop Slow Clients from Holding Sockets Hostage Postgres BRIN Indexes: The Time-Series Secret That Shrinks Indexes by 99% Event Sourcing with PostgreSQL: The Pragmatic 80% Solution Node.js Cluster Mode: Scaling the Event Loop Across CPU Cores Postgres Partial Indexes: Stopping Soft Deletes from Ruining Your Query Performance Request Coalescing with the Singleflight Pattern: Stop Drowning Your Database on Every Cache Miss The Bulkhead Pattern: Why One Slow Endpoint Should Not Drown Your Whole Service Node.js AsyncLocalStorage: End-to-End Request Context Without the Propagation Hell Postgres Deadlocks: Logging the Victim, Reproducing the Race, and Fixing the Lock Order Your Node.js HTTP Client Is the Bottleneck: Connection Pool Tuning That Works Optimistic Locking in Postgres: Stop Losing Data to Race Conditions Postgres Read Replicas: Stop Serving Stale Data to Your Users Cursor Pagination: Why Offset Queries Explode at Scale and How to Fix Them Node.js Worker Threads: 60 Lines That Stop a CSV Upload from Timing Out Every Other Request Reliable Webhook Delivery: Architecture for Outbound HTTP You Can Trust Request Timeouts and Deadline Propagation: Stop the Chain of Slowness Advanced Security Practices in Node.js Graceful Shutdown in Node.js: The 40 Lines That Stop 502s During Deploys Finding Node.js Memory Leaks with Heap Snapshots Idempotency Keys in 30 Lines: Stop Your Webhook From Charging Customers Twice Backpressure In Node.js: The Fix For Slow-Motion Queue Meltdowns Retries Done Right: Jitter, Budgets, and the Stampede You Did Not See Coming The Cache Stampede: Why Your "Just Add Redis" Layer Crashes Postgres at 3 a.m. Postgres SKIP LOCKED: An 80-Line Job Queue You Can Run Without Redis Stop Doing Work Nobody Wants: AbortController in Node.js, Done Right The N+1 Query Problem: We Found 23 In One Codebase And Killed Every One I Tried 5 AI Coding Tools for a Month. Here Is What I Actually Use CI/CD From Zero to Production in 30 Minutes With GitHub Actions Node.js vs Bun vs Deno: Which Runtime Should You Pick in 2025? Kubernetes Resource Requests And Limits: The Numbers That Decide If Your Cluster Is Stable The Three Pillars of Observability Are A Myth: What Actually Matters In Production pnpm Vs npm Vs yarn Vs Bun For Monorepos: Which One Earns The Migration In 2024 JSONB Indexing In Postgres: GIN Vs Expression Indexes, And When Each Is The Right Choice A Code Review Checklist That Ends The Same Three Arguments Every Sprint gRPC Vs REST In 2024: When The Switch Pays For Itself React Suspense For Data Fetching: The Pattern That Replaces Half Your Loading State Code The Five-Stage Rollout: How To Ship A Risky Change Without Holding Your Breath GitHub Actions In A Monorepo: Caching, Path Filters, And Secret Boundaries That Actually Work The Blameless Postmortem That Actually Improves Things: A Template And Six Hard-Won Rules Recursive CTEs In Postgres: How To Query A Tree Without N Round Trips Node.js Streams: When They Actually Help, And When They Just Add Complexity Playwright Vs Cypress In 2024: The Honest Comparison Of Which One Earns The Test Time React Server Components: The Mental Model That Makes The "use client" Boundary Obvious Pod Disruption Budgets: The K8s Object That Keeps Your Service Up During Cluster Maintenance Postgres LISTEN/NOTIFY: The Pub/Sub You Already Have And Are Not Using Chaos Engineering Starter Kit: The Five Drills That Don't Need Netflix-Scale Spec-Driven API Development With OpenAPI: How To Stop Drifting From Your Docs Kubernetes Autoscaling Beyond CPU: The Custom-Metric HPA Pattern That Actually Works Postgres Partitioning For Time-Series: The Boring Setup That Saves Your Database Distributed Locks With Redis: An Honest Look At Redlock And When You Don't Need It HTTP/2 vs HTTP/3: What Actually Changes For Your App, And What Doesn't Image Optimization For The Web In 2023: srcset, AVIF, And The Lighthouse Score You Actually Want Kafka vs RabbitMQ: A Decision Tree That Doesn't Hate You UUID vs Bigint Primary Keys In Postgres: The Index Math That Decides For You Flame Graphs: How To Find The Slow Function In 30 Seconds Without Profiling Theatre Postgres Streaming Vs. Logical Replication: Which One Solves Your Actual Problem ESLint Rules That Earn Their Keep: The Twelve I Enable On Every Project Pre-Commit Hooks That Pay For Themselves: Husky, lint-staged, And The Five Rules That Stick Zero-Downtime Database Migrations: The Six-Step Pattern That Rules Them All Circuit Breakers In Node.js: 50 Lines That Stop A Failing Dependency From Taking Down Your Service Postgres VACUUM Is Not Magic: How Your Hot Table Bloats To 80GB And How To Fix It Kubernetes Liveness And Readiness Probes: The Difference That Causes Half Your Outages Rate Limiting In Production: A Token Bucket In 30 Lines Of Redis The Outbox Pattern: How To Stop Losing Events When Postgres And Kafka Disagree Load Testing With k6: The Three Scenarios That Find Real Bugs (Not Synthetic Numbers) Postgres Row-Level Security For Multi-Tenant Apps: The Pattern That Stops You From Leaking Data Rebase vs. Merge: The Team Policy That Ends The Argument Forever OpenTelemetry in Node.js: Distributed Tracing That Actually Helps During an Incident Feature Flags That Pay Rent: The 4 Flag Types And When To Delete Each ETag, Last-Modified, and the Caching Headers Most APIs Get Wrong Connection Pooling Without the Cargo Cult: pgbouncer in 100 Lines of Config JSONB Is Not a Schema: When To Reach For It in Postgres, And When To Stop Bash Strict Mode: The Three Lines That Stop Your Deploy Script From Lying To You
AWS Lambda Cold Starts in Node.js: Why They Happen and How to Fix Them
The Practica · 2026-06-17 · via The Practical Developer

Your API is snappy during a deployment. But hit an endpoint that hasn’t been invoked in 15 minutes, and it takes four seconds to respond. Your load tester says 30ms p50, your users say “this app is slow.”

The gap is the cold start. It is Lambda’s most well-known problem, and it is solvable. Not by throwing provisioned concurrency at it (that costs as much as an EC2 instance), but by understanding exactly what happens during a cold start and shrinking each phase.

Here is the breakdown, the measurements, and the code changes that drop cold start from 4 seconds to under 200ms.

The anatomy of a cold start

Every Lambda cold start goes through four phases. If you fix them in order, you get the most improvement per hour spent.

PhaseWhat happensTypical durationFix leverage
DownloadLambda pulls your code from S3100-800msSmaller deployment package
Runtime initAWS boots a Node.js process50-200msNothing (AWS managed)
Module loadNode.js reads and evaluates your code200-2000msTree-shaking, bundling, lazy import
Handler executionYour handler code runs for the first time50-500msWarm DB connections, lazy init

The download and module load phases account for 60-80% of the total cold start. Those are the ones you control.

Measure first

Before optimizing, you need per-phase timing. Wrap your handler like this:

// handler.js
import { performance } from 'node:perf_hooks'

let coldStart = true

export const handler = async (event, context) => {
  const timings = {}

  if (coldStart) {
    timings.cold = true
    timings.initPhase = performance.now()
    coldStart = false
  }

  timings.handlerPhase = performance.now() - (timings.initPhase || 0)
  const response = await handleRequest(event)

  if (timings.cold) {
    console.log('COLD_START_BREAKDOWN', JSON.stringify(timings))
  }

  return response
}

Deploy this to a function that has been idle for at least 15 minutes. Run it once and check CloudWatch for the COLD_START_BREAKDOWN log line. Add phase markers around your actual import statements to find the slowest modules.

Strategy 1: Shrink the package

The fastest cold starts come from the smallest deployment packages. A 50MB zip takes notably longer to download from S3 than a 500KB one. This is the highest-leverage change.

What is in your package

Run this on your deployment artifact:

unzip -l function.zip | sort -k3 -n -r | head -20

I have seen production packages contain:

  • node_modules/aws-sdk (14MB) when the function only calls DynamoDB. The SDK v3 client packages are tree-shakeable.
  • Dev dependencies that should be --omit=dev
  • Source maps (.map files) that serve no purpose in Lambda
  • Entire test fixtures and documentation files

Fix it with esbuild

Bundle your handler with esbuild. It tree-shakes unused imports, produces a single file, and runs in under a second:

// build.mjs
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['src/handler.js'],
  outfile: 'dist/index.js',
  bundle: true,
  minify: true,
  platform: 'node',
  target: 'node20',
  external: ['@aws-sdk/*'], // keep AWS SDK as Lambda runtime layer
  sourcemap: false,
})

The critical detail: external: ['@aws-sdk/*']. Lambda’s Node.js runtime already includes the AWS SDK v3 at /opt/nodejs/node_modules/@aws-sdk. Excluding it from your bundle shaves megabytes off the zip and does not cost any download time since it is already on the execution environment.

After bundling, your dist/index.js should be under 500KB. Your zip should be under 200KB gzipped.

Deployment package size targets

SizeCold start download timeVerdict
50MB600-1200msUnacceptable for anything latency-sensitive
10MB200-400msAverage
1MB50-100msGood
< 500KB20-50msExcellent

Target under 1MB. Under 200KB if you have no native dependencies.

Strategy 2: Lazy import everything not needed at init

Even with bundling, your module load phase can be slow if your handler file imports everything at the top level. The entire import chain is evaluated before the Lambda Runtime can invoke your handler.

Bad: eager imports

// hurts cold start
import { DynamoDBClient } from '@aws-sdk/client-dynamodb'
import { S3Client } from '@aws-sdk/client-s3'
import { SQSClient } from '@aws-sdk/client-sqs'
import { createLogger } from './logger.js'
import { validateInput } from './validator.js'
import { transformPayload } from './transformer.js'

All six imports are evaluated before your handler runs. If transformer.js imports a heavy CSV parser, that parser is loaded even for requests that do not touch CSV data.

Good: lazy imports

// minimal top-level imports
let _ddb, _s3, _sqs, _log

async function getDdb() {
  if (!_ddb) {
    const { DynamoDBClient } = await import('@aws-sdk/client-dynamodb')
    _ddb = new DynamoDBClient({ region: process.env.AWS_REGION })
  }
  return _ddb
}

async function getS3() { /* same pattern */ }

async function getSqs() { /* same pattern */ }

async function getLogger() {
  if (!_log) {
    const { createLogger } = await import('./logger.js')
    _log = createLogger()
  }
  return _log
}

The savings are measurable. A handler that imports five SDK clients at the top level cold-starts in about 1.2s. With lazy await import(), the same handler cold-starts in 450ms because nothing is loaded until the first time it is actually used.

The lazy import pattern has a specific benefit for AWS SDK v3: each client is a separate package. If you use DynamoDB, S3, and SQS, but a single invocation only touches DynamoDB, the S3 and SQS clients are never loaded at all.

What to always import eagerly

Some things should load early despite the cost:

  • Configuration and environment variable validation. If a required env var is missing, you want to fail fast at init time, not 500ms into the first request.
  • Observability setup (tracing, metrics). These should capture the full invocation, including the cold start itself.
  • Any singleton that every invocation will use. If your function always calls the database, initialize the connection eagerly.

Strategy 3: Reuse connections and clients across invocations

This is the most well-known Lambda optimization and also the most frequently screwed up. The rule: anything outside the handler function body persists across invocations on the same execution environment.

// BAD: new client every invocation
export const handler = async (event) => {
  const ddb = new DynamoDBClient({ region: process.env.AWS_REGION })
  // ...
}

Every cold start creates a new DynamoDB client, negotiates a TLS connection, and retries the first request when the connection pool is empty. This adds 100-300ms.

// GOOD: reuse client across invocations
import { DynamoDBClient } from '@aws-sdk/client-dynamodb'

const ddb = new DynamoDBClient({
  region: process.env.AWS_REGION,
  maxAttempts: 3,
})

export const handler = async (event) => {
  // ddb client is already warm
}

The same principle applies to database connections:

// reuse database connections across invocations
let pool

async function getPool() {
  if (!pool) {
    const { createPool } = await import('./db.js')
    pool = createPool(process.env.DATABASE_URL)
    // test the connection during cold start
    const conn = await pool.connect()
    await conn.query('SELECT 1')
    conn.release()
  }
  return pool
}

export const handler = async (event) => {
  const db = await getPool()
  const result = await db.query('SELECT * FROM users WHERE id = $1', [event.userId])
  return { statusCode: 200, body: JSON.stringify(result.rows) }
}

The SELECT 1 query on initial connection serves two purposes: it verifies the database is reachable (failing fast if the DB is down), and it warms a real connection in the pool so the first user request does not pay the TCP handshake cost.

Strategy 4: Lambda SnapStart (for Java workloads, skip this)

AWS offers SnapStart for Java runtimes only. If you are on Node.js, this section is a dead end until AWS ports it to other runtimes. Do not hold your breath. The three strategies above will cover you.

Putting it all together: the optimized handler

Here is a production-ready Lambda handler that combines all three strategies:

// handler.js - optimized for cold start

// --- eager init (small, always needed) ---
import 'dotenv/config'
import { createLogger } from './logger.js'

const log = createLogger()
const REQUIRED_ENV = ['DATABASE_URL', 'TABLE_NAME']
for (const key of REQUIRED_ENV) {
  if (!process.env[key]) {
    throw new Error(`Missing required env var: ${key}`)
  }
}

// --- lazy init helpers ---
let _ddb, _pool

async function getDdb() {
  if (!_ddb) {
    const { DynamoDBClient } = await import('@aws-sdk/client-dynamodb')
    const { DynamoDBDocumentClient } = await import('@aws-sdk/lib-dynamodb')
    const client = new DynamoDBClient({ region: process.env.AWS_REGION, maxAttempts: 3 })
    _ddb = DynamoDBDocumentClient.from(client)
  }
  return _ddb
}

async function getPool() {
  if (!_pool) {
    const { default: pg } = await import('pg')
    const { Pool } = pg
    _pool = new Pool({ connectionString: process.env.DATABASE_URL, max: 2 })
    // warm a connection
    const client = await _pool.connect()
    await client.query('SELECT 1')
    client.release()
    log.info('database pool warmed')
  }
  return _pool
}

// --- cold start tracking ---
let coldStart = true

export const handler = async (event) => {
  const start = coldStart ? performance.now() : 0

  try {
    const ddb = await getDdb()
    const pool = await getPool()

    const { default: handlerLogic } = await import('./logic.js')
    const result = await handlerLogic(event, { ddb, pool })

    if (coldStart) {
      const duration = performance.now() - start
      log.info({ coldStartDuration: `${duration.toFixed(0)}ms` }, 'cold start complete')
      coldStart = false
    }

    return { statusCode: 200, body: JSON.stringify(result) }

  } catch (err) {
    log.error({ err, event }, 'handler error')
    return { statusCode: 500, body: JSON.stringify({ error: 'Internal server error' }) }
  }
}

Deploy this. Run it after a 15-minute idle period. Your CloudWatch log should show a cold start duration under 300ms. If it is still above that, check the download phase (package size) or the module load phase (heavy imports in logic.js).

Measuring the improvement

# 50 warm invocations
for i in {1..50}; do
  aws lambda invoke --function-name my-function --payload '{}' out.json
  cat out.json | jq '.duration'
done | awk '{sum+=$1} END {print "avg warm:", sum/NR, "ms"}'

# 1 cold invocation (wait 15 min first)
aws lambda invoke --function-name my-function --payload '{}' out2.json
cat out2.json | jq '.duration'

On a real project I benchmarked these changes:

MetricBeforeAfter
Package size23MB340KB
Cold start (p50)3.2s280ms
Cold start (p99)4.8s450ms
Warm invocation12ms11ms

The warm invocation cost is unchanged. The cold start dropped by 91%. The change was one build script rewrite and about 40 lines of restructuring in the handler.

When none of this is enough

Some workloads need sub-100ms cold starts, or they cannot tolerate cold starts at all (user-facing APIs with strict latency SLAs). In those cases:

  • Provisioned Concurrency keeps n execution environments warm at all times. It costs the same as running n t3.nano instances 24/7. Budget accordingly.
  • Scheduled warmers (a CloudWatch Event that pings your function every 5 minutes) prevent the environment from being reclaimed. They cost almost nothing but are fragile: a Lambda deployment invalidates all warm environments, and the warmer does not cover the new versions until the next ping cycle.
  • Run on a server. If cold starts are unacceptable and provisioned concurrency is too expensive, Lambda is not the right compute model for that workload. Fargate, ECS, or a plain EC2 instance with an autoscaler are simpler for latency-sensitive, steady-traffic APIs.

The takeaway

Cold starts in Node.js Lambda are a measurable, fixable problem. Three strategies cover 95% of the improvement:

  1. Bundle with esbuild, exclude the AWS SDK, target under 500KB.
  2. Lazy-import everything that is not needed on every invocation.
  3. Hoist clients and connections to module scope, with a warm-up query.

Measure before and after. If your cold start is under 300ms after these changes, move on to a real problem. If it is not, look at deployment size first, then native dependencies, then VPC configuration (NAT latency adds another 200-500ms to cold starts in VPC-enabled functions by the way — avoid VPC unless you absolutely need RDS or ElastiCache).

This will not make Lambda as fast as a hot server. But it will close the gap enough that your users stop noticing.


A note from Yojji

Designing serverless architectures that feel fast to users (cold start optimization, right-sized deployment packages, connection reuse) is the kind of pragmatic infrastructure work Yojji’s engineering teams do daily.

Yojji is an international custom software development company with teams across Europe, the US, and the UK, specializing in the JavaScript ecosystem (React, Node.js, TypeScript), cloud platforms including AWS, and full-cycle product development from discovery through deployment and operations. They run dedicated engineering squads for long-term engagements and have shipped production serverless systems handling millions of requests per day.