Skip to content

Commit e55fd63

Browse files
authored
Make reader and writer buffers configurable (#2014)
* Make reader and writer buffers configurable Replace global sync pools with per-DB pools and make reader and writer buffers configurable. Default buffer sizes: 1Mb for reader buffer, 64Kb for writer buffer. Context: DB connections are pooled, usually there are not much of them available, so this is a resource used by goroutines, which have to wait for their turn in order to get a connection and use it before returning it back to the pool. Before getting into the waiting line every goroutine allocates a read and a write buffer from the sync.Pool of buffers. Currently hardcoded reader buffer size is 1Mb, so when 1000 goroutines wait in the queue, you get 1000Mb of buffers pre-allocated. So when an application unexpectedly gets a spike of traffic and all the database connections are being used, we don't get request timeouts as one could be expecting, the application is being OOM-killed instead. The patch addresses this issue by trading some allocations and (probably, though my benchmarks don't really show it) latency for the ability to serve more simultaneous connections. * Fix tests by using properly initialized Conn
1 parent a7fe379 commit e55fd63

File tree

10 files changed

+143
-77
lines changed

10 files changed

+143
-77
lines changed

base.go

+8-8
Original file line numberDiff line numberDiff line change
@@ -227,8 +227,8 @@ func (db *baseDB) ExecContext(c context.Context, query interface{}, params ...in
227227
}
228228

229229
func (db *baseDB) exec(ctx context.Context, query interface{}, params ...interface{}) (Result, error) {
230-
wb := pool.GetWriteBuffer()
231-
defer pool.PutWriteBuffer(wb)
230+
wb := db.pool.GetWriteBuffer()
231+
defer db.pool.PutWriteBuffer(wb)
232232

233233
if err := writeQueryMsg(wb, db.fmter, query, params...); err != nil {
234234
return nil, err
@@ -297,8 +297,8 @@ func (db *baseDB) QueryContext(c context.Context, model, query interface{}, para
297297
}
298298

299299
func (db *baseDB) query(ctx context.Context, model, query interface{}, params ...interface{}) (Result, error) {
300-
wb := pool.GetWriteBuffer()
301-
defer pool.PutWriteBuffer(wb)
300+
wb := db.pool.GetWriteBuffer()
301+
defer db.pool.PutWriteBuffer(wb)
302302

303303
if err := writeQueryMsg(wb, db.fmter, query, params...); err != nil {
304304
return nil, err
@@ -374,8 +374,8 @@ func (db *baseDB) copyFrom(
374374
) (res Result, err error) {
375375
var evt *QueryEvent
376376

377-
wb := pool.GetWriteBuffer()
378-
defer pool.PutWriteBuffer(wb)
377+
wb := db.pool.GetWriteBuffer()
378+
defer db.pool.PutWriteBuffer(wb)
379379

380380
if err := writeQueryMsg(wb, db.fmter, query, params...); err != nil {
381381
return nil, err
@@ -456,8 +456,8 @@ func (db *baseDB) copyTo(
456456
) (res Result, err error) {
457457
var evt *QueryEvent
458458

459-
wb := pool.GetWriteBuffer()
460-
defer pool.PutWriteBuffer(wb)
459+
wb := db.pool.GetWriteBuffer()
460+
defer db.pool.PutWriteBuffer(wb)
461461

462462
if err := writeQueryMsg(wb, db.fmter, query, params...); err != nil {
463463
return nil, err

base_test.go

+18-3
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import (
1212
)
1313

1414
/*
15-
The test is for testing the case that sending a cancel request when the timeout from connection comes earlier than ctx.Done().
15+
The test is for testing the case that sending a cancel request when the timeout from connection comes earlier than ctx.Done().
1616
*/
1717
func Test_baseDB_withConn(t *testing.T) {
1818
b := mockBaseDB{}
@@ -44,9 +44,10 @@ type mockPooler struct {
4444
}
4545

4646
func (m *mockPooler) NewConn(ctx context.Context) (*pool.Conn, error) {
47-
m.conn = &pool.Conn{ProcessID: 123, SecretKey: 234, Inited: true}
4847
m.mockConn = mockConn{}
49-
m.conn.SetNetConn(&m.mockConn)
48+
m.conn = pool.NewConn(&m.mockConn, pool.NewConnPool(&pool.Options{}))
49+
m.conn.ProcessID = 123
50+
m.conn.SecretKey = 234
5051
return m.conn, nil
5152
}
5253

@@ -83,6 +84,20 @@ func (m *mockPooler) Close() error {
8384
return nil
8485
}
8586

87+
func (m *mockPooler) GetWriteBuffer() *pool.WriteBuffer {
88+
return pool.NewWriteBuffer(1024)
89+
}
90+
91+
func (m *mockPooler) PutWriteBuffer(_ *pool.WriteBuffer) {
92+
}
93+
94+
func (m *mockPooler) GetReaderContext() *pool.ReaderContext {
95+
return pool.NewReaderContext(1024)
96+
}
97+
98+
func (m *mockPooler) PutReaderContext(_ *pool.ReaderContext) {
99+
}
100+
86101
type mockPGError struct {
87102
M map[byte]string
88103
}

internal/pool/conn.go

+8-6
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ var noDeadline = time.Time{}
1313
type Conn struct {
1414
netConn net.Conn
1515
rd *ReaderContext
16+
pool *ConnPool
1617

1718
ProcessID int32
1819
SecretKey int32
@@ -24,9 +25,10 @@ type Conn struct {
2425
Inited bool
2526
}
2627

27-
func NewConn(netConn net.Conn) *Conn {
28+
func NewConn(netConn net.Conn, pool *ConnPool) *Conn {
2829
cn := &Conn{
2930
createdAt: time.Now(),
31+
pool: pool,
3032
}
3133
cn.SetNetConn(netConn)
3234
cn.SetUsedAt(time.Now())
@@ -57,7 +59,7 @@ func (cn *Conn) LockReader() {
5759
if cn.rd != nil {
5860
panic("not reached")
5961
}
60-
cn.rd = NewReaderContext()
62+
cn.rd = NewReaderContext(cn.pool.opt.ReadBufferInitialSize)
6163
cn.rd.Reset(cn.netConn)
6264
}
6365

@@ -79,8 +81,8 @@ func (cn *Conn) WithReader(
7981

8082
rd := cn.rd
8183
if rd == nil {
82-
rd = GetReaderContext()
83-
defer PutReaderContext(rd)
84+
rd = cn.pool.GetReaderContext()
85+
defer cn.pool.PutReaderContext(rd)
8486

8587
rd.Reset(cn.netConn)
8688
}
@@ -97,8 +99,8 @@ func (cn *Conn) WithReader(
9799
func (cn *Conn) WithWriter(
98100
ctx context.Context, timeout time.Duration, fn func(wb *WriteBuffer) error,
99101
) error {
100-
wb := GetWriteBuffer()
101-
defer PutWriteBuffer(wb)
102+
wb := cn.pool.GetWriteBuffer()
103+
defer cn.pool.PutWriteBuffer(wb)
102104

103105
if err := fn(wb); err != nil {
104106
return err

internal/pool/pool.go

+46-7
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ type Pooler interface {
4242
Get(context.Context) (*Conn, error)
4343
Put(context.Context, *Conn)
4444
Remove(context.Context, *Conn, error)
45+
GetWriteBuffer() *WriteBuffer
46+
PutWriteBuffer(*WriteBuffer)
47+
GetReaderContext() *ReaderContext
48+
PutReaderContext(*ReaderContext)
4549

4650
Len() int
4751
IdleLen() int
@@ -54,12 +58,14 @@ type Options struct {
5458
Dialer func(context.Context) (net.Conn, error)
5559
OnClose func(*Conn) error
5660

57-
PoolSize int
58-
MinIdleConns int
59-
MaxConnAge time.Duration
60-
PoolTimeout time.Duration
61-
IdleTimeout time.Duration
62-
IdleCheckFrequency time.Duration
61+
PoolSize int
62+
MinIdleConns int
63+
ReadBufferInitialSize int
64+
WriteBufferInitialSize int
65+
MaxConnAge time.Duration
66+
PoolTimeout time.Duration
67+
IdleTimeout time.Duration
68+
IdleCheckFrequency time.Duration
6369
}
6470

6571
type ConnPool struct {
@@ -82,6 +88,9 @@ type ConnPool struct {
8288

8389
poolSize int
8490
idleConnsLen int
91+
92+
wbPool sync.Pool
93+
rbPool sync.Pool
8594
}
8695

8796
var _ Pooler = (*ConnPool)(nil)
@@ -93,6 +102,16 @@ func NewConnPool(opt *Options) *ConnPool {
93102
queue: make(chan struct{}, opt.PoolSize),
94103
conns: make([]*Conn, 0, opt.PoolSize),
95104
idleConns: make([]*Conn, 0, opt.PoolSize),
105+
wbPool: sync.Pool{
106+
New: func() interface{} {
107+
return NewWriteBuffer(opt.WriteBufferInitialSize)
108+
},
109+
},
110+
rbPool: sync.Pool{
111+
New: func() interface{} {
112+
return NewReaderContext(opt.ReadBufferInitialSize)
113+
},
114+
},
96115
}
97116

98117
p.connsMu.Lock()
@@ -182,7 +201,7 @@ func (p *ConnPool) dialConn(c context.Context, pooled bool) (*Conn, error) {
182201
return nil, err
183202
}
184203

185-
cn := NewConn(netConn)
204+
cn := NewConn(netConn, p)
186205
cn.pooled = pooled
187206
return cn, nil
188207
}
@@ -504,3 +523,23 @@ func (p *ConnPool) isStaleConn(cn *Conn) bool {
504523

505524
return false
506525
}
526+
527+
func (p *ConnPool) GetWriteBuffer() *WriteBuffer {
528+
wb := p.wbPool.Get().(*WriteBuffer)
529+
return wb
530+
}
531+
532+
func (p *ConnPool) PutWriteBuffer(wb *WriteBuffer) {
533+
wb.Reset()
534+
p.wbPool.Put(wb)
535+
}
536+
537+
func (p *ConnPool) GetReaderContext() *ReaderContext {
538+
rd := p.rbPool.Get().(*ReaderContext)
539+
return rd
540+
}
541+
542+
func (p *ConnPool) PutReaderContext(rd *ReaderContext) {
543+
rd.ColumnAlloc.Reset()
544+
p.rbPool.Put(rd)
545+
}

internal/pool/pool_single.go

+16
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,19 @@ func (p *SingleConnPool) IdleLen() int {
6161
func (p *SingleConnPool) Stats() *Stats {
6262
return &Stats{}
6363
}
64+
65+
func (p *SingleConnPool) GetWriteBuffer() *WriteBuffer {
66+
return p.pool.GetWriteBuffer()
67+
}
68+
69+
func (p *SingleConnPool) PutWriteBuffer(wb *WriteBuffer) {
70+
p.pool.PutWriteBuffer(wb)
71+
}
72+
73+
func (p *SingleConnPool) GetReaderContext() *ReaderContext {
74+
return p.pool.GetReaderContext()
75+
}
76+
77+
func (p *SingleConnPool) PutReaderContext(rd *ReaderContext) {
78+
p.pool.PutReaderContext(rd)
79+
}

internal/pool/pool_sticky.go

+16
Original file line numberDiff line numberDiff line change
@@ -200,3 +200,19 @@ func (p *StickyConnPool) IdleLen() int {
200200
func (p *StickyConnPool) Stats() *Stats {
201201
return &Stats{}
202202
}
203+
204+
func (p *StickyConnPool) GetWriteBuffer() *WriteBuffer {
205+
return p.pool.GetWriteBuffer()
206+
}
207+
208+
func (p *StickyConnPool) PutWriteBuffer(wb *WriteBuffer) {
209+
p.pool.PutWriteBuffer(wb)
210+
}
211+
212+
func (p *StickyConnPool) GetReaderContext() *ReaderContext {
213+
return p.pool.GetReaderContext()
214+
}
215+
216+
func (p *StickyConnPool) PutReaderContext(rd *ReaderContext) {
217+
p.pool.PutReaderContext(rd)
218+
}

internal/pool/reader.go

+1-22
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
package pool
22

3-
import (
4-
"sync"
5-
)
6-
73
type Reader interface {
84
Buffered() int
95

@@ -55,26 +51,9 @@ type ReaderContext struct {
5551
ColumnAlloc *ColumnAlloc
5652
}
5753

58-
func NewReaderContext() *ReaderContext {
59-
const bufSize = 1 << 20 // 1mb
54+
func NewReaderContext(bufSize int) *ReaderContext {
6055
return &ReaderContext{
6156
BufReader: NewBufReader(bufSize),
6257
ColumnAlloc: NewColumnAlloc(),
6358
}
6459
}
65-
66-
var readerPool = sync.Pool{
67-
New: func() interface{} {
68-
return NewReaderContext()
69-
},
70-
}
71-
72-
func GetReaderContext() *ReaderContext {
73-
rd := readerPool.Get().(*ReaderContext)
74-
return rd
75-
}
76-
77-
func PutReaderContext(rd *ReaderContext) {
78-
rd.ColumnAlloc.Reset()
79-
readerPool.Put(rd)
80-
}

internal/pool/write_buffer.go

+2-21
Original file line numberDiff line numberDiff line change
@@ -3,37 +3,18 @@ package pool
33
import (
44
"encoding/binary"
55
"io"
6-
"sync"
76
)
87

9-
const defaultBufSize = 65 << 10 // 65kb
10-
11-
var wbPool = sync.Pool{
12-
New: func() interface{} {
13-
return NewWriteBuffer()
14-
},
15-
}
16-
17-
func GetWriteBuffer() *WriteBuffer {
18-
wb := wbPool.Get().(*WriteBuffer)
19-
return wb
20-
}
21-
22-
func PutWriteBuffer(wb *WriteBuffer) {
23-
wb.Reset()
24-
wbPool.Put(wb)
25-
}
26-
278
type WriteBuffer struct {
289
Bytes []byte
2910

3011
msgStart int
3112
paramStart int
3213
}
3314

34-
func NewWriteBuffer() *WriteBuffer {
15+
func NewWriteBuffer(bufSize int) *WriteBuffer {
3516
return &WriteBuffer{
36-
Bytes: make([]byte, 0, defaultBufSize),
17+
Bytes: make([]byte, 0, bufSize),
3718
}
3819
}
3920

options.go

+24-6
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,14 @@ type Options struct {
9090
// but idle connections are still discarded by the client
9191
// if IdleTimeout is set.
9292
IdleCheckFrequency time.Duration
93+
// Connections read buffers stored in a sync.Pool to reduce allocations.
94+
// Using this option you can adjust the initial size of the buffer.
95+
// Default is 1 Mb.
96+
ReadBufferInitialSize int
97+
// Connections write buffers stored in a sync.Pool to reduce allocations.
98+
// Using this option you can adjust the initial size of the buffer.
99+
// Default is 64 Kb.
100+
WriteBufferInitialSize int
93101
}
94102

95103
func (opt *Options) init() {
@@ -164,6 +172,14 @@ func (opt *Options) init() {
164172
case 0:
165173
opt.MaxRetryBackoff = 4 * time.Second
166174
}
175+
176+
if opt.ReadBufferInitialSize == 0 {
177+
opt.ReadBufferInitialSize = 1048576 // 1Mb
178+
}
179+
180+
if opt.WriteBufferInitialSize == 0 {
181+
opt.WriteBufferInitialSize = 65536 // 64Kb
182+
}
167183
}
168184

169185
func env(key, defValue string) string {
@@ -318,11 +334,13 @@ func newConnPool(opt *Options) *pool.ConnPool {
318334
Dialer: opt.getDialer(),
319335
OnClose: terminateConn,
320336

321-
PoolSize: opt.PoolSize,
322-
MinIdleConns: opt.MinIdleConns,
323-
MaxConnAge: opt.MaxConnAge,
324-
PoolTimeout: opt.PoolTimeout,
325-
IdleTimeout: opt.IdleTimeout,
326-
IdleCheckFrequency: opt.IdleCheckFrequency,
337+
PoolSize: opt.PoolSize,
338+
MinIdleConns: opt.MinIdleConns,
339+
MaxConnAge: opt.MaxConnAge,
340+
PoolTimeout: opt.PoolTimeout,
341+
IdleTimeout: opt.IdleTimeout,
342+
IdleCheckFrequency: opt.IdleCheckFrequency,
343+
ReadBufferInitialSize: opt.ReadBufferInitialSize,
344+
WriteBufferInitialSize: opt.WriteBufferInitialSize,
327345
})
328346
}

0 commit comments

Comments
 (0)