Query icon
OutputValues icon
Log icon
If icon
SlackIncomingWebhook icon
Pause icon
Loop icon
Fail icon
Schedule icon

Oracle Sequence MAXVALUE Exhaustion Guard

Detect Oracle NOCYCLE sequences low on headroom before ORA-08004 stops inserts, raise MAXVALUE behind an approval pause, then re-read dba_sequences.

Categories
DataInfrastructure

A NOCYCLE sequence approaching its MAXVALUE is a time bomb. While there is headroom it looks perfectly healthy. The moment nextval would pass the ceiling, every INSERT that draws a key from it fails with ORA-08004, "sequence exceeds MAXVALUE and cannot be instantiated". The table is write-down from that instant, with no warning beforehand and no gradual slowdown, because a sequence has no concept of being nearly full.

The trap is that the healthy state and the dead state look almost identical in the data dictionary. dba_sequences shows a last_number climbing toward max_value run after run, which nobody watches, right up to the call that tips over. A sequence created years ago with a small explicit MAXVALUE reaches that point long before anyone expects a 28 digit counter to. So does one defined on a narrow NUMBER.

This blueprint reads dba_sequences, measures how many more nextval calls each ascending NOCYCLE sequence can serve before its ceiling, raises the MAXVALUE on the ones running short with ALTER SEQUENCE behind a dry run plus an approval pause, then reads dba_sequences again and fails the run if a target is still short.

This blueprint was created by zkasuran.

Safety

Raising a MAXVALUE is additive and non-destructive. It gives the sequence more room and changes nothing about the values already handed out, so it is one of the safer schema changes there is. It is still a schema change, so it is gated like one.

  • A raise runs only when auto_remediate is true, dry_run is false plus a human resumes the approval_gate pause with approved set to true. Three independent conditions, all off by default.
  • The new ceiling is computed in SQL as `GREATEST(max_value + increment_by, last_number
    • restore_headroom_values * increment_by)`, so it is always strictly above the current MAXVALUE. No sequence is ever lowered, which would itself cause ORA-08004 on the next draw.
  • The new ceiling is capped at 9999999999999999999999999999, the largest value a 28 digit Oracle NUMBER holds. A sequence already at that ceiling is reported, never raised, because there is no higher ceiling to move it to.
  • A CYCLE sequence is detected but never raised. When it reaches its MAXVALUE it wraps back to its MINVALUE rather than failing, so it does not have this outage. It is listed separately so the operator can see it was considered plus deliberately left alone.
  • Only ascending sequences (increment_by > 0) are considered. A descending sequence approaches its MINVALUE, which is a different check.

How it works

  1. totals_before (io.kestra.plugin.jdbc.oracle.Query, read-only) counts the sequences in the checked schemas, split into NOCYCLE plus CYCLE, with how many of each are already low on headroom.
  2. audit (read-only) lists the ascending NOCYCLE sequences whose remaining draw count, measured in SQL from last_number, max_value plus increment_by, is below min_headroom_values. For each it returns the current ceiling, the remaining count, the computed new ceiling plus the exact ALTER SEQUENCE statement. It tags each one RAISE or REPORT_ONLY when the ceiling is already at the NUMBER limit.
  3. excluded (read-only) lists ascending CYCLE sequences that are also low on headroom, for reporting only.
  4. targets (io.kestra.plugin.core.output.OutputValues) collects the raise statements, the report-only detail plus the comma-joined names of the raise targets. summary logs every group.
  5. route (io.kestra.plugin.core.flow.If):
    • clean: no NOCYCLE sequence is short, so it logs the counts and stops.
    • findings: alert posts the groups to Slack. ceiling_route explains any report-only sequences at the NUMBER limit.
      • remediate_gate runs only when auto_remediate is true, dry_run is false plus there is at least one raise target.
      • approval_gate (io.kestra.plugin.core.flow.Pause) holds the run. apply checks the resume answer, so an unanswered pause never reads as approval.
      • remediate (io.kestra.plugin.core.flow.Loop, concurrencyLimit: 1) runs one ALTER SEQUENCE ... MAXVALUE per target, in turn.
      • reverify re-reads dba_sequences for the raised sequences. totals_after re-reads the counts. verify_result fails the run through io.kestra.plugin.core.execution.Fail if any target is still short, otherwise remediated logs the before plus after headroom.
  6. errors posts to Slack if the flow fails for any reason.

What you get

  • A read of how close each sequence is to its ceiling in the one unit that matters, the number of nextval calls left, rather than a raw last_number nobody can judge.
  • An early warning while there is still headroom, so the raise happens on a schedule rather than during the incident when inserts are already failing.
  • CYCLE sequences told apart from NOCYCLE ones, so a sequence that wraps by design is not flagged as broken.
  • Sequences already at the NUMBER ceiling surfaced as a redesign item, not quietly raised to a value that does not exist.
  • A real verification step. The run passes only when the second read of dba_sequences shows every raised sequence back above the floor, with the counts before plus after in the log.
  • Slack alerts on the findings plus on any failure.

Who it's for

  • DBAs who have been surprised by ORA-08004 on a table that was fine yesterday.
  • Platform teams who inherited an instance with old sequences carrying small explicit MAXVALUEs set long before anyone measured the growth.
  • Anyone who wants sequence headroom checked on a schedule rather than discovered when the application starts rejecting writes.

Why orchestrate this with Kestra

The usual answer is a one-off ALTER SEQUENCE typed by a DBA after the outage has already started. Kestra turns it into a policy that runs before the outage: read dba_sequences on a schedule, measure the real headroom, post what is short to Slack, hold the schema change behind a human approval, then prove the raise landed by reading the dictionary again plus failing the run if it did not. Every run keeps its logs, so you can show which sequence was short, how many draws it had left plus what its ceiling became.

Tested end to end

On Kestra 2.0.5 OSS against Oracle Database Free 23ai (gvenzl/oracle-free, service FREEPDB1), with a local HTTP sink standing in for Slack. The schema APP held four sequences. SEQ_WARM was MAXVALUE 1000 NOCYCLE advanced with nextval to 998, so it had 2 draws left while still working. SEQ_HOT was MAXVALUE 1000 NOCYCLE advanced past its ceiling until nextval raised ORA-08004. SEQ_CYCLE was MAXVALUE 1000 CYCLE advanced to 998. SEQ_HEALTHY was a default sequence with the full 28 digit ceiling, untouched.

The real bite was measured, not assumed. With SEQ_HOT at its ceiling, SELECT app.seq_hot.nextval FROM dual raised ORA-08004: sequence SEQ_HOT.NEXTVAL exceeds MAXVALUE and cannot be instantiated. After the flow raised its ceiling, the same statement returned 1001. Every max_value plus last_number was read back from dba_sequences with sqlplus, not only from the Kestra log.

Run Setup and inputs Result
1 only SEQ_HEALTHY, defaults 0 findings, clean branch, SUCCESS
2 warm, hot, cycle, healthy present, defaults 2 NOCYCLE findings (SEQ_WARM, SEQ_HOT), SEQ_CYCLE reported as excluded, SEQ_HEALTHY untouched, posted to Slack, no change, SUCCESS
3 same, auto_remediate: true, dry_run: true Same report, Slack said no change applied, dba_sequences unchanged. The dry run gate held
4 same, armed, pause resumed approved: false Declined, nothing raised, SUCCESS
5 same, armed, pause resumed approved: true SEQ_WARM max_value 1000 to 100000998, SEQ_HOT max_value 1000 to 100001001, reverify found 0 short, SUCCESS. SEQ_HOT.nextval then returned 1001
6 low-privilege user without ALTER ANY SEQUENCE, armed, approved ALTER refused with ORA-01031, reverify still found the targets short, the run FAILED plus the errors block alerted Slack
7 SEQ_AT_CEILING created at the 28 digit MAXVALUE near its top, defaults 1 finding tagged REPORT_ONLY, 0 raise targets, reported not raised, SUCCESS

Runs 1 and 2 are the proof the check can fail: the same query returned 0 findings with only the healthy sequence present and exactly 2 NOCYCLE findings once the short ones were added, with the CYCLE sequence held out of the targets. Run 7 proves the NUMBER ceiling is respected. Run 6 proves the re-read gate: a raise that did not land left the run FAILED rather than reporting success.

Prerequisites

  • An Oracle Database reachable over the thin JDBC driver (jdbc:oracle:thin:@//host:1521/service).
  • A database user that can read dba_sequences (SELECT_CATALOG_ROLE or SELECT ANY DICTIONARY). Raising another schema's sequence needs ALTER ANY SEQUENCE. A user raising only its own sequences needs no extra privilege.

Secrets

  • ORACLE_PASSWORD: the database password. It is the default for the password input.
  • SLACK_WEBHOOK_URL: used by alert and the errors block.
  • In Kestra OSS, provide each one as a base64-encoded environment variable prefixed SECRET_.

Inputs

Input Default Purpose
jdbc_url jdbc:oracle:thin:@//localhost:1521/FREEPDB1 The Oracle instance to check
username SYSTEM The database user
password {{ secret('ORACLE_PASSWORD') }} The database password
sequence_owners [APP] Schema owners whose sequences are checked
min_headroom_values 1000000 A NOCYCLE sequence with fewer draws left than this is a finding
restore_headroom_values 100000000 How many draws of headroom a raise restores
auto_remediate false Allow ALTER SEQUENCE to raise a ceiling
dry_run true Must be false for a raise to run

Quick start

  1. Add the ORACLE_PASSWORD and SLACK_WEBHOOK_URL secrets.
  2. Import the flow, set jdbc_url plus username, then list the schemas in sequence_owners.
  3. Run it with the defaults and read which sequences are short on headroom.
  4. Set min_headroom_values to the warning you want, based on how fast the sequences grow.
  5. When you want it to raise ceilings, run with auto_remediate: true plus dry_run: false, then resume the pause with approved: true.
  6. Enable the daily trigger, which always runs in safe mode.

How to extend

  • Add a descending-sequence check that measures headroom down to MINVALUE, for sequences with a negative increment_by.
  • Compute min_headroom_values from the recent growth rate of each sequence rather than one fixed number, so a fast sequence is warned earlier than a slow one.
  • Fail the run as soon as the audit finds anything, for a read-only policy check with no raise.
  • Add a per sequence policy from a table instead of one headroom threshold for the whole schema.
  • Loop over several instances to check every database from one flow.

Links

See How

New to Kestra?

Use blueprints to kickstart your first workflows.