Docs
Using Orca
Orca turns a Mojo file of steps into a pipeline binary with a command line. This page covers orca-mojo 0.1.0: installing it, declaring steps and pipelines, their partitions and schedules, running them by hand or from the scheduler, on this machine, on Kubernetes or on EC2, the web UI, recording and resuming runs in Postgres, reading and writing Iceberg tables from a step, lineage across pipelines, and sensors and rebuilds. The plan is in the repository's DESIGN.md.
Build it
Orca is the mojoshelf tin orca-mojo. With pixi and mojoshelf's
pixi shelf installed, a new project adds it, and the tins it needs come
with it:
pixi init -c https://conda.modular.com/max-nightly -c conda-forge lake && cd lake
sed -i.bak 's/^\[workspace\]/[workspace]\npreview = ["pixi-build"]/' pixi.toml # tins build with pixi-build
pixi add "mojo-compiler==1.2.0"
pixi shelf add orca-mojo
A pipeline is one Mojo file whose main hands it to
orca_main (Steps and
Pipelines say what goes in it). Build it, then run it in
memory to try it:
pixi run mojo build lake.mojo -o build/lake
./build/lake run --partitions 2024-01..2024-03
The orca command, which registers pipelines, schedules runs and serves
the UI, is in the package too, as orca.command. A project's own
orca is two lines, in any file not named orca.mojo
(that name would be the module orca itself):
# bin/scheduler.mojo
from orca.command import orca_command
def main() raises:
orca_command() pixi run mojo build bin/scheduler.mojo -o build/orca
./build/orca locate build/lake --root . # where each step's code is, for the UI's links
ORCA_DATABASE_URL=postgresql:///orca ./build/orca register ./build/lake
ORCA_DATABASE_URL=postgresql:///orca ./build/orca ui # http://127.0.0.1:8270 To see the UI before running anything real, the repository's playground starts it on this machine with a throwaway Postgres, the example pipelines and their runs, two of them on a stand-in EC2:
git clone https://github.com/magmalake/orca.mojo && cd orca.mojo
git clone https://github.com/magmalake/dista.mojo ../dista.mojo
git clone https://github.com/magmalake/k8s.mojo ../k8s.mojo
pixi run playground # then http://127.0.0.1:8270, log in as demo / demo-password
Building from source works the same way: the repository compiles Dista and k8s.mojo
from those checkouts beside it, pixi run test runs the tests, and
pixi run orca builds the scheduler to build/orca. It targets
Mojo 1.2.0, with a nightly environment alongside. Its state goes through postgres-mojo,
which loads libpq; the tests start a throwaway Postgres server of their own.
orca.tables, the Iceberg part, has its tests in the data
environment (pixi run -e data test-tables).
Steps
A step is a top-level def that takes a Ctx and one input
and returns one output. It's declared once, with its name, its input and output
types, the function, its kind, and the step it follows:
from dista import Seq
from orca import Ctx, Gather, Mapped, Month, Pipeline, Step, orca_main
@fieldwise_init
struct Measured(Copyable, Movable):
var month: Month
var days: Int
def days(mut ctx: Ctx, m: Month) raises -> Measured: ...
def measure(mut ctx: Ctx, m: Measured) raises -> Measured: ...
def total(mut ctx: Ctx, ms: Seq[Measured]) raises -> Int: ...
comptime Days = Step["days", Month, Measured, days, Mapped]
comptime Measure = Step["measure", Measured, Measured, measure, Mapped, after=Days]
comptime Total = Step["total", Seq[Measured], Int, total, Gather, after=Measure]
A run is over a list of partition keys, months or days. A Mapped step
runs once per key; a Gather step runs once per run and takes every
key's output as a Seq. retries=2 in the declaration
retries a failing step twice, and ctx.attempt says which attempt this
is. ctx.log writes a line tagged with the step and partition, to standard
output and to the attempt's log file.
Step outputs should be small values, such as paths, snapshot IDs and counts. They're stored in Postgres through Dista's codec, so a resumed run hands a step exactly the input its first attempt had. The data itself goes in tables.
Pipelines
A pipeline is a name and its steps, and the binary's main hands it to orca_main:
comptime Toy = Pipeline["toy", Days, Measure, Total]
def main() raises:
orca_main[Toy]() Step and Pipeline check their edges with
comptime asserts, so a pipeline that breaks one of these rules doesn't
compile, and the compiler says which:
- A first mapped step takes the partition key, Month or Day; a first gather step takes a Seq of it.
- A mapped step takes the output of the mapped step it follows, and can't follow a gather step.
- A gather step after a mapped step takes a Seq of its output; after a gather step, its output.
- Steps are listed in run order, a step after the step it follows, with unique names.
- A step's after is the pipeline's own step of that name, compared by function.
The repository's tests/compile_fail/ holds one pipeline per rule, each
checked to fail with its message. A pipeline's registry hash, over its steps'
functions, kinds and edges, identifies the code a run ran.
Partitions and schedules
A pipeline says which partitions exist, and optionally when they're due, as parameters next to its steps:
comptime Toy = Pipeline[
"toy", Days, Measure, Total,
partitions=Monthly["2024-01"], # every month from January 2024
schedule=Cron["0 3 2 * *"], # 03:00 UTC on the 2nd
fills=Previous, # ... for the month before
max_runs=2, # at most two runs at once
run_retries=3, # a failed run is started again 3 times
] Monthly[start] and Daily[start] are the partition sets; the
first step's input must be their key, Month or Day, which
the compiler checks. Cron takes the five usual fields, with lists,
ranges, steps, names and the @daily-style shorthands, and a time zone:
Cron["30 2 * * *", "America/New_York"]. Times are matched on that zone's
wall clock. A time a spring-forward skips fires at the end of the gap, and a time a
fall-back repeats fires once, as Vixie cron and systemd timers do. An expression that
can never match, such as 0 0 30 2 *, doesn't parse. A fire fills the
partition before the one it falls in (fills=Previous) or the one it falls in
(Current), and catchup=False makes only the latest of the
fires missed while the scheduler was down. All of it is in describe.
Partitions by name. Data that isn't divided by date, such as 20,000 recordings named by ID, is partitioned by the values of a column in a dataset:
from orca import Name
from orca.keyed import Keyed
comptime Label = Step["label", Name, Int, label, Mapped]
comptime Labels = Pipeline[
"labels", Label, Score,
partitions=Keyed["av2.logs", "log_id"], # the keys are log_id's values
keys_per_run=100, # a run covers 100 keys
sensor=True, # new keys start runs
]
The first step takes a Name: 1 to 128 bytes of letters, digits and
._:-. The keys are the distinct values of the column in the dataset's
current snapshot, which <binary> keys prints. A keyed pipeline has
no schedule= or fills=, which the compiler checks; it runs
by hand, by backfill, or as a sensor. --partitions takes names separated
by commas, @all for every current key, or @file:<path>
for a list, one per line; a range or a name that isn't a key is refused. A key is
done once every mapped step has succeeded for it with the pipeline's current code,
so a backfill queues runs of keys_per_run keys for the ones that aren't,
and with sensor=True a new snapshot of the keys dataset starts runs for
its new keys only.
The command line
Every pipeline binary gets the same commands:
toy describe the DAG, partitions and schedule as JSON
toy run --partitions 2024-01..2024-03 starts a run and runs it
toy resume <run> [--allow-new-code] continues a run at what's unfinished
toy status [<run>] the latest runs, or one run's steps
toy fires [<count>] [--after <time>] the next fires and the partitions they fill
toy sense the current snapshot of each dataset it reads
labels keys a keyed pipeline's keys snapshot, then its keys --partitions takes keys of the pipeline's kind, months
(2024-01) or days (2024-01-31), and ranges of them,
separated by commas, or a keyed pipeline's names.
run and resume print one line per step and partition, then
the run's status. Without ORCA_DATABASE_URL, run runs in
memory and records nothing, and resume and status aren't
available. The exit code says how it went:
| Exit | When |
|---|---|
0 | the run succeeded |
1 | a step didn't, or the run was cancelled |
2 | a usage error |
3 | anything else: the database, a run that can't be resumed |
The scheduler
orca is a separate binary that links no pipeline. It knows a pipeline by
the describe it recorded and starts its runs as
<binary> resume <run>, so the run records its own progress
exactly as a run started by hand does. It needs ORCA_DATABASE_URL.
| Command | What it does |
|---|---|
orca locate <binary>... [--root <dir>] | finds each step's function, each Dista task and the pipeline in the source, and writes <binary>.source.json for the UI's links |
orca register [options] <binary>... | runs the binary's describe and records it, its path, its schedule and its limits, as a new version if its code or commit changed; --image, --head-type, --worker-type, --workers and --spot say how it runs on Kubernetes or EC2 |
orca backfill <pipeline> KEYS | queues one run per partition that hasn't succeeded and isn't queued or running |
orca tick | one pass: fires what's due, starts queued runs and retries up to max_runs, then exits |
orca scheduler | a tick every second, until it's stopped |
orca lineage <dataset> [<snapshot>] | what a snapshot (the latest, without one) was made from, and what was made from it |
orca rebuild <dataset> KEYS | reruns every pipeline downstream of the dataset over the keys, in dependency order |
orca ui | the web UI |
orca user add|remove <name>, orca user list | the UI's users; add reads the password from standard input |
./build/orca register ./build/taxi
./build/orca backfill taxi 2011-01..2024-12
./build/orca scheduler
Each fire is a row with a unique (pipeline, time), inserted with its queued run in one
transaction, so two schedulers ticking at once, or one killed mid-tick, never fire
twice. Fires missed while it was down are made on its next tick, once each. A queued
run starts only while fewer than max_runs of the pipeline's runs are live,
counted under a lock on the pipeline's row. A scheduled, backfilled or sensed run that fails
is started again after ORCA_RETRY_SECONDS, doubled with each start, plus
up to a tenth as jitter, up to run_retries times; one whose process died
is started again as soon as its heartbeat is stale. The scheduler holds nothing in
memory, so it can be stopped and restarted at any time. Registering a new build moves
the pipeline's queued runs to the new code.
On Kubernetes and EC2
By default the scheduler starts a run as a process on its own machine, and the run
forks its Dista workers there. ORCA_RUNNER starts each run on a
cluster of its own instead, from the image the pipeline was registered with:
orca register --image 123456789012.dkr.ecr.us-east-2.amazonaws.com/orca:data \
--head-type c7g.large --worker-type c7g.2xlarge --workers 3 ./build/taxi
ORCA_RUNNER=ec2 orca scheduler ORCA_RUNNER=ec2 starts a head instance, on demand,
running the Dista head and the driver (<binary> resume <run>),
and --workers worker instances pointed at it; --spot makes
the workers Spot instances, where the account allows them. Each instance is tagged
with its run, told to terminate when it shuts down, and recorded with its type, when
it started and ended, and why it ended (it shut itself down, it was terminated, or a
Spot interruption). ORCA_RUNNER=kubernetes creates a
driver Job, a head Pod and Service and the worker Pods in
ORCA_RUNS_NAMESPACE, all labelled with the run and owned by the Job.
A pipeline without Dista tasks gets only its driver.
Both clean up in layers. A run's instances or pods end when it does; every tick the
scheduler removes those of runs that aren't running any more and those older than
ORCA_RUN_DEADLINE, so nothing a killed scheduler left behind keeps
running. A run whose head goes is started again on new instances. Before making a
cluster, the scheduler adds up the vCPUs of every live run's pods or instances, and
a run that would take them past ORCA_VCPU_BUDGET waits.
The UI
orca ui serves pipelines and runs as web pages, from the same
orca binary, on 127.0.0.1:8270 unless
ORCA_UI_ADDR says otherwise. It holds no state: it reads the tables the
scheduler and the runs write, connecting to Postgres per request, so it can run beside
the scheduler or anywhere else that reaches the database. Nothing is fetched from
elsewhere, so it works offline.
ORCA_DATABASE_URL=postgresql:///orca ./build/orca ui
curl -H 'Accept: application/json' http://127.0.0.1:8270/runs/7 | Path | What it shows |
|---|---|
/ | pipelines, with their schedules, next runs and last runs, and runs per day over the last 30 days |
/pipelines/<name> | a pipeline's DAG, partitions, schedule, next runs, versions and runs, its code, and the forms to start a run and backfill a range; a keyed one's keys, done, live and failed |
/pipelines/<name>/keys/<key> | one key of a keyed pipeline: its steps in every run it was in |
/runs | runs, newest first; ?pipeline=, ?status=, ?trigger= |
/runs/<id> | a run's DAG in its steps' colours, its steps and partitions, attempts, errors, reads and commits, its source and code, its timeline and instances, and resume, rerun from a step and cancel |
/runs/<id>/attempts/<attempt> | an attempt's log, its last MiB, with its error |
/runs/<id>/timeline.json | the run's Dista timeline, for Perfetto |
/runs/<id>/driver.log | the run's driver output, as the scheduler kept it |
/runs/<id>/cluster/<role>-<instance>.log | a head's or worker's output, from an EC2 run |
/datasets | every dataset with its writers and readers, and the graph of pipelines and datasets |
/datasets/<name> | a dataset's place in that graph, its snapshots, its rebuilds and the form to start one |
/datasets/<name>/snapshots/<id> | a snapshot's lineage, back and forward, as a graph and as tables |
/rebuilds/<id> | a rebuild's pipelines by level, with their runs |
/login, /logout | a session for the actions, as a cookie |
Every path also answers JSON, to Accept: application/json or
?format=json, built from the same rows, so a script and a person see the
same thing. While a run or an attempt is going on, its page refreshes its DAG, steps
or log every two seconds, and stops once it has ended.
| Action | What it does |
|---|---|
| Start | a queued run over the partitions entered, trigger ui |
| Backfill | what orca backfill does: a queued run for each partition without one |
| Resume | a failed, cancelled or stalled run queued again, to resume at what hasn't succeeded; a run whose code has changed asks for confirmation first |
| Rerun from a step | a new run, trigger rerun, with the steps before the chosen one copied from the old run, so only it and what follows run |
| Cancel | a queued or failed run cancelled at once; a running one stops before its next attempt, and the attempt already going finishes |
| Rebuild | on a dataset's page: what orca rebuild does, over the partitions entered |
The UI never starts a pipeline binary. An action writes rows in Postgres, and the
scheduler starts the queued runs on its next tick, within max_runs, so
the UI does nothing without a scheduler running. It binds to localhost unless told
otherwise, and refuses a POST whose Origin, or
Referer without one, isn't its own, so another site's page can't post to
it through a browser.
Login. Anyone who can reach the UI can read it. Its actions need a
login once there's a user, or with ORCA_UI_LOGIN=required before there
is one:
orca user add alice # reads the password from standard input
orca user list
orca user remove alice # and ends their sessions
Passwords are kept as bcrypt hashes made by Postgres. A login is a session cookie,
HttpOnly and SameSite=Strict, and Secure with
ORCA_UI_SECURE_COOKIES=1 behind HTTPS. Every action is recorded with
its user and shown on its run's page, and so is every failed login: after 5 for one
name, or 20 from one address, in 15 minutes, /login answers 429 without
checking the password. Behind a proxy, ORCA_UI_TRUST_PROXY=1 takes the
address from X-Forwarded-For.
On a run's page, a run with Dista tasks draws its timeline: one lane
per worker, each task a bar coloured by its function. With
ORCA_UI_URL set to the UI's public address, an Open in Perfetto link
opens the full trace there. A run on EC2 lists its instances, with the number of
workers over the run, why each instance ended, and each one's log. The step names,
the pipeline and its Dista tasks link to their code (below),
in a new tab, and the headings link to this page's sections
(ORCA_DOCS_URL changes where).
Versions and code links
A pipeline binary can carry the repository and commit it was built from, which each run records and the UI links to:
mojo build -D ORCA_SOURCE=$(git remote get-url origin) \
-D ORCA_COMMIT=$(git rev-parse HEAD) \
-D ORCA_DIRTY=$(git diff --quiet HEAD && echo 0 || echo 1) \
lake.mojo -o build/lake
orca locate build/lake --root . # writes build/lake.source.json
orca register build/lake describe reports the source; the registry hash doesn't change with it.
orca register and every run and resume record a version: the registry
hash, the source, the commit, whether the tree had changes, and the image. A
pipeline's page lists its versions with their runs, and a run's page links the
commit it ran, at <repo>/tree/<commit>.
orca locate finds, in the source tree, the line of each step's
function, each Dista task and the pipeline's own declaration, and writes them beside
the binary, where orca register reads them; the UI then links each to
its line at the commit. A binary built without these just has no links.
Runs in Postgres
With ORCA_DATABASE_URL set, the binary creates and migrates an
orca schema at start-up and records every run, every step of every
partition, and every attempt, with the inputs and outputs of each. Each change of
state is one transaction.
ORCA_DATABASE_URL=postgresql:///orca ./build/toy run --partitions 2024-01..2024-03
./build/toy status # the latest runs
./build/toy status 7 # run 7's steps and attempts
./build/toy resume 7 resume reruns every step that hasn't succeeded, in order, with its
recorded input; steps that succeeded are read back, not run again. A run started by a
binary with a different registry hash is refused unless
--allow-new-code is passed.
One process owns a run at a time. It holds the run with a heartbeat, written from a
thread with its own connection, and resume refuses a run whose heartbeat
is younger than ORCA_STALE_SECONDS. A run whose process was killed goes
stale and can be resumed; a run that ended can be resumed at once. Each attempt also
checks that its process still owns the run, so a process that lost its run stops at
its next step rather than racing the new owner. tests/kill.sh kills a
run at a random step with kill -9, resumes it, and checks every step
ran to success exactly once.
Postgres must keep synchronous_commit on: with it off, an immediate
restart of the server can lose commits it already acknowledged. The chaos test from
PR #6 found this when a run's row
vanished.
Iceberg tables
A step reads and writes Iceberg tables through orca.tables, which is
what makes a rerun safe:
from orca.tables import append, scan
def load(mut ctx: Ctx, raw: RawFile) raises -> Loaded:
var src = Table.load_local(raw.table)
var batches = scan(ctx, src).to_batches()
var dst = Table.load_local(OUT)
var id = append(ctx, dst, transform(batches))
return Loaded(raw.month, id) scan reads a table at a pinned snapshot: its current
one on the step's first attempt, which is recorded, and the same one on every retry
and resume, whatever was committed in between. A table that had no snapshot when the
step first read it stays empty for that step.
append writes batches as one append
snapshot, and commit commits data files already
written, added and removed, as a snapshot of any operation, such as
replace for a compaction. Both put orca.* properties in
the snapshot's summary: pipeline, run, step, partition, attempt, commit number, code,
and the snapshots read. Before writing anything they look for a snapshot this step
already committed in this run. If an attempt committed and was killed before Postgres
heard of it, the retry finds that snapshot and returns it, so a month isn't loaded
twice. A table is known by its location.
Reads and commits are recorded in Postgres as they happen, not when the attempt ends. A run in memory has no run number of its own, so it pins reads across its retries but recognises no commits.
Datasets and lineage
A dataset is a table's name, namespace.table, that means the same table in
every pipeline. ORCA_WAREHOUSE is the root of a filesystem catalog:
lake.clean is the table at <warehouse>/lake/clean. A
step declares the datasets it reads and writes, and loads them through its context:
from orca.tables import append, ensure_table, scan, table
def clean(mut ctx: Ctx, m: Month) raises -> Int64:
var src = scan(ctx, table(ctx, "lake.raw")).to_batches()
var dst = ensure_table(ctx, "lake.clean", SCHEMA)
return append(ctx, dst, transform(src))
comptime Clean = Step[
"clean", Month, Int64, clean, Mapped,
reads=["lake.raw"], writes=["lake.clean"],
] scan fails the attempt on a dataset the step didn't declare in
reads=, and append and commit on one it didn't
declare in writes=, naming the step and the dataset, so the graph of
pipelines and datasets is known before anything runs and can't drift from what the
steps do. describe lists each step's datasets, and
orca register refuses a pipeline that would close a cycle across
pipelines. A table outside the warehouse is still read and written by location, and
isn't part of the graph.
Every read and commit is recorded with its dataset, so lineage is two walks over them. Back from a snapshot: the run, step, partition and code that made it, the snapshots that attempt read, what made each of those, and so on, to sources no Orca run made. Forward: everything made from it. Both stop after 20 steps.
$ ./build/orca lineage lake.report
lake.report@3141592653
made from:
0 lake.report@3141592653 by report run 9 report code 0x5e1f... from lake.clean@2718281828
1 lake.clean@2718281828 by clean run 8 clean 2024-02 code 0x9a0c... from lake.raw@1618033988
2 lake.raw@1618033988 by ingest run 6 load 2024-02 code 0x77d2...
made from it:
nothing Sensors and rebuilds
sensor=True on a pipeline runs it when a dataset it reads, and doesn't
write itself, gets a new snapshot:
comptime Report = Pipeline["report", Summarise, partitions=Monthly["2024-01"], sensor=True]
The scheduler finds snapshots in two ways. A snapshot a succeeded run of another
pipeline committed is in Postgres, and is seen on the next tick. One that something
other than Orca committed is seen by polling: every
ORCA_SENSE_SECONDS the scheduler runs <binary> sense,
which reads each dataset's current snapshot from its table's metadata, so the
scheduler itself still reads no table. Each snapshot starts one run, trigger
sensor, however many ticks or schedulers see it, and a run's page names
the snapshots that started it. Registering a sensor records what its datasets hold
then as already seen, so it doesn't rebuild history; a backfill or a rebuild does that.
The partitions a run covers come from the snapshot: the partition of the step that
committed it, or the run's partitions for a gather step. A writer outside Orca can
say which it holds with orca.partitions in its snapshot's summary;
without that, the pipeline runs the partition its schedule would fill now, or the one
today falls in. A day upstream is the month that contains it downstream, and a month
upstream is each of its days.
$ ./build/orca rebuild raw.events 2024-03
rebuild 1
level 0 clean 2024-03
level 1 report 2024-03 A rebuild runs every pipeline downstream of a dataset, sensor or not, over the partitions given. Each pipeline is placed at its longest distance from the dataset, so one that reads two rebuilt datasets runs after both, and the scheduler queues a level once every run before it has succeeded. A run that fails stops the rebuild; resuming it from the UI carries on. The rebuild's own commits don't also start the sensors it reruns, so each pipeline runs once.
Dista inside steps
A step runs in the run's driver process and calls Dista for anything distributed.
The pipeline names the Dista tasks its steps use, and a step gets the run's
runtime from its Ctx:
from dista import ObjectRef, Task
from orca import Tasks
comptime Trips = Task[Day, Int, trips]
comptime FanoutTasks = Tasks[Trips]
def month_trips(mut ctx: Ctx, m: Month) raises -> Int:
ref rt = ctx.runtime[FanoutTasks]()
var refs = List[ObjectRef[Int]]()
for d in range(1, days_in(m) + 1):
refs.append(rt.remote[Trips](Day(m.copy(), d)))
var total = 0
for r in refs:
total += rt.get(r)
return total
comptime Fanout = Pipeline["fanout", MonthTrips, AllTrips, tasks=FanoutTasks]
Each run gets one Dista runtime, started before anything else so that the head and
workers it forks inherit no database connection or thread. Locally it forks
ORCA_WORKERS workers; with DISTA_HEAD set it joins that
cluster instead, and with DISTA_ROLE set the pipeline binary is itself a
Dista head or worker. Asking for a runtime of tasks the pipeline didn't declare is
refused. The tasks are part of describe and the registry hash.
Logs
With ORCA_LOGS set to a directory, each attempt writes
<pipeline>/<run>/<step>/<partition>/<attempt>.log:
its start, its end, its error, and its ctx.log lines with UTC
timestamps. The path is recorded with the attempt and shown by
status <run>. A run with Dista tasks writes the runtime's timeline
beside them as timeline.json, in Chrome's trace format, which Perfetto
opens. The scheduler keeps a run's driver output as driver.log, and an
EC2 run's head and workers upload theirs to
cluster/<role>-<instance>.log every
ORCA_CLUSTER_LOG_SECONDS while they run, as an instance can go without
notice.
ORCA_LOGS and ORCA_WAREHOUSE can be locations in object
storage, s3://bucket/prefix, configured from the AWS_*
environment or the instance's role. An object can't be appended to, so a log is
written to a file under ORCA_LOG_STAGE and uploaded when it's complete:
an attempt's when the attempt ends, a timeline when the run does. The UI reads them
back from wherever they are.
Environment
| Variable | What it sets | Default |
|---|---|---|
ORCA_DATABASE_URL | the Postgres database runs are recorded in; unset, run runs in memory | unset |
ORCA_STALE_SECONDS | how old a run's heartbeat must be before another process may resume it | 30 |
ORCA_WORKERS | the local Dista workers a run forks | 4 |
ORCA_LOGS | where attempts' logs, timelines, driver logs and instance logs go: a directory or s3://bucket/prefix | unset |
ORCA_LOG_STAGE | where a log bound for object storage is written until it's complete | $TMPDIR/orca-logs |
ORCA_WAREHOUSE | the root of the filesystem catalog datasets are found in, a directory or s3://; ns.table is <warehouse>/ns/table | unset |
ORCA_RETRY_SECONDS | the scheduler's first wait before retrying a failed run, doubled with each start | 60 |
ORCA_SENSE_SECONDS | how often the scheduler asks a sensor's binary for its datasets' current snapshots | 60 |
ORCA_RUNNER | where the scheduler starts runs: local, kubernetes or ec2 | local |
ORCA_VCPU_BUDGET | the vCPUs all live runs' pods or instances may use together; a run that would go over waits | no limit |
ORCA_RUN_ENV | the scheduler's variables, by name and comma-separated, copied to every run's pods or instances | unset |
ORCA_RUN_DEADLINE | seconds after which a run's pods or instances are removed whatever their state | 86400 |
ORCA_IMAGE | the image a run's binary is in, recorded with its version; the runners set it | unset |
ORCA_EC2_HEAD_TYPE | the head instance's type, unless registration gave one | c7g.large |
ORCA_EC2_WORKER_TYPE | the worker instances' type, unless registration gave one | c7g.xlarge |
ORCA_EC2_AMI | the image run instances boot | unset |
ORCA_EC2_SUBNET, ORCA_EC2_SECURITY_GROUP, ORCA_EC2_PROFILE | where run instances go, their security group, and their instance profile | unset |
ORCA_EC2_DATABASE_URL | the database URL run instances use, such as the control instance's private address | ORCA_DATABASE_URL |
ORCA_EC2_PREAMBLE | shell run on each instance before the pipeline, such as an ECR login | unset |
ORCA_EC2_MODE | docker runs the image on the instance; direct runs the binary, for the tests' stand-in EC2 | docker |
ORCA_CLUSTER_LOG_SECONDS | how often a run instance uploads its head's or worker's log | 30 |
ORCA_RUNS_NAMESPACE | the Kubernetes namespace runs' pods go in | orca-runs |
ORCA_UI_ADDR | host:port orca ui listens on | 127.0.0.1:8270 |
ORCA_UI_LOGIN | required makes the UI's actions need a login even before any user exists | on once a user exists |
ORCA_UI_SESSION_HOURS | how long a login lasts | 12 |
ORCA_UI_SECURE_COOKIES | 1 marks the session cookie Secure, for a UI behind HTTPS | unset |
ORCA_UI_TRUST_PROXY | 1 takes a request's address from X-Forwarded-For, behind a proxy you run | unset |
ORCA_UI_URL | the UI's public address, for the Open in Perfetto link | unset |
ORCA_DOCS_URL | where the UI's Docs links point | https://mlake.dev/s/orca/docs/ |
ORCA_NOW | fixes the clock of fires and the scheduler, as 2024-03-10T07:00Z or epoch seconds, for tests | the clock |
ORCA_ZONEINFO | where time zones' TZif files are read from | /usr/share/zoneinfo |
DISTA_ROLE | head or worker: the binary runs as that Dista process instead | unset |
DISTA_HEAD | host:port of a Dista head to join instead of forking workers | local mode |
When things fail
A step that raises is retried up to its retries, then
fails. The steps downstream of it are skipped, while other partitions' steps that
don't depend on it carry on, so one bad month doesn't stop the others; the run ends
failed and can be resumed. A process that dies leaves its run
running with a stale heartbeat; resume marks the interrupted attempt
failed and continues from there. A commit that landed before the crash
is found by the retry rather than made again. Fuzzing, and a chaos test that kills
runs, restarts Postgres and races two resumes over many seeds, run in
CI.