ETag и conditional requests на собеседовании системного аналитика
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).
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, но не отвечает, что будет при одновременном редактировании.
Связанные темы
- HTTP методы и коды для SA
- Cache strategies для SA
- REST API для SA
- Idempotency key для SA
- Подготовка к собесу системного аналитика
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+ вопросами для собесов.