diff --git a/ydb/docs/en/core/concepts/datamodel/coordination-node.md b/ydb/docs/en/core/concepts/datamodel/coordination-node.md new file mode 100644 index 000000000000..27a14163475f --- /dev/null +++ b/ydb/docs/en/core/concepts/datamodel/coordination-node.md @@ -0,0 +1,39 @@ +# Coordination node + +A coordination node is an object in {{ ydb-short-name }} that allows client applications to coordinate their actions in a distributed manner. Typical use cases for coordination nodes include: + +* Distributed [semaphores](https://en.wikipedia.org/wiki/Semaphore_(programming)) and [mutexes](https://en.wikipedia.org/wiki/Mutual_exclusion). +* Service discovery. +* Leader election. +* Task queues. +* Publishing small amounts of data with the ability to receive change notifications. +* Ephemeral locking of arbitrary entities not known in advance. + +## Semaphores {#semaphore} + +Coordination nodes allow you to create and manage semaphores within them. Typical operations with semaphores include: + +* Create. +* Acquire. +* Release. +* Describe. +* Subscribe. +* Delete. + +A semaphore can have a counter that limits the number of simultaneous acquisitions, as well as a small amount of arbitrary data attached to it. + +{{ ydb-short-name }} supports two types of semaphores: persistent and ephemeral. A persistent semaphore must be created before acquisition and will exist either until it is explicitly deleted or until the coordination node in which it was created is deleted. Ephemeral semaphores are automatically created at the moment of their first acquisition and deleted at the last release, which is convenient to use, for example, in distributed locking scenarios. + +{% note info %} + +Semaphores in {{ ydb-short-name }} are **not** recursive. Thus, semaphore acquisition and release are idempotent operations. + +{% endnote %} + +## Usage {#usage} + +Working with coordination nodes and semaphores is done through [dedicated methods in {{ ydb-short-name }} SDK](../../reference/ydb-sdk/coordination.md). + +## Similar systems {#similar-systems} + +{{ ydb-short-name }} coordination nodes can solve tasks that are traditionally performed using systems such as [Apache Zookeeper](https://zookeeper.apache.org/), [etcd](https://etcd.io/), [Consul](https://www.consul.io/), and others. If a project uses {{ ydb-short-name }} for data storage along with one of these third-party systems for coordination, switching to {{ ydb-short-name }} coordination nodes can reduce the number of systems that need to be operated and maintained. \ No newline at end of file diff --git a/ydb/docs/en/core/concepts/datamodel/toc_i.yaml b/ydb/docs/en/core/concepts/datamodel/toc_i.yaml index c44e1bae04f9..685b86a8c57c 100644 --- a/ydb/docs/en/core/concepts/datamodel/toc_i.yaml +++ b/ydb/docs/en/core/concepts/datamodel/toc_i.yaml @@ -1,8 +1,9 @@ items: -- { name: Directory, href: dir.md } -- { name: Table, href: table.md } -- { name: View, href: view.md } -- { name: Topic, href: ../topic.md } +- { name: Directories, href: dir.md } +- { name: Tables, href: table.md } +- { name: Views, href: view.md } +- { name: Topics, href: ../topic.md } +- { name: Coordination nodes, href: coordination-node.md } - { name: Secrets, href: secrets.md } - { name: External tables, href: external_table.md } - { name: External data source, href: external_data_source.md } diff --git a/ydb/docs/en/core/concepts/glossary.md b/ydb/docs/en/core/concepts/glossary.md index 6615aea1e709..35a85c308cbb 100644 --- a/ydb/docs/en/core/concepts/glossary.md +++ b/ydb/docs/en/core/concepts/glossary.md @@ -209,6 +209,14 @@ A **consumer** is an entity that reads messages from a topic. **Replica object** is a mirror copy of the replicated object, automatically created by an [asynchronous replication instance](#async-replication-instance). Replica objects are typically read-only. +### Coordination node {#coordination-node} + +A **coordination node** is a schema object that allows client applications to create semaphores for coordinating their actions. Learn more about [coordination nodes](./datamodel/coordination-node.md). + +#### Semaphore {#semaphore} + +A **semaphore** is an object within a [coordination node](#coordination-node) that provides a synchronization mechanism for distributed applications. Semaphores can be persistent or ephemeral and support operations like creation, acquisition, release, and monitoring. Learn more about [semaphores in {{ ydb-short-name }}](./datamodel/coordination-node.md#semaphore). + ### YQL {#yql} **YQL ({{ ydb-short-name }} Query Language)** is a high-level language for working with the system. It is a dialect of [ANSI SQL](https://en.wikipedia.org/wiki/SQL). There's a lot of content covering YQL, including a [tutorial](../dev/yql-tutorial/index.md), [reference](../yql/reference/syntax/index.md), and [recipes](../recipes/yql/index.md). diff --git a/ydb/docs/en/core/recipes/ydb-sdk/config-publication.md b/ydb/docs/en/core/recipes/ydb-sdk/config-publication.md new file mode 100644 index 000000000000..baeed03a41cd --- /dev/null +++ b/ydb/docs/en/core/recipes/ydb-sdk/config-publication.md @@ -0,0 +1,10 @@ +# Configuration publication + +Let's consider a scenario where we need to publish a small configuration for multiple application instances that should promptly react to its changes. + +This scenario can be implemented using semaphores in [{{ ydb-short-name }} coordination nodes](../../reference/ydb-sdk/coordination.md) as follows: + +1. A semaphore is created (for example, named `my-service-config`). +1. The updated configuration is published through `UpdateSemaphore`. +1. Application instances call `DescribeSemaphore` with `WatchData=true`. In the result, the `Data` field will contain the current version of the configuration. +1. When the configuration changes, `OnChanged` is called. In this case, application instances make a similar `DescribeSemaphore` call and receive the updated configuration. \ No newline at end of file diff --git a/ydb/docs/en/core/recipes/ydb-sdk/distributed-lock.md b/ydb/docs/en/core/recipes/ydb-sdk/distributed-lock.md new file mode 100644 index 000000000000..d7dffe17f070 --- /dev/null +++ b/ydb/docs/en/core/recipes/ydb-sdk/distributed-lock.md @@ -0,0 +1,42 @@ +# Distributed lock + +Consider a scenario where it is necessary to ensure that only one instance of a client application accesses a shared resource at any given time. To achieve this, the semaphore mechanism in [{{ ydb-short-name }} coordination nodes](../../reference/ydb-sdk/coordination.md) can be utilized. + +## Semaphore lease mechanism + +In contrast to local multithreaded programming, clients in distributed systems do not directly acquire locks or semaphores. Instead, they lease them for a specified duration, which can be periodically renewed. Due to reliance on physical time which can vary between machines, clients and the server might encounter situations where multiple clients believe they have acquired the same semaphore simultaneously, even if the server's perspective differs. To reduce the likelihood of such occurrences, it is crucial to configure automatic time synchronization beforehand, both on servers hosting client applications and on the {{ ydb-short-name }} side, ideally using a unified time source. + +Therefore, while distributed locking through such mechanisms cannot guarantee the complete absence of simultaneous resource access, it can significantly lower the probability of such events. This approach serves as an optimization to prevent unnecessary competition among clients for a shared resource. Absolute guarantees against concurrent resource requests сould be implemented on the resource side. + +## Code example + +{% list tabs %} + +- Go + + ```go + for { + if session, err := db.Coordination().CreateSession(ctx, path); err != nil { + return fmt.Errorf("cannot create session: %v", err); + } + + if lease, err := session.AcquireSemaphore(ctx, + semaphore, + coordination.Exclusive, + options.WithEphemeral(true), + ); err != nil { + // the session is likely lost, try to create a new one and get the lock in it + session.Close(ctx); + continue; + } + + // lock acquired, start processing + select { + case <-lease.Context().Done(): + } + + // lock released, end processing + } + ``` + +{% endlist %} \ No newline at end of file diff --git a/ydb/docs/en/core/recipes/ydb-sdk/index.md b/ydb/docs/en/core/recipes/ydb-sdk/index.md index 944b565f109f..a0ea16ea2aba 100644 --- a/ydb/docs/en/core/recipes/ydb-sdk/index.md +++ b/ydb/docs/en/core/recipes/ydb-sdk/index.md @@ -25,6 +25,13 @@ Table of contents: - [Inserting data](upsert.md) - [Bulk upsert of data](bulk-upsert.md) - [Setting up the transaction execution mode](tx-control.md) +- Coordination + + - [Distributed lock](distributed-lock.md) + - [Service discovery](service-discovery.md) + - [Configuration publication](config-publication.md) + - [Leader election](leader-election.md) + - [Troubleshooting](debug.md) - [Enable logging](debug-logs.md) diff --git a/ydb/docs/en/core/recipes/ydb-sdk/leader-election.md b/ydb/docs/en/core/recipes/ydb-sdk/leader-election.md new file mode 100644 index 000000000000..2b30d7f3ac80 --- /dev/null +++ b/ydb/docs/en/core/recipes/ydb-sdk/leader-election.md @@ -0,0 +1,11 @@ +# Leader election + +Consider a scenario where multiple application instances need to elect a leader among themselves and be aware of the current leader at any given time. + +This scenario can be implemented using semaphores in [{{ ydb-short-name }} coordination nodes](../../reference/ydb-sdk/coordination.md) as follows: + +1. A semaphore is created (for example, named `my-service-leader`) with `Limit=1`. +1. All application instances call `AcquireSemaphore` with `Count=1`, specifying their endpoint in the `Data` field. +1. Only one application instance's call will complete quickly, while others will be queued. The application instance whose call completes successfully becomes the current leader. +1. All application instances call `DescribeSemaphore` with `WatchOwners=true` and `IncludeOwners=true`. The result's `Owners` field will contain at most one element, from which the current leader's endpoint can be determined via its `Data` field. +1. When the leader changes, `OnChanged` is called. In this case, application instances make a similar `DescribeSemaphore` call to learn about the new leader. \ No newline at end of file diff --git a/ydb/docs/en/core/recipes/ydb-sdk/service-discovery.md b/ydb/docs/en/core/recipes/ydb-sdk/service-discovery.md new file mode 100644 index 000000000000..c9318cb54115 --- /dev/null +++ b/ydb/docs/en/core/recipes/ydb-sdk/service-discovery.md @@ -0,0 +1,12 @@ +# Service discovery + +Consider a scenario where application instances are dynamically started and publish their endpoints, while other clients need to receive this list and respond to its changes. + +This scenario can be implemented using semaphores in [{{ ydb-short-name }} coordination nodes](../../reference/ydb-sdk/coordination.md) as follows: + +1. Create a semaphore (for example, named `my-service-endpoints`) with `Limit=Max()`. +1. All application instances call `AcquireSemaphore` with `Count=1`, specifying their endpoint in the `Data` field. +1. Since the semaphore limit is very high, all `AcquireSemaphore` calls should complete quickly. +1. At this point, publication is complete, and application instances only need to respond to session stops by republishing themselves through a new session. +1. Clients call `DescribeSemaphore` with `IncludeOwners=true` and optionally with `WatchOwners=true`. In the result, the `Owners` field's `Data` will contain the endpoints of registered application instances. +1. When the list of endpoints changes, `OnChanged` is called. In this case, clients make a similar `DescribeSemaphore` call and receive the updated list. \ No newline at end of file diff --git a/ydb/docs/en/core/recipes/ydb-sdk/toc_i.yaml b/ydb/docs/en/core/recipes/ydb-sdk/toc_i.yaml index b8aee6dcaeed..3fd9eb3a2087 100644 --- a/ydb/docs/en/core/recipes/ydb-sdk/toc_i.yaml +++ b/ydb/docs/en/core/recipes/ydb-sdk/toc_i.yaml @@ -39,6 +39,16 @@ items: href: bulk-upsert.md - name: Setting up the transaction execution mode href: tx-control.md +- name: Coordination + items: + - name: Distributed lock + href: distributed-lock.md + - name: Leader election + href: leader-election.md + - name: Service discovery + href: service-discovery.md + - name: Configuration publication + href: config-publication.md - name: Troubleshooting items: - name: Overview diff --git a/ydb/docs/en/core/reference/ydb-sdk/coordination.md b/ydb/docs/en/core/reference/ydb-sdk/coordination.md new file mode 100644 index 000000000000..bbc790d6ae87 --- /dev/null +++ b/ydb/docs/en/core/reference/ydb-sdk/coordination.md @@ -0,0 +1,325 @@ +# Working with coordination nodes + +This article describes how to use the {{ ydb-short-name }} SDK to coordinate the work of multiple client application instances using [coordination nodes](../../concepts/datamodel/coordination-node.md) and their semaphores. + +## Creating a coordination node + +Coordination nodes are created in {{ ydb-short-name }} databases in the same namespace as other schema objects, such as [tables](../../concepts/datamodel/table.md) and [topics](../../concepts/topic.md). + +{% list tabs %} + +- Go + + ```go + err := db.Coordination().CreateNode(ctx, + "/path/to/mynode", + ) + ``` + +- C++ + + ```cpp + TClient client(driver); + auto status = client + .CreateNode("/path/to/mynode") + .ExtractValueSync(); + Y_ABORT_UNLESS(status.IsSuccess()); + ``` + + When creating a node, you can optionally specify `TNodeSettings` with the following settings: + + - `ReadConsistencyMode` - defaults to `RELAXED`, allowing the reading of potentially outdated values during leader transitions. You can optionally enable the `STRICT` mode, where all reads are processed through the consensus algorithm, ensuring the most recent value is returned, albeit at a higher cost. + - `AttachConsistencyMode` - defaults to `STRICT`, requiring the consensus algorithm for session recovery. Optionally, the `RELAXED` mode can be enabled for session recovery during failures, bypassing this requirement. This mode may be necessary for a large number of clients, facilitating session recovery without consensus, which maintains overall correctness but may lead to outdated reads during leader transitions and session expiration in problematic scenarios. + - `SelfCheckPeriod` (default 1 second) - the interval at which the service performs self-liveness checks. It is not recommended to change this setting except under special circumstances. + + - A larger value reduces server load but increases the delay in detecting leader changes and informing the service. + - A smaller value increases server load and improves problem detection speed, but may result in false positives when the service incorrectly identifies issues. + + - `SessionGracePeriod` (default 10 seconds) - the duration during which a new leader refrains from closing existing open sessions, prolonging their validity. + + - A smaller value reduces the window during which sessions from non-existent clients, which failed to report their absence during leader changes, hold semaphores and block other clients. + - A smaller value also increases the likelihood of false positives, where a living leader might terminate itself as a precaution, uncertain if this period has concluded on a potential new leader. + - This value must be strictly greater than `SelfCheckPeriod`. + +{% endlist %} + +## Working with sessions {#session} + +### Creating a session {#create-session} + +To start working with coordination nodes, a client must establish a session within which it will perform all operations with the coordination node. + +{% list tabs %} + +- Go + + ```go + session, err := db.Coordination().CreateSession(ctx, + "/path/to/mynode", // Coordination Node name in the database + ) + ``` + +- C++ + + ```cpp + TClient client(driver); + const TSession& session = client + .StartSession("/path/to/mynode") + .ExtractValueSync() + .ExtractResult(); + ``` + + When establishing a session, you can optionally pass a `TSessionSettings` structure with the following settings: + + - `Description` - a text description of the session, displayed in internal interfaces and can be useful for problem diagnosis. + - `OnStateChanged` - called on significant changes during the session's life, passing the corresponding state: + + - `ATTACHED` - the session is connected and operating in normal mode. + - `DETACHED` - the session temporarily lost connection with the service but can still be restored. + - `EXPIRED` - the session lost connection with the service and cannot be restored. + + - `OnStopped` - called when the session stops attempting to restore the connection with the service, which can be useful for establishing a new connection. + - `Timeout` - the maximum timeout during which the session can be restored after losing connection with the service. + +{% endlist %} + +### Session control {#session-control} + +It's important for the client application to monitor the session state, as it can only rely on the state of captured semaphores while the session is alive. When the session ends by client or server initiative, the client can no longer assume that other clients in the cluster haven't captured its semaphores and changed their state. + +{% list tabs %} + +- Go + + In Go SDK, the session context `session.Context()` is used to track such situations, which terminates along with the session. The SDK can handle transport-level errors on its own and re-establish connection with the service, trying to restore the session if still possible. Thus, the client only needs to monitor the session context to react timely to its loss. + +- C++ + + In the C++ SDK, an active session continuously maintains and automatically re-establishes the connection with the {{ ydb-short-name }} cluster in the background. + +{% endlist %} + +## Working with semaphores {#semaphore} + +### Creating a semaphore {#create-semaphore} + +When creating a semaphore, you can specify its limit. The limit determines the maximum value to which it can be increased. Calls attempting to increase the semaphore value above this limit will wait until their increase requests can be fulfilled without exceeding the semaphore's limit. + +{% list tabs %} + +- Go + + ```go + err := session.CreateSemaphore(ctx, + "my-semaphore", // semaphore name + 10 // semaphore limit + ) + ``` + +- С++ + + ```cpp + session + .CreateSemaphore( + "my-semaphore", // semaphore name + 10 // semaphore limit + ) + .ExtractValueSync() + .ExtractResult(); + ``` + + You can also pass a string that will be stored with the semaphore and returned when it's captured: + + ```cpp + session + .CreateSemaphore( + "my-semaphore", // semaphore name + 10, // semaphore limit + "my-data" // semaphore data + ) + .ExtractValueSync() + .ExtractResult(); + ``` + +{% endlist %} + +### Acquiring a semaphore {#acquire-semaphore} + +To acquire a semaphore, the client must call the `AcquireSemaphore` method and wait for a special `Lease` object. This object represents confirmation that the semaphore value was successfully increased and can be considered as such until explicit release of such semaphore or termination of the session in which such confirmation was received. + +{% list tabs %} + +- Go + + ```go + lease, err := session.AcquireSemaphore(ctx, + "my-semaphore", // semaphore name + 5, // value to increase semaphore by + ) + ``` + + Similar to the session, the `Lease` object also has a context that terminates at one of these moments. + + To cancel waiting for semaphore acquisition, it's sufficient to cancel the passed context `ctx`. + +- C++ + + ```cpp + session + .AcquireSemaphore( + "my-semaphore", // semaphore name + TAcquireSemaphoreSettings().Count(5) // value to increase semaphore by + ) + .ExtractValueSync() + .ExtractResult(); + ``` + + When acquiring, you can optionally pass a `TAcquireSemaphoreSettings` structure with the following settings: + + - `Count` - value by which the semaphore is increased upon acquisition. + - `Data` - additional data that can be put into the semaphore. + - `OnAccepted` - called when the operation is queued. For example, if the semaphore couldn't be acquired immediately. + + - Won't be called if the semaphore is acquired immediately. + - It's important to consider that the call can happen in parallel with the `TFuture` result. + + - `Timeout` - maximum time during which the operation can stay in the queue on the server. + + - Operation will return `false` if the semaphore couldn't be acquired within `Timeout` after being added to the queue. + - With `Timeout` set to 0, the operation works like `TryAcquire`, i.e., the semaphore will either be acquired atomically and the operation will return `true`, or the operation will return `false` without using queues. + + - `Ephemeral` - if `true`, then the name is an ephemeral semaphore. Such semaphores are automatically created at first `Acquire` and automatically deleted with the last `Release`. + - `Shared()` - alias for setting `Count = 1`, acquiring semaphore in shared mode. + - `Exclusive()` - alias for setting `Count = max`, acquiring semaphore in exclusive mode. (For semaphores created with limit `Max()`.) + +{% endlist %} + +The taken value of an acquired semaphore can be decreased (but not increased) by calling the `AcquireSemaphore` method again with a smaller value. + +### Updating semaphore data {#update-semaphore} + +Using the `UpdateSemaphore` method, you can update (replace) the semaphore data that was attached during its creation. + +{% list tabs %} + +- Go + + ```go + err := session.UpdateSemaphore( + "my-semaphore", // semaphore name + options.WithUpdateData([]byte("updated-data")), // new semaphore data + ) + ``` + +- C++ + + ```cpp + session + .UpdateSemaphore( + "my-semaphore", // semaphore name + "updated-data" // new semaphore data + ) + .ExtractValueSync() + .ExtractResult(); + ``` + +{% endlist %} + +This call doesn't require acquiring the semaphore and doesn't lead to it. If you need only one specific client to update the data, this must be explicitly ensured, for example, by acquiring the semaphore, updating the data, and releasing the semaphore back. + +### Getting semaphore data {#describe-semaphore} + +{% list tabs %} + +- Go + + ```go + description, err := session.DescribeSemaphore( + "my-semaphore" // semaphore name + options.WithDescribeOwners(true), // to get list of owners + options.WithDescribeWaiters(true), // to get list of waiters + ) + ``` + +- C++ + + ```cpp + session + .DescribeSemaphore( + "my-semaphore" // semaphore name + ) + .ExtractValueSync() + .ExtractResult(); + ``` + + When getting information about a semaphore, you can optionally pass a `TDescribeSemaphoreSettings` structure with the following settings: + + - `OnChanged` - called once after data changes on the server (with a `bool` parameter, if `true` - the call occurred due to some changes, if `false` - it's a false call and you need to repeat `DescribeSemaphore` to restore the subscription). + - `WatchData` - call `OnChanged` when semaphore data changes. + - `WatchOwners` - call `OnChanged` when semaphore owners change. + - `IncludeOwners` - return the list of owners in the results. + - `IncludeWaiters` - return the list of waiters in the results. + + The call result is a structure with the following fields: + + - `Name` - semaphore name. + - `Data` - semaphore data. + - `Count` - the current value of the semaphore. + - `Limit` - the limit specified when creating the semaphore. + - `Owners` - list of semaphore owners. + - `Waiters` - list of clients waiting in the semaphore queue. + - `Ephemeral` - whether the semaphore is ephemeral. + + The `Owners` and `Waiters` fields in the result contain a list of structures with the following fields: + + - `OrderId` - sequence number of the acquire operation on the semaphore (can be used for identification, for example if `OrderId` changed, it means the session called `ReleaseSemaphore` and a new `AcquireSemaphore`). + - `SessionId` - identifier of the session that made this `AcquireSemaphore` call. + - `Timeout` - timeout value used in the `AcquireSemaphore` call for queued operations. + - `Count` - value requested in `AcquireSemaphore`. + - `Data` - data specified in `AcquireSemaphore`. + +{% endlist %} + +### Releasing a semaphore {#release-semaphore} + +{% list tabs %} + +- Go + + To release a semaphore acquired in a session, call the `Release` method on the `Lease` object. + + ```go + err := lease.Release() + ``` + +- C++ + + ```cpp + session + .ReleaseSemaphore( + "my-semaphore" // semaphore name + ) + .ExtractValueSync() + .ExtractResult(); + ``` + +{% endlist %} + +## Important implementation details + +The `AcquireSemaphore` and `ReleaseSemaphore` operations are idempotent. When `AcquireSemaphore` is invoked on a semaphore, subsequent calls to `AcquireSemaphore` only adjust the acquisition parameters. For instance, if `AcquireSemaphore` is called with `count=10`, the operation might be queued. You can call `AcquireSemaphore` again with `count=9` before or after successful acquisition, reducing the number of acquired units. The new operation replaces the old one, which will complete with an `ABORTED` code if it hasn't completed successfully yet. The queue position remains unchanged despite replacing one `AcquireSemaphore` operation with another. + +Both `AcquireSemaphore` and `ReleaseSemaphore` operations return a `bool` indicating whether the semaphore state was altered. For example, `AcquireSemaphore` might return `false` if the semaphore couldn't be acquired within the `Timeout` period because it was acquired by another entity. Similarly, `ReleaseSemaphore` might return `false` if the semaphore isn't acquired within the current session. + +A queued `AcquireSemaphore` operation can be prematurely terminated by calling `ReleaseSemaphore`. Regardless of how many `AcquireSemaphore` calls are made for a specific semaphore within one session, a single `ReleaseSemaphore` call releases it. Thus, `AcquireSemaphore` and `ReleaseSemaphore` operations cannot function as `Acquire`/`Release` on a recursive mutex. + +The `DescribeSemaphore` operation with `WatchData` or `WatchOwners` flags set establishes a subscription for semaphore changes. Any previous subscription to the same semaphore within the session is canceled, triggering `OnChanged(false)`. It is advisable to disregard `OnChanged` from earlier `DescribeSemaphore` calls if a new replacing call is made, for instance, by tracking a current call ID. + +The `OnChanged(false)` call might occur not only due to cancellation by a new `DescribeSemaphore` but also for various reasons, such as temporary connection loss between the gRPC client and server, temporary connection loss between the gRPC server and the current service leader, or service leader changes. This happens at the slightest suspicion that a notification might have been lost. To restore the subscription, client code must issue a new `DescribeSemaphore` call, properly managing the situation where the result of the new call might differ (for example, if the notification was indeed lost). + +## Examples + +* [Distributed lock](../../recipes/ydb-sdk/distributed-lock.md) +* [Leader election](../../recipes/ydb-sdk/leader-election.md) +* [Service discovery](../../recipes/ydb-sdk/service-discovery.md) +* [Configuration publication](../../recipes/ydb-sdk/config-publication.md) diff --git a/ydb/docs/en/core/reference/ydb-sdk/toc_i.yaml b/ydb/docs/en/core/reference/ydb-sdk/toc_i.yaml index 70fc14bee906..d5e95db29286 100644 --- a/ydb/docs/en/core/reference/ydb-sdk/toc_i.yaml +++ b/ydb/docs/en/core/reference/ydb-sdk/toc_i.yaml @@ -9,6 +9,8 @@ items: href: parameterized_queries.md - name: Working with topics href: topic.md + - name: Working with coordination nodes + href: coordination.md - name: Handling errors in the API href: error_handling.md - name: gRPC API diff --git a/ydb/docs/ru/core/concepts/datamodel/coordination-node.md b/ydb/docs/ru/core/concepts/datamodel/coordination-node.md new file mode 100644 index 000000000000..9e0e35eb5dea --- /dev/null +++ b/ydb/docs/ru/core/concepts/datamodel/coordination-node.md @@ -0,0 +1,38 @@ +# Узел координации + +Узел координации (coordination node) — это объект в {{ ydb-short-name }}, который позволяет клиентским приложениям распределённо координировать свои действия. Типовые сценарии использования узлов координации: + +* Распределённые [семафоры](https://ru.wikipedia.org/wiki/Семафор_(программирование)) и [мьютексы](https://ru.wikipedia.org/wiki/Мьютекс). +* Обнаружение сервисов (service discovery). +* Выбор лидера (leader election). +* Очереди задач (task queue). +* Публикация небольших объёмов данных с возможностью получать оповещения об изменениях. +* Эфеменрные блокировки произвольных заранее неизвестных сущностей. + +## Семафоры {#semaphore} + +Узлы координации позволяют создавать внутри себя семафоры и управлять ими. Типовые операции с семафорами: + +* Создание (create); +* Захват (acquire); +* Освобождение (release); +* Получение содержимого (describe); +* Удаление (delete). + +К семафору может быть приложен счётчик, который ограничивает количество одновременных его захватов, а также небольшой объём произвольных данных. + +{{ ydb-short-name }} поддерживает два типа семафоров: персистентные и эфемерные. Персистентный семафор перед захватом необходимо создать, он будет существовать либо до момента его явного удаления, либо до удаления узла координации, в котором он был создан. Эфемерные семафоры автоматически создаются в момент их первого захвата и удаляются при последнем освобождении, что удобно использовать, например, в сценариях распределённой блокировки. + +{% note info %} + +Семафоры в {{ ydb-short-name }} **не** являются рекурсивными. Таким образом, захват и освобождение семафора являются идемпотентными операциями. + +{% endnote %} + +## Использование {#usage} + +Работа с узлами координации и семафорами осуществляется через [отдельные методы в {{ ydb-short-name }} SDK](../../reference/ydb-sdk/coordination.md). + +## Аналогичные системы {#similar-systems} + +С помощью узлов координации {{ ydb-short-name }} можно решать задачи, которые традиционно выполняются с помощью таких систем, как [Apache Zookeeper](https://zookeeper.apache.org/), [etcd](https://etcd.io/), [Consul](https://www.consul.io/) и других. Если в проекте используется {{ ydb-short-name }} для хранения данных, а также одна из этих сторонних систем для координации, то переход на узлы координации {{ ydb-short-name }} может сократить количество систем, которые необходимо эксплуатировать и поддерживать. \ No newline at end of file diff --git a/ydb/docs/ru/core/concepts/datamodel/toc_i.yaml b/ydb/docs/ru/core/concepts/datamodel/toc_i.yaml index 325072d17e4d..62ed2cc3c2cc 100644 --- a/ydb/docs/ru/core/concepts/datamodel/toc_i.yaml +++ b/ydb/docs/ru/core/concepts/datamodel/toc_i.yaml @@ -3,6 +3,7 @@ items: - { name: Таблицы, href: table.md } - { name: Представления (VIEW), href: view.md, when: feature_view } - { name: Топики, href: ../topic.md } +- { name: Узлы координации, href: coordination-node.md } - { name: Секреты, href: secrets.md } - { name: Внешние источники данных, href: external_data_source.md } - { name: Внешние таблицы, href: external_table.md } diff --git a/ydb/docs/ru/core/concepts/glossary.md b/ydb/docs/ru/core/concepts/glossary.md index 9b5a12385ff6..f757be4ae1c0 100644 --- a/ydb/docs/ru/core/concepts/glossary.md +++ b/ydb/docs/ru/core/concepts/glossary.md @@ -161,7 +161,6 @@ ### Топик {#topic} - **Очередь сообщений** используется для надёжной асинхронной связи между различными системами посредством передачи сообщений. {{ ydb-short-name }} предоставляет инфраструктуру, обеспечивающую семантику "exactly once" (ровно один раз) в таких коммуникациях. С её использованием можно добиться гарантии отсутствия потерянных сообщений и случайных дубликатов. **Топик**, **тема**, **topic** — это именованная сущность в очереди сообщений, предназначенная для взаимодействия [писателей](#producer) и [читателей](#consumer). @@ -200,8 +199,6 @@ **Экземпляр асинхронной репликации** — это именованная сущность, хранящая настройки [асинхронной репликации](async-replication.md) (настройки подключения, список реплицируемых объектов и т.д.). Также с его помощью можно получить информацию о состоянии асинхронной репликации: [прогресс первоначального сканирования](async-replication.md#initial-scan), [отставание](async-replication.md#replication-of-changes), [ошибки](async-replication.md#error-handling) и т.д. -{% endif %} - #### Реплицируемый объект {#replicated-object} **Реплицируемый объект** — объект (например, таблица), для которого настроена асинхронная репликация. @@ -210,13 +207,23 @@ **Объект-реплика** — «зеркальная копия» реплицируемого объекта, автоматически созданная экземпляром асинхронной репликации. Как правило, доступен только для чтения. +{% endif %} + +### Узел координации {#coordination-node} + +**Узел координации** или **coordination node** — это объект схемы, который позволяет клиентским приложениям создавать семафоры для координации своих действий. Узлы координации применяются для реализации распределённых блокировок, обнаружения сервисов, выбора лидера и других сценариев. Более подробно об [узлах координации](./datamodel/coordination-node.md). + +#### Семафор {#semaphore} + +**Семафор** — это объект внутри [узла координации](#coordination-node), который предоставляет механизм синхронизации для распределённых приложений. Семафоры могут быть постоянными или временными и поддерживают операции создания, захвата, освобождения и мониторинга. Более подробно о [семафорах в {{ ydb-short-name }}](./datamodel/coordination-node.md#semaphore). + {% if feature_resource_pool == true and feature_resource_pool_classifier == true %} -#### Пул ресурсов {#resource-pool} +### Пул ресурсов {#resource-pool} **Пул ресурсов** или **resource pool** — объект схемы, который описывает ограничения, накладываемые на ресурсы (CPU, RAM и т.п.) доступные для выполнения запросов в этом пуле ресурсов. Запрос всегда выполняется в каком-то пуле ресурсов. По умолчанию все запросы выполняются в пуле ресурсов с именем `default`, который не накладывает каких-либо ограничений. Подробнее об использовании пулов ресурсов можно узнать в статье [{#T}](../dev/resource-consumption-management.md). -#### Классификатор пулов ресурсов {#resource-pool-classifier} +### Классификатор пулов ресурсов {#resource-pool-classifier} **Классификатор пулов ресурсов** или **resource pool classifier** — объект, предназначенный для управления распределением запросов между [пулами ресурсов](#resource-pool). Он описывает правила, по которым для каждого запроса выбирается пул ресурсов. Эти классификаторы глобальны для всей [базы данных](#database) и применяются ко всем запросам, поступающим в неё. Подробнее об их использовании можно узнать в статье [{#T}](../dev/resource-consumption-management.md). diff --git a/ydb/docs/ru/core/recipes/ydb-sdk/config-publication.md b/ydb/docs/ru/core/recipes/ydb-sdk/config-publication.md new file mode 100644 index 000000000000..e929409d50ef --- /dev/null +++ b/ydb/docs/ru/core/recipes/ydb-sdk/config-publication.md @@ -0,0 +1,10 @@ +# Публикация конфигурации + +Рассмотрим сценарий, где необходимо публиковать небольшую конфигурацию для экземпляров приложения, которые должны оперативно реагировать на её изменения. + +Этот сценарий можно реализовать с помощью семафоров в [узлах координации {{ ydb-short-name }}](../../reference/ydb-sdk/coordination.md) следующим образом: + +1. Создаётся семафор (например, с именем `my-service-config`). +1. Через `UpdateSemaphore` публикуется обновлённая конфигурация. +1. Экземпляры приложения делают `DescribeSemaphore` с `WatchData=true`, в результате вызова в `Data` будет текущая версия конфигурации. +1. В случае изменения конфигурации вызывается `OnChanged`. В этом случае экземпляры приложения делают аналогичный вызов `DescribeSemaphore` и получают обновлённую конфигурацию. diff --git a/ydb/docs/ru/core/recipes/ydb-sdk/distributed-lock.md b/ydb/docs/ru/core/recipes/ydb-sdk/distributed-lock.md new file mode 100644 index 000000000000..2116d44e45f3 --- /dev/null +++ b/ydb/docs/ru/core/recipes/ydb-sdk/distributed-lock.md @@ -0,0 +1,42 @@ +# Распределённая блокировка + +Рассмотрим сценарий, где необходимо обеспечить, чтобы с разделяемым ресурсом в один момент времени работал только один экземпляр клиентского приложения. Для его реализации можно воспользоваться механизмом семафоров в [узлах координации {{ ydb-short-name }}](../../reference/ydb-sdk/coordination.md). + +## Механизм аренды семафоров + +В отличие от локального многопоточного программирования, клиенты в распределённых системах не захватывают блокировки или семафоры напрямую. Вместо этого они арендуют их на определённое время, которое можно периодически продлевать. Поскольку физическое время может различаться на разных машинах, клиенты и сервер могут оказаться в ситуации, когда несколько клиентов считают, что их сессии захватили один и тот же семафор одновременно, даже если с точки зрения сервера это не так. Чтобы уменьшить вероятность таких ситуаций, важно заранее настроить автоматическую синхронизацию времени как на серверах с клиентскими приложениями, так и на стороне {{ ydb-short-name }}, желательно с использованием единого источника времени. + +Таким образом, реализация распределённой блокировки через такие механизмы не может гарантировать отсутствие одновременного доступа к ресурсу, но может значительно снизить вероятность такого события. Это может быть использовано как оптимизация, чтобы клиенты не конкурировали за общий ресурс, когда это не имеет смысла. Гарантии отсутствия конкурентных запросов к ресурсу могут быть реализованы на стороне самого ресурса. + +## Пример кода + +{% list tabs %} + +- Go + + ```go + for { + if session, err := db.Coordination().CreateSession(ctx, path); err != nil { + return fmt.Errorf("cannot create session: %v", err); + } + + if lease, err := session.AcquireSemaphore(ctx, + semaphore, + coordination.Exclusive, + options.WithEphemeral(true), + ); err != nil { + // the session is likely lost, try to create a new one and get the lock in it + session.Close(ctx); + continue; + } + + // lock acquired, start processing + select { + case <-lease.Context().Done(): + } + + // lock released, end processing + } + ``` + +{% endlist %} diff --git a/ydb/docs/ru/core/recipes/ydb-sdk/index.md b/ydb/docs/ru/core/recipes/ydb-sdk/index.md index 484f422b568b..01c52d5013b3 100644 --- a/ydb/docs/ru/core/recipes/ydb-sdk/index.md +++ b/ydb/docs/ru/core/recipes/ydb-sdk/index.md @@ -24,6 +24,13 @@ - [Вставка данных](upsert.md) - [Пакетная вставка данных](bulk-upsert.md) - [Установка режима выполнения транзакции](tx-control.md) +- Координация + + - [Распределённая блокировка](distributed-lock.md) + - [Обнаружение сервисов](service-discovery.md) + - [Публикация конфигурации](config-publication.md) + - [Выбор лидера](leader-election.md) + - [Диагностика проблем](debug.md) - [Включить логирование](debug-logs.md) diff --git a/ydb/docs/ru/core/recipes/ydb-sdk/leader-election.md b/ydb/docs/ru/core/recipes/ydb-sdk/leader-election.md new file mode 100644 index 000000000000..664c61d110ae --- /dev/null +++ b/ydb/docs/ru/core/recipes/ydb-sdk/leader-election.md @@ -0,0 +1,11 @@ +# Выбор лидера (leader election) + +Рассмотрим сценарий, где несколько экземпляров приложения хотят выбрать лидера и всегда знать, кто им является. + +Этот сценарий можно реализовать с помощью семафоров в [узлах координации {{ ydb-short-name }}](../../reference/ydb-sdk/coordination.md) следующим образом: + +1. Создаётся семафор (например, с именем `my-service-leader`) с `Limit=1`. +1. Все экземпляры приложения выполняют `AcquireSemaphore` с `Count=1`, указывая в `Data` свой endpoint. +1. Только у одного экземпляра приложения вызов завершится быстро, остальные встанут в очередь. Тот, у кого вызов завершился успешно, становится текущим лидером. +1. Все экземпляры приложения выполняют `DescribeSemaphore` с `WatchOwners=true` и `IncludeOwners=true`. В результате вызова в `Owners` будет максимум один элемент, из `Data` узнаётся endpoint текущего лидера. +1. При смене лидера вызывается `OnChanged`. В этом случае экземпляры приложения выполняют аналогичный вызов `DescribeSemaphore` и узнают endpoint нового лидера. diff --git a/ydb/docs/ru/core/recipes/ydb-sdk/service-discovery.md b/ydb/docs/ru/core/recipes/ydb-sdk/service-discovery.md new file mode 100644 index 000000000000..7bfda0f9107b --- /dev/null +++ b/ydb/docs/ru/core/recipes/ydb-sdk/service-discovery.md @@ -0,0 +1,12 @@ +# Обнаружение сервисов (service discovery) + +Рассмотрим сценарий, где экземпляры приложения динамически поднимаются и публикуют свой endpoint, а другие клиенты хотят получать этот список и реагировать на его изменения. + +Этот сценарий можно реализовать с помощью семафоров в [узлах координации {{ ydb-short-name }}](../../reference/ydb-sdk/coordination.md) следующим образом: + +1. Создаётся семафор (например, с именем `my-service-endpoints`) с `Limit=Max()`. +1. Все экземпляры приложения выполняют `AcquireSemaphore` с `Count=1`, указывая в `Data` свой endpoint. +1. Поскольку лимит семафора очень большой, все вызовы `AcquireSemaphore` должны завершиться быстро. +1. На этом этапе публикация завершена, и экземплярам приложения нужно только реагировать на остановку сессии, публикуя себя заново через новую сессию. +1. Клиенты выполняют `DescribeSemaphore` с `IncludeOwners=true` и, при необходимости, с `WatchOwners=true`. В результате вызова в поле `Owners` в `Data` будут содержаться endpoint'ы зарегистрированных экземпляров приложения. +1. При изменении списка endpoint'ов вызывается `OnChanged`. В этом случае клиенты выполняют аналогичный вызов `DescribeSemaphore` и получают обновлённый список. diff --git a/ydb/docs/ru/core/recipes/ydb-sdk/toc_i.yaml b/ydb/docs/ru/core/recipes/ydb-sdk/toc_i.yaml index 656c801495fc..d0aa13e4ea21 100644 --- a/ydb/docs/ru/core/recipes/ydb-sdk/toc_i.yaml +++ b/ydb/docs/ru/core/recipes/ydb-sdk/toc_i.yaml @@ -39,6 +39,16 @@ items: href: bulk-upsert.md - name: Установка режима выполнения транзакции href: tx-control.md +- name: Координация + items: + - name: Распределённая блокировка + href: distributed-lock.md + - name: Выбор лидера + href: leader-election.md + - name: Обнаружение сервисов + href: service-discovery.md + - name: Публикация конфигурации + href: config-publication.md - name: Диагностика проблем items: - name: Обзор diff --git a/ydb/docs/ru/core/reference/ydb-sdk/coordination.md b/ydb/docs/ru/core/reference/ydb-sdk/coordination.md new file mode 100644 index 000000000000..0d8c72f52ecb --- /dev/null +++ b/ydb/docs/ru/core/reference/ydb-sdk/coordination.md @@ -0,0 +1,323 @@ +# Работа с узлами координации + +Данная статья описывает как использовать {{ ydb-short-name }} SDK для координации работы нескольких экземпляров клиентского приложения посредством использования [узлов координации](../../concepts/datamodel/coordination-node.md) и находящихся в них семафоры. + +## Создание узла координации + +Узлы координации создаются в базах данных {{ ydb-short-name }} в том же пространстве имён, что и другие объекты схемы, такие как [таблицы](../../concepts/datamodel/table.md) и [топики](../../concepts/topic.md). + +{% list tabs %} + +- Go + + ```go + err := db.Coordination().CreateNode(ctx, + "/path/to/mynode", + ) + ``` + +- C++ + + ```cpp + TClient client(driver); + auto status = client + .CreateNode("/path/to/mynode") + .ExtractValueSync(); + Y_ABORT_UNLESS(status.IsSuccess()); + ``` + + При создании можно опционально указать `TNodeSettings` со следующими настройками: + + - `ReadConsistencyMode` - по умолчанию `RELAXED`, что допускает чтение не самого свежего значения в случае смены лидера. Опционально можно включить `STRICT` режим чтения, при котором все чтения проходят через алгоритм консенсуса и гарантируют возврат самого свежего значения, но становятся существенно дороже. + - `AttachConsistencyMode` - по умолчанию `STRICT`, что означает обязательное использование алгоритма консенсуса при восстановлении сессии. Опционально можно включить `RELAXED` режим восстановления сессии в случае сбоев, который отключает это требование. Расслабленный режим может потребоваться при очень большом количестве клиентов, позволяя восстанавливать сессию без прохождения через консенсус, что не влияет на общую корректность, но может усугублять чтение не самого свежего значения во время смены лидера, а также устаревание сессий в случае проблем. + - `SelfCheckPeriod` (по умолчанию 1 секунда) - периодичность с которой сервис производит проверки собственной живости. Не рекомендуется менять за исключением особых случаев. + + - Чем больше указанное значение, тем меньше нагрузка на сервер, но тем дольше возможная задержка между сменой лидера и тем, насколько оперативно об этом узнает сам сервис. + - Чем меньше указанное значение, тем больше нагрузка на сервер и большая оперативность в детектировании проблем, но возможна генерация false positive когда сервис ошибочно детектирует проблемы. + + - `SessionGracePeriod` (по умолчанию 10 секунд) - период, в течение которого новый лидер не закрывает открытые сессии, продлевая их. + + - Чем меньше значение, тем меньше окно, когда сессии от несуществующих клиентов, которые не успели сообщить о пропаже при смене лидера, будут удерживать семафоры и мешать другим клиентам. + - Чем меньше значение, тем выше вероятность ложных срабатываний, когда живой лидер может завершить работу для перестраховки, так как не будет уверен, что этот период не закончился у нового лидера. + - Должен быть строго больше, чем `SelfCheckPeriod`. + +{% endlist %} + +## Работа с сессиями {#session} + +### Создание сессии {#create-session} + +Для начала работы клиент должен установить сессию, в рамках которой он будет осуществлять все операции с узлом координации. + +{% list tabs %} + +- Go + + ```go + session, err := db.Coordination().CreateSession(ctx, + "/path/to/mynode", // имя Coordination Node в базе + ) + ``` + +- C++ + + ```cpp + TClient client(driver); + const TSession& session = client + .StartSession("/path/to/mynode") + .ExtractValueSync() + .ExtractResult(); + ``` + + При установке сессии можно опционально передать структуру `TSessionSettings` со следующими настройками: + + - `Description` - текстовое описацие сессии, отображается во внутренних интерфейсах и может быть полезно при диагностике проблем. + - `OnStateChanged` - вызывается на важных изменениях в процессе жизни сессии, передавая соответствующее состояние: + + - `ATTACHED` - сессия подключена и работает в нормальном режиме; + - `DETACHED` - сессия временно потеряла связь с сервисом, но ещё может быть восстановлена; + - `EXPIRED` - сессия потеряла связь с сервисом и не может быть восстановлена. + + - `OnStopped` - вызывается, когда сессия прекращает попытки восстановить связь с сервисом, что может быть полезно для установления нового соединения. + - `Timeout` - максимальный таймаут, в течение которого сессия может быть восстановлена после потери связи с сервисом. + +{% endlist %} + +### Контроль завершения сессии {#session-control} + +Клиентскому приложению необходимо следить за состоянием сессии, так как оно может полагаться на состояние захваченных семафоров только пока сессия активна. Когда сессия завершается по инициативе клиента или сервера, клиент больше не может быть уверен, что другие клиенты в кластере не захватили его семафоры и не изменили их состояние. + +{% list tabs %} + +- Go + + В Go SDK для отслеживания таких ситуаций используется контекст сессии `session.Context()`, который завершается вместе с сессией. SDK самостоятельно обрабатывает ошибки транспортного уровня и восстанавливает соединение с сервисом, пытаясь восстановить сессию, если это возможно. Таким образом, клиенту достаточно следить только за контекстом сессии, чтобы своевременно отреагировать на её потерю. + +- C++ + + В C++ SDK установленная сессия в фоне поддерживает и автоматически восстанавливает связь с кластером {{ ydb-short-name }}. + +{% endlist %} + +## Работа с семафорами {#semaphore} + +### Создание семафора {#create-semaphore} + +При создании семафора можно указать его лимит. Лимит определяет максимальное значение, на которое его можно увеличить. Вызовы, пытающиеся увеличить значение семафора выше этого лимита, начнут ждать, пока их запросы на увеличение смогут быть выполнены, так чтобы значение семафора не превышало его лимит. + +{% list tabs %} + +- Go + + ```go + err := session.CreateSemaphore(ctx, + "my-semaphore", // semaphore name + 10 // semaphore limit + ) + ``` + +- С++ + + ```cpp + session + .CreateSemaphore( + "my-semaphore", // semaphore name + 10 // semaphore limit + ) + .ExtractValueSync() + .ExtractResult(); + ``` + + Также при создании семафора можно передать строку, которая будет храниться вместе с семафором и возвращаться при его захвате: + + ```cpp + session + .CreateSemaphore( + "my-semaphore", // semaphore name + 10, // semaphore limit + "my-data" // semaphore data + ) + .ExtractValueSync() + .ExtractResult(); + ``` + +{% endlist %} + +### Захват семафора {#acquire-semaphore} + +Чтобы захватить семафор, клиент должен вызвать метод `AcquireSemaphore` и дождаться получения специального объекта `Lease`. Этот объект представляет собой подтверждение о том, что значение семафора было успешно увеличено и может считаться таковым до явного отпускания такого семафора или завершения сессии, в которой такое подтверждение было получено. + +{% list tabs %} + +- Go + + ```go + lease, err := session.AcquireSemaphore(ctx, + "my-semaphore", // semaphore name + 5, // value to increase semaphore by + ) + ``` + + Для отмены ожидания взятия семафора, достаточно отменить переданный в метод контекст `ctx`. + +- C++ + + ```cpp + session + .AcquireSemaphore( + "my-semaphore", // semaphore name + TAcquireSemaphoreSettings().Count(5) // value to increase semaphore by + ) + .ExtractValueSync() + .ExtractResult(); + ``` + + При захвате можно опционально передать структуру `TAcquireSemaphoreSettings` со следующими настройками: + + - `Count` - значение, на которое увеличивается семафор при захвате. + - `Data` - дополнительные данные, которые можно положить в семафор. + - `OnAccepted` - вызывается, когда операция встаёт в очередь (например, если семафор невозможно было захватить сразу). + + - Не будет вызвано, если семафор захватывается сразу. + - Важно учитывать, что вызов может произойти параллельно с результатом `TFuture`. + + - `Timeout` - максимальное время, в течение которого операция может пролежать в очереди на сервере. + + - Операция вернёт `false`, если за время `Timeout` после добавления в очередь не удалось захватить семафор. + - При `Timeout` установленном в 0 операция по смыслу работает как `TryAcquire`, т.е. семафор будет либо захвачен атомарно и операция вернёт `true`, либо операция вернёт `false` без использования очередей. + + - `Ephemeral` - если `true`, то имя является эфемерным семафором, такие семафоры автоматически создаются при первом `Acquire` и автоматически удаляются с последним `Release`. + - `Shared()` - алиас для выставления `Count = 1`, захват семафора в shared режиме. + - `Exclusive()` - алиас для выставления `Count = max`, захват семафора в exclusive режиме (для семафоров, созданных с лимитом `Max()`). + +{% endlist %} + +Взятое значение захваченного семафора можно снизить (но не увеличить), вновь вызвав для него метод `AcquireSemaphore` с меньшим значением. + +### Обновление данных семафора {#update-semaphore} + +С помощью метода `UpdateSemaphore` можно обновить (заменить) данные семафора, которые были привязаны при его создании. + +{% list tabs %} + +- Go + + ```go + err := session.UpdateSemaphore( + "my-semaphore", // semaphore name + options.WithUpdateData([]byte("updated-data")), // new semaphore data + ) + ``` + +- C++ + + ```cpp + session + .UpdateSemaphore( + "my-semaphore", // semaphore name + "updated-data" // new semaphore data + ) + .ExtractValueSync() + .ExtractResult(); + ``` + +{% endlist %} + +Этот вызов не требует захвата семафора и не приводит к нему. Если требуется, чтобы данные обновлял только один конкретный клиент, то это необходимо явным образом обеспечить, например, захватив семафор, обновив данные и отпустив семафор обратно. + +### Получение данных семафора {#describe-semaphore} + +{% list tabs %} + +- Go + + ```go + description, err := session.DescribeSemaphore( + "my-semaphore" // semaphore name + options.WithDescribeOwners(true), // to get list of owners + options.WithDescribeWaiters(true), // to get list of waiters + ) + ``` + +- C++ + + ```cpp + session + .DescribeSemaphore( + "my-semaphore" // semaphore name + ) + .ExtractValueSync() + .ExtractResult(); + ``` + + При получении информации о семафоре можно опционально передать структуру `TDescribeSemaphoreSettings` со следующими настройками: + + - `OnChanged` - вызывается один раз после изменения данных на сервере. C параметром `bool`, если `true` - то вызов произошёл из-за каких-то изменений, если `false` - то это ложный вызов и необходимо повторить `DescribeSemaphore` для восстановления подписки. + - `WatchData` - вызывать `OnChanged` в случае изменения данных семафора. + - `WatchOwners` - вызывать `OnChanged` в случае изменения владельцев семафора. + - `IncludeOwners` - вернуть список владельцев в результатах. + - `IncludeWaiters` - вернуть список ожидающих в результатах. + + Результат вызова представляет собой структуру со следующими полями: + + - `Name` - имя семафора. + - `Data` - данные семафора. + - `Count` - текущее значение семафора. + - `Limit` - максимальное количество токенов, указанное при создании семафора. + - `Owners` - список владельцев семафора. + - `Waiters` - список ожидающих в очереди на семафоре. + - `Ephemeral` - является ли семафор эфемерным. + + Поля `Owners` и `Waiters` в результате представляют собой список структур со следующими полями: + + - `OrderId` - порядковый номер операции захвата на семафоре. Может использоваться для идентификации, например если `OrderId` изменился, значит сессия сделала `ReleaseSemaphore` и новый `AcquireSemaphore`. + - `SessionId` - идентификатор сессии, которая делала данный `AcquireSemaphore`. + - `Timeout` - таймаут, с которым вызывался `AcquireSemaphore` для операций в очереди. + - `Count` - запрошенное в `AcquireSemaphore` значение. + - `Data` - данные, которые были указаны в `AcquireSemaphore`. + +{% endlist %} + +### Освобождение семафора {#release-semaphore} + +{% list tabs %} + +- Go + + Чтобы отпустить захваченный в сессии семафор, необходимо вызвать метод `Release` у объекта `Lease`. + + ```go + err := lease.Release() + ``` + +- C++ + + ```cpp + session + .ReleaseSemaphore( + "my-semaphore" // semaphore name + ) + .ExtractValueSync() + .ExtractResult(); + ``` + +{% endlist %} + +## Важные особенности + +Операции `AcquireSemaphore` и `ReleaseSemaphore` являются идемпотентными. Если на семафоре был вызван `AcquireSemaphore`, повторные вызовы `AcquireSemaphore` изменяют только параметры захвата. Например, вызов `AcquireSemaphore` с `count=10` может добавить операцию в очередь. До или после успешного захвата можно повторно вызвать `AcquireSemaphore` с `count=9`, уменьшая количество захваченных единиц; новая операция заменит старую (которая завершится с кодом `ABORTED`, если она ещё не была успешно завершена). Позиция в очереди при этом не изменяется, несмотря на замену одной операции `AcquireSemaphore` на другую. + +Операции `AcquireSemaphore` и `ReleaseSemaphore` возвращают `bool`, указывающий, изменила ли операция состояние семафора. Например, `AcquireSemaphore` вернёт `false`, если захват семафора не удался в течение времени `Timeout`, так как он был захвачен другим. Операция `ReleaseSemaphore` может вернуть `false`, если семафор не захвачен в текущей сессии. + +Операцию `AcquireSemaphore`, находящуюся в очереди, можно завершить досрочно, вызвав `ReleaseSemaphore`. Независимо от количества вызовов `AcquireSemaphore` для конкретного семафора в одной сессии, освобождение происходит одним вызовом `ReleaseSemaphore`, то есть операции `AcquireSemaphore` и `ReleaseSemaphore` нельзя использовать как аналог `Acquire` или `Release` на рекурсивном мьютексе. + +Операция `DescribeSemaphore` с флагами `WatchData` или `WatchOwners` создаёт подписку на изменения семафора. Любая более старая подписка на тот же семафор в сессии отменяется, вызывая `OnChanged(false)`. Рекомендуется игнорировать `OnChanged` от предыдущих вызовов `DescribeSemaphore`, если выполняется новый замещающий вызов, например, запоминая текущий id вызова. + +Вызов `OnChanged(false)` может происходить не только из-за отмены новым `DescribeSemaphore`, но и по другим причинам, например, при временном разрыве соединения между grpc клиентом и сервером, при временном разрыве соединения между grpc сервером и текущим лидером сервиса, при изменении лидера сервиса, то есть при малейшем подозрении, что нотификация могла быть потеряна. Для восстановления подписки клиентский код должен выполнить новый вызов `DescribeSemaphore`, правильно обрабатывая ситуацию, что результат нового вызова может быть другим (например, если нотификация действительно была потеряна). + +## Примеры + +* [Распределённая блокировка](../../recipes/ydb-sdk/distributed-lock.md) +* [Выбор лидера](../../recipes/ydb-sdk/leader-election.md) +* [Обнаружение сервисов](../../recipes/ydb-sdk/service-discovery.md) +* [Публикация конфигурации](../../recipes/ydb-sdk/config-publication.md) \ No newline at end of file diff --git a/ydb/docs/ru/core/reference/ydb-sdk/index.md b/ydb/docs/ru/core/reference/ydb-sdk/index.md index 9ad295c2f8d0..b48400452245 100644 --- a/ydb/docs/ru/core/reference/ydb-sdk/index.md +++ b/ydb/docs/ru/core/reference/ydb-sdk/index.md @@ -22,6 +22,9 @@ - [Установка](install.md) - [Аутентификация](auth.md) - [Сравнение возможностей SDK](feature-parity.md) +- [Параметризованные запросы](parameterized_queries.md) +- [Работа с топиками](topic.md) +- [Работа с узлами координации](coordination.md) Смотрите также: diff --git a/ydb/docs/ru/core/reference/ydb-sdk/toc_i.yaml b/ydb/docs/ru/core/reference/ydb-sdk/toc_i.yaml index 1256e0777033..a00eee976a1f 100644 --- a/ydb/docs/ru/core/reference/ydb-sdk/toc_i.yaml +++ b/ydb/docs/ru/core/reference/ydb-sdk/toc_i.yaml @@ -9,6 +9,8 @@ items: href: parameterized_queries.md - name: Работа с топиками href: topic.md + - name: Работа с узлами координации + href: coordination.md - name: Обработка ошибок в API href: error_handling.md - name: gRPC API