Repository navigation
Conversation
Add backward-compatible shard identifiers to link requests and internal request headers. Resolve allowlisted shard directories with validation and cover configuration and request compatibility with unit tests.
Route create, describe and destroy operations through allowlisted shard directories, including scheme-cache and backup reads. Test isolated SSD/HDD lifecycles across two YDB subdomains and reject unknown shards.
Resolve explicit shard headers and isolate cached volume connections by canonical shard directory and disk ID. Cover same-name SSD/HDD volumes, local aliases, keep-alives and unknown-shard rejection.
Propagate source and destination shard addresses through link control, mirrored writes and source cleanup. Add shard-aware NBS volume operations and CLI options, with SSD/HDD control-path and compatibility tests.
Keep source partitions running while a follower needs data and release copy-only ownership on cancellation. Cover detached and online SSD/HDD copy matrices, concurrent writes and zeroes, source cleanup, session recovery and local shard aliases.
Enforce cancellation checks on source and destination tablets. Add restart, reverse-copy and detached-cancellation coverage, shard-aware legacy fixtures and internal API documentation. Split long service scenarios into independent test chunks. Validation: nbsd and blockstore-client built; 1142 tests passed across nine suites.
|
Note This is an automated comment that will be appended during run. Note All workloads for linux-x86_64-relwithdebinfo have completed. Tip Planned checks for linux-x86_64-relwithdebinfo.
|
agalibin
left a comment
There was a problem hiding this comment.
AI Review Summary
Review mode: deep
Verdict: ❌ 3 PR-related critical issue(s) found
Critical issues
- 🔴 Critical | High: Удаление remote DR-диска освобождает устройства локального диска —
cloud/blockstore/libs/storage/service/service_actor_destroy.cpp:194 - 🔴 Critical | High: Проверка отмены не атомарна с удалением связи —
cloud/blockstore/libs/storage/volume/volume_actor_leader.cpp:215 - 🔴 Critical | High: Часть запросов с ShardId выполняется на локальном диске —
cloud/blockstore/libs/storage/service/service_actor_alter.cpp:238
Direct issues
- 🟡 Major | High: После отмены copy-only source остаётся READY с остановленными partitions —
cloud/blockstore/libs/storage/volume/volume_actor_startstop.cpp:496 - 🟡 Major | High: Эквивалентные shard aliases не находят существующую связь —
cloud/blockstore/libs/storage/service/service_actor_create_volume_link.cpp:69
Unrelated issues
- 🔴 Critical | High: Запоздалый progress update восстанавливает отменённую связь —
cloud/blockstore/libs/storage/volume/volume_actor_leader.cpp:273 - 🟡 Major | High: Перезапуск destination не возобновляет завершение cleanup —
cloud/blockstore/libs/storage/volume/volume_actor_loadstate.cpp:190
Positive observations
VolumeProxyпоследовательно использует канонический directory в ключах соединений, изолируя одноимённые диски разных shards.- Добавлено существенное покрытие SSD/HDD-комбинаций, конкурентных записей и zeroes, перезапусков tablets и обратного переноса.
Issue details
Unrelated issues
🔴 Запоздалый progress update восстанавливает отменённую связь
Severity: Critical | Confidence: High
Механизм уже существует в base, volume_actor_leader.cpp:273–303.
Если старый migration actor отправил progress/DataReady update, а отмена от другого sender успела удалить связь первой, обработчик принимает поздний update: monotonicity проверяется только для существующего follower, а для отсутствующего достаточно непустого UUID. Транзакция снова записывает отменённую связь.
PoisonPill не отзывает отправленные сообщения. Кроме того, migration actor в zombie-state всё ещё передаёт state responses владельцу, который может продолжить leadership propagation. Между успешной отменой и поздним DataReady source уже мог принять новые записи без mirroring.
Риск: Возобновление отменённого переноса, рассогласование данных и потеря записей при последующем transfer/cleanup.
🟡 Перезапуск destination не возобновляет завершение cleanup
Severity: Major | Confidence: High
В base, volume_actor_loadstate.cpp:190–196 отсутствует восстановление cleanup для destination с сохранённым состоянием Leader, если нет pending requests, mounts и необходимости запуска partitions для GC. Такой target допустим и в прежнем same-shard workflow.
Удаление планируется через DestroyOutdatedLeaderIfNeeded, но после reload этот метод не вызывается независимо от запуска partitions. При tablet reboot scheduled delete может сохраниться: его recipient — storage service. Однако response адресован прежнему target actor, поэтому новый actor не фиксирует переход Leader → Principal. При process/node restart до deadline теряется и scheduled event.
Риск: Статус остаётся LINK_STATUS_LEADERSHIP_TRANSFERRED; исходный диск может остаться неудалённым, а завершение операции требует внешнего I/O или mount.
|
|
||
| auto request = std::make_unique<TEvService::TEvStatVolumeRequest>(); | ||
| request->Record.SetDiskId(DiskId); | ||
| request->Record.MutableHeaders()->SetShardId(ShardId); |
There was a problem hiding this comment.
🔴 Удаление remote DR-диска освобождает устройства локального диска
Severity: Critical | Confidence: High
В новом маршруте DestroyVolume StatVolume, GracefulShutdown и schema deletion учитывают ShardId, а MarkDiskForCleanup и DeallocateDisk продолжают обращаться к локальному DiskRegistryProxy.
Если shards A и B используют разные DiskRegistry и содержат одноимённые DR-диски, удаление диска B через endpoint A помечает allocation диска A, удаляет schema диска B и освобождает устройства A. Реальная деаллокация удаляет allocation и передаёт устройства на очистку. При отсутствии локального совпадения allocation B остаётся утекшим.
Ограничение remote CreateVolume этого не предотвращает: существующие DR-диски можно создать через собственные endpoints. CreateVolumeLink также не проверяет media kinds обоих endpoints, поэтому ошибочный маршрут достижим через автоматический cleanup после копирования.
Риск: Потеря данных другого, локального диска.
Предлагаемое решение: До любых обращений к DiskRegistry отклонять нелокальное удаление DR-дисков; отдельно исключить локальную деаллокацию при remote Sync/not-found. Для заявленного SSD/HDD scope проверять media kinds обоих endpoints до сохранения cross-shard link.
There was a problem hiding this comment.
Нелокальное удаление DiskRegistry-дисков теперь отклоняется до обращений к registry и изменения схемы. Для remote Sync/not-found исключена локальная деаллокация. При создании межшардовой связи оба endpoint проверяются на replicated SSD/HDD до сохранения связи.
| .FollowerShardId = msg->Record.GetFollowerShardId()}; | ||
|
|
||
| auto follower = State->FindFollower(link); | ||
| if (msg->Record.GetRequireCancellable() && follower && |
There was a problem hiding this comment.
🔴 Проверка отмены не атомарна с удалением связи
Severity: Critical | Confidence: High
Новые проверки RequireCancellable выполняются в обработчике до ExecuteTx. Аналогично устроен destination-side guard. Сами ExecuteRemoveFollower и ExecuteRemoveLeader безусловно удаляют запись.
Это существенное окно: YDB executor действительно может отложить Execute. Уже поставленный в очередь переход Preparing → DataReady может исполниться после проверки отмены, но перед удалением.
Дополнительно source запускает destruction propagation ещё до commit удаления. Этот запрос не несёт RequireCancellable и может удалить уже принявшую лидерство связь на destination. Возможна последовательность, где destination успел запланировать удаление source, а отмена затем вернула source в Principal и разрешила новые немирроренные записи.
Риск: Нарушение границы передачи лидерства и удаление source вместе с записями, принятыми после успешной отмены.
Предлагаемое решение: Проверять актуальное состояние непосредственно в удаляющей транзакции, передавать результат отказа в Complete и запускать propagation только после успешно принятого удаления. Destination-side удаление должно сохранять ту же границу отмены.
There was a problem hiding this comment.
Проверку границы отмены перенёс внутрь ExecuteRemoveFollower и ExecuteRemoveLeader: она использует актуальное состояние в удаляющей транзакции. Отказ передаётся через Complete без удаления и дальнейших действий. Распространение отмены запускается только после успешной фиксации удаления и также несёт RequireCancellable.
| { | ||
| StartPartitionsForGc(ctx); | ||
| } else { | ||
| PartitionsStartedReason = EPartitionsStartedReason::NOT_STARTED; |
There was a problem hiding this comment.
🟡 После отмены copy-only source остаётся READY с остановленными partitions
Severity: Major | Confidence: High
Сценарий: replicated source после reboot работает в STARTED_FOR_COPY, partitions готовы, StartPartitionsNeeded=false; затем link отменяют в состоянии Preparing.
В новой ветке RestartPartition сначала вызывается StopPartitions, затем reason становится NOT_STARTED. При этом partition state и ссылки на actors не сбрасываются. Поздний STOPPED игнорируется, поскольку Bootstrapper уже очищен.
В результате Ready() продолжает возвращать true. WaitReady и I/O не запускают partitions заново, а forwarding использует прежние actor IDs. Remote mount может успешно добавить клиента без перезапуска tablet, поэтому сам remount проблему не исправляет.
Риск: После успешной отмены здоровый исходный диск перестаёт обслуживать I/O до внешнего перезапуска tablet.
Предлагаемое решение: Сбрасывать состояние остановленных partitions, как это уже делает GC-путь:
} else {
State->Reset();
PartitionsStartedReason = EPartitionsStartedReason::NOT_STARTED;
}There was a problem hiding this comment.
Добавил State->Reset() при переходе copy-only тома в NOT_STARTED после остановки partitions. Состояние READY и старые ссылки больше не сохраняются; последующий WaitReady/I/O запускает partitions заново.
| RequestInfo->CallContext); | ||
| request->Record.SetDiskId(LeaderDiskId); | ||
| request->Record.SetFollowerDiskId(FollowerDiskId); | ||
| request->Record.SetLeaderShardId(LeaderShardId); |
There was a problem hiding this comment.
🟡 Эквивалентные shard aliases не находят существующую связь
Severity: Major | Confidence: High
Конфигурация и VolumeProxy считают пустой local selector и aliases одного directory эквивалентными адресами. Но создание link передаёт raw shard strings, а поиск связи без UUID сравнивает эти строки буквально.
Например, local link создан без shard IDs, а отмена отправлена с разрешённым local для обеих сторон. Запрос приходит в правильные tablets, однако обе стороны не находят сохранённый tuple и отвечают S_ALREADY. Связь остаётся активной. GetLinkStatus с эквивалентным selector аналогично возвращает NOT_FOUND.
Риск: Клиент получает успешную отмену, хотя копирование продолжает работать и может перенести лидерство и удалить source.
Предлагаемое решение: Согласовать нормализацию адресов и идентичность link. При сопоставлении учитывать эквивалентность aliases и ранее сохранённых пустых local IDs либо использовать устойчивый UUID операции.
There was a problem hiding this comment.
Сопоставление связей теперь использует эквивалентность канонических directories из конфигурации, а не буквальное равенство shard aliases. Это применяется и к сохранённым пустым local selectors, и к ожидающим операциям создания. При наличии UUID сохраняется строгая идентичность поколения операции.
| // Destination storage shard for internal cross-shard requests. | ||
| // Must be present in ShardDirectories. Empty selects the local shard. | ||
| // CellId identifies the caller and must not be used for routing. | ||
| string ShardId = 17; |
There was a problem hiding this comment.
[OUTER CONTEXT]
🔴 Часть запросов с ShardId выполняется на локальном диске
Severity: Critical | Confidence: High
Уже в целевой ветви (main, base de06bca2): Resize/Alter описывают диск без shard selector, после чего изменяют найденный локальный path. AssignVolume также создаёт ModifyVolumeRequest без shard. Сервисные sessions кешируются только по DiskId; mount lookup не передаёт shard, а прямое forwarding к известному клиенту обходит VolumeProxy.
Добавлено или изменено в PR: Headers.ShardId объявлен destination selector, а документация разрешает использовать его для последующих запросов после переноса. Однако перечисленные пути не обновлены.
При одинаковом DiskId в двух shards запрос ResizeVolume, AlterVolume или AssignVolume с target header изменяет локальный диск. Для mounted requests выбор cached actor также игнорирует selector. После удаления source remount через исходный endpoint с target header продолжает искать локальный source и не восстанавливает session.
Риск: Изменение другого диска и невозможность заявленного восстановления сессии через ShardId.
Предлагаемое решение: Передавать selector через schema operations, включить канонический shard в идентичность service/session и проверять его перед прямым forwarding. Пока конкретный путь не поддержан, запрос с нелокальным selector должен явно отклоняться.
There was a problem hiding this comment.
Ограничил нелокальный ShardId в StorageService явно поддержанными CreateVolume, DescribeVolume, DestroyVolume и StatVolume. Остальные запросы отклоняются до обработчика и поиска локальной session; неизвестные selectors также отклоняются. Remote StatVolume направляется через VolumeProxy без использования кешированной локальной session. В инструкции уточнил, что mount и session-bound I/O после переноса идут через endpoint целевого шарда.
Fence cancellation and stale progress updates in volume transactions. Preserve alias-equivalent link identity and restore copy-only readiness and idle target cleanup. Reject unsupported remote service and DiskRegistry operations before side effects. Add 11 regression scenarios and clarify supported shard routing. Validation: nbsd and blockstore-client built; 1153 tests passed across nine suites.
|
По двум замечаниям из сводки ревью:
|
|
Note This is an automated comment that will be appended during run. Note All workloads for linux-x86_64-relwithdebinfo have completed. Tip Planned checks for linux-x86_64-relwithdebinfo.
|
Bind cleanup to the expected source incarnation and atomic schema preconditions. Keep one actor-owned cleanup obligation, persist cancelled UUID fences, preserve pending creation identity and diagnostic unlink flags. Revalidate recovered link admission and reject foreign DiskRegistry descriptions. Add 10 regression scenarios and a deterministic executor-queue cancellation test. Build nbsd and blockstore-client; all 1163 tests pass across nine suites. Leave unrelated legacy issues unchanged.
|
Note This is an automated comment that will be appended during run. Note All workloads for linux-x86_64-relwithdebinfo have completed. Tip Planned checks for linux-x86_64-relwithdebinfo.
|
Persist cancellation delivery until the destination acknowledgment is committed. Restore pending cancellations after restart without restarting partitions on retries. Release stale cleanup reservations across cancellation and completion races. Fence destination CREATE by tablet incarnation and recover legacy Created links. Reject conditional DR deletion before mutations while preserving local DR copy cleanup. Add nine regression tests and update the cross-shard copy documentation. Validation: 1172 tests passed in nine suites; nbsd and blockstore-client built.
|
Note This is an automated comment that will be appended during run. Note All workloads for linux-x86_64-relwithdebinfo have completed. Tip Planned checks for linux-x86_64-relwithdebinfo. 🟢 linux-x86_64-relwithdebinfo target: cloud/blockstore/ (test time: 12327s): all tests PASSED for commit a27e119.
|
Skip legacy DiskRegistry deallocation when conditional Sync deletion receives a not-found Stat result. Release copy-only partitions after a committed recovery error. Promote partitions to normal use instead of stopping active remote clients. Add seven regression tests and document the corrected lifecycle boundaries. Validation: all 1179 tests passed in nine suites; nbsd and blockstore-client built.
|
Note This is an automated comment that will be appended during run. Note All workloads for linux-x86_64-relwithdebinfo have completed. Tip Planned checks for linux-x86_64-relwithdebinfo. 🟢 linux-x86_64-relwithdebinfo target: cloud/blockstore/ (test time: 11921s): all tests PASSED for commit 0a071b9.
|
Replace per-UUID cancellation history with a durable destination generation watermark while preserving destination reuse. Persist generations before CREATE, fence delayed requests and durable retries, compact legacy fences safely, and keep the watermark across reboot and resize. Preserve active legacy links during compaction and reject cancelled legacy recovery without assigning a fresh generation. Add ten regression tests, adapt private-status request mocks, and make the conditional cleanup retry test deterministic without weakening its checks. Validation: 1189 tests passed in nine suites; nbsd and blockstore-client built. Overlay and base-disk behavior is unchanged.
|
Note This is an automated comment that will be appended during run. Note All workloads for linux-x86_64-relwithdebinfo have completed. Tip Planned checks for linux-x86_64-relwithdebinfo.
|
…nerations" This reverts commit 8d9697c.
|
Note This is an automated comment that will be appended during run. Note All workloads for linux-x86_64-relwithdebinfo have completed. Tip Planned checks for linux-x86_64-relwithdebinfo. 🔴 linux-x86_64-relwithdebinfo target: cloud/blockstore/ (test time: 11784s): some tests FAILED for commit 3ca0399.
🟢 linux-x86_64-relwithdebinfo target: cloud/blockstore/ (test time: 112s): all tests PASSED for commit 3ca0399.
|
Summary
Extend the existing NBS linked-volume copy mechanism to copy disks between storage shards within one availability zone and one YDB cluster. Reuse background copying, mirrored writes, automatic leadership transfer and deletion of the old source volume.
Current state and scope
The existing workflow does not consistently carry shard addresses through schema resolution, request routing and copy operations. This change makes those addresses explicit without introducing a separate copy engine.
Solution overview
Headers.ShardIdand explicit source/destination shard fields to link requests.ShardDirectoriesallowlist.Compatibility and rollout
Requests without shard identifiers continue to use the receiving node's local shard. Cross-shard links require explicit identifiers for both endpoints; unknown identifiers are rejected.
Participating nodes must have the same shard mapping. The caller creates the destination with the required parameters and routes subsequent requests to the destination shard after transfer. This change does not update Disk Manager or Compute placement metadata.
File summaries
cloud/blockstore/public/api/protos/headers.proto,volume.protocloud/blockstore/config/storage.proto,libs/storage/core/config.{h,cpp}cloud/blockstore/libs/storage/volume_proxy/volume_proxy.cppcloud/blockstore/libs/storage/volume/actors/volume_as_partition_actor.{h,cpp}cloud/blockstore/libs/storage/volume/volume_actor_{leader,follower}.cppdoc/blockstore/cross-shard-copy.mdVerification
Successfully built
nbsdandblockstore-client. All 1,142 tests across nine suites passed.Coverage includes SSD/HDD copy combinations, concurrent writes and zeroes, detached disks, source/destination tablet restarts, reverse copying, cancellation and old-source cleanup. Existing linked-copy regression suites also pass.
Live-cluster validation has not been performed.