# API etcd

> Обзор основной конструкции API etcd

---

Индекс LLMS: [llms.txt](/ru/llms.txt)

---

Этот документ содержит обзор основной конструкции API etcd v3.
Не следует путать его с API etcd v2, объявленным устаревшим в etcd v3.5.
Документ не претендует на полноту, а сосредоточен на основных идеях, необходимых для понимания etcd, без отвлечения на менее распространённые вызовы API.
Все API etcd определены в [службах gRPC][grpc-service], которые группируют удалённые вызовы процедур (RPC), распознаваемые сервером etcd.
Полный перечень RPC etcd приведён в формате Markdown в [справочнике API gRPC][grpc-api].

## Службы gRPC {#grpc-services}

Каждый запрос API, отправленный серверу etcd, является удалённым вызовом процедуры gRPC. RPC в etcd объединяются в службы по назначению.

К важным для работы с пространством ключей etcd службам относятся:
* KV — создаёт, обновляет, получает и удаляет пары «ключ — значение».
* Watch — отслеживает изменения ключей.
* Lease — предоставляет примитивы для обработки клиентских сообщений поддержания активности.

К службам управления самим кластером относятся:
* Auth — механизм аутентификации пользователей на основе ролей.
* Cluster — предоставляет сведения о составе кластера и средства конфигурации.
* Maintenance — создаёт снимки для восстановления, дефрагментирует хранилище и возвращает сведения о состоянии отдельных участников.

### Запросы и ответы {#requests-and-responses}

Все RPC в etcd имеют одинаковый формат. Каждый RPC содержит функцию `Name`, которая принимает `NameRequest` как аргумент и возвращает `NameResponse` как ответ. Например, RPC `Range` описывается следующим образом:

```protobuf
service KV {
  Range(RangeRequest) returns (RangeResponse)
  ...
}
```

### Заголовок ответа {#response-header}

Все ответы API etcd содержат заголовок с метаданными кластера для данного ответа:

```proto
message ResponseHeader {
  uint64 cluster_id = 1;
  uint64 member_id = 2;
  int64 revision = 3;
  uint64 raft_term = 4;
}
```

* Cluster_ID — идентификатор кластера, создавшего ответ.
* Member_ID — идентификатор участника, создавшего ответ.
* Revision — ревизия хранилища ключей и значений на момент создания ответа.
* Raft_Term — срок полномочий Raft участника на момент создания ответа.

Приложение может прочитать поле `Cluster_ID` или `Member_ID`, чтобы убедиться, что взаимодействует с предполагаемым кластером (участником).

По полю `Revision` приложения могут узнать последнюю ревизию хранилища ключей и значений. Это особенно полезно, когда приложение задаёт историческую ревизию для `time travel query` и хочет определить последнюю ревизию на момент запроса.

С помощью `Raft_Term` приложения могут определить момент завершения новых выборов лидера в кластере.

## API ключей и значений {#key-value-api}

API ключей и значений управляет парами «ключ — значение», хранящимися в etcd. Обычно большинство запросов к etcd относится именно к ним.

### Системные примитивы {#system-primitives}

### Пара «ключ — значение» {#key-value-pair}

Пара «ключ — значение» — минимальная единица, которой может управлять соответствующий API. Каждая пара содержит ряд полей, определённых в [формате protobuf][kv-proto]:

```protobuf
message KeyValue {
  bytes key = 1;
  int64 create_revision = 2;
  int64 mod_revision = 3;
  int64 version = 4;
  bytes value = 5;
  int64 lease = 6;
}
```

* Key — ключ в байтах. Пустой ключ недопустим.
* Value — значение в байтах.
* Version — версия ключа. Удаление сбрасывает её в ноль, а любое изменение ключа увеличивает версию.
* Create_Revision — ревизия последнего создания ключа.
* Mod_Revision — ревизия последнего изменения ключа.
* Lease — идентификатор аренды, присоединённой к ключу. Если lease равен 0, аренда к ключу не присоединена.


Помимо ключа и значения etcd добавляет в сообщение ключа метаданные ревизии. Они упорядочивают ключи по времени создания и изменения, что полезно для управления параллелизмом при распределённой синхронизации. [Распределённые совместные блокировки][locks] клиента etcd используют ревизию создания при ожидании владения блокировкой. Аналогично, ревизия изменения применяется для обнаружения конфликтов набора чтения [программной транзакционной памяти][STM] и ожидания обновлений [выборов лидера][elections].

#### Ревизии {#revisions}

etcd поддерживает общий для кластера 64-битный счётчик — ревизию хранилища, которая увеличивается при каждом изменении пространства ключей. Ревизия служит глобальными логическими часами, последовательно упорядочивающими все обновления хранилища. Изменение, представленное новой ревизией, является инкрементным: связанные с ревизией данные — это данные, изменившие хранилище. На внутреннем уровне новая ревизия означает запись изменений в B+tree бэкенда с увеличенной ревизией в качестве ключа.

Особую ценность ревизии приобретают в бэкенде etcd с [многоверсионным управлением параллелизмом][mvcc]. Модель MVCC позволяет просматривать хранилище ключей и значений на прошлых ревизиях, поскольку исторические ревизии ключей сохраняются. Администраторы кластера могут настроить политику хранения этой истории для точного управления хранилищем; обычно etcd удаляет старые ревизии ключей по таймеру. Типичный кластер etcd хранит замещённые данные ключей несколько часов. Это также обеспечивает надёжную обработку длительных отключений клиентов, а не только временных сетевых сбоев: наблюдатели просто продолжают работу с последней замеченной исторической ревизии. Аналогично, для чтения хранилища в определённый момент запрос чтения можно пометить ревизией, чтобы вернуть ключи из представления пространства на момент фиксации этой ревизии.

#### Диапазоны ключей {#key-ranges}

Модель данных etcd индексирует все ключи в плоском двоичном пространстве. Этим она отличается от других хранилищ ключей и значений, использующих иерархическую организацию ключей в каталогах. Вместо перечисления по каталогам ключи перечисляются по интервалам `[a, b)`.

В etcd эти интервалы часто называют «диапазонами». Операции над диапазонами мощнее операций над каталогами. Подобно иерархическому хранилищу, интервалы поддерживают поиск одного ключа через `[a, a+1)` (например, ['a', 'a\x00') ищет 'a') и поиск в каталоге посредством кодирования ключей по глубине каталога. Кроме того, интервалы могут кодировать префиксы: например, интервал `['a', 'b')` ищет все ключи с префиксом 'a'.

По соглашению диапазон запроса обозначается полями `key` и `range_end`. Поле `key` содержит первый ключ диапазона и не должно быть пустым. `range_end` — ключ, следующий за последним ключом диапазона. Если `range_end` не задан или пуст, диапазон содержит только аргумент key. Если `range_end` равен `key` плюс один (например, "aa"+1 == "ab", "a\xff"+1 == "b"), диапазон представляет все ключи с префиксом key. Если и `key`, и `range_end` равны '\0', диапазон представляет все ключи. Если '\0' равен только `range_end`, диапазон содержит все ключи, большие либо равные аргументу key.

### Range {#range}

Ключи извлекаются из хранилища ключей и значений вызовом API `Range`, принимающим `RangeRequest`:

```protobuf
message RangeRequest {
  enum SortOrder {
	NONE = 0; // default, no sorting
	ASCEND = 1; // lowest target value first
	DESCEND = 2; // highest target value first
  }
  enum SortTarget {
	KEY = 0;
	VERSION = 1;
	CREATE = 2;
	MOD = 3;
	VALUE = 4;
  }

  bytes key = 1;
  bytes range_end = 2;
  int64 limit = 3;
  int64 revision = 4;
  SortOrder sort_order = 5;
  SortTarget sort_target = 6;
  bool serializable = 7;
  bool keys_only = 8;
  bool count_only = 9;
  int64 min_mod_revision = 10;
  int64 max_mod_revision = 11;
  int64 min_create_revision = 12;
  int64 max_create_revision = 13;
}
```

* Key, Range_End — диапазон извлекаемых ключей.
* Limit — максимальное количество ключей в ответе. Значение limit, равное 0, означает отсутствие ограничения.
* Revision — момент состояния хранилища ключей и значений для диапазона. Если revision меньше или равна нулю, диапазон относится к последнему состоянию хранилища. Если ревизия компактизирована, возвращается ответ ErrCompacted.
* Sort_Order — порядок для отсортированных запросов.
* Sort_Target — поле пары «ключ — значение» для сортировки.
* Serializable — задаёт использование сериализуемого локального чтения с участника для диапазонного запроса. По умолчанию Range линеаризуем и отражает текущий консенсус кластера. Ради повышения производительности и доступности ценой возможного чтения устаревших данных сериализуемый запрос диапазона обслуживается локально без достижения консенсуса с другими узлами кластера.
* Keys_Only — возвращать только ключи без значений.
* Count_Only — возвращать только количество ключей в диапазоне.
* Min_Mod_Revision — нижняя граница ревизий изменения ключей; меньшие ревизии отфильтровываются.
* Max_Mod_Revision — верхняя граница ревизий изменения ключей; большие ревизии отфильтровываются.
* Min_Create_Revision — нижняя граница ревизий создания ключей; меньшие ревизии отфильтровываются.
* Max_Create_Revision — верхняя граница ревизий создания ключей; большие ревизии отфильтровываются.

В ответ на вызов `Range` клиент получает сообщение `RangeResponse`:

```protobuf
message RangeResponse {
  ResponseHeader header = 1;
  repeated mvccpb.KeyValue kvs = 2;
  bool more = 3;
  int64 count = 4;
}
```

* Kvs — список пар «ключ — значение», соответствующих диапазонному запросу. При заданном `Count_Only` поле `Kvs` пусто.
* More — при заданном `limit` указывает, остались ли в запрошенном диапазоне ключи для возврата.
* Count — общее количество ключей, удовлетворяющих диапазонному запросу.

Для больших диапазонов ключей, когда буферизация полного ответа нежелательна, используйте [RangeStream](#rangestream).

### RangeStream {#rangestream}

`RangeStream` возвращает тот же набор результатов, что и `Range`, однако сервер разбивает ответ на последовательность фрагментов и передаёт их клиенту потоком. Благодаря этому ни одной стороне не нужно целиком буферизовать большие диапазоны в памяти. `RangeStream` принимает тот же `RangeRequest`, что и `Range`.

В ответ на вызов `RangeStream` клиент получает поток сообщений `RangeStreamResponse`:

```protobuf
message RangeStreamResponse {
  RangeResponse range_response = 1;
}
```

Заполнение полей во фрагментах:

* Kvs — каждый фрагмент содержит непересекающуюся часть результата. Объединение `kvs` всех фрагментов в порядке получения даёт тот же набор ключей, что и один вызов `Range`.
* Header, More, Count — заполняются только в последнем фрагменте и лишь при завершении потока без ошибки. В предыдущих фрагментах эти поля имеют нулевые значения. Применение `proto.Merge` ко всем `range_response` фрагментов даёт `RangeResponse`, эквивалентный ответу `Range`.

Если поток завершается ошибкой, ни один фрагмент не содержит действительных `header`, `more` или `count`.

Все фрагменты потока обслуживаются относительно одной ревизии. Если запрос не задаёт `Revision`, при запуске потока сервер фиксирует последнюю зафиксированную ревизию и использует её до конца потока.

`RangeStream` не поддерживает пользовательские порядки сортировки и фильтры ревизий (`min_mod_revision`, `max_mod_revision`, `min_create_revision`, `max_create_revision`). Использующие их запросы возвращают `Unimplemented`. Прокси gRPC etcd также не поддерживает `RangeStream`.

Существует два распространённых способа обработки `RangeStream`:

1. **Обрабатывать каждый фрагмент независимо.** Подходит для высокопроизводительных сценариев, когда клиент хочет декодировать и обрабатывать ключи по мере поступления, а не сначала собирать весь результат. Клиент перебирает фрагменты и обрабатывает `kvs` каждого, а после успешного завершения потока читает `header`, `more` или `count` из последнего фрагмента.
2. **Собрать единый ответ.** Подходит, когда клиенту требуется результат, эквивалентный унарному `Range`. Клиент объединяет `range_response` каждого фрагмента в один `RangeResponse` (например, через `proto.Merge`). Объединённый результат содержит полный `kvs`, а также `header`, `more` и `count` из последнего фрагмента. Для этого шаблона клиент Go предоставляет вспомогательную функцию `clientv3.GetStreamToGetResponse`.

### Put {#put}

Ключи сохраняются в хранилище ключей и значений вызовом `Put`, принимающим `PutRequest`:

```protobuf
message PutRequest {
  bytes key = 1;
  bytes value = 2;
  int64 lease = 3;
  bool prev_kv = 4;
  bool ignore_value = 5;
  bool ignore_lease = 6;
}
```

* Key — имя ключа, записываемого в хранилище ключей и значений.
* Value — значение в байтах, связываемое с ключом в хранилище.
* Lease — идентификатор аренды, связываемой с ключом. Значение аренды 0 означает отсутствие аренды.
* Prev_Kv — если задано, в ответе возвращаются данные пары «ключ — значение» до обновления запросом `Put`.
* Ignore_Value — если задано, ключ обновляется без изменения текущего значения. Если ключ не существует, возвращается ошибка.
* Ignore_Lease — если задано, ключ обновляется без изменения текущей аренды. Если ключ не существует, возвращается ошибка.

В ответ на вызов `Put` клиент получает сообщение `PutResponse`:

```protobuf
message PutResponse {
  ResponseHeader header = 1;
  mvccpb.KeyValue prev_kv = 2;
}
```

* Prev_Kv — пара «ключ — значение», перезаписанная операцией `Put`, если в `PutRequest` было задано `Prev_Kv`.

### Удаление диапазона {#delete-range}

Диапазоны ключей удаляются вызовом `DeleteRange`, принимающим `DeleteRangeRequest`:

```protobuf
message DeleteRangeRequest {
  bytes key = 1;
  bytes range_end = 2;
  bool prev_kv = 3;
}
```

* Key, Range_End — удаляемый диапазон ключей.
* Prev_Kv — если задано, возвращает содержимое удалённых пар «ключ — значение».

В ответ на вызов `DeleteRange` клиент получает сообщение `DeleteRangeResponse`:

```protobuf
message DeleteRangeResponse {
  ResponseHeader header = 1;
  int64 deleted = 2;
  repeated mvccpb.KeyValue prev_kvs = 3;
}
```

* Deleted — количество удалённых ключей.
* Prev_Kv — список всех пар «ключ — значение», удалённых операцией `DeleteRange`.

### Транзакция {#transaction}

Транзакция — атомарная конструкция If/Then/Else над хранилищем ключей и значений. Она предоставляет примитив для объединения запросов в атомарные блоки (then/else), выполнение которых защищено условием (if), основанным на содержимом хранилища. Транзакции позволяют защищать ключи от непреднамеренных параллельных обновлений, строить операции сравнения с обменом и создавать механизмы управления параллелизмом более высокого уровня.

Транзакция может атомарно обработать несколько запросов в одном запросе. При изменении хранилища его ревизия увеличивается только один раз на транзакцию, а все созданные ею события имеют одинаковую ревизию. Однако многократное изменение одного ключа в рамках одной транзакции запрещено.

Все транзакции защищены конъюнкцией сравнений, подобной оператору `If`. Каждое сравнение проверяет один ключ в хранилище: отсутствие или наличие значения, равенство заданному значению либо ревизию или версию ключа. Два разных сравнения могут относиться к одному или разным ключам. Все сравнения применяются атомарно. Если они истинны, транзакция считается успешной и etcd применяет блок запросов then / `success`; иначе транзакция считается неудачной и применяется блок else / `failure`.

Каждое сравнение кодируется сообщением `Compare`:

```protobuf
message Compare {
  enum CompareResult {
    EQUAL = 0;
    GREATER = 1;
    LESS = 2;
    NOT_EQUAL = 3;
  }
  enum CompareTarget {
    VERSION = 0;
    CREATE = 1;
    MOD = 2;
    VALUE= 3;
  }
  CompareResult result = 1;
  // target is the key-value field to inspect for the comparison.
  CompareTarget target = 2;
  // key is the subject key for the comparison operation.
  bytes key = 3;
  oneof target_union {
    int64 version = 4;
    int64 create_revision = 5;
    int64 mod_revision = 6;
    bytes value = 7;
  }
}
```

* Result — тип логической операции сравнения (например, равно, меньше и т. д.).
* Target — сравниваемое поле пары «ключ — значение»: версия ключа, ревизия создания, ревизия изменения либо значение.
* Key — ключ для сравнения.
* Target_Union — заданные пользователем данные сравнения.

После обработки блока сравнений транзакция применяет блок запросов. Блок представляет собой список сообщений `RequestOp`:

```protobuf
message RequestOp {
  // request is a union of request types accepted by a transaction.
  oneof request {
    RangeRequest request_range = 1;
    PutRequest request_put = 2;
    DeleteRangeRequest request_delete_range = 3;
  }
}
```

* Request_Range — `RangeRequest`.
* Request_Put — `PutRequest`. Ключи должны быть уникальны и не могут пересекаться с ключами других операций Put или Delete.
* Request_Delete_Range — `DeleteRangeRequest`. Ключи не могут пересекаться с ключами запросов Put или Delete.

В итоге транзакция выполняется вызовом API `Txn`, принимающим `TxnRequest`:

```protobuf
message TxnRequest {
  repeated Compare compare = 1;
  repeated RequestOp success = 2;
  repeated RequestOp failure = 3;
}
```

* Compare — список предикатов, представляющих конъюнкцию условий защиты транзакции.
* Success — список запросов, обрабатываемых, если все сравнения истинны.
* Failure — список запросов, обрабатываемых, если хотя бы одно сравнение ложно.

В ответ на вызов `Txn` клиент получает сообщение `TxnResponse`:

```protobuf
message TxnResponse {
  ResponseHeader header = 1;
  bool succeeded = 2;
  repeated ResponseOp responses = 3;
}
```

* Succeeded — результат вычисления `Compare`: true или false.
* Responses — список ответов, соответствующих результатам применения блока `Success`, если succeeded равно true, либо блока `Failure`, если succeeded равно false.

Список `Responses` соответствует результатам применённого списка `RequestOp`, причём каждый ответ кодируется как `ResponseOp`:

```protobuf
message ResponseOp {
  oneof response {
    RangeResponse response_range = 1;
    PutResponse response_put = 2;
    DeleteRangeResponse response_delete_range = 3;
  }
}
```

Включённый в каждый внутренний ответ `ResponseHeader` не следует интерпретировать каким-либо образом.
Если клиенту нужна последняя ревизия, он всегда должен проверять верхнеуровневый `ResponseHeader` в `TxnResponse`.


## API наблюдения {#watch-api}

API `Watch` предоставляет событийный интерфейс для асинхронного отслеживания изменений ключей. Наблюдение etcd ожидает изменения, непрерывно отслеживая ключи с заданной текущей или исторической ревизии, и потоком отправляет обновления клиенту.

### События {#events}

Каждое изменение любого ключа представлено сообщением `Event`. Сообщение `Event` содержит данные и тип обновления:

```protobuf
message Event {
  enum EventType {
    PUT = 0;
    DELETE = 1;
  }
  EventType type = 1;
  KeyValue kv = 2;
  KeyValue prev_kv = 3;
}
```

* Type — тип события. PUT означает сохранение новых данных по ключу, DELETE — удаление ключа.
* KV — связанный с событием KeyValue. Событие PUT содержит текущую пару kv. PUT с kv.Version=1 означает создание ключа. DELETE содержит удалённый ключ, у которого ревизия изменения равна ревизии удаления.
* Prev_KV — пара «ключ — значение» из ревизии непосредственно перед событием. Для экономии пропускной способности заполняется, только если явно включена в наблюдении.

### Потоки наблюдения {#watch-streams}

Наблюдения — длительные запросы, использующие потоки gRPC для передачи данных событий. Поток наблюдения двунаправлен: клиент записывает в него для создания наблюдений и читает для получения событий. Один поток может мультиплексировать множество отдельных наблюдений, помечая события их идентификаторами. Это снижает потребление памяти и накладные расходы соединений в основном кластере etcd.

Гарантии для событий наблюдения описаны в разделе [гарантии API etcd][watch-api-guarantees].

Клиент создаёт наблюдение, отправляя `WatchCreateRequest` через поток, возвращённый `Watch`:

```protobuf
message WatchCreateRequest {
  bytes key = 1;
  bytes range_end = 2;
  int64 start_revision = 3;
  bool progress_notify = 4;

  enum FilterType {
    NOPUT = 0;
    NODELETE = 1;
  }
  repeated FilterType filters = 5;
  bool prev_kv = 6;
}
```

* Key, Range_End — наблюдаемый диапазон ключей.
* Start_Revision — необязательная ревизия, с которой включительно начинается наблюдение. Если не задана, поток передаёт события после ревизии из заголовка ответа о создании наблюдения. Всю доступную историю событий можно наблюдать с последней ревизии компактизации.
* Progress_Notify — если задано и недавних событий нет, наблюдение периодически получает WatchResponse без событий. Это полезно для восстановления отключённого наблюдателя с недавней известной ревизии. Сервер etcd выбирает частоту уведомлений по текущей нагрузке.
* Filters — список типов событий, отфильтровываемых на стороне сервера.
* Prev_Kv — если задано, наблюдение получает данные пары «ключ — значение» до события. Это позволяет узнать, какие данные были перезаписаны.

В ответ на `WatchCreateRequest` либо при появлении нового события для созданного наблюдения клиент получает `WatchResponse`:

```protobuf
message WatchResponse {
  ResponseHeader header = 1;
  int64 watch_id = 2;
  bool created = 3;
  bool canceled = 4;
  int64 compact_revision = 5;

  repeated mvccpb.Event events = 11;
}
```

* Watch_ID — идентификатор наблюдения, соответствующего ответу.
* Created — равно true, если это ответ на запрос создания наблюдения. Клиент должен сохранить идентификатор и ожидать события наблюдения в потоке. Все отправленные созданному наблюдателю события имеют одинаковый watch_id.
* Canceled — равно true, если это ответ на запрос отмены наблюдения. Отменённому наблюдателю больше не отправляются события.
* Compact_Revision — минимальная доступная etcd историческая ревизия, если наблюдатель пытается начать с компактизированной ревизии. Такое происходит при создании наблюдателя на компактизированной ревизии или когда наблюдатель не успевает за изменениями хранилища. Наблюдатель отменяется; создание новых наблюдений с тем же start_revision завершится ошибкой.
* Events — упорядоченный список новых событий, соответствующих данному идентификатору наблюдения.

Чтобы прекратить получение событий наблюдения, клиент отправляет `WatchCancelRequest`:

```protobuf
message WatchCancelRequest {
   int64 watch_id = 1;
}
```

* Watch_ID — идентификатор отменяемого наблюдения, которому больше не будут передаваться события.

## API аренды {#lease-api}

Аренды служат механизмом определения активности клиента. Кластер выдаёт аренды со сроком жизни. Аренда истекает, если кластер etcd не получает keepAlive в течение заданного периода TTL.

Для связи аренд с хранилищем каждый ключ можно присоединить не более чем к одной аренде. При истечении или отзыве аренды все присоединённые ключи удаляются. Каждый истёкший ключ создаёт событие удаления в истории событий.

### Получение аренд {#obtaining-leases}

Аренды получают вызовом API `LeaseGrant`, принимающим `LeaseGrantRequest`:

```protobuf
message LeaseGrantRequest {
  int64 TTL = 1;
  int64 ID = 2;
}
```

* TTL — рекомендуемый срок жизни в секундах.
* ID — запрошенный идентификатор аренды. Если ID равен 0, etcd выбирает идентификатор самостоятельно.

В ответ на вызов `LeaseGrant` клиент получает `LeaseGrantResponse`:

```protobuf
message LeaseGrantResponse {
  ResponseHeader header = 1;
  int64 ID = 2;
  int64 TTL = 3;
}
```

* ID — идентификатор выданной аренды.
* TTL — выбранный сервером срок жизни аренды в секундах.

```protobuf
message LeaseRevokeRequest {
  int64 ID = 1;
}
```

* ID — идентификатор отзываемой аренды. При отзыве все присоединённые ключи удаляются.

### Поддержание активности {#keep-alives}

Аренды обновляются через двунаправленный поток, созданный вызовом API `LeaseKeepAlive`. Чтобы обновить аренду, клиент отправляет через поток `LeaseKeepAliveRequest`:

```protobuf
message LeaseKeepAliveRequest {
  int64 ID = 1;
}
```

* ID — идентификатор аренды, активность которой поддерживается.

Поток поддержания активности отвечает сообщением `LeaseKeepAliveResponse`:

```protobuf
message LeaseKeepAliveResponse {
  ResponseHeader header = 1;
  int64 ID = 2;
  int64 TTL = 3;
}
```

* ID — аренда, обновлённая с новым TTL.
* TTL — новый оставшийся срок жизни аренды в секундах.

[watch-api-guarantees]: /ru/docs/etcd/learning/api_guarantees/#watch-apis
[elections]: https://github.com/etcd-io/etcd/blob/main/client/v3/concurrency/election.go
[grpc-api]: /ru/docs/etcd/dev-guide/api_reference_v3/
[grpc-service]: https://github.com/etcd-io/etcd/blob/main/api/etcdserverpb/rpc.proto
[kv-proto]: https://github.com/etcd-io/etcd/blob/main/api/mvccpb/kv.proto
[locks]: https://github.com/etcd-io/etcd/blob/main/client/v3/concurrency/mutex.go
[mvcc]: https://en.wikipedia.org/wiki/Multiversion_concurrency_control
[stm]: https://github.com/etcd-io/etcd/blob/main/client/v3/concurrency/stm.go

---

Обратные ссылки:

- [Интеракция с etcd](/ru/docs/etcd/dev-guide/interacting_v3/)
- [Гарантии API etcd](/ru/docs/etcd/learning/api_guarantees/)
- [Как выполнить несколько записей в транзакции](/ru/docs/etcd/tasks/developer/how-to-transactional-write/)
