Skip to Content
OpenETL
ReferenceResource specs

Pipeline

Every field of a Pipeline document, with its type, required or optional status, default and meaning.

Edit on GitHub

A Pipeline copies streams from a Source to a Destination, in syncs on a schedule or continuously. This page lists every field its document takes. Pipelines as code shows how to write one, plan it and apply it.

The Required column applies within the containing object or list entry. Optional objects may be omitted; when you include one, its required children must be set. Conditional requirements are stated in the table; “one of” means exactly one of the alternatives. Default describes what omission means.

apiVersion: openetl.io/v1alpha1
kind: Pipeline
metadata:
  name: shop-to-warehouse
spec:
  schedule: { cron: "CRON_TZ=Europe/Berlin 0 6 * * *", jitter: 5m }
  source:
    ref: shop-db
    streams:
      - name: orders
        sync: incremental
        cursor: updated_at
      - name: customers
        sync: cdc
        columns:
          balance: { type: string }
  destination:
    ref: warehouse
    namespace: shop
    write: merge
    deletes: hard
  schemaPolicy: allow_columns
  delivery:
    require: effectively_once
    commit: { interval: 2m, maxBytes: 512MB, scope: stream }
  resources: { size: m, pool: batch }
  postSync:
    - dbt: { project: shop-models, command: build, select: "tag:daily" }
  heal: { mode: suggest }

The document

FieldTypeRequiredDefaultMeaning
apiVersionstringYes—openetl.io/v1alpha1
kindstringYes—Pipeline
metadata.namenameYes—The pipeline's name: lowercase letters, digits and hyphens, at most 63 characters, starting and ending with a letter or digit
metadata.workspacenameNoThe workspace openetl apply --workspace names, default unless it saysThe workspace it belongs to
specobjectYes—The fields in the sections below

In spec:

  • A field the spec doesn't have is refused, and so is a value of the wrong type.
  • Enum values are lowercase, as in merge.
  • A duration is a Go duration, such as 90s, 15m or 1h30m.
  • A byte count is an integer number of bytes or a number with a unit, such as 256MB or 64MiB. KB, MB, GB and TB use powers of 1,000. KiB, MiB, GiB and TiB use powers of 1,024.
  • A delivery guarantee is at_least_once, effectively_once or exactly_once. Concepts explains each.
  • A secret reference is { secret: NAME }, naming a secret in the same workspace.
  • Some defaults depend on the source's streams and the destination's connector. openetl plan shows what they come to: each stream's write mode and guarantee, and the job size.

Running

FieldTypeRequiredDefaultMeaning
modescheduled or continuousNoscheduledA scheduled pipeline copies in syncs, which start on its schedule. A continuous one runs one job that keeps reading, and commits as it goes. A continuous pipeline has no schedule, postSync, transform.spark or resources.finishWithin.
scheduleobjectYes, when mode: scheduled—When syncs start: one of every and cron. A new scheduled pipeline starts its first sync when it's applied. A start that comes while a sync is still running is skipped.
schedule.everyduration, positiveOne of every or cronNoneStart a sync this often, such as 1h, counting from midnight UTC on 1 January 1970: 1h starts one on the hour.
schedule.croncron expressionOne of every or cronNoneStart a sync at the times a cron expression gives, with five fields: minute, hour, day of the month, month and day of the week, as in 0 6 * * *. It's in UTC unless it starts with CRON_TZ= and a time zone, as in CRON_TZ=Europe/Berlin 0 6 * * *.
schedule.jitterdurationNoNoneA random delay of up to this much before each start, so that pipelines on the same schedule don't all start at once
dataPlanestringNoThe server's data plane: its --data-plane setting, default unless setThe data plane whose runners run the pipeline's jobs
healobjectNomode: auto_safeWhat OpenETL does about the pipeline's incidents
heal.modeoff, suggest, auto_safe or autoNoauto_safeoff opens incidents and notifies, and takes no action. suggest proposes the actions that hold data back, such as pausing the pipeline, rather than taking them. auto_safe applies every playbook's actions. auto is the same as auto_safe.

Resources

FieldTypeRequiredDefaultMeaning
resourcesobjectNoSized automatically, in the default poolHow much CPU and memory the pipeline's jobs get, and which runners run them
resources.sizejob size, or autoNoautoEach job's size. auto takes it from the connectors' hints, and then from the job's measured peaks; with neither, it's s.
resources.minjob sizeNoNoneThe smallest size auto chooses
resources.maxjob sizeNoxl with auto, and otherwise resources.size itselfThe largest size a job grows to: auto's choice, and a retry after the job runs out of memory
resources.finishWithinduration, positiveNoNoneScheduled only, with an automatic size: run as many lanes as the last sync's bytes need, at each lane's measured rate, to finish within this long.
resources.poolpool nameNodefaultThe pool of runners that runs the jobs: at most 64 letters, digits, dots, hyphens and underscores, starting with a letter or digit

When a job size is an object rather than a preset, both cpu and memory are required; neither has a default.

resources.min can't be larger than resources.size or resources.max, nor resources.size larger than resources.max. A job size is a preset or amounts:

SizeCPUMemory
xs0.5 vCPU1 GiB
s1 vCPU2 GiB
m2 vCPUs4 GiB
l4 vCPUs8 GiB
xl8 vCPUs16 GiB
{ cpu: 3, memory: 6Gi }cpu vCPUs, a positive numbermemory, positive: bytes, or a whole number with Ki, Mi, Gi or Ti

Source and streams

FieldTypeRequiredDefaultMeaning
sourceobjectYes—What to read
source.refnameYes—The Source to read, by its name in the same workspace
source.streamslistNoEvery stream the source offers, each with the defaults belowThe streams to copy. Naming a stream the source doesn't offer is a problem in the plan.
source.streams[].namestringYes—The stream's name, as openetl discover lists it. Each stream appears once.
source.streams[].syncfull_refresh, incremental, cdc or streamNoThe first way the source says it reads the stream; in a continuous pipeline, the first of those that's cdc or streamHow the stream is read. A continuous pipeline's streams are read with cdc or stream.
source.streams[].cursorcolumn nameWhen the read mode needs a cursor and the source supplies noneThe stream's cursor, if the source names oneThe column an incremental read goes by, such as updated_at. An incremental stream without a cursor is a problem in the plan, and so is a stream with a primary key that's read with stream, whose cursor tells one event of a key from the next.
source.streams[].timestampsmicrosecond or nanosecondNomicrosecondThe unit timestamps arrive in. Finer values are truncated. Nanoseconds need a destination that keeps them, and hold only 1677-09-21 to 2262-04-11. A Spark transform's inputs can't keep nanoseconds.
source.streams[].requiredelivery guaranteeNodelivery.requireThe lowest guarantee this stream may have, in place of delivery.require
source.streams[].collapsebooleanNotrue for a stream whose reads may repeat rows (the plan's reason says superset replay) and that requires more than at_least_once; otherwise falseFor a stream without a primary key: rows with the same content become one row. A stream with a primary key ignores it, and the plan warns.
source.streams[].quarantinerow or cellNorowWhat a value that doesn't fit its type holds back: its whole row, or only its cell
source.streams[].heldnull, value or lastNonull, or value when heldValue is setWhat a held cell shows, in the columns that columns doesn't set it for: null; value, the value in heldValue; or last, the value the destination already has, which needs merge.
source.streams[].heldValueany valueYes with held: valueNoneWith held: value, what held cells show; with held: last, what they show in rows the destination doesn't have yet. A value of the column's delivered type. Set alone, it means held: value. held: null takes none.
source.streams[].groupslist of lists of column namesNoNoneColumns held together, by their source names: holding one holds the rest. Each group has two columns or more.
source.streams[].typeChangesrefuse or allowNodestination.typeChangesdestination.typeChanges for this stream alone
source.streams[].columnsmap of column names to objectsNoNoneSettings for single columns, by their source names
source.streams[].columns.NAMEobjectNoNoneOne column's settings. It sets a type, a held or a heldValue, or more than one.
source.streams[].columns.NAME.typestring or float64NoThe decimalFor a column the source declares as a decimal: string delivers the source's text for the value, NaN included; float64 delivers a double, which can lose digits, and keeps NaN and infinities. Setting it on any other column is a problem in the plan.
source.streams[].columns.NAME.heldnull, value or lastNoThe stream's heldWhat this column's held cells show, as the stream's held
source.streams[].columns.NAME.heldValueany valueYes with this column's held: valueThe stream's heldValueAs the stream's heldValue, for this column

The plan checks hold requirements:

  • Cell quarantine and the held, heldValue and groups settings require a primary key. The destination must support patching for the selected write mode. These requirements also apply to column-level settings.
  • held: last additionally requires merge writes and destination support for preserving the last value.
  • Transform inputs cannot set quarantine, held, heldValue, groups, collapse or typeChanges.

Destination

FieldTypeRequiredDefaultMeaning
destinationobjectYes—Where and how to write
destination.refnameYes—The Destination to write to, by its name in the same workspace
destination.namespacestringNoThe destination's defaultThe schema or dataset to write into
destination.tablesmap of stream names to namesNoEach stream's own name, through destination.namingStreams' names in the destination, exactly as written: a table, or what the destination has instead, such as a topic. A stream left out goes by its own name. Two streams can't share one, and it can't be used with a transform, whose outputs name their tables. Changing one on a running pipeline leaves what was delivered in the old table, and the new one starts with what's read next; openetl reset --stream reads it all again.
destination.writeappend, overwrite or mergeNoFor each stream, where the destination has the mode: overwrite for a stream read with full_refresh, and merge for a stream with a primary key read with cdc; append otherwiseHow every stream is written. merge needs streams with a primary key, and overwrite streams read with full_refresh.
destination.deletessoft or hardNosoftHow merge applies a delete. soft keeps the row, with _openetl_deleted true and its other columns as they were; hard removes it. Other write modes don't apply deletes, and the plan warns.
destination.namingnormalized or sourceNonormalizedHow tables and columns are named. normalized names are lowercase, with anything outside a-z, 0-9 and _ replaced; source names are the source's, quoted.
destination.typeChangesrefuse or allowNorefuseWhether the destination changes a column's type itself when the stream's type widens, for every stream that doesn't say. With refuse, the destination refuses, and the column is held, or the stream stops, until a person changes the column. With allow, the destination changes the type in place, which on a database can rewrite the table under a lock.

Schema

FieldTypeRequiredDefaultMeaning
schemaPolicyallow_all, allow_columns, block or ignore_newNoallow_allWhat a change to a stream's schema does, once the destination has the stream: see the table below.
typesobjectNoinfinity: clampHow values map to types where the pipeline has a choice
types.infinityclamp or quarantineNoclampWhat becomes of infinite dates and timestamps: clamp turns them into the type's largest or smallest value, and quarantine quarantines them.
PolicyA new columnA type that widensA type that changes otherwise
allow_allAddedWidenedDelivered to a new column, COLUMN__v2
allow_columnsAddedWidenedHeld, or where the column can't be held, fails the sync with SCHEMA_DRIFT
blockHeld, or fails the syncHeld, or fails the syncHeld, or fails the sync
ignore_newDroppedValues cast to the column's typeValues cast to the column's type

Widening lets a column represent a broader range of values. Numeric widening includes these conversions:

Source typeWider types
int16int32, int64, float32, float64
int32int64, float64
int64, float32float64
Integer or decimalA decimal with sufficient precision and scale

Any type can widen to a string. To prevent integer rounding, a column that has contained values beyond ±2^53 widens to text instead of floating point.

The destination changes an existing column's type only when typeChanges permits it. Otherwise, it refuses the change, and OpenETL holds the column or stops the stream. Under ignore_new, values that do not fit the existing type are quarantined according to quarantine. In a continuous pipeline, an error that would fail a scheduled sync fails the running job.

Delivery

FieldTypeRequiredDefaultMeaning
deliveryobjectNoThe defaults belowHow the pipeline commits, and the guarantee it requires
delivery.requiredelivery guaranteeNoexactly_onceThe lowest guarantee any stream may have. A stream whose guarantee is lower is a problem in the plan.
delivery.commitobjectNoThe defaults belowWhen the pipeline commits what it has read
delivery.commit.intervalduration, positiveNo5 minutes in a scheduled pipeline; in a continuous one, the destination's own interval, or else 10 secondsHow often to commit. Outside the destination's minimum and maximum, if it has them, it's a problem in the plan.
delivery.commit.maxBytesbyte countNo256MB, which grows when commits can't keep upCommit once this much has been read since the last commit. Once set, it doesn't grow.
delivery.commit.scopepipeline or streamNopipeline where the destination can, otherwise streamCommit every stream together (pipeline), or each on its own (stream). A scope the destination can't do is a problem in the plan.
delivery.resumePointTimeoutduration, positive, or noneNo1mHow long a commit waits for a point the source can resume from exactly, before it commits without one. It commits without one only where the stream keeps its guarantee anyway. none waits forever.

Transforms

A transform is your own code between the source and the destination: Spark in a scheduled pipeline, Flink in a continuous one. The source's streams are its inputs, and its outputs are what the destination gets. The Spark and Flink guides cover writing and running one.

FieldTypeRequiredDefaultMeaning
transformobjectNoNoneOne of spark and flink
transform.sparkobjectOne of spark or flink, when transform is setNoneRuns your openetl-spark Transform on each epoch of a sync, on a Spark cluster. Scheduled pipelines only.
transform.spark.clusternameYes—A SparkCluster in the same workspace
transform.spark.codeobjectYes—The code: one of jar and python, and its sha256. The runner checks the file and gives the cluster its own copy.
transform.spark.code.jarURIOne of jar or pythonNoneA jar, at an s3://, s3a:// or https:// URI the runner can read
transform.spark.code.pythonURIOne of jar or pythonNoneA Python file that calls openetl_spark.run with its transform, at an s3://, s3a:// or https:// URI the runner can read
transform.spark.code.sha25664 lowercase hex digitsYes—The file's SHA-256
transform.spark.code.classclass nameYes with jar—The class in the jar that implements openetl-spark's Transform. A Python file takes none.
transform.spark.confmap of stringsNoNoneSpark properties for the application, over the cluster's. Each name starts with spark., and isn't one the runner sets: spark.master, spark.submit.deployMode, spark.app.name, spark.yarn.tags, spark.kubernetes.driver.pod.name, or any that starts with spark.openetl..
transform.spark.outputslistNoEach input stream, under its name and with its settingsThe transform's outputs, which are the destination's streams
transform.spark.outputs[].namestringYes—The output's name. Each output appears once.
transform.spark.outputs[].primaryKeylist of column namesNoThe primary key of the input stream with the output's name, if there is oneThe output's primary key
transform.spark.outputs[].writeappend, overwrite or mergeNodestination.write; without it, overwrite when every input is read with full_refresh, and merge when the input stream with the output's name is read with cdc and the output has a primary key, where the destination has the mode; append otherwiseHow the output is written. overwrite needs every input read with full_refresh, and merge a primary key.
transform.spark.outputs[].requiredelivery guaranteeNoThe require of the input stream with the output's name, then delivery.requireThe lowest guarantee the output may have
transform.flinkobjectOne of spark or flink, when transform is setNoneRuns your Flink job, built with openetl-flink, between a continuous pipeline's source and destination, on a Flink cluster. The job's checkpoints are the pipeline's. Continuous pipelines only.
transform.flink.clusternameYes—A FlinkCluster in the same workspace
transform.flink.codeobjectYes—The job's jar and its sha256. The runner checks the file and gives the cluster its own copy.
transform.flink.code.jarURIYes—A jar, at an s3://, s3a:// or https:// URI the runner can read
transform.flink.code.sha25664 lowercase hex digitsYes—The file's SHA-256
transform.flink.code.classclass nameNoThe class the jar's manifest namesThe job's main class
transform.flink.parallelisminteger, 0 or moreNo0: the cluster's defaultThe job's parallelism
transform.flink.confmap of stringsNoNoneFlink configuration for the job, over the cluster's. Each name has a dot in it, and isn't one the runner sets, listed below.
transform.flink.outputslistNoEach input stream, under its name and with its settingsAs transform.spark.outputs
transform.flink.outputs[].namestringYes—As transform.spark.outputs[].name
transform.flink.outputs[].primaryKeylist of column namesNoAs transform.spark.outputs[].primaryKeyAs transform.spark.outputs[].primaryKey
transform.flink.outputs[].writeappend, overwrite or mergeNoAs transform.spark.outputs[].writeAs transform.spark.outputs[].write
transform.flink.outputs[].requiredelivery guaranteeNoAs transform.spark.outputs[].requireAs transform.spark.outputs[].require

The runner controls these Flink options. transform.flink.conf cannot override them:

  • parallelism.default
  • pipeline.name
  • execution.runtime-mode
  • execution.target
  • jobmanager.execution.failover-strategy
  • kubernetes.cluster-id
  • kubernetes.namespace
  • yarn.application.name
  • yarn.tags

It also rejects names starting with these prefixes:

  • execution.checkpointing.
  • state.checkpoints.
  • state.savepoints.
  • execution.state-recovery.
  • execution.savepoint
  • restart-strategy
  • high-availability
  • kubernetes.operator.
  • openetl.
  • $internal.

A Flink transform's inputs must each replay exactly from a resume point, since its checkpoints restore them; the plan says which don't.

Steps after a sync

FieldTypeRequiredDefaultMeaning
postSynclistNoNoneSteps that run in order after a sync's data is committed. Each has one of dbt and spark. A step that fails fails the sync, and the steps after it don't run. Scheduled pipelines only.
postSync[].dbtobjectOne of dbt or spark per stepNoneRuns a dbt project against the pipeline's destination
postSync[].dbt.projectnameYes—A DbtProject in the same workspace. A sync runs the project's files as they were when it started.
postSync[].dbt.commandbuild, run or testNobuildThe dbt command to run
postSync[].dbt.selectstringNoNonedbt's --select
postSync[].dbt.excludestringNoNonedbt's --exclude
postSync[].dbt.schemastringNoThe schema the pipeline writes toThe target schema
postSync[].sparkobjectOne of dbt or spark per stepNoneRuns your own Spark application on a Spark cluster
postSync[].spark.clusternameYes—A SparkCluster in the same workspace. A sync runs the cluster's settings as they were when it started.
postSync[].spark.codeobjectYes—The application: one of jar and python, and its sha256. The runner checks the file and gives the cluster its own copy.
postSync[].spark.code.jarURIOne of jar or pythonNoneA jar, at an s3://, s3a:// or https:// URI the runner can read
postSync[].spark.code.pythonURIOne of jar or pythonNoneA Python file, at an s3://, s3a:// or https:// URI the runner can read
postSync[].spark.code.sha25664 lowercase hex digitsYes—The file's SHA-256
postSync[].spark.code.classclass nameNoThe class the jar's manifest namesA jar's main class. A Python file takes none.
postSync[].spark.argslist of stringsNoNoneThe application's arguments
postSync[].spark.confmap of stringsNoNoneSpark properties for the application, over the cluster's, named as for transform.spark.conf

Access to the data plane's storage

A transform or step uses the cluster's identity to access pipeline storage by default. That identity can access every pipeline's storage prefix. sparkAccess and flinkAccess provide credentials or a runtime role for the pipeline's applications instead.

FieldTypeRequiredDefaultMeaning
sparkAccessobjectNoThe cluster's own identityHow the pipeline's Spark applications reach its _spark/ prefix of the data plane's store. It sets emrRole, keys, or both, and needs a Spark transform or step.
sparkAccess.emrRoleIAM role ARNOne or both of emrRole and keys, when sparkAccess is setNoneOn EMR: the runtime role each step runs as (ExecutionRoleArn, EMR 6.7 and later). It's all the step's AWS access, to your own data too.
sparkAccess.keysobjectOne or both of emrRole and keys, when sparkAccess is setNoneElsewhere: S3A keys for the data plane's bucket alone
sparkAccess.keys.accessKeyIdstring, or secret referenceYes in keys—The access key's ID
sparkAccess.keys.secretAccessKeysecret referenceYes in keys—The secret access key, as a secret reference, so the key isn't kept in the spec
flinkAccessobjectNoThe cluster's own identityHow the pipeline's Flink job reaches its _flink/ prefix of the data plane's store, where its code, checkpoints and savepoints are. It sets emrRole, keys, or both, and needs a Flink transform.
flinkAccess.emrRoleIAM role ARNOne or both of emrRole and keys, when flinkAccess is setNoneOn EMR: the runtime role each step runs as (ExecutionRoleArn)
flinkAccess.keysobjectOne or both of emrRole and keys, when flinkAccess is setNoneElsewhere: S3 keys for the data plane's bucket alone, for Flink's Hadoop S3 filesystem. A session cluster reads its filesystems' settings when it starts, so it can't take them.
flinkAccess.keys.accessKeyIdstring, or secret referenceYes in keys—The access key's ID
flinkAccess.keys.secretAccessKeysecret referenceYes in keys—The secret access key, as a secret reference

A continuous pipeline

apiVersion: openetl.io/v1alpha1
kind: Pipeline
metadata:
  name: orders-live
spec:
  mode: continuous
  source:
    ref: shop-db
    streams:
      - name: orders
        sync: cdc
  destination: { ref: lake, namespace: shop }
  delivery:
    commit: { interval: 30s }
  transform:
    flink:
      cluster: flink
      code:
        jar: s3://acme-jobs/enrich-orders-1.4.0.jar
        sha256: 9f2c4a8e0b6d1f3a5c7e9b2d4f6a8c0e1b3d5f7a9c2e4b6d8f0a1c3e5b7d9f2a
      parallelism: 4
      outputs:
        - name: orders_enriched
          primaryKey: [order_id]
          write: merge
  flinkAccess:
    keys:
      accessKeyId: { secret: lake-flink-key-id }
      secretAccessKey: { secret: lake-flink-key }

On this page