[Перевод] Delta Lake 4.3: Выборочная замена данных и API-интерфейсы каталога Unity Delta

Методы replaceUsing и replaceOn обеспечивают более удобный способ перезаписи, а каталог теперь проверяет каждую операцию, а не только фиксацию.
Выпуск Delta Lake 4.3 состоялся в июне 2026 года, и он основан на Apache Spark 4.1.0 и 4.0.1. Если прочитать примечания к выпуску от начала до конца, вы увидите длинный список изменений, охватывающих Delta Spark, ядро, UniForm, Sharing и Flink, который легко принять за релиз с исправлениями, дополненный описанием каталога обновлений.
Два момента заслуживают вашего внимания, и они находятся на противоположных концах стека. Первый — небольшой и сразу полезный: replaceUsingнаконец replaceOn-то API DataFrame получает возможность выборочной перезаписи, которая не является replaceWhere. Второй — структурный: теперь каждая операция с таблицей, управляемой каталогом, проходит через API Unity Catalog Delta , а не только фиксация изменений. В Delta 4.2 фиксации изменений были скоординированы с каталогом. В Delta 4.3 остальная часть интерфейса, загрузка таблиц, CREATE, CTAS, REPLACE, и запись метаданных, объединены в один проверенный путь.
В этой статье рассматривается версия 4.3 с точки зрения разработчика конвейера: какие изменения в коде вы вносите, какие изменения происходят под вашим контролем и что следует проверить перед обновлением. Приводятся примеры testing.defaultдля каталога и схемы, а также пример компании CH Enterprise. Ссылки на исходный код находятся внизу.
Выборочная замена данных: более эффективная замена, чем replaceWhere.
Это изменение, которое большинство читателей используют в первую очередь. До сих пор замена части таблицы Delta из DataFrame означала replaceWhere, что вам приходилось самостоятельно задавать предикат: вы записывали данные, затем отдельно писали фильтр, описывающий область таблицы, которую эти данные должны занимать, и Delta проверяла, совпадают ли они. Это работает, но предикат является вторым источником истины, который постоянно меняется. Каждая запоздалая секция и каждое окно заполнения — это еще один шанс допустить ошибку в строке.
replaceUsingУдаляет предикат. Вы указываете столбцы, которые идентифицируют строку, и Delta заменяет каждую целевую строку, значения в которых совпадают с чем-либо в исходных данных:
updates = spark.read.table( "testing.default.orders_updates" ) (updates.write .mode( "overwrite" ) .option( "replaceUsing" , "order_date, region" ) .saveAsTable( "testing.default.orders" ) )Строки, в ordersкоторых (order_date, region)присутствует пара значений, updatesзаменяются, строки, в которых пара значений отсутствует, остаются без изменений, а исходные строки, содержащие новую пару значений, вставляются. Нет предиката для синхронизации, поэтому заполнение, внезапно включающее новый регион, выполняется правильно без изменения кода.
Обратите внимание, что это не перезапись разделов. Динамическая перезапись данных распространяется на секционированные таблицы, несекционированные таблицы и таблицы с жидкостной кластеризацией, и столбцы соответствия не обязательно должны быть столбцами разделов. (В Databricks для полного обеспечения такой функциональности требуется Databricks Runtime 17.2 или более поздняя версия; версии 16.3–17.1 по-прежнему требуют секционированной таблицы и полного набора столбцов разделов.) Именно это делает его реальной заменой для partitionOverwriteMode, от которой Databricks теперь отводит новые рабочие нагрузки.
replaceOnЭто лазейка для логического сопоставления, которое нельзя выразить с помощью равенства. Вы задаете логическое условие для псевдонимов источника и цели, поэтому <=>становится возможным сравнение с NULL-безопасным результатом:
updates = spark.read.table( "testing.default.orders_updates" ) (updates.alias( "s" ) .write .mode( "overwrite" ) .option( "targetAlias" , "t" ) .option( "replaceOn" , "s.order_date <=> t.order_date AND s.region <=> t.region" ) .saveAsTable( "testing.default.orders" ) )Почему это важно — это задокументированное поведение, а не частный случай: как и JOIN USING, replaceUsingобрабатывает NULL как ничто, поэтому строка, где regionNULL с обеих сторон, никогда не совпадает, устаревшая целевая строка сохраняется, и вы незаметно получаете дубликат. <=>обрабатывает два NULL как равные, и строка заменяется. Если ваши ключи никогда не бывают NULL, оставайтесь на replaceUsing; это проще и является рекомендуемым вариантом.
Перед рефакторингом стоит знать ряд важных ограничений. В Python и Scala операторы replaceOnand и or replaceUsingнельзя комбинировать с replaceWhere, partitionOverwriteMode, or или overwriteSchema,, и все три теперь отклоняют предикаты подзапросов. Одно важное отличие в поведении касается идемпотентных повторных запусков: при пустом исходном запросе операторы replaceUsingand replaceOnничего не удаляют, тогда как replaceWhereor может очистить соответствующий диапазон. Если сбой в вышестоящем проекте когда-либо незаметно очистил раздел, это само по себе является причиной для миграции.
В Databricks SQL-формы доступны уже некоторое время ( REPLACE USINGв Databricks Runtime 16.3 и выше, REPLACE ONв 17.1 и выше), но для работы с DataFrame на Python и Scala требуется Databricks Runtime 18.2 или более поздняя версия. Перед переписыванием конвейера проверьте свою среду выполнения.
API-интерфейсы дельта-изменений каталога Unity: теперь каталог проверяет всё.
В версии 4.3 структурное изменение заключается в том, что Delta Spark по умолчанию использует новый REST API Unity Catalog Delta для таблиц Delta, управляемых UC. Загрузка таблиц, а CREATEтакже все операции записи, изменяющие метаданные, включая DML, изменение схемы, автоматическое слияние и поддерживаемые обновления, проходят через него. Внешние таблицы и таблицы, не относящиеся к Delta, по имени или по пути, сохраняют устаревший делегат.CTASREPLACECREATE OR REPLACEALTER TABLE
Из этого вытекают три гарантии. Серверная проверка коммитов отклоняет некорректные или конфликтующие коммиты до их окончательного применения, поэтому ошибка в записи ничего не повредит. Функции таблиц, объявляемые сервером, позволяют каталогу сообщать движку, какие функции должна поддерживать новая таблица, вместо того, чтобы каждый движок решал это самостоятельно. Обновления метаданных на основе намерений позволяют движку объявлять, что он хочет изменить, а каталог проверяет и применяет это, вместо прямой записи метаданных.
В результате достигается единообразие: Spark и все коннекторы на основе ядра, использующие API UC Delta, получают одинаково четко определенное поведение для одних и тех же таблиц. В этом и заключается смысл работы с управляемыми каталогами: DuckDB, Flink, Trino и Spark читают и записывают данные в один набор таблиц по одному набору правил, вместо того чтобы каждый движок использовал свой собственный протокол чтения. Это незаметно в вашем коде, что является комплиментом. Вам следует извлечь из этого урок: операционный риск многопроцессорного доступа к управляемым таблицам снизился, поэтому интеграцию, которую вы ранее исключали, стоит пересмотреть.
UniForm: атомарные преобразования и векторы удаления больше не являются дисквалифицирующими.
UniForm синхронизирует метаданные Iceberg с дельта-коммитами, поэтому читатели Iceberg могут запрашивать данные из дельта-таблиц без их копии. В версии 4.3 произошли два изменения.
Теперь преобразование происходит atomic and incremental . Крупные коммиты преобразуются в метаданные Iceberg в рамках транзакции Delta, а не после нее, что устраняет пробел в согласованности при массовых коммитах, когда читатель Iceberg мог увидеть устаревший снимок. Инкрементальное преобразование восстанавливает только измененный диапазон журнала Delta, а не полный снимок при каждом коммите, что делает UniForm доступным для таблиц с долгой историей.
Более существенное нововведение — это IcebergCompatV3 , пока ещё экспериментальная функция, которая позволяет таблице одновременно использовать векторы удаления и UniForm. Раньше это был сложный выбор: векторы удаления для быстрого удаления и слияния или UniForm для читателей Iceberg, но не оба варианта одновременно. Теперь же вы можете включить оба варианта с самого начала:
CREATE TABLE testing.default.orders ( order_date DATE , region STRING, order_id STRING, amount DECIMAL ( 12 , 2 ) ) USING DELTA TBLPROPERTIES ( 'delta.enableIcebergCompatV3' = 'true' , 'delta.universalFormat.enabledFormats' = 'iceberg' , 'delta.feature.catalogManaged' = 'supported' , 'delta.enableDeletionVectors' = 'true' );IcebergCompatV3 также добавляет совместимость с геометрией и географическими данными благодаря новейшему протоколу записи Iceberg. UniForm теперь построен на основе Iceberg-spark 1.11.0 и поддерживает как Spark 4.0, так и Spark 4.1.
Одно замечание следует включить в ваши примечания по обновлению, а не в архитектурную схему: в таблицах UniForm с вектором удаления Iceberg DataFile.recordCountтеперь отображает физическое количество строк до применения вектора удаления, а не логическое. Любой последующий процесс, считывающий это значение как количество строк, должен применить вектор удаления, чтобы восстановить это число.
Потоковая передача и изменение данных в потоке данных достигают таблиц, управляемых каталогом.
Структурированная потоковая передача и передача данных об изменениях теперь работают с управляемыми каталогом таблицами Delta из Apache Spark. Источники потоковой передачи поддерживают полный набор стандартных параметров чтения, а пакетная передача данных об изменениях, управляемая каталогом, предоставляется в виде CHANGESусловия, учитывающего вектор удаления и доступного через флаг:
SET spark.databricks.delta.changelogV2.enabled = true ; -- Воспроизвести все изменения, начиная с известной версии SELECT * FROM testing.default.orders CHANGES FROM VERSION 0 ; -- Или с определенного момента времени SELECT * FROM testing.default.orders CHANGES FROM TIMESTAMP '2026-06-01 00:00:00' ;Это тот же CHANGESсинтаксис, который Apache Spark 4.2 стандартизировал для всех коннекторов, поэтому логика инкрементального чтения, написанная с его использованием, остается переносимой.
Функция Delta Sharing обеспечивает возможность сопоставления. Потоковые запросы к общим таблицам в формате Delta теперь могут считывать поток данных об изменениях, при этом управление смещениями синхронизировано с базовым источником Delta, и запускать конвейеры с последующим завершением обработки Trigger.AvailableNow. Если вы используете общую таблицу и выполняете полное обновление, поскольку инкрементальное обновление было недоступно, этот обходной путь имеет ограниченный срок действия.
Мелкие детали, которые отразятся на ваших показателях.
В версии V2 по умолчанию для каждого сайдкара выполняется 50 000 действий. Файлы сайдкаров автоматически разбиваются на несколько частей, а запись контрольных точек выполняется параллельно по умолчанию. При работе с большими таблицами это обеспечивает дополнительную пропускную способность по пути, который вы никогда не настраивали.
Статистика столбцов Variant при записи. Delta Spark теперь собирает минимальные/максимальные значения для столбцов Variant, что позволяет пропускать данные в таблицах с удаленными столбцами Variant. Полуструктурированные столбцы перестают быть препятствием для очистки файлов.
Неявное приведение типов для записи DataFrame по имени. При записи по имени, за исключением случаев save()и saveAsTable().mode("overwrite"), теперь применяются неявные приведения типов Spark для согласования значений источника со схемой цели, в соответствии с SQL INSERT BY NAME.
Ядро получает более быструю диагностику. Диагностика tableSizeBytes выполняется numFilesпостепенно на основе контрольных сумм версий, а не путем полного воспроизведения логов. Кроме того, ядро теперь может открыть таблицу, lastcheckpointуказатель на которую отсутствует или устарел, используя сканирование логов. Этот режим отказа устранен.
Перед обновлением
Три изменения совместимости заслуживают внимательного прочтения. MERGE INTOПри использовании пустой схемы теперь возникает ошибка mergeSchema=false, тогда как ранее при перезаписи целевой схемы исходной схемой без предупреждения; delta.schemaAutoMerge.enabled=trueсначала необходимо установить или выровнять схемы. DataFile.recordCountИзменения Iceberg касаются UniForm и таблиц векторов удаления, как описано выше. А в ядре enableVariantShreddingтеперь включена функция variantShredding-preview, поэтому, если вам нужна готовая к использованию функция, вам необходимо явно запросить её с помощью delta.feature.variantShredding=supported.
Что бы я на самом деле сделал на этой неделе
Найдите конвейер обработки данных, где replaceWhereпредикат доставил вам проблемы, и перепишите его с помощью replaceUsing. Ограниченное изменение, немедленная отдача в плане корректности и поведение с пустым исходным кодом сами по себе делают повторные запуски более безопасными. Затем проверьте, не находится ли какая-либо таблица в вашей системе в компромиссе между вектором удаления и UniForm, поскольку IcebergCompatV3 устраняет этот компромисс, экспериментальный статус принят. Все остальное в версии 4.3 — это инфраструктура, которая улучшается независимо от того, взаимодействуете вы с ней или нет, и это лучший вариант.
И ещё одно замечание по поводу сроков: Delta Lake 4.4 был выпущен в августе 2026 года с Apache Spark 4.2 в качестве версии по умолчанию. Если вы планируете обновление сейчас, планируйте его с учётом версии 4.4 и рассматривайте эту статью как карту того, что было выпущено на пути к ней.
Нашли это полезным? Подпишитесь на меня, чтобы получать больше практического контента по инженерии данных.
Полный текст статьи в формате ноутбука находится на GitHub: delta-lake-4–3 .
Источники
KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.