DEV Community

Cover image for Concurrent Resource Scheduler v1.2.3: Sharded Priority Heaps Under Concurrent Load
Feroz
Feroz

Posted on

Concurrent Resource Scheduler v1.2.3: Sharded Priority Heaps Under Concurrent Load

GitHub: https://github.com/phero20/concurrent-resource-scheduler

Go documentation: https://pkg.go.dev/github.com/phero20/concurrent-resource-scheduler

Concurrent Resource Scheduler v1.2.3: Sharded Priority Heaps Under Concurrent Load

A resource scheduler sounds simple until many goroutines start competing for a small number of resources.

At that point, the problem stops being:

"How do I pick the next item?"

and becomes:

"How do I pick the right item while thousands of goroutines are acquiring, releasing, updating, and observing resources concurrently?"

That is the problem I built Concurrent Resource Scheduler (CRS) to solve.

CRS is a domain-agnostic Go library for managing reusable resources with:

  • sharded priority heaps
  • concurrent-safe resource lookup
  • configurable acquisition strategies
  • shared and exclusive acquisition
  • affinity routing
  • resource lifecycle management
  • cooldown extensions
  • asynchronous events
  • optional Prometheus telemetry

The current release is v1.2.3.

This post is a release-focused technical deep dive into the architecture, the reasoning behind it, and the actual measurements from the current release.


First: What Problem Are We Actually Solving?

Imagine a service with a pool of reusable resources.

Those resources could be:

  • API keys
  • database connections
  • GPU workers
  • proxy endpoints
  • backend workers
  • LLM providers
  • service instances
  • connection pools
  • rate-limited accounts

Now imagine:

             10,000 concurrent requests
                       │
                       ▼
              ┌─────────────────┐
              │    Scheduler    │
              └────────┬────────┘
                       │
          ┌────────────┼────────────┐
          ▼            ▼            ▼
       Resource A   Resource B   Resource C
Enter fullscreen mode Exit fullscreen mode

The scheduler needs to answer several questions at once:

  1. Which resource should this request receive?
  2. Is that resource active?
  3. Is it already exclusively acquired?
  4. Can it be shared?
  5. Which resource has the best priority?
  6. Should affinity influence the selection?
  7. Has a resource entered cooldown?
  8. What happens when it is released?
  9. What happens when its priority changes?
  10. How do we observe all of this without putting telemetry directly on the hot path?

A simple slice and mutex can answer some of these questions.

The difficult part is doing all of them concurrently and predictably.


The Obvious Design: One Slice, One Mutex

The first design most people naturally reach for is something like:

type Scheduler struct {
    mu        sync.Mutex
    resources []*Resource
}
Enter fullscreen mode Exit fullscreen mode

Then acquisition becomes:

func (s *Scheduler) Acquire() (*Resource, error) {
    s.mu.Lock()
    defer s.mu.Unlock()

    // Scan resources.
    // Find the best available resource.
    // Mark it acquired.
    // Return it.

    return resource, nil
}
Enter fullscreen mode Exit fullscreen mode

For a small pool, this is completely reasonable.

The problem appears when concurrency and resource count grow.

Suppose:

Resources:           10,000
Concurrent goroutines: 10,000
Enter fullscreen mode Exit fullscreen mode

Every operation now enters the same synchronization boundary:

                 GLOBAL MUTEX
                      │
       ┌──────────────┼──────────────┐
       ▼              ▼              ▼
   goroutine       goroutine      goroutine
       │              │              │
       └──────────────┼──────────────┘
                      ▼
                    WAIT
Enter fullscreen mode Exit fullscreen mode

Even if the actual operation only concerns a small part of the resource pool, the lock protects everything.

There is another problem.

If resources are stored in a slice and the scheduler has to find the best candidate, acquisition can require a linear scan:

O(N)
Enter fullscreen mode Exit fullscreen mode

Now the same global lock is protecting an operation whose amount of work grows with the number of resources.

That is the combination I wanted to avoid.


The Main Architectural Decision

The core idea behind CRS is:

Partition the active resource pool into independently synchronized shards.

Instead of:

                    ONE LOCK
                       │
                 ONE BIG POOL
Enter fullscreen mode Exit fullscreen mode

CRS uses:

                         Scheduler
                             │
              ┌──────────────┼──────────────┐
              ▼              ▼              ▼
           Shard 0        Shard 1        Shard N
              │              │              │
            Heap           Heap           Heap
              │              │              │
            Mutex          Mutex          Mutex
Enter fullscreen mode Exit fullscreen mode

Each shard owns a priority heap.

Each heap has its own synchronization boundary.

That gives us a much more useful concurrency model:

goroutine A ──► shard 0 ──► heap 0
goroutine B ──► shard 1 ──► heap 1
goroutine C ──► shard 2 ──► heap 2
Enter fullscreen mode Exit fullscreen mode

The goal is not to claim that sharding magically eliminates contention.

It doesn't.

The goal is to reduce the amount of unrelated work competing for the same lock.

That distinction matters.


Why a Priority Heap?

Sharding solves one problem:

How do we reduce contention?

We still need to solve another:

How do we efficiently maintain resource priority?

A slice makes this straightforward but potentially expensive:

resources:
[ A, B, C, D, E, F, G, ... ]

Find minimum priority:
scan everything
Enter fullscreen mode Exit fullscreen mode

A priority heap gives us an ordered structure where the highest-priority candidate can be accessed from the heap root.

Conceptually:

             best resource
                  │
                  ▼
               [ 10 ]
              /      \
           [ 20 ]   [ 30 ]
           /   \
        [40]   [50]
Enter fullscreen mode Exit fullscreen mode

So CRS combines the two ideas:

                 RESOURCE POOL
                       │
             ┌─────────┴─────────┐
             ▼                   ▼
          Sharding            Priority
             │                   │
             ▼                   ▼
        Lower lock           Heap ordering
         contention          / fast candidate
Enter fullscreen mode Exit fullscreen mode

This is the central design tradeoff of the project.


The Scheduler Is More Than a Heap

One thing I learned while building CRS is that the heap is actually only one part of the problem.

The scheduler has several independent concerns:

                         CRS
                          │
       ┌──────────────────┼──────────────────┐
       │                  │                  │
       ▼                  ▼                  ▼
   Acquisition          State             Lookup
    Strategy          Lifecycle             Map
       │                  │                  │
       ▼                  ▼                  ▼
    Adaptive          Active/Inactive       O(1)
    Weighted          Acquire/Release       lookup
    Round Robin       Include/Exclude
    Affinity
Enter fullscreen mode Exit fullscreen mode

Then there are extensions:

                Event Dispatcher
                       │
             ┌─────────┴─────────┐
             ▼                   ▼
          Cooldown           Telemetry
                                 │
                                 ▼
                             Prometheus
Enter fullscreen mode Exit fullscreen mode

Keeping these responsibilities separated makes the implementation easier to reason about and lets the core remain domain-agnostic.


Resource Lookup Is a Separate Problem

Priority ordering tells us:

"Which resource should be considered first?"

But APIs also need direct resource lookup.

For example:

resource, err := sched.Get("worker-42")
Enter fullscreen mode Exit fullscreen mode

We don't want to search every heap for that.

CRS therefore maintains a lookup structure alongside the active heap shards.

Conceptually:

                resource ID
                     │
                     ▼
              ┌─────────────┐
              │ Lookup Map  │
              └──────┬──────┘
                     │
                     ▼
                  node
                     │
             ┌───────┴───────┐
             ▼               ▼
          resource          shard
Enter fullscreen mode Exit fullscreen mode

This gives the scheduler a useful separation:

  • heap → candidate ordering
  • lookup map → direct identity lookup

These are different access patterns, so they shouldn't be forced into the same data structure.


Acquisition Strategies Are Pluggable

Another design decision was not to hard-code one definition of "best resource."

Different systems want different behavior.

For example:

Adaptive

Choose resources based on observed scheduling state.

Weighted

Some resources should receive more traffic than others.

Round Robin

Distribute acquisitions sequentially.

Affinity

Prefer a resource or shard associated with some identifier.

That means the scheduler's core doesn't need to know why an application considers one resource better than another.

The application provides the policy.

The scheduler provides the concurrency-safe machinery around that policy.


Shared vs Exclusive Acquisition

This distinction is especially important.

A resource might support:

Shared
Enter fullscreen mode Exit fullscreen mode

where multiple callers can use it simultaneously.

Or:

Exclusive
Enter fullscreen mode Exit fullscreen mode

where only one caller can own it at a time.

These are not just two API names.

They produce different synchronization behavior.

For example, under shared acquisition, the scheduler can inspect the best candidate without removing it from the heap.

Conceptually:

Shared:

heap
  │
  ▼
peek candidate
  │
  ▼
use resource
Enter fullscreen mode Exit fullscreen mode

Whereas exclusive acquisition may need to remove the candidate from the active scheduling position:

Exclusive:

heap
  │
  ▼
pop candidate
  │
  ▼
exclusive owner
Enter fullscreen mode Exit fullscreen mode

This distinction is also reflected in the documentation and complexity model.


Resource Lifecycle

Resources don't simply exist or disappear.

They move through states.

A simplified lifecycle looks like:

              ┌──────────────┐
              │    ACTIVE    │
              └──────┬───────┘
                     │
            acquire / exclude
                     │
                     ▼
              ┌──────────────┐
              │   INACTIVE   │
              └──────┬───────┘
                     │
               include / release
                     │
                     ▼
              ┌──────────────┐
              │    ACTIVE    │
              └──────────────┘
Enter fullscreen mode Exit fullscreen mode

There are also terminal operations such as removal and shutdown.

The important part is that the scheduler must maintain consistent state while these operations happen concurrently.

That's why lifecycle management is part of the scheduler design rather than an afterthought.


Cooldown Is an Extension, Not Core Scheduling Logic

Cooldown is a good example of why the architecture is modular.

Suppose a resource fails or needs temporary exclusion:

Resource
   │
   ▼
Cooldown
   │
   ├── removed from active scheduling
   │
   └── restored after duration
Enter fullscreen mode Exit fullscreen mode

The core scheduler shouldn't need to understand every possible reason a resource temporarily leaves the pool.

The cooldown extension coordinates with the scheduler's lifecycle controller.

This was also one of the areas tightened in v1.2.3: the cooldown documentation and example now use the correct LifecycleController wrapper pattern instead of trying to initialize the cooldown manager before the scheduler exists.

That ordering matters because the scheduler is the object that ultimately owns the resource lifecycle.


Why v1.2.3 Exists

v1.2.3 is not a giant feature release.

It is a correctness, consistency, and release-hardening release.

The main fixes include:

Cooldown initialization documentation

The cooldown extension's initialization example was corrected to use the proper lifecycle-controller wrapper pattern.

This avoids the circular initialization problem of trying to construct a component that needs the scheduler before the scheduler has been created.

Cooldown example timing

The cooldown example was adjusted so it does not immediately race an asynchronous exclusion event.

The example now demonstrates the intended lifecycle deterministically.

Benchmark package boundary

The scheduler benchmark file was moved to the black-box package boundary:

package scheduler_test
Enter fullscreen mode Exit fullscreen mode

because it only relies on exported APIs.

That makes the benchmark follow the same testing convention as the rest of the package.

Documentation consistency

README and API documentation were updated to match the implementation, including error documentation and complexity behavior.

Prometheus extension

The optional Prometheus module is also aligned to:

v1.2.3
Enter fullscreen mode Exit fullscreen mode

while remaining a separate nested Go module.


The Numbers: Actual v1.2.3 Microbenchmarks

Architecture diagrams are useful.

Numbers are better.

I ran the current benchmark suite with:

go test -run="^$" -bench="." -benchmem ./...
Enter fullscreen mode Exit fullscreen mode

The measurements below were collected locally on:

OS:   Windows
Arch: amd64
CPU:  AMD Ryzen 5 6600H with Radeon Graphics
Enter fullscreen mode Exit fullscreen mode

These are local measurements, not universal performance guarantees.


Acquire Strategy Benchmarks

The current release produced the following verified measurements:

Strategy Select/GetShard Cost
ConsistentHashRing.GetShard 7.2 ns
WeightedStrategy.Select 19.1 ns
AdaptiveStrategy.Select 27.0 ns

The strategy selection costs are all in the low-nanosecond range on this machine. The scheduler benchmarks below separately report allocation counts for the larger operations.


Scheduler Hot-Path Benchmarks

The scheduler benchmarks are more representative of actual scheduler operations.

Operation HeapCount=1 HeapCount=8 HeapCount=32 Allocs/op
Add 591.9 ns 836.2 ns 718.5 ns 3
Update 280.6 ns 228.9 ns 200.4 ns 1
BatchAdd (1,000 resources) 308.1 µs 351.3 µs 331.1 µs ~1,100
Acquire (Shared, Sequential) 12.36 ns 11.52 ns 11.23 ns 0
Acquire (Shared, Parallel) 63.14 ns 16.80 ns 17.99 ns 0
Acquire + Release (Exclusive) 241.2 ns 249.2 ns 208.8 ns 0

There is also a parallel acquisition benchmark:

Benchmark Heap count ns/op B/op allocs/op
AcquireSharedParallel 1 63.14 0 0
AcquireSharedParallel 8 16.80 0 0
AcquireSharedParallel 32 17.99 0 0

The parallel benchmark is particularly useful when thinking about the sharding architecture.

On this machine and workload, moving from a single heap to multiple heaps reduced the measured AcquireSharedParallel time substantially:

HeapCount=1   63.14 ns/op
HeapCount=8   16.80 ns/op
HeapCount=32  17.99 ns/op
Enter fullscreen mode Exit fullscreen mode

That is one workload on one machine, not a universal scaling law.

But it is exactly the kind of behavior the architecture is intended to make measurable.


Why We Benchmark Multiple Heap Counts

It would be easy to benchmark only:

HeapCount = 1
Enter fullscreen mode Exit fullscreen mode

and stop there.

That wouldn't tell us much about the reason for sharding.

Instead, the scheduler benchmarks compare:

HeapCount = 1
HeapCount = 8
HeapCount = 32
Enter fullscreen mode Exit fullscreen mode

This gives us a way to observe how the synchronization structure behaves as the number of shards changes.

The expected tradeoff is not:

"More shards are always faster."

It is:

"The right number of shards depends on workload, resource count, contention, and scheduling strategy."

More shards also mean more structures to manage.

So sharding is a tuning dimension, not a magic constant.


Batch Operations Have a Different Cost Profile

The benchmark suite also measures batch insertion:

Benchmark Heap count ns/op B/op allocs/op
BatchAdd 1 308.1 µs 283,715 1,042
BatchAdd 8 351.3 µs 283,530 1,095
BatchAdd 32 331.1 µs 282,314 1,223

This is important because it prevents cherry-picking only the fastest numbers.

AcquireShared is extremely cheap in the measured workload.

BatchAdd is much more expensive.

That's expected.

Adding a large batch involves substantially more work than peeking at an already-populated scheduling structure.

A useful benchmark suite should expose those differences instead of presenting one "magic" performance number.


Validation Is More Important Than a Single Benchmark

A concurrency library can produce beautiful microbenchmarks and still be broken.

That's why v1.2.3 was validated with multiple layers.

Normal tests

go test ./...
Enter fullscreen mode Exit fullscreen mode

Result:

PASS
Enter fullscreen mode Exit fullscreen mode

All tested packages completed successfully.

Race detector

go test -race ./...
Enter fullscreen mode Exit fullscreen mode

Result:

PASS
Enter fullscreen mode Exit fullscreen mode

No data races were reported.

Static analysis

go vet ./...
Enter fullscreen mode Exit fullscreen mode

Result:

PASS
Enter fullscreen mode Exit fullscreen mode

Formatting

gofmt -l .
Enter fullscreen mode Exit fullscreen mode

Result:

(no output)
Enter fullscreen mode Exit fullscreen mode

The working tree is therefore clean according to gofmt.


Why the Race Detector Matters

For a concurrency-heavy library, this command is not optional validation theater:

go test -race ./...
Enter fullscreen mode Exit fullscreen mode

A normal test can pass while a race exists.

The race detector instruments memory accesses and can expose unsafe concurrent access that ordinary functional assertions don't catch.

It does not prove that a concurrent system is mathematically bug-free.

But it is one of the most important tools available for catching a class of real concurrency bugs.

For v1.2.3, the race-enabled test suite completed without reported races.


The Load-Test Question

Microbenchmarks answer:

"How quickly does this small operation execute?"

A load test asks a different question:

"What happens when the whole scheduler is placed inside a realistic concurrent workload?"

For CRS, the dedicated load-test harness models things such as:

  • concurrent workers
  • resource pools
  • backend processing delay
  • request duration
  • failures
  • cancellation
  • resource utilization
  • acquisition latency
  • total latency
  • throughput
  • burst traffic
  • cooldown behavior

The distinction is important.

A benchmark like:

25 ns/op
Enter fullscreen mode Exit fullscreen mode

does not mean a real request takes 25 ns.

A real request also includes:

scheduler
   +
application work
   +
network
   +
backend
   +
serialization
   +
other system costs
Enter fullscreen mode Exit fullscreen mode

So I treat microbenchmarks and load tests as different measurements.


10,000 Concurrent Workers

One of the release validation workloads uses:

10,000 concurrent workers
Enter fullscreen mode Exit fullscreen mode

This is useful because it puts the scheduler under a very different kind of pressure from a small benchmark.

The important question isn't:

"Can Go start 10,000 goroutines?"

Of course it can.

The question is:

"Can the resource-management layer maintain correct lifecycle and acquisition behavior while thousands of workers continuously compete for a small pool?"

That is where the sharded architecture, lookup synchronization, acquisition strategies, and lifecycle rules interact.

For the load-test results themselves, I treat the numbers as workload-specific measurements rather than claiming they represent every deployment.

The load-test harness also separates scheduler-side failures from simulated backend failures, because those are operationally different failure modes.


A Failure Is Not Necessarily a Scheduler Failure

This distinction is easy to lose in a load test.

Imagine:

10,000 workers
       │
       ▼
    Scheduler
       │
       ▼
    Resource
       │
       ▼
 Backend request
       │
       ▼
    FAILURE
Enter fullscreen mode Exit fullscreen mode

The backend failed.

That doesn't mean:

scheduler failed
Enter fullscreen mode Exit fullscreen mode

Similarly:

scheduler acquire failure
Enter fullscreen mode Exit fullscreen mode

is not the same thing as:

backend failure
Enter fullscreen mode Exit fullscreen mode

A serious load test should keep these categories separate.

That's why the harness tracks acquisition behavior, backend behavior, release behavior, and timeout/cancellation behavior independently.


Why This Matters for Resource Pools

Consider four backend resources:

backend-1
backend-2
backend-3
backend-4
Enter fullscreen mode Exit fullscreen mode

and:

10,000 concurrent workers
Enter fullscreen mode Exit fullscreen mode

You cannot turn four resources into 10,000 concurrent backend operations just because there are 10,000 goroutines.

If the acquisition policy is exclusive, the resource pool remains the bottleneck:

10,000 workers
      │
      ▼
 ┌───────────┐
 │ Scheduler │
 └─────┬─────┘
       │
       ▼
  4 resources
Enter fullscreen mode Exit fullscreen mode

That is not a failure.

That's exactly what resource scheduling is supposed to enforce.


Domain-Agnostic by Design

The scheduler doesn't know whether a resource is:

type APIKey struct {
    ID string
}
Enter fullscreen mode Exit fullscreen mode

or:

type GPUWorker struct {
    ID string
}
Enter fullscreen mode Exit fullscreen mode

or:

type DatabaseReplica struct {
    ID string
}
Enter fullscreen mode Exit fullscreen mode

The application provides the resource type and the functions needed to identify and compare it.

For example:

compare := func(a, b *Worker) int {
    if a.Priority < b.Priority {
        return -1
    }

    if a.Priority > b.Priority {
        return 1
    }

    return 0
}

keyFunc := func(w *Worker) string {
    return w.ID
}
Enter fullscreen mode Exit fullscreen mode

The scheduler doesn't need to understand what Priority means.

It only needs a consistent comparison function.

That's what makes the library reusable across domains.


A Minimal v1.2.3 Example

The basic flow remains intentionally small:

package main

import (
    "fmt"
    "log"

    "github.com/phero20/concurrent-resource-scheduler/config"
    "github.com/phero20/concurrent-resource-scheduler/scheduler"
)

type Worker struct {
    ID       string
    Priority int
}

func main() {
    compare := func(a, b *Worker) int {
        if a.Priority < b.Priority {
            return -1
        }

        if a.Priority > b.Priority {
            return 1
        }

        return 0
    }

    keyFunc := func(w *Worker) string {
        return w.ID
    }

    cfg := config.Config[*Worker, string]{
        HeapCount: 8,
        Comparator: compare,
        KeyFunc:    keyFunc,
    }

    sched, err := scheduler.New(cfg)
    if err != nil {
        log.Fatal(err)
    }

    defer sched.Shutdown()

    _ = sched.Add(&Worker{
        ID:       "worker-1",
        Priority: 10,
    })

    _ = sched.Add(&Worker{
        ID:       "worker-2",
        Priority: 20,
    })

    resource, err := sched.Acquire()
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println("Acquired:", resource.ID)
}
Enter fullscreen mode Exit fullscreen mode

The important thing is how little domain logic is inside the scheduler.

The application defines:

Resource type
Key function
Comparator
Configuration
Enter fullscreen mode Exit fullscreen mode

CRS manages the rest.


What v1.2.3 Changed Technically

The release isn't about replacing the architecture.

It is about tightening the implementation and making the public surface accurately represent what the implementation already does.

The release includes:

1. Cooldown initialization correction

The cooldown manager now has documentation and examples that correctly reflect its dependency on a lifecycle controller.

2. Cooldown example stabilization

The example accounts for asynchronous event dispatch so the demonstration is deterministic instead of racing an event-driven state transition.

3. Benchmark boundary cleanup

The scheduler benchmark uses:

package scheduler_test
Enter fullscreen mode Exit fullscreen mode

rather than relying on internal package access.

4. Documentation corrections

The README and API documentation were aligned with the actual implementation, including exported errors and shared/exclusive complexity behavior.

5. Prometheus module alignment

The optional Prometheus extension is tagged:

extensions/prometheus/v1.2.3
Enter fullscreen mode Exit fullscreen mode

and references the corresponding core release.


What I Don't Want These Numbers to Mean

This is probably the most important disclaimer in the entire post.

I don't want to say:

"CRS is 10x faster than every mutex-based scheduler."

I haven't proven that.

I don't want to say:

"CRS handles 37,000 requests/sec in production."

A workload-specific test is not a production guarantee.

I don't want to say:

"32 shards is always optimal."

It isn't.

The benchmarks show what happened under a particular machine and workload.

The architecture explains why those measurements are interesting.

That's a much more useful claim.


When Sharding Helps

Sharding is most interesting when:

many concurrent operations
          +
multiple independent resource groups
          +
shared scheduling state
Enter fullscreen mode Exit fullscreen mode

It can reduce contention by allowing unrelated operations to work against different synchronization boundaries.

But it introduces its own tradeoffs:

  • more heaps
  • more bookkeeping
  • more complex selection logic
  • shard-distribution decisions
  • potentially uneven workloads if the routing strategy is poor

So the design isn't:

"Sharding is always better."

It's:

"Sharding is a useful way to control contention when the workload benefits from partitioning."


Why I Chose This Architecture

The design can be summarized as four decisions:

1. Heap
   ↓
efficient priority ordering

2. Sharding
   ↓
reduce shared lock contention

3. Lookup map
   ↓
fast direct resource access

4. Pluggable strategies
   ↓
separate scheduling policy from
resource-management mechanics
Enter fullscreen mode Exit fullscreen mode

Then the lifecycle/event system sits around those primitives:

                Scheduler Core
                      │
       ┌──────────────┼──────────────┐
       ▼              ▼              ▼
     Heap           Lookup         Policy
       │              │              │
       └──────────────┼──────────────┘
                      │
                      ▼
                  Lifecycle
                      │
              ┌───────┴───────┐
              ▼               ▼
           Cooldown        Events
                              │
                              ▼
                          Telemetry
Enter fullscreen mode Exit fullscreen mode

That separation is the part I'm most interested in continuing to improve.


v1.2.3 Release Checklist

Before publishing this release, I wanted the project to pass more than just a version bump.

The current validation includes:

go test ./...
Enter fullscreen mode Exit fullscreen mode
go test -race ./...
Enter fullscreen mode Exit fullscreen mode
go vet ./...
Enter fullscreen mode Exit fullscreen mode
gofmt -l .
Enter fullscreen mode Exit fullscreen mode

The benchmark suite was also run with:

go test -run="^$" -bench="." -benchmem ./...
Enter fullscreen mode Exit fullscreen mode

And the release artifacts are available through the Go module ecosystem:

github.com/phero20/concurrent-resource-scheduler@v1.2.3

github.com/phero20/concurrent-resource-scheduler/extensions/prometheus@v1.2.3
Enter fullscreen mode Exit fullscreen mode

The core module targets Go 1.22+.

The optional Prometheus extension is a separate module targeting the newer Go toolchain used by that extension.


Where This Project Goes Next

v1.2.3 is a good point to stop and evaluate the architecture under more workloads.

The areas I'm particularly interested in are:

  • larger resource pools
  • different shard counts
  • different acquire strategies
  • mixed shared/exclusive traffic
  • heavier update workloads
  • affinity-heavy workloads
  • more aggressive cooldown behavior
  • longer-running stress tests
  • different CPU architectures

The goal isn't simply to produce a bigger benchmark number.

The goal is to understand where the scheduler's architecture helps, where it doesn't, and what tradeoffs become visible at scale.


Final Thoughts

Building a concurrent scheduler taught me that the difficult part isn't implementing:

Acquire()
Enter fullscreen mode Exit fullscreen mode

The difficult part is everything around it.

You need to coordinate:

                 RESOURCE SCHEDULING

                      Priority
                         │
                         ▼
Concurrency ───────► Scheduler ◄────── Acquisition Policy
                         │
             ┌───────────┼───────────┐
             ▼           ▼           ▼
           Lookup     Lifecycle    Events
             │           │           │
             ▼           ▼           ▼
          O(1) map    Active/     Telemetry
                      Inactive        │
                                     ▼
                                 Prometheus
Enter fullscreen mode Exit fullscreen mode

That is what CRS is trying to provide.

Not just a priority queue.

Not just a resource pool.

But a reusable concurrency layer where:

  • resource ordering
  • acquisition policy
  • shard synchronization
  • direct lookup
  • shared/exclusive semantics
  • lifecycle state
  • cooldowns
  • events
  • observability

can exist as separate pieces without forcing the application to rebuild the entire system.

v1.2.3 is the current release.

If you're building an LLM gateway, proxy pool, database router, GPU worker pool, API-key manager, or another system where many concurrent requests compete for reusable resources, I'd genuinely like to hear how you're solving it.


Project

Concurrent Resource Scheduler

GitHub: https://github.com/phero20/concurrent-resource-scheduler

Go documentation: https://pkg.go.dev/github.com/phero20/concurrent-resource-scheduler

Current release:

v1.2.3
Enter fullscreen mode Exit fullscreen mode

Optional Prometheus extension:

extensions/prometheus/v1.2.3
Enter fullscreen mode Exit fullscreen mode

If you find the project useful, a GitHub star is appreciated.

If you find a concurrency bug, even better: open an issue.

The most useful validation for a concurrency library isn't another diagram.

It's putting it under a workload the author didn't design for.

Top comments (0)