open atlas
↑ К треку
Next.js с нуля до senior NEXT · 06 · 02

Edge против Node в проде: изоляты, холодные старты и ловушка локальности данных

Edge runtime — V8-изоляты: ~0 мс холодного старта против 200-800 мс у Node-лямбд, ценой отсутствия Node API, нативных зависимостей и лимита размера кода. Edge выигрывает, когда данных нет или они реплицированы; edge с БД в одном регионе платит межконтинентальный RTT за запрос.

NEXT Senior ◷ 18 min
Уровень
ОсновыJuniorMiddleSenior

p95 чекаута из Сиднея — 480 мс; трейсы показывают, что API-роут крутится в us-east-1, а холодные старты выбрасывают его за секунду. Команда выкатывает очевидный фикс: export const runtime = 'edge' на роуте чекаута. Холодные старты исчезают с дашборда — а p95 утраивается до 1,4 с. Откат в два ночи, постмортем в понедельник, и график задним числом унизителен. Роут делает четыре последовательных запроса в Postgres. Раньше запрос из Сиднея платил один хоп ~200 мс до Вирджинии, а дальше — четыре запроса с субмиллисекундным RTT рядом с базой. Теперь compute переехал в сиднейский PoP — пять миллисекунд от пользователя и двести от данных. Четыре последовательных запроса по ~210 мс RTT — это 840 мс чистого налога на скорость света до того, как выполнится хоть байт бизнес-логики. Edge придвинул вычисления к пользователю и отодвинул их от данных, а этот роут разговаривает с данными четыре раза за запрос. Это никто не замерил — на слайде был только холодный старт.

V8-изоляты: чего на самом деле стоит почти нулевой холодный старт

Edge runtime — это не «Node поменьше», это другая модель исполнения. Ваш код работает в V8-изоляте: свежем JavaScript-контексте, порождаемом внутри уже работающего процесса, — архитектура, которую первым сделал Cloudflare Workers. Породить изолят — значит выделить кучу и глобальный объект: единицы миллисекунд, часто меньше. Холодный старт Node-лямбды — это, напротив, стопка настоящей работы: поднять microVM (~50–200 мс), загрузить сам Node (~50–100 мс), затем require фреймворка и графа страницы (50–500 мс, на тяжёлых деревьях зависимостей — секунды). Вот честный диапазон за фразой «у edge ~0 холодных стартов против 200–800 мс»: изоляты пропускают загрузку, а не физику. Тёплые вызовы на обоих рантаймах различаются единицами миллисекунд — весь latency-аргумент за edge — это холодный путь и география.

Цена — поверхность API. Изолят даёт только Web API — fetch, Request/Response, URL, crypto.subtle, стримы — и ничего из Node: ни fs, ни net, ни child_process, ни нативных аддонов. Последнее кусает больнее всего: sharp, bcrypt, всё с бинарником .node и классические драйверы БД с нативными движками — мимо; нужны WASM-сборки, HTTP-драйверы для serverless или другой рантайм. Динамическое исполнение кода (eval, new Function) запрещено наотрез. Бандлы ограничены — порядка 1–4 МБ в сжатом виде в зависимости от плана, — потому что модель изолятов остаётся дешёвой, только пока код маленький. Next позволяет выбирать рантайм на сегмент роутинга — в этом и смысл: решение не общеприложенческое.

// app/api/checkout/route.ts — рядом с базой: остаёмся на Node
export const runtime = 'nodejs';

// app/api/geo-banner/route.ts — без данных, чувствителен к latency: edge — бесплатная скорость
export const runtime = 'edge';

export async function GET(request: Request) {
  const country = request.headers.get('x-vercel-ip-country') ?? 'US';
  return Response.json({ banner: country === 'DE' ? 'eu-promo' : 'default' });
}
Викторина

Почему edge-изолят может холодно стартовать за единицы миллисекунд, когда Node-лямбде регулярно нужно 200-800 мс?

Ловушка локальности данных

Числа, решающие спор edge против node, — это RTT (Round-Trip Time, время прохождения пакета туда и обратно), и их асимметрия безжалостна: внутри региона меньше 1–2 мс, через США 60–80 мс, через Атлантику 80–100 мс, США–APAC 180–250 мс. Latency роута — примерно RTT клиент→compute + N последовательных запросов × RTT compute→данные + работа. Node рядом с базой делает длинный хоп один раз — клиентский RTT, — а каждый запрос после этого субмиллисекундный. Edge переворачивает: первый хоп сжимается до единиц миллисекунд, но каждый последовательный запрос теперь платит полный RTT compute→данные. Один запрос — примерно ничья. Четыре последовательных запроса из сиднейского PoP в вирджинский Postgres — это 800+ мс одного только RTT, строго хуже того, что было, — ровно инцидент из вступления. Есть и вторая, тихая статья расходов: классические пулы TCP-соединений с базой не выживают в модели изолятов, поэтому доступ к БД с эджа идёт через HTTP-драйверы — каждый запрос несёт HTTP-накладные вместо тёплого сокета из пула.

Значит, edge-compute выигрывает ровно в двух ситуациях. Вычисления, не трогающие данные: редиректы, геоперсонализация по заголовкам запроса, раскладка по A/B-корзинам, проверка подписанного токена через crypto.subtle — чистый CPU над самим запросом, и выполнять его в 10 мс от пользователя вместо 200 — бесплатная скорость. Либо данные, реплицированные туда, где работает compute: edge-KV, кеши в PoP (Point of Presence — точка присутствия сети), региональные read-реплики — чтения локальны, и география работает на вас. Оба условия вместе задают одну чёткую границу: если роут не свободен от данных и данные не реплицированы в PoP, edge сделает его медленнее, а не быстрее. Всё остальное — чекаут, дашборд с N запросами, любая транзакционность против одного регионального primary — живёт на Node в регионе базы, где последовательные запросы стоят микросекунды, а длинный хоп пользователь платит ровно один раз.

Middleware — налог на каждый запрос

Middleware в Next выполняется до кеша и роутера, на каждом запросе, пропущенном matcher-ом: страницы, RSC-payload, префетчи, а при неряшливом matcher — и статика. Это делает его latency плоским налогом на весь сайт: всё, что делает middleware, прибавляется к каждому TTFB (Time To First Byte — время до первого байта ответа). Дисциплина, держащая налог около нуля: middleware занимается решениями о маршрутизации, перезаписью заголовков и cookie и локальной проверкой подписанных токенов — HMAC или проверка подписи JWT через crypto.subtle стоит десятки микросекунд и ничего не трогает. Чего он не должен делать никогда — разговаривать с базой или делать fetch в собственный API: middleware, дёргающий /api/session, чтобы закрывать роуты, ставит круговой хоп edge→origin плюс запрос в БД перед каждой загрузкой каждой страницы, удваивая TTFB глобально, — и это невидимо в профилировании по роутам, потому что цена ложится до старта роута.

// middleware.ts — проверяй локально, никогда не fetch-ай собственный API отсюда
import { NextResponse } from 'next/server';
import { jwtVerify } from 'jose';

export const config = {
  matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'], // исключи ассеты — иначе платят и они
};

export async function middleware(req: Request) {
  const token = req.headers.get('cookie')?.match(/__Host-session=([^;]+)/)?.[1];
  if (!token) return NextResponse.redirect(new URL('/login', req.url));
  try {
    await jwtVerify(token, secret); // локальная криптография: ~0,05 мс, ноль IO
    return NextResponse.next();
  } catch {
    return NextResponse.redirect(new URL('/login', req.url));
  }
}
Почему это работает

Зачем платформа вообще предлагает edge для API-роутов, если математика локальности данных так беспощадна? Потому что для нагрузок, под которые edge проектировался, математика переворачивается: маршрутизация, персонализация и auth-гейтинг имеют форму запроса, а не форму данных — они читают заголовки и cookie, а не таблицы. Для них существует только одна latency — от клиента до compute, и перенос compute в PoP её стирает. Ловушка — взять инструмент для обработки запросов без данных и направить его на транзакционную нагрузку, где та же география, что сжала первый хоп, умножает каждый следующий.

Викторина

Команда закрывает все роуты через middleware, который fetch-ает /api/session, проверяющий сессию в Postgres. Каков эффект в проде?

Вспомните перед уходом
  1. 01
    Разверните уравнение latency для edge против co-located Node с честными RTT. Когда выигрывает каждый?
  2. 02
    Что middleware можно, чего нельзя никогда, и почему запрет абсолютен?
Итог

Edge runtime — другая модель исполнения, а не облегчённый Node: V8-изоляты порождаются как свежие контексты внутри уже работающего процесса, так что холодный старт — это выделение кучи, единицы миллисекунд против 200-800 мс, которые Node-лямбда тратит на поднятие microVM, загрузку Node и require графа фреймворка. Цена — поверхность: только Web API, без fs и net, без нативных аддонов — sharp, bcrypt и нативные движки БД мимо, — без eval, и сжатые бандлы с потолком около 1-4 МБ. Тёплая производительность сравнима; аргумент за edge — холодные пути и география. И именно на географии команды обжигаются: latency роута — клиентский RTT плюс последовательные запросы, умноженные на RTT compute-данные, поэтому перенос compute в PoP в пяти миллисекундах от пользователя и в двухстах от единственного регионального Postgres превращает роут с четырьмя запросами в 800+ мс налога на скорость света — инцидент «p95 стало хуже». Edge выигрывает только в двух формах: вычисления без данных (редиректы, геоперсонализация, A/B-корзины, проверка подписи) и данные, реплицированные в PoP (edge-KV, региональные read-реплики). Всё транзакционное остаётся на Node рядом с primary, с выбором на каждый роут через export const runtime. Middleware заслуживает отдельной паранойи: он выполняется до кеша на каждом совпавшем запросе, так что его цена — плоский последовательный налог на TTFB всего сайта; держите его на маршрутизации, перезаписи заголовков и локальных проверках crypto.subtle, исключайте статику в matcher и никогда не пускайте его в базу или в fetch собственного API — это удваивает каждую загрузку страницы, прячась от профилирования по роутам. Теперь, встречая в PR строку export const runtime = 'edge' на API-роуте, задайте первый вопрос: сколько последовательных запросов в базу делает этот роут?

Практика

Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.

вспомнитьприменитьуглубить0 из 6 завершено

Что-то непонятно?

Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.

Примени это

Примени этот урок в реальном проекте.

хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.