Skip to content

Commit bf53552

Browse files
committed
fix: major performance issues with bytea performance brianc#2240
1 parent 64c78b0 commit bf53552

File tree

2 files changed

+94
-54
lines changed

2 files changed

+94
-54
lines changed

packages/pg-protocol/src/parser.ts

+91-37
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,18 @@ const enum MessageCodes {
7373

7474
export type MessageCallback = (msg: BackendMessage) => void
7575

76+
interface CombinedBuffer {
77+
combinedBuffer: Buffer
78+
combinedBufferOffset: number
79+
combinedBufferLength: number
80+
combinedBufferFullLength: number
81+
reuseRemainingBuffer: boolean
82+
}
83+
7684
export class Parser {
77-
private buffer: Buffer = emptyBuffer
78-
private bufferLength: number = 0
79-
private bufferOffset: number = 0
85+
private remainingBuffer: Buffer = emptyBuffer
86+
private remainingBufferLength: number = 0
87+
private remainingBufferOffset: number = 0
8088
private reader = new BufferReader()
8189
private mode: Mode
8290

@@ -88,65 +96,111 @@ export class Parser {
8896
}
8997

9098
public parse(buffer: Buffer, callback: MessageCallback) {
91-
this.mergeBuffer(buffer)
92-
const bufferFullLength = this.bufferOffset + this.bufferLength
93-
let offset = this.bufferOffset
94-
while (offset + HEADER_LENGTH <= bufferFullLength) {
99+
const {
100+
combinedBuffer,
101+
combinedBufferOffset,
102+
combinedBufferLength,
103+
reuseRemainingBuffer,
104+
combinedBufferFullLength,
105+
} = this.mergeBuffer(buffer)
106+
let offset = combinedBufferOffset
107+
while (offset + HEADER_LENGTH <= combinedBufferFullLength) {
95108
// code is 1 byte long - it identifies the message type
96-
const code = this.buffer[offset]
109+
const code = combinedBuffer[offset]
110+
97111
// length is 1 Uint32BE - it is the length of the message EXCLUDING the code
98-
const length = this.buffer.readUInt32BE(offset + CODE_LENGTH)
112+
const length = combinedBuffer.readUInt32BE(offset + CODE_LENGTH)
113+
99114
const fullMessageLength = CODE_LENGTH + length
100-
if (fullMessageLength + offset <= bufferFullLength) {
101-
const message = this.handlePacket(offset + HEADER_LENGTH, code, length, this.buffer)
115+
116+
if (fullMessageLength + offset <= combinedBufferFullLength) {
117+
const message = this.handlePacket(offset + HEADER_LENGTH, code, length, combinedBuffer)
102118
callback(message)
103119
offset += fullMessageLength
104120
} else {
105121
break
106122
}
107123
}
108-
if (offset === bufferFullLength) {
109-
// No more use for the buffer
110-
this.buffer = emptyBuffer
111-
this.bufferLength = 0
112-
this.bufferOffset = 0
113-
} else {
114-
// Adjust the cursors of remainingBuffer
115-
this.bufferLength = bufferFullLength - offset
116-
this.bufferOffset = offset
117-
}
124+
this.consumeBuffer({
125+
combinedBuffer,
126+
combinedBufferOffset: offset,
127+
combinedBufferLength,
128+
reuseRemainingBuffer,
129+
combinedBufferFullLength,
130+
})
118131
}
119132

120-
private mergeBuffer(buffer: Buffer): void {
121-
if (this.bufferLength > 0) {
122-
const newLength = this.bufferLength + buffer.byteLength
123-
const newFullLength = newLength + this.bufferOffset
124-
if (newFullLength > this.buffer.byteLength) {
133+
private mergeBuffer(buffer: Buffer): CombinedBuffer {
134+
let combinedBuffer = buffer
135+
let combinedBufferLength = buffer.byteLength
136+
let combinedBufferOffset = 0
137+
let reuseRemainingBuffer = this.remainingBufferLength > 0
138+
if (reuseRemainingBuffer) {
139+
const newLength = this.remainingBufferLength + combinedBufferLength
140+
const newFullLength = newLength + this.remainingBufferOffset
141+
if (newFullLength > this.remainingBuffer.byteLength) {
125142
// We can't concat the new buffer with the remaining one
126143
let newBuffer: Buffer
127-
if (newLength <= this.buffer.byteLength && this.bufferOffset >= this.bufferLength) {
144+
if (newLength <= this.remainingBuffer.byteLength && this.remainingBufferOffset >= this.remainingBufferLength) {
128145
// We can move the relevant part to the beginning of the buffer instead of allocating a new buffer
129-
newBuffer = this.buffer
146+
newBuffer = this.remainingBuffer
130147
} else {
131148
// Allocate a new larger buffer
132-
let newBufferLength = this.buffer.byteLength * 2
149+
let newBufferLength = this.remainingBuffer.byteLength * 2
133150
while (newLength >= newBufferLength) {
134151
newBufferLength *= 2
135152
}
136153
newBuffer = Buffer.allocUnsafe(newBufferLength)
137154
}
138155
// Move the remaining buffer to the new one
139-
this.buffer.copy(newBuffer, 0, this.bufferOffset, this.bufferOffset + this.bufferLength)
140-
this.buffer = newBuffer
141-
this.bufferOffset = 0
156+
this.remainingBuffer.copy(
157+
newBuffer,
158+
0,
159+
this.remainingBufferOffset,
160+
this.remainingBufferOffset + this.remainingBufferLength
161+
)
162+
this.remainingBuffer = newBuffer
163+
this.remainingBufferOffset = 0
142164
}
143165
// Concat the new buffer with the remaining one
144-
buffer.copy(this.buffer, this.bufferOffset + this.bufferLength)
145-
this.bufferLength = newLength
166+
buffer.copy(this.remainingBuffer, this.remainingBufferOffset + this.remainingBufferLength)
167+
combinedBuffer = this.remainingBuffer
168+
combinedBufferLength = this.remainingBufferLength = newLength
169+
combinedBufferOffset = this.remainingBufferOffset
170+
}
171+
const combinedBufferFullLength = combinedBufferOffset + combinedBufferLength
172+
return {
173+
combinedBuffer,
174+
combinedBufferOffset,
175+
combinedBufferLength,
176+
reuseRemainingBuffer,
177+
combinedBufferFullLength,
178+
}
179+
}
180+
181+
private consumeBuffer({
182+
combinedBufferOffset,
183+
combinedBufferFullLength,
184+
reuseRemainingBuffer,
185+
combinedBuffer,
186+
combinedBufferLength,
187+
}: CombinedBuffer) {
188+
if (combinedBufferOffset === combinedBufferFullLength) {
189+
// No more use for the buffer
190+
this.remainingBuffer = emptyBuffer
191+
this.remainingBufferLength = 0
192+
this.remainingBufferOffset = 0
146193
} else {
147-
this.buffer = buffer
148-
this.bufferOffset = 0
149-
this.bufferLength = buffer.byteLength
194+
this.remainingBufferLength = combinedBufferFullLength - combinedBufferOffset
195+
if (reuseRemainingBuffer) {
196+
// Adjust the cursors of remainingBuffer
197+
this.remainingBufferOffset = combinedBufferOffset
198+
} else {
199+
// To avoid side effects, copy the remaining part of the new buffer to remainingBuffer with extra space for next buffer
200+
this.remainingBuffer = Buffer.allocUnsafe(combinedBufferLength * 2)
201+
combinedBuffer.copy(this.remainingBuffer, 0, combinedBufferOffset)
202+
this.remainingBufferOffset = 0
203+
}
150204
}
151205
}
152206

packages/pg/bench.js

+3-17
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
const pg = require('./lib')
2+
const pool = new pg.Pool()
23

34
const params = {
45
text:
@@ -16,7 +17,7 @@ const seq = {
1617
}
1718

1819
const exec = async (client, q) => {
19-
await client.query({
20+
const result = await client.query({
2021
text: q.text,
2122
values: q.values,
2223
rowMode: 'array',
@@ -39,7 +40,6 @@ const run = async () => {
3940
const client = new pg.Client()
4041
await client.connect()
4142
await client.query('CREATE TEMP TABLE foobar(name TEXT, age NUMERIC)')
42-
await client.query('CREATE TEMP TABLE buf(name TEXT, data BYTEA)')
4343
await bench(client, params, 1000)
4444
console.log('warmup done')
4545
const seconds = 5
@@ -61,21 +61,7 @@ const run = async () => {
6161
console.log('insert queries:', queries)
6262
console.log('qps', queries / seconds)
6363
console.log('on my laptop best so far seen 5799 qps')
64-
65-
console.log('')
66-
console.log('Warming up bytea test')
67-
await client.query({
68-
text: 'INSERT INTO buf(name, data) VALUES ($1, $2)',
69-
values: ['test', Buffer.allocUnsafe(104857600)],
70-
})
71-
console.log('bytea warmup done')
72-
const start = Date.now()
73-
const results = await client.query('SELECT * FROM buf')
74-
const time = Date.now() - start
75-
console.log('bytea time:', time, 'ms')
76-
console.log('bytea length:', results.rows[0].data.byteLength, 'bytes')
77-
console.log('on my laptop best so far seen 1107ms and 104857600 bytes')
78-
64+
console.log()
7965
await client.end()
8066
await client.end()
8167
}

0 commit comments

Comments
 (0)