fix(ingestion): stop silent jira sync hang via lightweight substream parent - #1283
Conversation
…parent
jira syncs on large instances hung silently: the job stayed "running" with
0 records committed for hours while Argo poll timed out. Root cause: the
low-code CDK auto-caches a SubstreamPartitionRouter parent's HTTP responses
in a SQLite requests-cache, and the parent was jira_issue with
fields=*all + expand=names. On an instance with ~1000 fields (44 system +
959 custom) each /search/jql response is ~2 MB, so the cache ballooned to
~226 MB after ~108 responses and the read stalled before emitting anything.
Fix: add a lightweight jira_issue_keys stream (fields=updated only; id/key
arrive top-level for free) and repoint jira_issue_history, jira_comments
and jira_worklogs to it as their parent. jira_issue stays the full-record
emitter but is no longer a cached substream parent. Verified with isolated
`read --debug` runs: parent cache 226 MB -> ~4 KB, parent enumeration
completes (9994 keys), children emit steadily (4210 history records in one
2-day window) with per-partition state advancing.
Since reconcile (ADR-0015) auto-selects every discovered stream,
jira_issue_keys lands as a real bronze table: it carries the standard
tenant_id/source_id/unique_key stamp ({tenant}-{source}-{key}), hoists the
`updated` cursor field to the top level like jira_issue does, and is added
to the promote_bronze_to_rmt list so RMT dedup caps its growth.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
📝 WalkthroughWalkthroughThis PR adds a lightweight ChangesJira Issue Keys Parent Stream and Substream Re-parenting
Sequence DiagramsequenceDiagram
participant JiraAPI as Jira API
participant IssueKeys as jira_issue_keys
participant Substreams as jira_issue_history/comments/worklogs
JiraAPI->>IssueKeys: JQL query (fields: updated)
IssueKeys->>IssueKeys: Hoist `updated`, stamp identity, emit `unique_key`
IssueKeys->>Substreams: Emit partitions (id_readable, unique_key)
Substreams->>JiraAPI: Fetch per-issue payloads using partitions
🎯 4 (Complex) | ⏱️ ~45 minutes
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
Capture the operational and authoring lessons from the jira silent-hang fix so the next connector doesn't repeat them: - create.md: forbid heavy (fields=*all) SubstreamPartitionRouter parents (CDK auto-caches parent responses in SQLite; 226 MB cache -> permanent silent stall), document the lightweight key-parent pattern (jira_issue_keys) and the inline-parent alternative (_scrum_boards); require the identity stamp + RMT promotion on every top-level stream since reconcile (ADR-0015) auto-selects all discovered streams; require hoisting nested cursor fields (fields.updated) to the top level. Two new runtime-landmine rows + a cache-size acceptance criterion. - validate.md: checklist items for the above (no heavy parents, top-level cursor field, stamped + promoted helper streams). - test.md: stall diagnosis for silent reads — SQLite cache growth check, CPU-time vs frozen counter, CLOSE_WAIT-after-idle false positive. - SKILL.md: never trust Airbyte job status — verify recordsCommitted and bronze freshness (green-but-empty and forever-running failure modes). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
🧹 Nitpick comments (1)
cypilot/.core/skills/connector/SKILL.md (1)
57-61: ⚡ Quick winConsider adding a brief example for checking
aggregatedStats.recordsCommitted.The guidance correctly identifies the real health signals (
aggregatedStats.recordsCommittedand bronze freshness), but operators may not know how to retrieveaggregatedStats.recordsCommittedfrom the jobs API. Consider adding a one-line example or cross-referencing the Airbyte API endpoint (e.g.,GET /api/v1/jobs/get→.attempts[].stats.recordsCommitted) to make the guidance immediately actionable.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@cypilot/.core/skills/connector/SKILL.md` around lines 57 - 61, Add a one-line example showing how to read aggregatedStats.recordsCommitted from the Airbyte jobs API: reference the GET /api/v1/jobs/get endpoint and indicate the path attempts[].stats.recordsCommitted (or jobs[].attempts[].stats.recordsCommitted / .aggregatedStats.recordsCommitted as applicable) so operators can immediately locate the metric; include this short example sentence near the paragraph that recommends checking aggregatedStats.recordsCommitted and bronze freshness.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@cypilot/.core/skills/connector/SKILL.md`:
- Around line 57-61: Add a one-line example showing how to read
aggregatedStats.recordsCommitted from the Airbyte jobs API: reference the GET
/api/v1/jobs/get endpoint and indicate the path
attempts[].stats.recordsCommitted (or jobs[].attempts[].stats.recordsCommitted /
.aggregatedStats.recordsCommitted as applicable) so operators can immediately
locate the metric; include this short example sentence near the paragraph that
recommends checking aggregatedStats.recordsCommitted and bronze freshness.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 771fea94-3d49-4296-b465-561a0b1778a5
📒 Files selected for processing (4)
cypilot/.core/skills/connector/SKILL.mdcypilot/.core/skills/connector/workflows/create.mdcypilot/.core/skills/connector/workflows/test.mdcypilot/.core/skills/connector/workflows/validate.md
✅ Files skipped from review due to trivial changes (2)
- cypilot/.core/skills/connector/workflows/validate.md
- cypilot/.core/skills/connector/workflows/create.md
…#1308 (#1310) Reconcile republishes a nocode declarative manifest only on descriptor version drift (descriptor version vs the version stored on the Airbyte definition; equal -> noop per ADR-0015). #1308 changed connector.yaml (default_concurrency 1 -> 4) without bumping descriptor versions, so reconcile on the deployed 0.1.59 correctly no-op'd and the deadlock fix never reached Airbyte: the active jira manifest (v3) and confluence manifest (v2) still carry default_concurrency: 1. (#1283 only got published because an unrelated image-ref bump had already moved jira's descriptor to 1.2.0.) - jira: 1.2.0 -> 1.2.1 - confluence: 1.1.0 -> 1.1.1 Also encode the lesson in the /connector skill (deploy prerequisites + validate descriptor checklist): bump descriptor.yaml version in the same PR as any connector.yaml change, or the manifest silently never ships. Co-authored-by: Roman Mitasov <Roman.Mitasov@constructor.tech> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…list (#1316) Replaces the manually-maintained jira_project_keys secret field with runtime project discovery. Supersedes #941 (idea and per-project JQL by mozhaev-dev, cyberfabric/cyber-insight#616), reworked against the current manifest: - New inline-only parent `jira_project_discovery` under `definitions` (never visible to discover, so reconcile cannot select it as a bronze table): GET /rest/api/3/project/search?expand=insight enumerates every project the API token can see. - BOTH jira_issue and jira_issue_keys (the lightweight parent of jira_issue_history/comments/worklogs from #1283) are partitioned per project with JQL `project = "<KEY>"`. #941 predates jira_issue_keys and would have silently broken all three substreams by removing the config their JQL still referenced. - Incremental discovery gate: client-side cursor on the hoisted insight.lastIssueUpdateTime — first sync emits all projects, later syncs only projects whose issues changed. Verified live: first read enumerated 211 projects (25 with fresh issues, 2478 records — the 4-project allowlist would have missed 9 of them), resume read touched only 3/211 partitions. Projects without issues default to epoch and stay filtered until their first issue. - Timezone tail fix done safely: lookback_window PT1H -> PT14H on the issue scans. #941 instead pushed end_datetime 14h into the future, which advances the cursor past the actual query time and permanently skips records updated in between. - jira_project_keys removed from spec, descriptor required_fields, secret example and README. - descriptor 1.2.1 -> 2.0.0 (major): per-project partitioning resets incremental state; reconcile dispatches a full refresh on major bumps, which is the intended migration. Known costs, documented in-manifest: the first sync after rollout is a full re-sync of every visible project since jira_start_date; the client-side gate compares strictly against the cursor (lookback does not widen it on resume), so a lagging insight aggregate delays that project until its next update. validate (CDK runtime): manifest valid. validate-strict: 14 pre-existing errors on main (jira is the known whole-object-$ref anti-template); unchanged by this diff. Co-authored-by: Roman Mitasov <Roman.Mitasov@constructor.tech> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Problem
jira syncs on large instances hang silently: the Airbyte job stays
runningwith 0 records committed for hours, Argopolltimes out (workflowFailed), and the orphaned replication pod keeps spinning. On the virtuozzo tenant this meantbronze_jiranever received a single completed sync.Root cause
The low-code CDK auto-caches a
SubstreamPartitionRouterparent's HTTP responses in a SQLite requests-cache so child streams can reuse them. The parent ofjira_issue_history/jira_comments/jira_worklogswasjira_issue, which requestsfields=*all + expand=names. On an instance with ~1000 fields (44 system + 959 custom, mostly(migrated)duplicates), each/search/jqlresponse is ~2 MB. The cache ballooned to ~226 MB after ~108 responses and the read stalled before emitting anything — diagnosed with an isolatedread --debugrun of the bare source (no destination/orchestrator), which reproduced the prod hang exactly.Fix
Mirrors the official
source-jirapattern (minimal-fields parent for partition enumeration,*allonly on the terminal emitter):jira_issue_keys: same/search/jql+ JQL window, butfields=updatedonly (id/key arrive top-level for free, noexpand).jira_issue_history,jira_comments,jira_worklogsrepointed to it as their parent (#/streams/4→#/streams/13).jira_issuestays the full-record emitter but is no longer a cached substream parent.Since reconcile (ADR-0015) auto-selects every discovered stream,
jira_issue_keyslands as a real (tiny: key + updated per issue) bronze table, so it:tenant_id/source_id/unique_key({tenant}-{source}-{key}),updatedcursor field to the top level (the search response nests it asfields.updated;jira_issuedoes the same hoist),promote_bronze_to_rmtlist (jira__bronze_promoted.sql) so RMT dedup caps its growth. The macro skips missing tables, so ordering vs. first sync is safe.Verification (isolated
readruns against a live large instance)jira_issue_historyjira_issue_keysstandaloneunique_key=virtuozzo-jira-main-VSTOR-…, exit 0No schema changes to existing bronze tables: children emit byte-identical records (
id_readablecomes fromrecord['key']in both old and new parent); dbt staging/silver untouched apart from the added promotion line.Note: confluence has the same architecture (
wiki_pagesparents 3 substreams) and will get the same treatment in a follow-up.🤖 Generated with Claude Code
Summary by CodeRabbit
Also included
docs(skills): the/connectorskill (cypilot/.core/skills/connector/) now encodes these lessons — forbidden heavy substream parents + the lightweight key-parent / inline-parent patterns, identity stamp & RMT promotion required on every top-level stream (reconcile auto-selects all), nested-cursor hoist rule, silent-stall diagnosis forread, and "never trust Airbyte job status — verify recordsCommitted / bronze freshness" ops guidance.