ETag и conditional requests на собеседовании системного аналитика

Проверь себя · 1/3разбор после ответа
Нужно посчитать сумму оплаченных заказов по каждому пользователю. В таблице orders есть поля user_id, amount, status. Какой запрос корректен и наиболее эффективен?

Почему ETag спрашивают у SA

Системный аналитик проектирует REST API и описывает контракты между сервисами, поэтому механику условных запросов (conditional requests) от него ждут почти всегда. ETag — это способ на уровне HTTP решить сразу две задачи: не гонять по сети то, что клиент уже видел, и не затирать чужие правки при параллельном редактировании.

На собесе вопрос звучит просто: «Как API понять, что ресурс не менялся, и не отдавать его заново?» или «Два клиента редактируют одну запись — как не потерять изменения?». Интервьюер смотрит, различаете ли вы If-None-Match и If-Match, знаете ли про коды 304 и 412, и понимаете ли, откуда вообще берётся значение ETag. Всё это описано в RFC 7232.

Что такое ETag

ETag (entity tag) — HTTP-заголовок, который идентифицирует текущую версию ресурса. Сервер отдаёт его в ответе, а клиент запоминает и присылает обратно, чтобы спросить: «У меня версия abc123, она ещё актуальна?».

GET /users/42
HTTP/1.1 200 OK
ETag: "abc123"
{...}

Значение генерирует сервер — обычно это хеш содержимого (например, MD5 тела ответа) или номер версии записи из базы. Главное правило: ETag меняется тогда и только тогда, когда меняется сам ресурс. Если два ответа с одинаковым телом дают разные ETag — механизм сломан.

Кеширование и условный GET

Клиент (браузер или CDN) сохраняет ответ вместе с его ETag. При следующем запросе он присылает заголовок If-None-Match с сохранённым значением, и сервер решает, изменился ли ресурс.

GET /users/42
If-None-Match: "abc123"

Ответ, если ресурс не изменился:
HTTP/1.1 304 Not Modified
(без тела)

Ответ, если ресурс изменился:
HTTP/1.1 200 OK
ETag: "xyz789"
{...новые данные...}

Смысл в экономии трафика: при коде 304 тело не передаётся вообще, а большой JSON или картинка не гоняются повторно, если у клиента уже есть валидная версия. Именно так работают браузерные кеши и CDN. По сравнению с Last-Modified ETag точнее: он реагирует на любое изменение содержимого, а не только на изменение с точностью до секунды.

Оптимистичная блокировка

Тот же ETag решает проблему конкурентной записи. При обновлении клиент присылает If-Match с ETag той версии, которую он редактировал, а сервер выполняет запись, только если версия всё ещё актуальна.

PUT /users/42
If-Match: "abc123"
{...обновлённые данные...}

Ответ, если ETag совпал:
HTTP/1.1 200 OK

Ответ, если другой клиент уже обновил ресурс:
HTTP/1.1 412 Precondition Failed

Это оптимистичная блокировка (optimistic concurrency): мы не держим блокировку на запись, а просто проверяем в момент коммита, что за время редактирования никто ничего не поменял. Получив 412, клиент повторяет цикл: заново читает актуальную версию (GET), применяет свои правки к ней и снова делает PUT с новым ETag. Без этого механизма API работает по принципу last-write-wins — кто записал последним, тот и прав, а промежуточные изменения теряются (lost update).

Готовься к собесу аналитика как в Duolingo
10 минут в день — SQL, Python, A/B, метрики. 1700+ вопросов в Telegram
Открыть Карьерник в Telegram

Strong vs weak

Strong ETag. Гарантирует побайтовую идентичность: одинаковый ETag означает, что ресурсы совпадают до байта.

ETag: "abc123"

Weak ETag. Помечается префиксом W/ и означает лишь семантическую эквивалентность: тела «одинаковы по смыслу», но могут отличаться в незначимых деталях (сжатие, форматирование, порядок несущественных заголовков).

ETag: W/"abc123"

Weak-версию используют для сжатого или динамически собираемого контента, где побайтовое совпадение не гарантировано, но для кеша этого достаточно. Важный нюанс для собеса: range-запросы (частичная загрузка через Range) требуют strong ETag — сервер не может корректно отдать кусок ресурса, если не уверен в его точной версии.

Частые ошибки

Путать If-None-Match и If-Match. If-None-Match — для кеширования на чтении (GET, ждём 304). If-Match — для защиты конкурентной записи (PUT/PATCH/DELETE, ждём 412). На собесе их постоянно меняют местами.

Нестабильный ETag. Если в значение попадает время генерации ответа или случайные данные, ETag меняется на каждый запрос, и кеш перестаёт срабатывать — клиент всегда получает 200 вместо 304.

Weak ETag там, где нужен strong. На range-запросах и при сравнении на точное совпадение слабый ETag недопустим — теряется поддержка докачки.

Игнорировать оптимистичную блокировку. В API, где одну запись правят несколько пользователей или устройств, без If-Match неизбежны потерянные обновления. Это классический провал в system design: кандидат описывает CRUD, но не отвечает, что будет при одновременном редактировании.

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

FAQ

Чем ETag лучше Last-Modified?

Last-Modified работает с точностью до секунды и ломается, если ресурс поменялся несколько раз за одну секунду или откатился к прежнему содержимому с новой датой. ETag завязан на само содержимое (хеш или версия), поэтому точнее. На практике их часто отдают вместе, а клиент использует ETag как приоритетный валидатор.

Что вернуть, если If-Match не совпал?

Код 412 Precondition Failed — версия на сервере отличается от той, что редактировал клиент. Клиент должен перечитать актуальный ресурс, применить свои изменения заново и повторить запрос. Отдавать 200 и молча перезаписывать — как раз тот самый lost update, который спрашивают на собесе.

Как сервер вычисляет значение ETag?

Два типовых подхода: хеш от тела ответа (детерминированный, но требует собрать ответ, чтобы посчитать хеш) или версия/ревизия записи из БД, например поле version или updated_at, инкрементируемое при каждом апдейте. Второй вариант дешевле, потому что не нужно хешировать весь ответ.

Weak или strong ETag выбрать?

Если контент отдаётся как есть и важно точное совпадение (например, поддержка докачки через Range) — strong. Если ответ проходит через сжатие или динамическую сборку и достаточно смысловой эквивалентности для кеша — weak. Для большинства обычных JSON-API подойдёт strong ETag на основе версии записи.

ETag или оптимистичная блокировка через поле версии в теле?

Это одно и то же по смыслу — версионирование, — но на разных уровнях. ETag живёт в HTTP-заголовках и работает прозрачно для CDN и браузеров. Поле version в теле запроса даёт больше контроля на уровне приложения, но не даёт бесплатного HTTP-кеширования. В REST-контракте чаще ждут именно ETag.

Это официальная информация?

Нет. Статья основана на RFC 7232 и типовых практиках проектирования REST API. Конкретные требования зависят от компании и уровня позиции.


Тренируйте системный анализ — откройте тренажёр с 1500+ вопросами для собесов.