Skip to content

Commit 893c590

Browse files
committed
new efficient growth in pool efficient taking into account idle connections.
1 parent faad920 commit 893c590

File tree

6 files changed

+572
-107
lines changed

6 files changed

+572
-107
lines changed

docs/pool-efficient-strategy.md

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
# Pool Efficient Strategy
2+
3+
## Overview
4+
5+
The `efficient` scaling strategy provides optimized pool growth behavior that only creates new connections when existing idle connections are exhausted. This addresses the inefficiency in the default `aggressive` strategy where the pool grows to the ceiling immediately on open.
6+
7+
## Problem Solved
8+
9+
The original pool behavior had these inefficiencies:
10+
11+
1. **Eager growth**: With `aggressive` strategy, pools grew to ceiling immediately on open
12+
2. **Unnecessary connections**: Created connections even when idle ones were available
13+
3. **Resource waste**: Maintained more connections than needed for typical workloads
14+
15+
## Solution: Efficient Strategy
16+
17+
### Behavior
18+
19+
- **Initial creation**: Creates connections only up to `floor` on pool open
20+
- **Conservative growth**: Only grows when work queue exceeds idle connections
21+
- **Incremental scaling**: Adds connections based on actual demand
22+
23+
### Usage
24+
25+
```javascript
26+
const pool = new sql.Pool({
27+
connectionString: '...',
28+
floor: 5, // Start with 5 connections
29+
ceiling: 20, // Maximum 20 connections
30+
scalingStrategy: 'efficient', // Use efficient strategy
31+
scalingIncrement: 3 // Grow by 3 when needed
32+
})
33+
```
34+
35+
### Comparison of Strategies
36+
37+
| Strategy | Initial Growth | Additional Growth | Use Case |
38+
|----------|----------------|------------------|----------|
39+
| `aggressive` | To ceiling immediately | None needed | High constant load |
40+
| `gradual` | To ceiling incrementally | Fixed increments | Predictable growth |
41+
| `exponential` | To floor, then exponential | By growth factor | Adaptive scaling |
42+
| `efficient` | To floor only | Only when needed | Variable/bursty load |
43+
44+
## Examples
45+
46+
### Scenario 1: Burst Workload
47+
```javascript
48+
// With efficient strategy
49+
const pool = new sql.Pool({
50+
floor: 5,
51+
ceiling: 20,
52+
scalingStrategy: 'efficient'
53+
})
54+
55+
// 1. Pool starts with 5 connections
56+
// 2. Submit 5 queries → uses existing connections, no growth
57+
// 3. Submit 10 queries → grows only when idle connections exhausted
58+
// 4. Later: idle connections reused for new work
59+
```
60+
61+
### Scenario 2: Variable Load
62+
```javascript
63+
// Perfect for applications with:
64+
// - Variable query load
65+
// - Periods of low activity
66+
// - Need to minimize connection overhead
67+
// - Want to reuse existing connections efficiently
68+
```
69+
70+
## Benefits
71+
72+
1. **Resource efficiency**: Uses fewer connections for typical workloads
73+
2. **Better connection reuse**: Maximizes use of existing idle connections
74+
3. **Reduced overhead**: Lower memory and connection overhead
75+
4. **Backward compatible**: Other strategies remain unchanged
76+
77+
## Migration
78+
79+
Existing code continues to work unchanged. To opt into efficient behavior:
80+
81+
```javascript
82+
// Old (aggressive by default)
83+
const pool = new sql.Pool({ connectionString, ceiling: 20 })
84+
85+
// New (efficient)
86+
const pool = new sql.Pool({
87+
connectionString,
88+
floor: 5,
89+
ceiling: 20,
90+
scalingStrategy: 'efficient'
91+
})
92+
```
93+
94+
## Testing
95+
96+
The efficient strategy is thoroughly tested to ensure:
97+
- Only grows when idle connections are exhausted
98+
- Respects floor and ceiling limits
99+
- Maintains backward compatibility
100+
- Handles burst workloads correctly
101+
102+
See `test/pool-efficient-strategy.test.js` for comprehensive test coverage.

lib/index.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,8 +187,9 @@ declare namespace MsNodeSqlV8 {
187187
* - 'aggressive': immediately create all needed connections (default)
188188
* - 'gradual': create connections in fixed increments
189189
* - 'exponential': create connections based on exponential growth factor
190+
* - 'efficient': only grow when idle connections are exhausted
190191
*/
191-
scalingStrategy?: 'aggressive' | 'gradual' | 'exponential'
192+
scalingStrategy?: 'aggressive' | 'gradual' | 'exponential' | 'efficient'
192193

193194
/**
194195
* number of connections to create at once when using 'gradual' strategy

lib/pool.js

Lines changed: 62 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -434,28 +434,49 @@ const poolModule = (() => {
434434
return
435435
}
436436

437-
// First, promote any unpaused items from pause queue
438-
promotePause()
439-
440-
// Process work with existing idle connections
441-
while (workQueue.length > 0 && idle.length > 0) {
442-
const work = workQueue.pop()
443-
if (work.poolNotifier.isPendingCancel()) {
444-
_this.emit('debug', `query work id = ${work.id} has been cancelled waiting in pool to execute, workQueue = ${workQueue.length}`)
445-
doneFree(work.poolNotifier)
446-
} else if (work.poolNotifier.isPaused()) {
447-
pause.unshift(work)
448-
} else {
449-
const description = checkout('work')
450-
item(description, work)
437+
// If using 'efficient' strategy, only grow when work queue exceeds idle connections
438+
if (options.scalingStrategy === 'efficient') {
439+
// First process work with existing idle connections
440+
promotePause()
441+
while (workQueue.length > 0 && idle.length > 0) {
442+
const work = workQueue.pop()
443+
if (work.poolNotifier.isPendingCancel()) {
444+
_this.emit('debug', `query work id = ${work.id} has been cancelled waiting in pool to execute, workQueue = ${workQueue.length}`)
445+
doneFree(work.poolNotifier)
446+
} else if (work.poolNotifier.isPaused()) {
447+
pause.unshift(work)
448+
} else {
449+
const description = checkout('work')
450+
item(description, work)
451+
}
451452
}
452-
}
453-
454-
// Only grow the pool if we still have work queued and no idle connections
455-
if (workQueue.length > 0 && idle.length === 0) {
456-
_this.emit('debug', `crank needs to grow pool: workQueue = ${workQueue.length}, idle = ${idle.length}, busy = ${busyConnectionCount}`)
453+
454+
// Only grow if we still have work queued and no idle connections
455+
if (workQueue.length > 0 && idle.length === 0) {
456+
const existing = idle.length + busyConnectionCount + pendingCreates + parkingConnectionCount
457+
if (existing < options.ceiling) {
458+
_this.emit('debug', `efficient crank growing: workQueue = ${workQueue.length}, idle = ${idle.length}, busy = ${busyConnectionCount}`)
459+
void grow().then(() => {
460+
// Process any remaining work after growth
461+
promotePause()
462+
while (workQueue.length > 0 && idle.length > 0) {
463+
const work = workQueue.pop()
464+
if (work.poolNotifier.isPendingCancel()) {
465+
_this.emit('debug', `query work id = ${work.id} has been cancelled waiting in pool to execute, workQueue = ${workQueue.length}`)
466+
doneFree(work.poolNotifier)
467+
} else if (work.poolNotifier.isPaused()) {
468+
pause.unshift(work)
469+
} else {
470+
const description = checkout('work')
471+
item(description, work)
472+
}
473+
}
474+
})
475+
}
476+
}
477+
} else {
478+
// Original behavior for backward compatibility
457479
void grow().then(() => {
458-
// After growing, process any remaining work
459480
promotePause()
460481
while (workQueue.length > 0 && idle.length > 0) {
461482
const work = workQueue.pop()
@@ -664,10 +685,11 @@ const poolModule = (() => {
664685
return
665686
}
666687

667-
// For initial pool creation, grow to floor if not yet reached
668-
const targetSize = opened ? options.ceiling : options.floor
669-
if (existing >= targetSize) {
670-
return
688+
// For efficient strategy, limit initial growth to floor unless there's work waiting
689+
if (options.scalingStrategy === 'efficient' && !opened) {
690+
if (existing >= options.floor && workQueue.length === 0) {
691+
return
692+
}
671693
}
672694

673695
function connectionOptions (c) {
@@ -685,9 +707,24 @@ const poolModule = (() => {
685707

686708
// Calculate how many connections to create based on strategy
687709
let connectionsToCreate = 0
688-
const needed = targetSize - existing
710+
const needed = options.ceiling - existing
689711

690712
switch (options.scalingStrategy) {
713+
case 'efficient': {
714+
// For efficient strategy, create conservatively
715+
if (!opened) {
716+
// During initial open, only create up to floor
717+
connectionsToCreate = Math.min(options.floor - existing, needed)
718+
} else {
719+
// After opening, grow incrementally based on work queue
720+
const queuedWork = workQueue.length
721+
const additionalNeeded = Math.max(0, queuedWork - idle.length)
722+
connectionsToCreate = Math.min(additionalNeeded, needed, options.scalingIncrement || 2)
723+
}
724+
connectionsToCreate = Math.max(0, connectionsToCreate)
725+
break
726+
}
727+
691728
case 'gradual':
692729
// Create a fixed increment of connections
693730
connectionsToCreate = Math.min(options.scalingIncrement, needed)

0 commit comments

Comments
 (0)