Skip to content

Fixes #31582: Prevent asynchronous database pool exhaustion - #31583

Open
harshach wants to merge 3 commits into
mainfrom
harshach/diagnose-login-db-timeout
Open

Fixes #31582: Prevent asynchronous database pool exhaustion#31583
harshach wants to merge 3 commits into
mainfrom
harshach/diagnose-login-db-timeout

Conversation

@harshach

Copy link
Copy Markdown
Collaborator

Describe your changes:

Fixes #31582

I bounded database-heavy asynchronous work, serialized Data Insights across servers, batched fan-out cleanup and CSV event writes, bounded RDF indexing, fixed a JDBI handle leak, and shortened database failure timeouts so background workloads cannot starve login and API requests.

Type of change:

  • Bug fix

High-level design:

  • Separate bounded DB-task lane from the raw virtual-thread executor used by non-DB continuations; expose per-operation active, queued, submitted, and limit metrics.
  • Configurable defaults: 25 concurrent DB tasks per server, 8 RDF writes, and Data Insights capped at 16 tasks and 10% of the Hikari pool.
  • Cross-server Data Insights lease with heartbeat and race-safe stale-lock takeover.
  • Batched test-case cleanup and CSV change-event persistence; cancellable bounded search work; bounded RDF lookup caches.
  • Five-minute SQL/socket ceiling, shorter pool acquisition/validation defaults, and automatic JDBI connection closure.
  • No migration or API compatibility impact; all new limits and timeout defaults remain environment-configurable.

Growing the Hikari pool was rejected because unbounded background fan-out would consume any larger pool and continue starving request traffic.

Tests:

Use cases covered

  • Excess DB-heavy tasks queue without blocking raw async continuations.
  • Cancellation and failures release DB-task capacity.
  • Data Insights preserves request-pool capacity.
  • Bulk test-case deletion performs one batched cleanup.
  • JDBI applies the configured query timeout.

Unit tests

  • I added unit tests for the new/changed logic.
  • Files added/updated: AsyncServiceTest, BoundedAsyncExecutorTest, AsyncOperationsConfigurationTest, DataAssetsWorkflowConcurrencyTest, JdbiUtilsTest, and TestCaseRepositoryTest.
  • Coverage %: not measured.
  • Result: 32 focused tests passed with 0 failures, errors, or skips.

Backend integration tests

  • Not applicable (no backend API changes).

Ingestion integration tests

  • Not applicable (no ingestion changes).

Playwright (UI) tests

  • Not applicable (no UI changes).

Manual testing performed

Not performed; validation used focused Maven tests, Spotless, and git diff --check.

UI screen recording / screenshots:

Not applicable.

Checklist:

  • I have read the CONTRIBUTING document.
  • My PR title is Fixes <issue-number>: <short explanation>
  • My PR is linked to a GitHub issue via Fixes #<issue-number> above.
  • I have commented on my code, particularly in hard-to-understand areas.
  • For JSON Schema changes: I updated the migration scripts or explained why it is not needed.
  • For UI changes: I attached a screen recording and/or screenshots above.
  • I have added tests (unit / integration / Playwright as applicable) and listed them above.
  • I have added a test that covers the exact scenario we are fixing.

@github-actions github-actions Bot added backend safe to test Add this label to run secure Github workflows on PRs labels Aug 15, 2026
Comment thread openmetadata-service/src/main/java/org/openmetadata/service/rdf/RdfUpdater.java Outdated
@harshach
harshach marked this pull request as ready for review August 15, 2026 19:35
@harshach
harshach requested review from a team, akash-jain-10 and tutte as code owners August 15, 2026 19:35
Copilot AI lite review requested due to automatic review settings August 15, 2026 19:35
@github-actions

github-actions Bot commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

✅ PR checks passed

The linked issue has a description and all required Shipping project fields set. Thanks!

@harshach harshach added the To release Will cherry-pick this PR into the release branch label Aug 15, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses issue #31582 by preventing database-heavy background workloads from exhausting the Hikari connection pool and starving request traffic. It introduces bounded execution lanes for DB-consuming async work, improves cluster-wide scheduling behavior for Data Insights, batches high-fan-out cleanup/writes, bounds RDF indexing concurrency/caches, and tightens DB timeout defaults.

Changes:

  • Introduce a DB-bounded async executor lane with per-operation backlog metrics, and route DB-heavy background tasks through it.
  • Reduce DB fan-out by batching test-case result cleanup and CSV change-event persistence; bound RDF write concurrency and caches.
  • Add cluster-wide Data Insights job locking with heartbeat, plus configurable concurrency budgets and tighter DB timeout defaults.

Reviewed changes

Copilot reviewed 38 out of 38 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
openmetadata-service/src/test/java/org/openmetadata/service/util/jdbi/JdbiUtilsTest.java Adds unit coverage for applying configured JDBI statement timeout.
openmetadata-service/src/test/java/org/openmetadata/service/util/BoundedAsyncExecutorTest.java Adds concurrency/cancellation/ordering tests for the bounded executor wrapper.
openmetadata-service/src/test/java/org/openmetadata/service/util/AsyncServiceTest.java Adds tests for DB-bounded async lane behavior and cancellation semantics.
openmetadata-service/src/test/java/org/openmetadata/service/jdbi3/TestCaseRepositoryTest.java Verifies test-case cleanup is batched (single delete) during bulk hard delete.
openmetadata-service/src/test/java/org/openmetadata/service/config/AsyncOperationsConfigurationTest.java Validates default async-operation limits and stable config defaulting behavior.
openmetadata-service/src/test/java/org/openmetadata/service/apps/bundles/insights/workflows/dataAssets/DataAssetsWorkflowConcurrencyTest.java Covers Data Insights concurrency budget calculation behavior.
openmetadata-service/src/main/java/org/openmetadata/service/util/OpenMetadataOperations.java Fixes a JDBI handle leak in connection checking and makes validity explicit.
openmetadata-service/src/main/java/org/openmetadata/service/util/jdbi/JdbiUtils.java Centralizes SqlStatements configuration and applies query timeout when using Hikari config.
openmetadata-service/src/main/java/org/openmetadata/service/util/BoundedAsyncExecutor.java Introduces an ExecutorService wrapper to cap concurrent task execution.
openmetadata-service/src/main/java/org/openmetadata/service/util/AsyncService.java Splits raw vs DB-bounded async execution, adds per-operation backlog stats + metrics.
openmetadata-service/src/main/java/org/openmetadata/service/resources/search/SearchResource.java Routes reindex work through cancellable DB-bounded async lane.
openmetadata-service/src/main/java/org/openmetadata/service/resources/glossary/GlossaryTermResource.java Runs glossary move async work through DB-bounded lane.
openmetadata-service/src/main/java/org/openmetadata/service/resources/EntityResource.java Moves async delete/restore and bulk-tag ops onto the DB-bounded lane.
openmetadata-service/src/main/java/org/openmetadata/service/resources/columns/ColumnResource.java Routes CSV import and bulk column updates through the DB-bounded lane.
openmetadata-service/src/main/java/org/openmetadata/service/resources/apps/AppResource.java Runs async app deletion/cleanup through the DB-bounded lane.
openmetadata-service/src/main/java/org/openmetadata/service/resources/ai/AuditPackGenerator.java Routes audit pack generation through DB-bounded lane.
openmetadata-service/src/main/java/org/openmetadata/service/rdf/translator/RdfPropertyMapper.java Replaces unbounded concurrent maps with bounded Caffeine caches for RDF lookups.
openmetadata-service/src/main/java/org/openmetadata/service/rdf/RdfUpdater.java Adds RDF write concurrency limits and routes RDF DB work through DB-bounded async lane.
openmetadata-service/src/main/java/org/openmetadata/service/OpenMetadataApplicationConfig.java Adds asyncOperations configuration block to application config.
openmetadata-service/src/main/java/org/openmetadata/service/OpenMetadataApplication.java Initializes AsyncService from config and passes async config into RDF init.
openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/UserRepository.java Routes post-delete cleanup work through DB-bounded lane.
openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TestSuiteRepository.java Routes async test-suite deletion through DB-bounded lane.
openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TestCaseResultRepository.java Adds batch-delete support for test-case result cleanup and updates search delete script params.
openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TestCaseRepository.java Batches test-case result cleanup and routes cleanup work through DB-bounded lane.
openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TestCaseDimensionResultRepository.java Adds batch-delete API for test-case dimension results time series.
openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/HikariCPDataSourceFactory.java Adds configurable query timeout seconds to DB factory config.
openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/CollectionDAO.java Adds chunked batch-delete DAO methods and tightens stale-lock deletion semantics.
openmetadata-service/src/main/java/org/openmetadata/service/governance/workflows/elements/nodes/userTask/CreateTask.java Routes workflow termination dispatch through DB-bounded async lane.
openmetadata-service/src/main/java/org/openmetadata/service/config/AsyncOperationsConfiguration.java Introduces configurable limits for DB-heavy async work, RDF writes, and Data Insights DB budget.
openmetadata-service/src/main/java/org/openmetadata/service/audit/AuditLogRepository.java Routes auth audit event writes through DB-bounded lane.
openmetadata-service/src/main/java/org/openmetadata/service/apps/bundles/insights/workflows/dataAssets/DataAssetsWorkflow.java Computes Data Insights concurrency budget using CPU, pool-size fraction, and configured cap.
openmetadata-service/src/main/java/org/openmetadata/service/apps/bundles/insights/DataInsightsApp.java Adds cross-server lease/heartbeat lock so only one server runs Data Insights at a time.
openmetadata-service/src/main/java/org/openmetadata/csv/EntityCsv.java Batches/persists CSV change events asynchronously via DB-bounded lane.
docker/development/distributed-test/local/server1.yaml Adds asyncOperations config and tightens DB timeout defaults for distributed tests.
docker/development/distributed-test/local/server2.yaml Adds asyncOperations config and tightens DB timeout defaults for distributed tests.
docker/development/distributed-test/local/server3.yaml Adds asyncOperations config and tightens DB timeout defaults for distributed tests.
conf/openmetadata.yaml Adds asyncOperations config and tightens DB timeout defaults in default config.
conf/openmetadata-h2-test.yaml Adds asyncOperations defaults for H2 test config and tightens DB timeout defaults.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Copilot AI review requested due to automatic review settings August 15, 2026 19:51

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 38 out of 38 changed files in this pull request and generated no new comments.

Suppressed comments (2)

openmetadata-service/src/main/java/org/openmetadata/service/util/AsyncService.java:151

  • submitCancellableDatabaseTask increments OperationStats.queued in recordSubmission(), but if the returned Future is cancelled before it starts running, FutureTask.run() becomes a no-op and stats.call(task) never executes (so queued is never decremented). This can permanently inflate queued metrics and generate false backlog warnings.

Make the cancellable task decrement queued on successful cancellation-before-start, and only increment/decrement active when the task actually begins executing.

  public <T> Future<T> submitCancellableDatabaseTask(
      DatabaseOperation operation, String context, Callable<T> task) {
    final OperationStats stats = recordSubmission(operation, context);
    final FutureTask<T> future = new FutureTask<>(() -> stats.call(task));
    try {
      databaseExecutorService.execute(future, () -> cancelDatabaseTask(stats, future));
    } catch (RuntimeException e) {
      stats.cancelSubmission();
      throw e;
    }
    return future;

openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TestCaseResultRepository.java:356

  • SearchRepository.deleteByScript uses an Elasticsearch/OpenSearch script query (boolean predicate). The current script uses an if { ... } statement, which does not evaluate to a boolean value; other call sites pass a plain boolean expression (e.g., ReportDataRepository). This risks delete-by-script failing or deleting nothing.

Use a boolean expression that safely handles missing fields instead of an if statement.

    Map<String, Object> params = Map.of("fqns", testCaseFQNs);
    searchRepository.deleteByScript(
        TEST_CASE_RESULT,
        "if (!(doc['testCaseFQN.keyword'].empty)) { params.fqns.contains(doc['testCaseFQN.keyword'].value) }",
        params);

@github-actions

github-actions Bot commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

✅ Playwright Results — workflow succeeded

Validated commit f3bc945a7e16f4876bde91fdf3a485855c55aa35 in Playwright run 31927705540, attempt 1.

✅ 1467 passed · ❌ 0 failed · 🟡 2 flaky · ⏭️ 1 skipped · 🧰 0 lifecycle flaky

Performance

Blocking targets: ✅ met · Optimization targets: 🟡 in progress

Shard-job maxima below are not the full workflow wall time; the linked run includes build, fixture, planning, and reporting.

🕒 Full workflow signal wall (to summary) 28m 4s

⏱️ Max setup 3m 7s · max shard execution 19m 15s · max shard-job elapsed before upload 22m 42s · reporting 9s

🌐 199.08 requests/attempt · 2.11 app boots/UI scenario · 29.26% common-shard skew

Optimization targets still in progress:

  • Common shard skew was 29.26% (convergence target: at most 15%).
  • Application boot ratio was 2.11 per UI scenario (3397 boots / 1610 scenarios; convergence target: at most 1).
Shard Passed Failed Flaky Skipped Lifecycle failed Lifecycle flaky
✅ Shard chromium-01 182 0 0 1 0 0
✅ Shard chromium-02 156 0 0 0 0 0
🟡 Shard chromium-03 150 0 1 0 0 0
✅ Shard chromium-04 154 0 0 0 0 0
✅ Shard chromium-05 138 0 0 0 0 0
✅ Shard chromium-06 146 0 0 0 0 0
✅ Shard chromium-07 145 0 0 0 0 0
🟡 Shard chromium-08 175 0 1 0 0 0
✅ Shard data-asset-rules-01 61 0 0 0 0 0
✅ Shard domain-isolation-01 14 0 0 0 0 0
✅ Shard global-state-01 34 0 0 0 0 0
✅ Shard import-export-01 35 0 0 0 0 0
✅ Shard ingestion-01 32 0 0 0 0 0
✅ Shard reindex-01 5 0 0 0 0 0
✅ Shard search-01 11 0 0 0 0 0
✅ Shard search-rbac-01 29 0 0 0 0 0
🟡 2 flaky test(s) (passed on retry)
  • Pages/Glossary.spec.tsApprove and reject glossary term from Glossary Listing (shard chromium-03, 1 retry)
  • Pages/Glossary.spec.tsTerm should stay approved when changes made by reviewer (shard chromium-08, 1 retry)

📦 Download artifacts

How to debug locally
# Download playwright-test-results-<shard> artifact and unzip
npx playwright show-trace path/to/trace.zip    # view trace

Copilot AI review requested due to automatic review settings August 16, 2026 04:53
@gitar-bot

gitar-bot Bot commented Aug 16, 2026

Copy link
Copy Markdown
Code Review ✅ Approved 2 resolved / 2 findings

Bounds asynchronous database work and adds request-pool protections to prevent pool exhaustion, addressing the rdfWritePermits static field volatility and DB-task metric leak findings.

✅ 2 resolved
Bug: rdfWritePermits static field is non-volatile and reassigned

📄 openmetadata-service/src/main/java/org/openmetadata/service/rdf/RdfUpdater.java:37-38 📄 openmetadata-service/src/main/java/org/openmetadata/service/rdf/RdfUpdater.java:48-50 📄 openmetadata-service/src/main/java/org/openmetadata/service/rdf/RdfUpdater.java:301-315
rdfWritePermits is a non-volatile static Semaphore that is reassigned inside initialize(...). runRdfTask reads the field twice — once for acquire() and once for release() in the finally block. If initialize() were ever re-invoked while a write is in flight, the release would target a different Semaphore instance than the acquire, leaking a permit on the old instance and over-releasing on the new one; non-volatile publication also risks other threads observing the pre-initialization default. In practice initialize() only runs at startup so the risk is low, but marking the field volatile and capturing the instance into a local variable in runRdfTask before acquire/release removes the hazard.

Bug: DB-task queued/active metrics can leak on shutdownNow

📄 openmetadata-service/src/main/java/org/openmetadata/service/util/AsyncService.java:160-174 📄 openmetadata-service/src/main/java/org/openmetadata/service/util/AsyncService.java:357-371 📄 openmetadata-service/src/main/java/org/openmetadata/service/util/BoundedAsyncExecutor.java:45-59
AsyncService.OperationStats.recordSubmission() increments the queued counter, and it is only decremented inside stats.run()/stats.call(). When BoundedAsyncExecutor.runWithPermit is interrupted while waiting for a permit (e.g. during shutdownNow), it cancels the wrapped Future/command and returns without ever invoking the task, so queued is never decremented for that submission. This permanently skews the async.operations.db.queued gauge after a shutdown/restart cycle. Since it only occurs on interruption during shutdown the impact is limited to metric drift, but decrementing the counter on the drop path would keep the gauges accurate.

Options

Display: compact → Showing less information.

Comment with these commands to change the behavior for this request:

Compact
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Powered by Gitar — free for open source

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 38 out of 38 changed files in this pull request and generated no new comments.

Suppressed comments (4)

openmetadata-service/src/test/java/org/openmetadata/service/util/BoundedAsyncExecutorTest.java:114

  • This test relies on an exact queued count via Semaphore#getQueueLength(), which is only an estimate. Waiting for == 1 can hang intermittently; prefer asserting the queue has at least one waiter.
    openmetadata-service/src/test/java/org/openmetadata/service/util/BoundedAsyncExecutorTest.java:168
  • Semaphore#getQueueLength() is an estimate, so waiting for the queued count to equal an exact value can be flaky. For ordering verification, it’s sufficient to wait until the queued count reaches at least the expected minimum before releasing the first task.
    openmetadata-service/src/test/java/org/openmetadata/service/util/BoundedAsyncExecutorTest.java:54
  • BoundedAsyncExecutor.getQueuedCount() is derived from Semaphore#getQueueLength(), which is documented as an estimate and may not be exact. Asserting an exact queued count here can make this test flaky under timing/VM differences; it’s enough to assert the queue is non-empty once concurrency is saturated.

This issue also appears in the following locations of the same file:

  • line 112
  • line 165
    openmetadata-service/src/main/java/org/openmetadata/service/util/AsyncService.java:90
  • AsyncService.getInstance() will eagerly initialize the singleton with default limits if it’s accessed before OpenMetadataApplication calls AsyncService.initialize(...). Any later initialize(config) call is then silently ignored, which can lead to running with unintended async DB limits (and misleadingly “configured” YAML). Consider logging when initialize is called after instantiation (or allowing re-init/update) so misconfiguration is detectable.
  public static synchronized void initialize(AsyncOperationsConfiguration config) {
    if (instance == null) {
      instance = new AsyncService(Objects.requireNonNull(config));
      instance.registerMetrics();
    }
  }

@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend safe to test Add this label to run secure Github workflows on PRs To release Will cherry-pick this PR into the release branch

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Database-heavy background work can exhaust the connection pool

2 participants