0.2.0 (2025-03-25)
Кратко
- Реализована поддержка lineage на уровне колонок.
- Партиции HDFS/S3 теперь удаляются из пути таблицы.
- Добавлена общая статистика по запускам/операциям (входные/выходные байты, строки, файлы).
- Улучшения UX графа lineage
- Улучшения в интеграции Kafka -> потребитель.
Критические изменения
- Изменена схема ответа
GET /operations. (#158)
Свойства операций перемещены в ключ data, добавлен новый ключ statistics.
Это позволяет отображать статистику операций в UI без построения графа lineage
Примеры ответов операций
=== Было
{
"meta": {
// ...
},
"items": [
{
"kind": "OPERATION",
"id": "00000000-0000-0000-0000-000000000000",
"name": "abc",
"description": "some",
// ...
}
],
}
=== Стало
{
"meta": {
// ...
},
"items": [
{
"id": "00000000-0000-0000-0000-000000000000",
"data": {
"id": "00000000-0000-0000-0000-000000000000",
"name": "abc",
"description": "some",
// ...
},
"statistics": {
"inputs": {
"total_datasets": 2,
"total_bytes": 123456,
"total_rows": 100,
"total_files": 0,
},
"outputs": {
"total_datasets": 2,
"total_bytes": 123456,
"total_rows": 100,
"total_files": 0,
},
},
}
],
}
- Изменена схема ответа
GET /runs. (#159)
Свойства запуска (Run) перемещены в ключ data, добавлен новый ключ statistics.
Это позволяет отображать статистику запуска в UI без построения графа lineage
Примеры ответов запусков (Run)
=== Было:
{
"meta": {
// ...
},
"items": [
{
"kind": "RUN",
"id": "00000000-0000-0000-0000-000000000000",
"external_id": "abc",
"description": "some",
// ...
}
],
}
=== Стало
{
"meta": {
// ...
},
"items": [
{
"id": "00000000-0000-0000-0000-000000000000",
"data": {
"id": "00000000-0000-0000-0000-000000000000",
"external_id": "abc",
"description": "some",
// ...
},
"statistics": {
"inputs": {
"total_datasets": 2,
"total_bytes": 123456,
"total_rows": 100,
"total_files": 0,
},
"outputs": {
"total_datasets": 2,
"total_bytes": 123456,
"total_rows": 100,
"total_files": 0,
},
"operations": {
"total_operations": 10,
},
},
}
],
}
- Изменена схема ответа
GET /locations. (#160)
Свойства location перемещены в ключ data, добавлен новый ключ statistics.
Это позволяет отображать статистику location в UI.
Примеры ответов Location
=== Было
{
"meta": {
// ...
},
"items": [
{
"kind": "LOCATION",
"id": 123,
"name": "rnd_dwh",
"type": "hdfs",
// ...
}
],
}
=== Стало
{
"meta": {
// ...
},
"items": [
{
"id": "123",
"data": {
"id": "123",
"name": "rnd_dwh",
"type": "hdfs",
// ...
},
"statistics": {
"datasets": {"total_datasets": 2},
"jobs": {"total_jobs": 0},
},
}
],
}
То же самое для PATCH /locations/:id:
Примеры ответа Location
=== Было
{
"kind": "LOCATION",
"id": 123,
"name": "abc",
// ...
}
=== Стало
{
"id": "123",
"data": {
"id": "123",
"name": "abc",
// ...
},
"statistics": {
"datasets": {"total_datasets": 2},
"jobs": {"total_jobs": 0},
},
}
- Изменена схема ответа
GET /datasets. (#161)
Свойства набора данных перемещены в ключ data.
Это делает API-ответ более согласованным с другими (например, GET /runs, GET /operations).
### Примеры ответов
=== Было
{
"meta": {
// ...
},
"items": [
{
"kind": "DATASET",
"id": 123,
"name": "abc",
// ...
}
],
}
=== Стало
{
"meta": {
// ...
},
"items": [
{
"id": "123",
"data": {
"id": "123",
"name": "abc",
// ...
},
}
],
}
- Изменена схема ответа
GET /jobs. (#162)
Свойства задания (Job) перемещены в ключ data.
Это делает API-ответ более согласованным с другими (например, GET /runs, GET /operations).
Примеры ответов Jobs
=== Было
{
"meta": {
// ...
},
"items": [
{
"kind": "JOB",
"id": 123,
"name": "abc",
// ...
}
],
}
=== Стало
{
"meta": {
// ...
},
"items": [
{
"id": "123",
"data": {
"id": "123",
"name": "abc",
// ...
},
}
],
}
- Изменена схема ответа
GET /:entity/lineage. (#164)
Список всех узлов (например, list[Node]) разделен по типу узла и преобразован в словарь (например, dict[str, Dataset], dict[str, Job]).
Список всех отношений (например, list[Relation]) разделен по типу отношения (например, list[DatasetSymlink], list[Input]).
Примеры ответов lineage
=== Было
{
"relations": [
{
"kind": "PARENT",
"from": {"kind": "JOB", "id": 123},
"to": {"kind": "RUN", "id": "00000000-0000-0000-0000-000000000000"},
},
{
"kind": "SYMLINK",
"from": {"kind": "DATASET", "id": 234},
"to": {"kind": "DATASET", "id": 999},
},
{
"kind": "INPUT",
"from": {"kind": "DATASET", "id": 234},
"to": {"kind": "OPERATION", "id": "11111111-1111-1111-1111-111111111111"},
},
{
"kind": "OUTPUT",
"from": {"kind": "OPERATION", "id": "11111111-1111-1111-1111-111111111111"},
"to": {"kind": "DATASET", "id": 234},
},
],
"nodes": [
{"kind": "DATASET", "id": 123, "name": "abc"},
{"kind": "JOB", "id": 234, "name": "cde"},
{
"kind": "RUN",
"id": "00000000-0000-0000-0000-000000000000",
"external_id": "def",
},
{
"kind": "OPERATION",
"id": "11111111-1111-1111-1111-111111111111",
"name": "efg",
},
],
}
=== Стало
{
"relations": {
"parents": [
{
"from": {"kind": "JOB", "id": "123"},
"to": {"kind": "RUN", "id": "00000000-0000-0000-0000-000000000000"},
},
],
"symlinks": [
{
"from": {"kind": "DATASET", "id": "234"},
"to": {"kind": "DATASET", "id": "999"},
},
],
"inputs": [
{
"from": {"kind": "DATASET", "id": "234"},
"to": {
"kind": "OPERATION",
"id": "11111111-1111-1111-1111-111111111111",
},
},
],
"outputs": [
{
"from": {
"kind": "OPERATION",
"id": "11111111-1111-1111-1111-111111111111",
},
"to": {"kind": "DATASET", "id": "234"},
},
],
},
"nodes": {
"datasets": {
"123": {"id": "123", "name": "abc"},
},
"jobs": {
"234": {"id": "234", "name": "cde"},
},
"runs": {
"00000000-0000-0000-0000-000000000000": {
"id": "00000000-0000-0000-0000-000000000000",
"external_id": "def",
},
},
"operations": {
"11111111-1111-1111-1111-111111111111": {
"id": "11111111-1111-1111-1111-111111111111",
"name": "efg",
},
},
},
}
Это позволяет заменить фильтры на стороне UI со сложностью O(n), например:
// O(n)
relations.filter((relation) => relation.kind == "INPUT" && relation.from.kind == "DATASET" && relation.from.id == dataset_id)
// снова O(n)
nodes.filter((node) => node.kind == "DATASET" && node.id == dataset_id)
на гораздо более эффективные:
// O(n) с гораздо меньшим n
relations.inputs.filter((relation) => relation.from.kind == "DATASET" && relation.from.id == dataset_id)
// O(1)
nodes.datasets[dataset_id]
Размер выходного JSON не сильно отличается.
Обратите внимание, что идентификаторы наборов данных (dataset), заданий (Job) и локаций (location) во всех ответах были преобразованы из целых чисел в строки, поскольку в JSON ключи объектов должны быть строками.
Также узлы и отношения больше не имеют поля kind.
- Изменен тип значения
DATA_RENTGEN__KAFKA__BOOTSTRAP_SERVERSсо строки (один элементhost:port) на список (["host1:port1", "host2:port2"]). (#183) - Синхронизированы значения
DATA_RENTGEN__KAFKA__SECURITY__TYPEсо значениями клиента Kafka, например,scram-sha256->SCRAM-SHA-256. (#183)
Функциональность
- Потребитель теперь может захватывать и сохранять происхождение колонок OpenLineage. (#155)
Настоятельно рекомендуется обновиться до OpenLineage 1.23 и использовать columnLineage.datasetLineageEnabled=true,
чтобы уменьшить как размер JSON события, так и снизить нагрузку на CPU потребителя DataRentgen.
- Включен суммарный расчет входов и выходов в ответы lineage (#171)
Например, если пользователь запросил происхождение с granularity=OPERATION, включать входы и выходы с детализацией до RUN (сумма всех включенных операций по run_id) и JOB (сумма всех включенных операций по job_id).
Это позволяет показать, что определенная операция составляет некоторый процент от всех операций в рамках этого запуска или задания.
- Добавлено lineage колонок в ответы lineage
GET /:entity/lineage. (#172)
### Еще примеры ответов lineage
=== Было
{
"relations": {
"parents": [
{
"from": {"kind": "JOB", "id": "123"},
"to": {"kind": "RUN", "id": "00000000-0000-0000-0000-000000000000"},
},
],
"symlinks": [
{
"from": {"kind": "DATASET", "id": "234"},
"to": {"kind": "DATASET", "id": "999"},
},
],
"inputs": [
{
"from": {"kind": "DATASET", "id": "234"},
"to": {
"kind": "OPERATION",
"id": "11111111-1111-1111-1111-111111111111",
},
},
],
"outputs": [
{
"from": {
"kind": "OPERATION",
"id": "11111111-1111-1111-1111-111111111111",
},
"to": {"kind": "DATASET", "id": "234"},
},
],
},
"nodes": {
"datasets": {
"123": {"id": "123", "name": "abc"},
},
"jobs": {
"234": {"id": "234", "name": "cde"},
},
"runs": {
"00000000-0000-0000-0000-000000000000": {
"id": "00000000-0000-0000-0000-000000000000",
"external_id": "def",
},
},
"operations": {
"11111111-1111-1111-1111-111111111111": {
"id": "11111111-1111-1111-1111-111111111111",
"name": "efg",
},
},
},
}
=== Стало
{
"relations": {
"parents": [
{
"from": {"kind": "JOB", "id": "123"},
"to": {"kind": "RUN", "id": "00000000-0000-0000-0000-000000000000"},
},
],
"symlinks": [
{
"from": {"kind": "DATASET", "id": "234"},
"to": {"kind": "DATASET", "id": "999"},
},
],
"inputs": [
{
"from": {"kind": "DATASET", "id": "234"},
"to": {
"kind": "OPERATION",
"id": "11111111-1111-1111-1111-111111111111",
},
},
],
"outputs": [
{
"from": {
"kind": "OPERATION",
"id": "11111111-1111-1111-1111-111111111111",
},
"to": {"kind": "DATASET", "id": "234"},
},
],
// Здесь |
// v
"direct_column_lineage": [
{
"from": {"kind": "DATASET", "id": "234"},
"to": {"kind": "DATASET", "id": "235"},
"fields": {
"target_column_1": [
{
"field": "direct_source_column_1",
"last_used_at": "2008-09-15T15:53:00+05:00",
"types": [
"TRANSFORMATION_MASKING",
"AGGREGATION",
],
},
{
"field": "direct_source_column_2",
"last_used_at": "2008-09-15T15:53:00+05:00",
"types": [
"AGGREGATION",
],
},
],
"target_column_2": [
{
"field": "direct_source_column_1",
"last_used_at": "2008-09-15T15:53:00+05:00",
"types": [
"TRANSFORMATION_MASKING",
"AGGREGATION",
],
},
]
},
},
],
"indirect_column_lineage": [
{
"from": {"kind": "DATASET", "id": "234"},
"to": {"kind": "DATASET", "id": "235"},
"fields": [
{
"field": "indirect_source_column_1",
"last_used_at": "2008-09-15T15:53:00+05:00",
"types": ["JOIN"],
},
]
},
],
},
"nodes": {
"datasets": {
"123": {"id": "123", "name": "abc"},
},
"jobs": {
"234": {"id": "234", "name": "cde"},
},
"runs": {
"00000000-0000-0000-0000-000000000000": {
"id": "00000000-0000-0000-0000-000000000000",
"external_id": "def",
},
},
"operations": {
"11111111-1111-1111-1111-111111111111": {
"id": "11111111-1111-1111-1111-111111111111",
"name": "efg",
},
},
},
}
- Добавлена поддержка аутентификации Kafka GSSAPI. (#183)
- Разрешено получение
GET /v1/runs?since=...без параметра запросаsearch_query. (#184)
Улучшения
- Исправлен множественный
proxyUrlдля спарк-фасета сmaster="yarn". (#154)
Когда приложение spark отправляет lineage поле proxyUrl может приходить в такой форме:
http://node-mn-0001.msk.mts.ru:8088/proxy/application_1733,http://node-mn-0002.msk.mts.ru:8088/proxy/application_7400
Мы используем только первый (до ,)
- Добавлен парсинг имени набора данных для удаления части, похожей на партицию, из имени. (#175)
=== Было
Два разных набора данных:
Dataset(name="/app/warehouse/somedb.db/sometable/business_dt=2025-01-01/reg_id=99")
Dataset(name="/app/warehouse/somedb.db/sometable/business_dt=2025-02-01/reg_id=99")
=== Стало
Объединение двух партиций в один набор данных:
Dataset(name="/app/warehouse/somedb.db/sometable")
- Изменена логика для схемы выходного/входного набора данных в ответе происхождения. (#185)
Добавлены типы схемы в ответе 'EXACT_MATCH' и 'LATEST_KNOWN'.
'EXACT_MATCH' - когда последний и первый (по порядку created_at по возрастанию) schema_ids одинаковы. 'LATEST_KNOWN' - когда последний и первый не совпадают, в этом случае возвращается последний schema_id.
Примеры набора данных в ответе происхождения
=== Было
{
"relations": {
"direct_column_lineage": [],
"indirect_column_lineage": [],
"inputs": [
{
"from": {
"id": "2697",
"kind": "DATASET"
},
"last_interaction_at": "2025-03-14T15:22:30.572000Z",
"num_bytes": 13166146,
"num_files": 240,
"num_rows": 22793,
"schema": {
"fields": [
{
"description": null,
"fields": [],
"name": "dt",
"type": "timestamp"
},
{
"description": null,
"fields": [],
"name": "customer_id",
"type": "decimal(20,0)"
},
{
"description": null,
"fields": [],
"name": "total_spent",
"type": "float"
}
],
"id": "1418"
},
"to": {
"id": "1260",
"kind": "JOB"
}
},
{
"from": {
"id": "3300",
"kind": "DATASET"
},
"last_interaction_at": "2025-03-17T08:45:58.439000Z",
"num_bytes": 13060345,
"num_files": 112,
"num_rows": 13723,
"schema": null,
"to": {
"id": "0195a347-fa5f-7a72-aa14-bc510fadfd3a",
"kind": "RUN"
}
}
]
}
}
=== Стало
{
"relations": {
"direct_column_lineage": [],
"indirect_column_lineage": [],
"inputs": [
{
"from": {
"id": "2697",
"kind": "DATASET"
},
"last_interaction_at": "2025-03-14T15:22:30.572000Z",
"num_bytes": 13166146,
"num_files": 240,
"num_rows": 22793,
"schema": {
"fields": [
{
"description": null,
"fields": [],
"name": "dt",
"type": "timestamp"
},
{
"description": null,
"fields": [],
"name": "customer_id",
"type": "decimal(20,0)"
},
{
"description": null,
"fields": [],
"name": "total_spent",
"type": "float"
}
],
"id": "1418",
"relevance_type": "EXACT_MATCH" // <--
},
"to": {
"id": "1260",
"kind": "JOB"
}
},
{
"from": {
"id": "3300",
"kind": "DATASET"
},
"last_interaction_at": "2025-03-17T08:45:58.439000Z",
"num_bytes": 13060345,
"num_files": 112,
"num_rows": 13723,
"schema": {
"fields": [
{
"description": null,
"fields": [],
"name": "dt",
"type": "timestamp"
},
{
"description": null,
"fields": [],
"name": "customer_id",
"type": "decimal(20,0)"
},
{
"description": null,
"fields": [],
"name": "total_spent",
"type": "float"
}
],
"id": "1657",
"relevance_type": "LATEST_KNOWN" // <--
},
"to": {
"id": "0195a347-fa5f-7a72-aa14-bc510fadfd3a",
"kind": "RUN"
}
}
]
}
}