Как мы и предполагали, пошардовый обход — самый быстрый, в нём идёт фильтрация только по bucket_id. Но одна из проблем метода — дубликаты или потери. Если чанки в самом начале делить на отрезки и не модифицировать их, тогда чанк будет разрастаться по количеству объектов. Нам же важна возможность балансировать нагрузку по шардам, а для этого нужно поддерживать оптимальный размер чанка. Задачу решает фоновый процесс, который находит большие чанки и делит их в определённой пропорции.
Второй фоновый процесс — перемещение чанка с одного шарда на другой. Если шард деградирует по CPU или по памяти, мы переводим этот чанк на другой свободный шард. Например, при обходе с фильтрацией только по bucket_id листим первый шард, потом второй шард, чанк переехал, листим третий шард — и вот у нас уже появляются дубликаты метаданных в целевом бакете.
Но может быть и обратная ситуация, например, мы не долистим какой-то чанк (он переедет в моменте перехода на следующий шард). Транзакции Repeatable Read не спасут ситуацию. Синхронизации между шардами в этом случае нет.
Кроме того, в пошардовом обходе параллельность задач ограничена количеством шардов, на которых живёт бакет. При обходе по чанкам всё зависит от числа чанков бакета, а в случае с погрупповым обходом мы можем сами регулировать количество задач на обработку.
Есть нюансы и по транзакциям. При пошардовом обходе мы берём repeatable-транзакцию и потом фильтруем по bucket_id. Объектов на шарде может быть много, а значит, транзакция будет долгой. В почанковом и групповом обходах транзакции достаточно короткие, поэтому листить можно быстрее.
В части случаев групповой обход даёт примерно те же результаты, что и почанковый, но выигрывает, если много чанков находится на одном шарде (идут последовательно, их можно листить сразу пачкой).
Каждый из способов сбора метаданных мы тестировали с одним и тем же потоком. Например, у нас был бакет-миллиардник (№ 4 на скрине) — обход по шардам справился за 29 часов, по чанкам — за 61 час, а по группам — за 54 часа: