Skip to content

fix: on-demand lease manager for cold start concurrency queue#4281

Merged
mnafees merged 9 commits into
mainfrom
nafees/cold-start-concurrency-lease
Jun 26, 2026
Merged

fix: on-demand lease manager for cold start concurrency queue#4281
mnafees merged 9 commits into
mainfrom
nafees/cold-start-concurrency-lease

Conversation

@mnafees

@mnafees mnafees commented Jun 25, 2026

Copy link
Copy Markdown
Member

Description

This PR aims to fix the cold start latency concurrency workflows can face which increases the latency between QUEUED -> ASSIGNED_TO_WORKER status progression.

Type of change

  • Bug fix (non-breaking change which fixes an issue)

What's Changed

  • Adds method to acquire on demand concurrency lease for cold starts of queues
  • Adds unit test for the behavior
  • Removes redundant RMQ message type and mechanism

Checklist

Changes have been:

  • Tested (unit, integration, or manually with steps specified)
  • Linted and formatted
  • Documented (where applicable)

🤖 AI Disclosure
  • I acknowledge that an LLM was used in the creation of this Pull Request, in accordance with Hatchet's AI_POLICY.md.
  • Details: [e.g. generating tests, writing docs]

@mnafees mnafees self-assigned this Jun 25, 2026
@vercel

vercel Bot commented Jun 25, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
hatchet-docs Ready Ready Preview, Comment Jun 26, 2026 5:52pm

Request Review

@github-actions github-actions Bot added the engine Related to the core Hatchet engine label Jun 25, 2026
@github-actions

github-actions Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Benchmark results

goos: linux
goarch: amd64
pkg: github.com/hatchet-dev/hatchet/pkg/scheduling/v1
cpu: AMD Ryzen 9 7950X3D 16-Core Processor          
              │ /tmp/old.txt │            /tmp/new.txt             │
              │    sec/op    │    sec/op     vs base               │
RateLimiter-8    48.33µ ± 6%   56.92µ ± 16%  +17.76% (p=0.015 n=6)

              │ /tmp/old.txt │         /tmp/new.txt          │
              │     B/op     │     B/op      vs base         │
RateLimiter-8   137.7Ki ± 0%   137.7Ki ± 0%  ~ (p=0.193 n=6)

              │ /tmp/old.txt │          /tmp/new.txt          │
              │  allocs/op   │  allocs/op   vs base           │
RateLimiter-8    1.022k ± 0%   1.022k ± 0%  ~ (p=1.000 n=6) ¹
¹ all samples are equal

Compared against main (06d7745)

@mnafees mnafees requested review from abelanger5, grutt and mrkaye97 June 25, 2026 15:51
@mnafees mnafees marked this pull request as ready for review June 25, 2026 15:51
Copilot AI review requested due to automatic review settings June 25, 2026 15: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

This PR reduces cold-start latency for concurrency-keyed scheduling by acquiring concurrency-strategy leases on-demand when a task arrives for a strategy that currently has no in-memory ConcurrencyManager, avoiding the periodic (5s) lease-poll delay.

Changes:

  • Add an on-demand, blocking concurrency-strategy lease acquisition path and immediately notify the newly created ConcurrencyManager to schedule waiting tasks promptly.
  • Add an integration regression test that reproduces the “cold strategy waits for lease poll” behavior and asserts the task is scheduled within a short deadline.
  • Remove the now-redundant RMQ message type/handler for “new concurrency strategy”, and re-enable the stale concurrency deactivation operation.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
pkg/scheduling/v1/tenant_manager.go Creates/awakens concurrency managers on-demand for cold strategies and triggers immediate scheduling.
pkg/scheduling/v1/lease_manager.go Adds a blocking on-demand lease acquisition method that returns the strategy directly (bypassing the lossy channel).
pkg/scheduling/v1/concurrency_integration_test.go Adds regression coverage for cold-start scheduling latency on concurrency strategies.
internal/services/shared/tasktypes/v1/scheduler.go Removes the now-unused “new concurrency strategy” message payload/builder.
internal/services/scheduler/v1/scheduler.go Removes the handler for the deleted “new concurrency strategy” message.
internal/services/controllers/task/controller.go Re-enables the periodic operation that deactivates stale step concurrency strategies.
internal/msgqueue/message_ids.go Removes the message ID constant for the deleted message type.

Comment thread pkg/scheduling/v1/lease_manager.go Outdated
Comment thread pkg/scheduling/v1/tenant_manager.go Outdated
return nil
}

func (s *Scheduler) handleNewConcurrencyStrategy(ctx context.Context, msg *msgqueue.Message) error {

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.

Why are we removing this? It feels like this is a different case that we should also handle: a completely new concurrency strategy being registered from PutWorkflow. We likely are not propagating this message yet, but we probably should.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Restored the handler. In terms of the PutWorkflow change - I think my changes already address this when the very first task arrives with concurrency? So IMO the publishing of this RMQ message from PutWorkflow is only proactive optimization?

Comment thread pkg/scheduling/v1/lease_manager.go Outdated
// hot path in their own goroutine, so blocking is fine -- and it guarantees the acquisition is not
// silently dropped under contention (the previous TryLock + non-blocking-send approach would no-op
// when several cold strategies raced each other or the periodic poll, falling back to the 5s poll).
func (l *LeaseManager) acquireConcurrencyStrategyOnDemand(ctx context.Context, strategyId int64) (*sqlcv1.V1StepConcurrency, error) {

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.

I'm not sure why this needs to use a different path than the notifyNewQueue mechanism. I think we can rethink the non-blocking write to the concurrencyLeasesCh to potentially make it blocking, if that's what we're worried about.

From what I can tell there are two important things here:

  1. That we call notify after we instantiate the concurrency strategy (which we should probably do anyway)
  2. That we call notifyNewConcurrencyStrategy when a message arrives with a strategy ID which isn't currently in our leases.

@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Optional test failure: The load-deadlock job failed on this PR (optimistic-scheduling=true). This check is non-mandatory and does not block merging, but may be worth investigating. View logs

@grutt grutt requested a review from abelanger5 June 26, 2026 11:50
@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Optional test failure: The load-deadlock job failed on this PR (optimistic-scheduling=false). This check is non-mandatory and does not block merging, but may be worth investigating. View logs

Comment thread pkg/scheduling/v1/tenant_manager.go Outdated
Comment on lines +290 to +291
// notify the new manager so it schedules its waiting task now instead of waiting for its first tick.
c.notify(ctx)

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.

nit: we should consider doing this on concurrency manager instantiation instead? I think the manager has a notifier channel as well, on instantiation we can just put a message into that channel so that it runs immediately after startup? having to remember to notify each time we call newConcurrencyManager is poor design I think

Comment thread pkg/scheduling/v1/tenant_manager.go Outdated
Comment on lines +316 to +317
// mark this strategy as already managed so we don't try to acquire a lease for it below
delete(strategyIdsMap, c.strategy.ID)

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.

are we sure about this? I see immediately above this we're doing:

if _, ok := strategyIdsMap[c.strategy.ID]; !ok {
		continue
}

So it seems like we might be doing more processing as a result of deleting this entry. Perhaps we need two copies of the map?

Comment thread pkg/scheduling/v1/tenant_manager.go Outdated

// notify the new manager so it picks up already-queued work now instead of waiting for its first
// tick (matters on startup/rebalance). notify() is a non-blocking send, so it's fine under the lock.
c.notify(ctx)

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.

same comment as above/below

Comment on lines +64 to +68
replayEnabled bool
analyzeCronInterval time.Duration
signaler *signal.OLAPSignaler
tw *trigger.TriggerWriter
promGate *prometheus.Gate

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.

nit: is this linted properly?

@mnafees mnafees merged commit b4be7ad into main Jun 26, 2026
58 checks passed
@mnafees mnafees deleted the nafees/cold-start-concurrency-lease branch June 26, 2026 19:02
@gregfurman gregfurman mentioned this pull request Jun 29, 2026
2 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

engine Related to the core Hatchet engine

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants