Configure the OpenLineage job
Every key of the OPENLINEAGE job configuration, with its default: snapshot mode, allowed job namespaces, namespace mappings, System policies, quality, cleanup, limits and dry run
This page describes the JSON configuration of an agent job of type OPENLINEAGE, key by key. You create one job definition per producer, as described in Set up the endpoint
.
The configuration is a JSON object. Unknown keys are ignored without an error. Every key is optional except allowedJobNamespaces, and a missing key takes the default shown. Without outputOptions, the job is a dry run.
Start here
Two configurations cover the first steps. Everything else has a default and is described key by key below.
1. A dry run, to see what the producer’s events would become. It writes nothing:
{
"allowedJobNamespaces": ["github://my-org/ds-quality"]
}
2. Live, once the report of the dry run looks right. Add the BLINDATA connection that writes to Blindata:
{
"allowedJobNamespaces": ["github://my-org/ds-quality"],
"outputOptions": { "dryRun": false, "targetConnectionName": "blindata-prod" }
}
Then decide, with systemPolicies
, which Systems the job may create and clean, and, with snapshotMode
, whether a batch is a complete snapshot. The Recommended setups
give a starting point per kind of producer.
Top-level keys
| Key | Type | Default | Description |
|---|---|---|---|
snapshotMode
|
string | ADDITIVE |
ADDITIVE or BATCH_SNAPSHOT. |
allowedJobNamespaces
|
string[] | none, required | Patterns of the job namespaces this definition accepts. At least one. |
lineageReplacement
|
string | NONE |
NONE or JOB. |
openLineageSystem
|
object | { "name": "OpenLineage" } |
The System that holds job entities and SQL routines, for the job namespaces that no jobSystems rule places elsewhere. |
jobSystems
|
object[] | [] |
Rules that place the job entities and routines of a job namespace in a System of their own. |
processLayer |
boolean | true |
Catalogs each OpenLineage job as an entity of its job System. With false, no job entity is created, and the flows between jobs (orchestration, and job entries of the lineage facet) are not published. |
namespaceMappings
|
object[] | [] |
Maps dataset namespaces to existing Systems. |
systemPolicies
|
object[] | [] |
Per-System rules: creation, naming, documentation, cleanup. |
ignoredNamespaces
|
string[] | ["opensearch://*", "elasticsearch://*"] |
Dataset namespaces that are ignored. |
includeIndirect |
boolean | false |
Also describes indirect transformations (filters, joins, group by) in the description of the flows. They never create flows of their own. |
quality
|
object | see below | Quality checks, results and metrics. |
cleanup
|
object | see below | Removal of deprecated metadata and its safety cap. |
limits
|
object | see below | Limits on the events of a definition. |
queryBucketName |
string | OpenLineage SQL <job definition uuid> |
Name of the query bucket
where the SQL of the jobs is analyzed. It must not start with ol:. |
outputOptions
|
object | dry run | Where the result is written, and whether it is written at all. |
A glob in this page is a pattern where * matches any sequence of characters (also none and /), and everything else matches literally. A pattern matches the whole namespace: s3://* matches every S3 bucket, github://my-org/* every repository of the organization.
snapshotMode
How the definition treats a batch.
| Value | Meaning |
|---|---|
ADDITIVE |
Events add to what Blindata knows. Nothing is deleted because a producer did not repeat it. This is the mode for runtime producers (Airflow, Spark, dbt, Trino, Flink), which only describe what ran. Removal is age-based. |
BATCH_SNAPSHOT |
A batch sent to /lineage/batch fully describes the job namespaces it contains. Receiving a batch starts a run, and what the namespaces owned and the batch no longer describes is deleted. This is the mode for a repository that publishes the complete set of its JobEvents. |
A batch is a snapshot only if every event of it was accepted. See Removal of deprecated metadata .
allowedJobNamespaces
The job namespaces this definition accepts, as globs: ["github://my-org/ds-quality"], ["airflow-prod"], ["spark-*"]. An event whose job namespace matches none is rejected (Job namespace not allowed: <namespace>, non-retriable). A job namespace belongs to one definition: scopes then never overlap between producers, which is what makes the removal safe.
Rules:
- The job namespace is the one of the job, not the one of its datasets. Dataset events carry no job and are not checked.
- A job namespace that contains
|is always rejected, and one longer than 200 characters too. - For a
BATCH_SNAPSHOTdefinition, use one pattern without*: it is the condition for removing dataset entities by cutoff (seecleanupMode).
lineageReplacement
| Value | Meaning |
|---|---|
NONE |
A completed run adds lineage. |
JOB |
A completed run is the authoritative description of its job: the lineage previously published for that job and not described by the run is removed. Set it only for producers whose one run describes all the lineage of the job. A producer with dynamic inputs would lose lineage. |
A JobEvent is always authoritative for its job. See Removal of deprecated metadata
.
openLineageSystem
| Key | Default | Description |
|---|---|---|
name |
OpenLineage |
Name of the System that holds job entities and routines. It is found by name, or created (subtype SERVICE, technology OpenLineage). |
uuid |
none | Uuid of an existing System, used instead of the name. |
It is looked up, or created, only when a job namespace goes to it.
jobSystems
Places the job entities and routines of a job namespace in a System chosen by rules, so that, for example, the jobs of Airflow production and those of each repository are in Systems of their own. The rules are read in order: the first whose match matches the job namespace decides. A namespace that no rule matches goes to openLineageSystem
. All the jobs of a namespace are in the same System, in the schema named after the namespace.
| Key | Default | Description |
|---|---|---|
match |
none, required | Pattern of the job namespaces, with * as wildcard. |
systemUuid |
none | Uuid of an existing System. It is never created. |
systemName |
none | Name of the System. Used when systemUuid is not set. |
nameTemplate |
none | Name of the System computed from the namespace, when neither systemUuid nor systemName is set. Placeholders: {namespace}, {authority} and {lastSegment}. |
creation |
IF_MISSING |
IF_MISSING creates a missing System found by name; NEVER does not. |
subtype, technology |
SERVICE, OpenLineage |
Of a System the job creates. |
shared |
false |
Allows an existing System that does not hold OpenLineage jobs yet, such as a crawled one. |
The placeholders, for two example namespaces:
| Placeholder | github://my-org/ds-quality |
airflow-prod |
|---|---|---|
{namespace} |
github://my-org/ds-quality |
airflow-prod |
{authority} |
my-org |
airflow-prod |
{lastSegment} |
ds-quality |
airflow-prod |
A System that holds OpenLineage jobs carries the additional property openLineageJobSystem, with the uuid of the definition that created it or first used it with shared. A rule may use any System that carries it. An existing System without it is used only with shared set to true; otherwise the jobs of the namespace go to openLineageSystem and the run reports JOB_SYSTEM_NOT_SHARED. A System that is missing and may not be created, or an unknown systemUuid, sends the jobs to openLineageSystem too, with JOB_SYSTEM_NOT_FOUND.
Warning
Prefer Systems dedicated to the jobs. On a crawled System, a crawler that cleans the whole System removes the job entities it does not crawl.
Changing a rule moves the jobs of a namespace: the next run creates the job entities and routines in the new System and removes them from the former one (see Job Systems ). Descriptions and documents that people added to the former entities are not moved.
"jobSystems": [
{ "match": "airflow-prod", "systemName": "Airflow production" },
{ "match": "github://my-org/*", "nameTemplate": "{lastSegment}", "technology": "GitHub" }
]
namespaceMappings
Maps dataset namespaces to existing Systems. The first entry that matches is used, before anything else.
| Key | Description |
|---|---|
namespacePattern |
Glob matched against the dataset namespace. |
systemName |
Name of the System. |
systemUuid |
Uuid of the System. Use one of the two. |
An entry whose System is not found is skipped. A mapped System is not changed (no property is added to it).
To map a namespace once, for every producer, add the additional property openLineageNamespace with the namespace as value to the System in Blindata (for example redshift://my-dwh.eu-west-1:5439 on the Redshift System that your crawler already maintains). The property is tenant-wide: every definition resolves that namespace to that System. Resolution order is in Metadata mapping
.
systemPolicies
Rules per dataset System, matched by namespace. The first policy whose match fits the namespace is used.
| Key | Type | Default | Description |
|---|---|---|---|
match |
glob | none | Matched against the dataset namespace (s3://*, redshift://*). |
creation |
string | NEVER |
NEVER: an unresolved namespace is a gap. IF_MISSING: the System is created when no System resolves the namespace. |
nameTemplate |
string | {bucket} for s3://, s3a:// and s3n://, {authority} otherwise |
Name of a created System. Placeholders: {bucket} and {authority} (the host of the namespace, or the bucket), {namespace} (the whole namespace). Another placeholder is rejected. |
subtype |
string | DATABASE |
Subtype of a created System. |
technology |
string | by scheme | Technology of a created System: Amazon S3 for s3, s3a, s3n; Amazon Redshift for redshift; PostgreSQL for postgres and postgresql; Amazon Athena for awsathena; AWS Glue for arn:aws:glue; the scheme itself for any other scheme. |
entityCreation |
boolean | true for Systems created by OpenLineage, false otherwise |
Whether the job creates entities of the System. With false, only entities that already exist (crawled) are used. Lineage is published on crawled tables. |
replaceUserDocumentation |
boolean | false |
Allows the job to overwrite documentation that a user edited. See Metadata mapping . |
cleanupMode |
string | NONE |
NONE, CUTOFF or DAYS: see below. |
cleanupDays |
integer | 30 |
Days without update after which DAYS removes entities and fields. |
honourLifecycleDrop |
boolean | false |
When true, a lifecycleStateChange facet with DROP deletes the entity of that System. Otherwise it is only reported. |
Creation needs care. A System created for a store that a crawler also covers gives two Systems for the same data. Use creation: NEVER for warehouse schemes that you crawl (redshift://*, postgres://*) and IF_MISSING for object stores. One System per S3 bucket: the namespace s3://my-bucket is one System, named after the bucket. When a System with the planned name already exists, it is used and stamped with the namespace. A created System is stamped with openLineageNamespace and with openLineageOwner, the uuid of the definition that created it.
cleanupMode says what the job may delete from the System’s entities and fields:
| Mode | Meaning | When to use it |
|---|---|---|
NONE |
The job never cleans this System. | Default. Mapped and crawled Systems. |
DAYS |
Entities and fields not updated for cleanupDays days are deleted. Age-based, also for snapshots. |
Systems shared with other producers, or of unknown ownership. |
CUTOFF |
The System is owned by this definition: after a snapshot, entities and fields that the run did not describe again are deleted in the same run. | A bucket that only this repository writes. |
CUTOFF is kept only when both hold, otherwise it is changed to DAYS and the run reports SHARED_SYSTEM_DOWNGRADED:
- The System carries
openLineageOwnerequal to the uuid of this definition. A System with no owner, or owned by another definition, is not owned. Only a System created by the definition has the stamp. - The definition’s
allowedJobNamespacesis exactly one pattern without*.
Why: an entity that only another repository’s events refer to was not touched by this run, and a cutoff cleanup would delete it. See Removal of deprecated metadata .
ignoredNamespaces
Globs of dataset namespaces that are not catalogued. An event dataset that matches is skipped and counted (IGNORED_NAMESPACE). The default skips OpenSearch and Elasticsearch, since a search engine does not generate data lineage. Setting the key replaces the default list.
quality
| Key | Type | Default | Description |
|---|---|---|---|
enabled |
boolean | true |
Quality checks and results from assertions and tests. false skips quality entirely, metrics included. |
metricsEnabled |
boolean | true |
Profiling metric records from quality metrics and statistics. |
suiteCode |
string | openlineage:<job definition uuid> |
Code of the quality suite that holds the checks. |
See OpenLineage to Blindata quality .
cleanup
| Key | Type | Default | Description |
|---|---|---|---|
enabled |
boolean | true |
false turns every kind of cleanup off. |
staleJobDays |
integer | 30 |
For ADDITIVE definitions: jobs, routines and flows not updated for this many days are deleted. |
maxRemovalsPerRun |
integer | 500 |
The most objects a run may delete, summed over all its cleanup calls. A cleanup whose dry-run counts exceed it is not executed: the run ends with a warning and the counts. 0 forbids any removal. Must not be negative. |
batchSize |
integer | 500 |
Size of the batches of one cleanup call. |
limits
| Key | Type | Default | Description |
|---|---|---|---|
maxEventsPerRun |
integer | 50000 |
Events read by one run. The rest waits for the next run. A run does not cut a batch: it reads past the limit to finish the last batch it started. |
maxPendingEvents |
integer | 100000 |
Unprocessed events a definition accepts. Over it, the receiver answers Inbox full. |
maxEventAgeDays |
integer | 7 |
Unprocessed events older than this are deleted, with a warning. |
lockStaleMinutes |
integer | 120 |
A run holds a lock on its definition and refreshes it before each round. A lock not refreshed for this long is taken over by the next run; the run that lost it stops before writing anything more. |
maxDrainsPerRun |
integer | 3 |
How many times one run reads the inbox: a run that is skipped because of the lock asks the running one to read again before it ends. At least 1. |
outputOptions
| Key | Type | Default | Description |
|---|---|---|---|
dryRun |
boolean | true |
true computes and reports what the run would write, creates and delete, and writes nothing to Blindata. |
targetConnectionName |
string | none | Name of the Blindata connection (type BLINDATA) on the agent. Every write of the run uses it, cleanup included, whatever producer sent the events. Required when dryRun is false. |
defaultHidden |
string | none | Passed as the isHidden parameter of the Blindata uploads. Leave it empty unless you need it. |
The user of the connection needs the permissions listed in Who writes to Blindata
, SYSTEMS_ADMIN included for the cleanup. The producers’ own users need only AGENTS_EDITOR to send events.
A dry run still discards its events after processing. Events are never replayed, so a dry run is a way to validate what a producer sends, not to preview and then publish the same events. To go live, change dryRun and send the events again. A dry run sends nothing to Blindata, cleanup included: the cleanup calls are planned and reported, with zero counts, so a dry run does not tell you what a real run would remove. Because it reads nothing from Blindata, it treats every System and entity as new, so its counts of what it creates are an upper bound.
Full example
A BATCH_SNAPSHOT definition for one repository: warehouse tables are crawled and only mapped, S3 buckets are created and cleaned.
{
"snapshotMode": "BATCH_SNAPSHOT",
"allowedJobNamespaces": ["github://my-org/ds-quality"],
"lineageReplacement": "NONE",
"openLineageSystem": { "name": "OpenLineage" },
"processLayer": true,
"namespaceMappings": [],
"systemPolicies": [
{ "match": "redshift://*", "creation": "NEVER", "entityCreation": false, "cleanupMode": "NONE" },
{
"match": "s3://*",
"creation": "IF_MISSING",
"subtype": "DATABASE",
"technology": "Amazon S3",
"entityCreation": true,
"replaceUserDocumentation": false,
"cleanupMode": "CUTOFF"
}
],
"ignoredNamespaces": ["opensearch://*", "elasticsearch://*"],
"includeIndirect": false,
"quality": { "enabled": false, "metricsEnabled": false },
"cleanup": { "enabled": true, "staleJobDays": 30, "maxRemovalsPerRun": 100, "batchSize": 500 },
"limits": { "maxEventsPerRun": 50000, "maxPendingEvents": 100000, "maxEventAgeDays": 7, "lockStaleMinutes": 120, "maxDrainsPerRun": 3 },
"outputOptions": { "targetConnectionName": "blindata-prod", "dryRun": false }
}
With these outputOptions, every write, cleanup included, is made by the user of the connection blindata-prod, which needs the write permissions and SYSTEMS_ADMIN.
Here the Redshift System must carry the property openLineageNamespace with the namespace used in the events (for example redshift://my-dwh.eu-west-1:5439), because creation is NEVER: otherwise the datasets are reported as UNMAPPED_NAMESPACE.
Recommended setups
Each row has a step-by-step guide in How-to guides .
| Producer | Suggested configuration |
|---|---|
| Repository of design-time events, published on merge | BATCH_SNAPSHOT, one allowedJobNamespaces entry without *, quality.enabled false (events carry no results), cleanupMode CUTOFF for the buckets only this repository writes and DAYS for shared ones, a low maxRemovalsPerRun. |
| Pull request validation of the same repository | A second definition with the same configuration and dryRun true. Several repositories can share it. |
| Runtime producer (Airflow, Spark, dbt, Trino, Flink) | ADDITIVE, a cron schedule (every few minutes to every hour), entityCreation false for crawled Systems, lineageReplacement NONE, staleJobDays suited to how often the jobs run. |
| Runtime producer whose run describes the whole job | As above with lineageReplacement JOB. |
| Catalog first | Add the property openLineageNamespace to the crawled Systems, keep creation NEVER, and read the gaps UNMAPPED_NAMESPACE in the report. |
Validation errors
The job validates the configuration before doing anything. Messages start with Configuration cannot be parsed:.
| Message | Cause |
|---|---|
OpenLineage jobs run only from a job definition |
The job was started without a job definition. Run it from a schedule. |
Missing Job configs |
The configuration is empty. |
Configuration cannot be parsed |
The configuration is not valid JSON of the right shape. |
allowedJobNamespaces must not be empty |
No allowed job namespace. |
outputOptions.targetConnectionName is required when dryRun is false |
A live job without a Blindata connection. |
target connection not found |
targetConnectionName names no connection of the agent. |
cleanup.maxRemovalsPerRun must be >= 0 |
A negative cap. |
limits.maxDrainsPerRun must be >= 1 |
A run must read the inbox at least once. |
queryBucketName must not start with ol: |
The prefix is reserved for scopes. |
<key> must not contain a blank pattern |
A pattern of allowedJobNamespaces, ignoredNamespaces, namespaceMappings or systemPolicies is empty. |
systemPolicies.nameTemplate has an unknown placeholder {x}; use {bucket}, {authority} or {namespace} |
A wrong placeholder. |
If a run starts while another run of the same definition holds the lock, it logs OpenLineage run skipped: another run holds the lock; re-run requested (information, not a warning) and the running one reads the inbox again before it ends. If that run reaches maxDrainsPerRun, it logs OpenLineage run read the inbox <n> times; <m> events wait for the next run.
If a run finds that another run took its lock over (it was slower than lockStaleMinutes), it stops with OpenLineage run stopped: the inbox lock was taken over: it writes and deletes nothing more, and its events stay for the run that holds the lock.
Read the run report
Every run returns a report in the job history of the agent:
| Key | Content |
|---|---|
events |
read, processed and expired events, and drains: how many times the run read the inbox. |
runs |
merged, complete, failed, aborted and pending runs. |
senders |
Who sent the events the run read, in order of first event: uuid (the Blindata user uuid), name (the user name, or the uuid for a Bearer-token caller) and events. Identity only, never a credential. |
catalogue |
systemsResolved, systemsCreated, entities, fields, documents, documentsKeptUserEdited, jobEntities, routines, and jobSystems: each System that holds jobs, with its namespaces, whether it was created and whether it is the fallback (openLineageSystem). |
lineage |
tableFlows, columnFlows, orchestrationFlows, routinesAnalyzed. |
quality |
checks, results, metricRecords, disabledChecks. |
cleanup |
mode (NONE, CUTOFF or DAYS), cutoff (the run start taken from the Blindata API clock), skippedReason, calls with the dry-run counts and the real counts per object type, and removedJobs. |
gaps |
Problems by code, with their number: see OpenLineage to Blindata lineage . |
unknownFacets |
Facets that the job does not know, by key, and how many times each was ignored. |
dryRun |
true when nothing was written. |
A run that logs a problem that changes the outcome (a gap, a skipped cleanup) ends in WARNING.