Атрибуция: Этот материал является переводом и адаптацией статьи Apache Doris 4.1 on Iceberg V3: Running the Full Lakehouse Lifecycle from One SQL Engine за авторством Mingyu Chen (Rayner), опубликованной по лицензии CC BY 4.0. Адаптация выполнена для русскоязычной аудитории Хабра с дополнительным контекстом по архитектурным компромиссам.
Сегодня поговорим про Iceberg V3 и Apache Doris 4.1. Но не в формате «у нас теперь есть поддержка новой версии формата, хлопаем в ладоши». Гораздо интереснее другой вопрос: что меняется, когда SQL-движок умеет не только читать Iceberg-таблицу, но и делать маленькие исправления, reconciliation, maintenance и диагностику без отдельного Spark-кластера на каждое телодвижение?
Спойлер: Spark никто не отменял. Flink тоже. Но есть целый класс ежедневной работы, где переключение движка дороже самой операции.
Что вы узнаете из этой статьи
-
UPDATE на Iceberg без Spark job. Doris 4.1 делает
UPDATE,DELETE,MERGE INTOна Iceberg-таблицах из того же SQL-клиента, где вы уже нашли проблему. Цикл «query → fix → verify» сжимается с 14 часов до минут. -
Deletion Vectors вместо Position Delete files. В Iceberg V3 DML больше не копит отдельные parquet delete files при каждом commit. Один Puffin bitmap вместо 320 файлов. Query latency перестаёт деградировать с ростом delete ratio (бенчмарк: 2–3x ускорение).
-
Row Lineage как CDC watermark.
_last_updated_sequence_numberдвигается только при реальном DML, а не при compaction. Downstream больше не переобрабатывает строки послеrewrite_data_files. Один integer watermark вместо snapshot diff. -
Doris не заменяет Spark. Heavy ETL, backfills и streaming ingestion остаются за Spark/Flink. Doris забирает adjacent workflow: small DML, incremental reconciliation, maintenance и диагностику.
Хотите сразу попробовать? Переходите в Часть 8: Quick Start там путь от нуля до V3-таблицы за пять минут.
Часть 1: 3 часа ночи, одна строка сломана
Представим конкретную ситуацию.
Data engineer в e-commerce компании сидит в Doris и разбирает Iceberg-таблицу с заказами. Видит, что вчерашний batch из ERP проставил status = 'cancelled' для 200 заказов, которые на самом деле были оплачены — upstream-система отправила кривой snapshot. Запрос подтверждает: вот они, 200 строк с неверным статусом. Исправление — банальный WHERE order_id IN (...) и SET status = 'paid'.
В идеальном мире это выглядит так:
UPDATE orders_icebergSET status = 'paid'WHERE order_id IN (SELECT order_id FROM bad_batch_ids);
Что происходит дальше в реальном мире — красиво описано в оригинальной статье Mingyu Chen. Инженер закрывает SQL-клиент и начинает другой рабочий процесс:
-
Пишет Spark job.
-
Подстраивает scheduler.
-
Открывает PR.
-
Ждёт review.
-
Чаще всего — передаёт задачу платформенной команде, потому что у него нет прямого доступа к write-кластеру.
-
Утром возвращается в Doris и проверяет, что всё отработало.
Полный цикл — 14 часов. Код по смыслу — один UPDATE.
Есть и менее драматичный, но более частый сценарий. CDC-поток пишет в Iceberg, small files и delete files копятся, запросы постепенно деградируют. Все понимают, что нужен rewrite_data_files.
В командах с self-service инфраструктурой инженер запустит compaction сам. Но во многих средних и крупных организациях write-доступ к Iceberg-таблицам живёт у выделенной platform team — и тогда задача, которая по сути один SQL-вызов, превращается в ticket, maintenance window и ожидание. Именно этот организационный паттерн описывает автор оригинальной статьи, и он встречается чаще, чем хотелось бы.
Iceberg решил огромную часть проблемы: он сделал таблицу открытым transactional-форматом поверх файлов в object storage. Но работа с этой таблицей часто остаётся не такой уж открытой:
-
читаем в одном движке;
-
пишем в другом;
-
compaction и maintenance делаем через третий;
-
CDC и streaming ingestion живут отдельно.
Получается два налога на каждое маленькое действие: switch-engine tax и cross-team tax. Apache Doris 4.1 пытается уменьшить оба.
Часть 2: Где Doris находится в Iceberg-экосистеме
Важно сразу снять лишнее ожидание: Doris не пытается заменить Spark во всех задачах.
Apache Doris — это MPP SQL engine, который начинал как data warehouse, а за последние два мажорных релиза сместил центр тяжести в сторону lakehouse workloads. Spark при этом остаётся нормальным инструментом для cross-source backfills, тяжёлого ETL, больших batch-job’ов и широкой batch/streaming-экосистемы. Flink остаётся естественным выбором для streaming ingestion и continuous CDC write paths.
Роль Doris в этом контексте узкая и конкретная: real-time query layer для Iceberg, который постепенно забирает соседний workflow:
-
вы уже пришли в SQL-клиент с запросом;
-
увидели проблему или расхождение;
-
хотите сделать небольшое исправление;
-
хотите запустить incremental reconciliation;
-
хотите выполнить maintenance или диагностику;
-
не хотите ради этого прыгать в другой кластер.
В Doris 4.1 матрица Iceberg-поддержки выглядит довольно широко:
|
Зона |
Что поддерживается |
|---|---|
|
Read |
V1/V2/V3, time travel, branch/tag, views, system tables, Position/Equality/DV delete |
|
Write |
|
|
DDL |
create/drop table, schema change, partition evolution, branch/tag management |
|
Maintain |
|
|
Diagnose |
data file distribution, dangling delete |
Архитектура: один SQL-движок для Read, Write, DDL, Maintenance и Diagnostics на Iceberg V3 таблицах. Источник: оригинальная статья Mingyu Chen.
Но в этой статье нас интересует не вся матрица. Фокус на двух вещах:
-
DML completeness:
UPDATE,DELETE,MERGE INTOпрямо из SQL-клиента. -
Iceberg V3 mechanics: Deletion Vectors и Row Lineage, без которых DML быстро начинает копить технический долг.
Часть 3: DML возвращается в query layer
Начнём с простого.
Исправить одну строку:
UPDATE iceberg_tblSET name = 'Alice-fixed'WHERE id = 1;
Откатить плохой batch:
DELETE FROM iceberg_tblWHERE dt = '2026-04-01' AND source = 'bad_pipeline';
Сделать upsert по incremental batch:
MERGE INTO iceberg_tbl tUSING ( SELECT 1 AS id, 'Alice_new' AS name, 26 AS age, 'U' AS flag UNION ALL SELECT 2, 'Bob', 30, 'D' UNION ALL SELECT 4, 'Dora', 28, 'I') sON t.id = s.idWHEN MATCHED AND s.flag = 'D' THEN DELETEWHEN MATCHED THEN UPDATE SET name = s.name, age = s.ageWHEN NOT MATCHED THEN INSERT (id, name, age) VALUES (s.id, s.name, s.age);
MERGE INTO здесь самая важная операция. Именно на ней держатся типичные CDC/upsert-сценарии:
-
изменения из PostgreSQL или MySQL прилетают через Flink CDC или похожий инструмент;
-
downstream Iceberg-таблица должна принять inserts, updates и deletes вместе;
-
incremental materialized views должны обновлять wide tables по change key.
Без MERGE INTO вы либо пересобираете данные целиком, либо уносите логику в Spark/Flink и вручную обслуживаете отдельный pipeline.
В Doris 4.1 MERGE INTO поддерживает partitioned targets, subqueries как source и expressions в UPDATE-части, например age = age * 2 + 1.
Но caveat сразу: target table должен быть format-version >= 2, а сам MERGE INTO в оригинальной статье помечен как experimental. Это не «сразу катим в прод в пятницу вечером», а «делаем POC на своём workload».
Часть 4: Почему DML без V3 быстро копит долги
Может показаться, что раз Doris теперь умеет UPDATE, DELETE, MERGE INTO на Iceberg — задача решена.
Не совсем. Наличие DML — это ещё не хороший DML.
В Iceberg V2 каждая такая операция создаёт Position Delete file. Это отдельный parquet-файл, в котором записано: «вот эти позиции из data file нужно считать удалёнными». Чтобы вернуть корректный результат, reader вынужден выполнять anti-join: прочитать data files, прочитать все связанные delete files, отфильтровать помеченные строки.
Один delete file — ничего страшного. Десять — терпимо. Сотни и тысячи маленьких DML commits в CDC-таблице — уже неприятно.
Проблема растёт линейно:
-
каждый DML commit добавляет новые delete files;
-
каждый последующий query вынужден открывать и объединять все релевантные delete files;
-
периодически нужен
rewrite_data_files, чтобы вернуть таблицу в нормальное состояние; -
а значит, снова maintenance job, scheduler и платформа — тот самый Jira-ticket из начала статьи.
Это первый долг: performance debt. DML вроде есть, но каждый commit медленно ухудшает чтение.
Есть второй долг: observability debt. После того как commit произошёл, downstream-системы больше не знают, когда конкретная строка была последний раз изменена. Они полагаются на snapshot diff. Но compaction и rewrite_data_files тоже создают новые snapshots, хотя данные логически не менялись.
В результате downstream pipeline видит физический rewrite и интерпретирует его как новые данные — и начинает переобрабатывать строки, которых никто не трогал.
Если коротко:
DML без V3 быстро превращается в два долга: performance debt и observability debt.
Iceberg V3 отвечает на них двумя механизмами:
-
Deletion Vectors — чтобы DML не раздувал delete files.
-
Row Lineage — чтобы row-level changes были наблюдаемыми.
Часть 5: Deletion Vectors — меньше delete files, меньше anti-join
Deletion Vector — это bitmap, который хранит, какие строки в data file считаются удалёнными. В Iceberg V3 он хранится в Puffin file format.
Контраст с V2 такой:
-
V2: каждый DML commit создаёт отдельный Position Delete parquet.
-
V3: все пометки на удаление для одного data file схлопываются в один Puffin Deletion Vector.
-
V2 reader: читает data file, потом anti-join со всеми связанными delete files.
-
V3 reader: читает data file + DV и применяет bitmap за один проход. Anti-join исчезает.
V2 копит Position Delete parquet-файлы при каждом DML commit. V3 схлопывает все пометки в один Puffin Deletion Vector. Источник: оригинальная статья Mingyu Chen.
Мини-пример V2:
CREATE TABLE orders_v2 ( id INT, status STRING, amount DECIMAL(10,2)) PROPERTIES ('format-version' = '2');INSERT INTO orders_v2 VALUES (1, 'pending', 100), (2, 'pending', 200), (3, 'pending', 300);UPDATE orders_v2 SET status = 'shipped' WHERE id = 1;UPDATE orders_v2 SET status = 'shipped' WHERE id = 2;DELETE FROM orders_v2 WHERE id = 3;
После трёх DML commits в V2 появляются три Position Delete files.
Та же логика на V3:
CREATE TABLE orders_v3 ( id INT, status STRING, amount DECIMAL(10,2)) PROPERTIES ('format-version' = '3');INSERT INTO orders_v3 VALUES (1, 'pending', 100), (2, 'pending', 200), (3, 'pending', 300);UPDATE orders_v3 SET status = 'shipped' WHERE id = 1;UPDATE orders_v3 SET status = 'shipped' WHERE id = 2;DELETE FROM orders_v3 WHERE id = 3;
Теперь вместо набора parquet delete files рядом с data file лежит Puffin Deletion Vector. SQL на поверхности тот же, физическая механика другая.
Проверить это можно через system table:
SELECT content, file_path, record_countFROM orders_v3$files;
В нормальном V3-сценарии вы ожидаете увидеть data file и .puffin DV, а не растущий список ...delete.parquet.
Что дают числа
В оригинальной статье приведён Doris-side benchmark на нескольких сценариях. Самое важное:
|
Сценарий |
Iceberg V2: Position Deletes |
Iceberg V3: Deletion Vector |
|---|---|---|
|
16 data files, 20% deleted |
336 files to open: 16 data + 320 delete |
17 files: 16 data + 1 Puffin |
|
100M rows, 99% deleted |
98 MiB delete storage |
3.8 MiB DV storage |
|
Delete metadata reduction |
— |
~96% |
Latency на 16 data files и 1M rows:
|
Delete % |
Doris V2 |
Doris V3 |
Speedup |
|---|---|---|---|
|
5% |
0.31s |
0.15s |
2.1x |
|
10% |
0.35s |
0.16s |
2.2x |
|
20% |
0.43s |
0.17s |
2.5x |
|
30% |
0.46s |
0.14s |
3.3x |
|
40% |
0.39s |
0.17s |
2.3x |
Для large file с 99% deleted:
|
Table version |
Doris Q1 |
Doris Q2 |
|---|---|---|
|
V2, Position Delete |
3.42s |
3.28s |
|
V3, Deletion Vector |
1.03s |
0.86s |
|
Speedup |
~3x |
~3x |
Doris V3 vs V2: latency остаётся плоской при росте delete ratio, в то время как V2 деградирует линейно. Источник: оригинальная статья Mingyu Chen.
Смысл не в конкретном множителе «всегда 3x». Паттерн другой: под V2 anti-join cost растёт вместе с числом delete files, и query latency деградирует по мере роста delete ratio. Под V3 bitmap применяется за один проход, и latency остаётся практически плоской независимо от того, сколько строк помечено на удаление.
Что именно Doris реализует для Deletion Vectors
Важный нюанс из оригинала: чтобы Deletion Vectors приносили пользу, движок должен уметь и читать, и писать DV. Read-only движок может потреблять DV, записанные кем-то другим, но сам их не создаёт. Write-only движок создаёт DV, которые никто не прочитает.
Doris 4.1 поддерживает обе стороны:
-
Read: Puffin-format Deletion Vectors читаются без дополнительной настройки. V3-таблицы queryable сразу.
-
Write:
DELETE,UPDATE,MERGE INTOна V3-таблице автоматически создают Puffin DV вместо Position Delete files. Пользователю достаточно указатьformat-version = 3при создании таблицы — SQL-синтаксис не меняется.
Caveats по Deletion Vectors
Три практических ограничения:
-
DELETE,UPDATE,MERGE INTOтребуютformat-version >= 2. -
Deletion Vectors появляются только при
format-version = 3. -
Concurrent writes используют Iceberg optimistic concurrency control, так что конфликты будут всплывать как transaction exceptions. Для high-conflict workloads нужны retry или сериализация.
Часть 6: Row Lineage — watermark для CDC без snapshot diff
Deletion Vectors отвечают на performance debt. Теперь про observability debt.
В Iceberg V1/V2 change tracking в основном живёт на уровне snapshot. Это нормальная модель для многих задач, но у неё есть неприятный эффект: физический rewrite может выглядеть как логическое изменение.
Например:
-
downstream-система подписана на изменения Iceberg-таблицы;
-
она сравнивает snapshots;
-
между checkpoint’ами прошёл
rewrite_data_files; -
физические файлы новые, snapshot новый;
-
downstream не знает, изменились строки логически или просто переехали между файлами.
И начинает переобрабатывать лишнее.
Iceberg V3 добавляет Row Lineage: две скрытые системные колонки для каждой строки.
Row Lineage: _row_id даёт стабильную идентичность строки, _last_updated_sequence_number — watermark для CDC. Источник: оригинальная статья Mingyu Chen.
|
Колонка |
Что означает |
|---|---|
|
|
стабильный numeric identifier строки |
|
|
sequence number последнего логического изменения строки |
Обе колонки поддерживаются системой. Пользователь не записывает их руками.
Самая важная часть: _last_updated_sequence_number меняется при настоящем UPDATE или MERGE INTO, но не меняется при compaction и physical rewrite. Значит, его можно использовать как CDC watermark.
Пример:
CREATE TABLE users_v3 ( id INT, name STRING, email STRING) PROPERTIES ('format-version' = '3');SET show_hidden_columns = true;INSERT INTO users_v3 VALUES (1, 'Alice', 'alice@x.com'), (2, 'Bob', 'bob@x.com'), (3, 'Carol', 'carol@x.com');SELECT id, name, email, _row_id, _last_updated_sequence_numberFROM users_v3;
После первого insert все три строки получают стабильные _row_id, а _last_updated_sequence_number равен 1.
Дальше меняем Bob:
UPDATE users_v3SET email = 'bob@newmail.com'WHERE id = 2;
У Bob _row_id остаётся тем же, но _last_updated_sequence_number становится 2. Alice и Carol остаются с SN = 1.
Добавляем Dora:
INSERT INTO users_v3VALUES (4, 'Dora', 'dora@x.com');
Dora получает новый _row_id и SN = 3.
Теперь downstream, который синхронизирован по watermark 1, может запросить только реальные изменения:
SELECT id, name, email, _last_updated_sequence_numberFROM users_v3WHERE _last_updated_sequence_number > 1;
Вернутся Bob и Dora. Alice и Carol не попадут в pipeline. Если между checkpoint’ами случится compaction, они всё равно не попадут, потому что physical rewrite не двигает _last_updated_sequence_number.
Это и есть главный выигрыш: downstream хранит один integer watermark, а не пытается угадывать смысл snapshot diff.
Как меняется CDC-pipeline
Контраст в архитектуре:
V2 (snapshot diff): Upstream (PostgreSQL и т.д.) → Flink CDC → Iceberg V2 Sink → Downstream делает snapshot diff, но не может отличить реальное изменение от compaction/rewrite → false positives → переобработка.
V3 (Row Lineage): Upstream → Flink CDC → Iceberg V3 Sink → Downstream делает WHERE _last_updated_sequence_number > :watermark → только реальные изменения → без false positives.
Разница не только в точности, но и в простоте: вместо инфраструктуры для snapshot diffing — один SQL-запрос с одним integer watermark.
Что Doris реализует для Row Lineage
В Doris скрытые колонки можно читать явно или включить их отображение:
SET show_hidden_columns = true;
UPDATE и MERGE INTO на V3-таблицах автоматически обновляют _last_updated_sequence_number. Пользователь не добавляет отдельный audit trigger, не пишет отдельную таблицу lineage, не синхронизирует внешний log.
Но caveat снова важный: Row Lineage в оригинале помечен как experimental. Проверять на своём workload обязательно.
Часть 7: Audit trail через _row_id
Ещё один полезный сценарий — аудит.
Финансовый аудитор спрашивает: «Order 102 менялся три раза. Какие суммы были на каждом шаге?»
В V2 обычно приходится идти во внешнюю audit log, потом пытаться совместить её с текущим состоянием таблицы, потом учитывать compaction, file rewrite и прочие радости жизни. Это возможно, но хрупко: audit system и table state живут в разных местах.
В V3 появляется более удобная опора: _row_id остаётся lifetime identity строки.
Условно:
|
Snapshot SN |
Amount |
Event |
|---|---|---|
|
SN=1 |
80.00 |
Order created |
|
SN=2 |
90.00 |
Customer complaint, partial refund |
|
SN=3 |
100.00 |
Final settlement |
Сама Row Lineage не заменяет time travel и не хранит полную историю «магически». Важная деталь именно в связке:
-
_row_idдаёт стабильный ключ строки; -
snapshots/time travel дают возможность смотреть прошлые состояния;
-
_last_updated_sequence_numberпоказывает логическую хронологию изменений; -
compaction не ломает эту идентичность.
Примерный запрос для восстановления состояния:
SELECT order_id, amountFROM orders_v3 FOR VERSION AS OF <snapshot_id_at_SN2>WHERE _row_id = 1;
Да, вам всё ещё нужны snapshots. Но _row_id и _last_updated_sequence_number — это логические свойства строки, а не физические. Если между шагами прошёл ALTER TABLE orders_v3 EXECUTE rewrite_data_files(), физические файлы будут новые, а _row_id останется прежним и SN не изменится. Под V2 тот же compaction создал бы новый snapshot, и без внешней audit log вы бы не знали, менялись строки логически или нет.
Часть 8: Quick Start
Если хочется быстро потрогать механику руками, минимальный путь такой.
Prerequisites
Нужны:
-
Apache Doris 4.1.0 или новее (скачать или
docker pull apache/doris:4.1.0). -
Iceberg catalog с поддержкой V3.
-
Object store, доступный Doris BE: S3, MinIO, OSS или HDFS.
По catalog’ам в оригинале рекомендация такая:
-
REST Catalog — наиболее надёжный путь для V3. Например, Apache Polaris, Lakekeeper или Tabular open-source REST catalog image.
-
Hive Metastore-backed catalogs подходят для V1/V2 reads, но отстают по V3.
-
AWS Glue и Aliyun DLF на момент оригинальной статьи указаны как read-only.
1. Подключить catalog
CREATE CATALOG iceberg_v3 PROPERTIES ( 'type' = 'iceberg', 'iceberg.catalog.type' = 'rest', 'uri' = 'http://your-rest-catalog:8181', 'warehouse' = 's3://your-bucket/warehouse', 's3.endpoint' = 'https://s3.us-west-2.amazonaws.com', 's3.access_key' = '<AK>', 's3.secret_key' = '<SK>', 's3.region' = 'us-west-2');SWITCH iceberg_v3;CREATE DATABASE IF NOT EXISTS demo;USE demo;
2. Создать V3-таблицу и сделать DML
CREATE TABLE orders ( id INT, status STRING, amount DECIMAL(10,2)) PROPERTIES ('format-version' = '3');INSERT INTO orders VALUES (1, 'pending', 100), (2, 'pending', 200), (3, 'pending', 300);UPDATE orders SET status = 'shipped' WHERE id = 1;DELETE FROM orders WHERE id = 3;
3. Проверить V3-поведение
Проверка Deletion Vector:
SELECT content, file_path, record_countFROM orders$files;
Ожидаем .puffin DV рядом с data file, а не россыпь parquet delete files.
Проверка Row Lineage (скрытые колонки по умолчанию не видны):
SET show_hidden_columns = true;SELECT id, status, _row_id, _last_updated_sequence_numberFROM orders;
4. Попробовать MERGE INTO
MERGE INTO orders tUSING (SELECT 1 AS id, 'delivered' AS status, 110 AS amount) sON t.id = s.idWHEN MATCHED THEN UPDATE SET status = s.status, amount = s.amountWHEN NOT MATCHED THEN INSERT (id, status, amount)VALUES (s.id, s.status, s.amount);
Напоминание: MERGE INTO experimental. Это хороший кандидат для POC, но не для blind rollout в production.
Если orders$files показывает один Puffin file рядом с data file, а выжившие строки несут корректные значения _row_id / _last_updated_sequence_number — ваш стек V3-ready end to end.
Полный справочник по конфигурации: Doris Iceberg catalog docs.
Часть 9: Common gotchas
Короткий список того, обо что легко удариться:
-
format-version = 3нужно задать при создании таблицы или мигрировать существующую V2-таблицу черезALTER TABLE ... SET PROPERTIES ('format-version' = '3'). -
Hidden columns (
_row_id,_last_updated_sequence_number) работают только на V3. На V1/V2 таблицах запрос к ним даст ошибку. -
Если
MERGE INTOне парсится, проверьте Doris 4.1.0+ и target table V2/V3. -
DML требует
format-version >= 2; Deletion Vectors появляются только на V3. -
Concurrent writers могут ловить optimistic concurrency conflicts. Для high-conflict workloads нужны retry, backoff или сериализация.
-
REST Catalog behavior зависит от конкретной реализации. Для production лучше свериться с support matrix в Doris docs.
-
AWS Glue и Aliyun DLF в оригинале указаны как read-only на момент статьи.
Часть 10: Где граница Doris, Spark и Flink
Самая опасная версия этой статьи звучала бы так: «Теперь Doris заменяет Spark для Iceberg».
Это плохая версия. Не надо так.
Более честная матрица:
|
Инструмент |
Где уместен |
|---|---|
|
Doris |
real-time queries, small DML, incremental reconciliation, row-level provenance, day-to-day maintenance, диагностика Iceberg tables из SQL |
|
Spark |
heavy backfills, cross-source ETL, long-running batch jobs |
|
Flink |
streaming ingestion, continuous CDC write paths |
Doris 4.1 не заменяет Spark. Он сокращает число случаев, когда Spark приходится запускать ради мелкой операции вокруг проблемы, которую вы уже нашли.
Если запрос уже привёл вас в SQL-клиент — маленькое исправление не должно превращаться в distributed-computing ритуал с отдельным кластером.
Итог
Одна фраза хорошо описывает направление Doris на Iceberg:
Если запрос уже привёл вас в SQL-клиент, маленькое исправление, incremental reconciliation или day-to-day maintenance не должны заставлять уходить в другой движок.
Что меняется в Apache Doris 4.1:
-
UPDATE,DELETE,MERGE INTOзакрывают loop «query → fix → verify» внутри одного SQL-клиента. -
Deletion Vectors в Iceberg V3 не дают DML накапливать performance debt через растущий набор Position Delete files.
-
Row Lineage даёт
_row_idи_last_updated_sequence_number, чтобы CDC и audit не ломались от compaction и physical rewrite. -
Maintenance и диагностика становятся ближе к человеку, который уже смотрит на данные.
Что не меняется:
-
Spark остаётся для тяжёлых batch/backfill/ETL задач.
-
Flink остаётся для streaming ingestion.
-
MERGE INTOи Row Lineage в оригинале помечены как experimental. -
Production adoption требует POC на своём catalog, storage, concurrency pattern и workload.
Если коротко: Iceberg V3 делает DML архитектурно более здоровым, а Doris 4.1 пытается сделать этот DML доступным из того же SQL-движка, где вы уже нашли проблему.
Для data engineer’а это не звучит как революция. Это звучит как минус один ticket, минус один job, минус один context switch.
А иногда именно это и есть настоящая продуктивность.
Ссылки
-
Оригинальная статья Mingyu Chen (Rayner) — CC BY 4.0
-
Datanomix.pro Партнер Apache Doris / VeloDB в Казахстане и Узбекистане
Оригинал по лицензии CC BY 4.0 © 2026 Mingyu Chen (Rayner).
ссылка на оригинал статьи https://habr.com/ru/articles/1030676/