Skip to content

Commit 1331698

Browse files
committed
improve encode performance
1 parent f8dacb7 commit 1331698

File tree

8 files changed

+53
-42
lines changed

8 files changed

+53
-42
lines changed

packages/client/lib/client/RESP2/encoder.spec.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@ import encodeCommand from './encoder';
55
describe('RESP2 Encoder', () => {
66
it('1 byte', () => {
77
assert.deepEqual(
8-
[...encodeCommand(['a', 'z'])],
8+
encodeCommand(['a', 'z']),
99
['*2\r\n$1\r\na\r\n$1\r\nz\r\n']
1010
);
1111
});
1212

1313
it('2 bytes', () => {
1414
assert.deepEqual(
15-
[...encodeCommand(['א', 'ת'])],
15+
encodeCommand(['א', 'ת']),
1616
['*2\r\n$2\r\nא\r\n$2\r\nת\r\n']
1717
);
1818
});
@@ -26,7 +26,7 @@ describe('RESP2 Encoder', () => {
2626

2727
it('buffer', () => {
2828
assert.deepEqual(
29-
[...encodeCommand([Buffer.from('string')])],
29+
encodeCommand([Buffer.from('string')]),
3030
['*1\r\n$6\r\n', Buffer.from('string'), '\r\n']
3131
);
3232
});

packages/client/lib/client/RESP2/encoder.ts

Lines changed: 16 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,34 +2,29 @@ import { RedisCommandArgument, RedisCommandArguments } from '../../commands';
22

33
const CRLF = '\r\n';
44

5-
export default function* encodeCommand(args: RedisCommandArguments): IterableIterator<RedisCommandArgument> {
6-
let strings = `*${args.length}${CRLF}`,
7-
stringsLength = 0;
5+
export default function encodeCommand(args: RedisCommandArguments): Array<RedisCommandArgument> {
6+
const toWrite: Array<RedisCommandArgument> = [];
7+
8+
let strings = `*${args.length}${CRLF}`;
9+
810
for (let i = 0; i < args.length; i++) {
911
const arg = args[i];
10-
if (Buffer.isBuffer(arg)) {
11-
yield `${strings}$${arg.length}${CRLF}`;
12+
if (typeof arg === 'string') {
13+
const byteLength = Buffer.byteLength(arg);
14+
strings += `$${byteLength}${CRLF}`;
15+
strings += arg;
16+
} else if (arg instanceof Buffer) {
17+
toWrite.push(`${strings}$${arg.length}${CRLF}`);
1218
strings = '';
13-
stringsLength = 0;
14-
yield arg;
19+
toWrite.push(arg);
1520
} else {
16-
const string = arg?.toString?.() ?? '',
17-
byteLength = Buffer.byteLength(string);
18-
strings += `$${byteLength}${CRLF}`;
19-
20-
const totalLength = stringsLength + byteLength;
21-
if (totalLength > 1024) {
22-
yield strings;
23-
strings = string;
24-
stringsLength = byteLength;
25-
} else {
26-
strings += string;
27-
stringsLength = totalLength;
28-
}
21+
throw new TypeError('Invalid argument type');
2922
}
3023

3124
strings += CRLF;
3225
}
3326

34-
yield strings;
27+
toWrite.push(strings);
28+
29+
return toWrite;
3530
}

packages/client/lib/client/commands-queue.ts

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import * as LinkedList from 'yallist';
22
import { AbortError, ErrorReply } from '../errors';
3-
import { RedisCommandArgument, RedisCommandArguments, RedisCommandRawReply } from '../commands';
3+
import { RedisCommand, RedisCommandArgument, RedisCommandArguments, RedisCommandRawReply } from '../commands';
44
import RESP2Decoder from './RESP2/decoder';
5+
import encodeCommand from './RESP2/encoder';
56

67
export interface QueueCommandOptions {
78
asap?: boolean;
@@ -337,17 +338,28 @@ export default class RedisCommandsQueue {
337338

338339
getCommandToSend(): RedisCommandArguments | undefined {
339340
const toSend = this.#waitingToBeSent.shift();
340-
if (toSend) {
341-
this.#waitingForReply.push({
342-
args: toSend.args,
343-
resolve: toSend.resolve,
344-
reject: toSend.reject,
345-
channelsCounter: toSend.channelsCounter,
346-
returnBuffers: toSend.returnBuffers
347-
} as any);
341+
if (!toSend) return;
342+
343+
let encoded: RedisCommandArguments;
344+
try {
345+
encoded = encodeCommand(toSend.args);
346+
} catch (err) {
347+
toSend.reject(err);
348+
return;
348349
}
349-
this.#chainInExecution = toSend?.chainId;
350-
return toSend?.args;
350+
351+
this.#waitingForReply.push({
352+
resolve: toSend.resolve,
353+
reject: toSend.reject,
354+
channelsCounter: toSend.channelsCounter,
355+
returnBuffers: toSend.returnBuffers
356+
});
357+
this.#chainInExecution = toSend.chainId;
358+
return encoded;
359+
}
360+
361+
rejectLastCommand(err: unknown): void {
362+
this.#waitingForReply.pop()!.reject(err);
351363
}
352364

353365
onReplyChunk(chunk: Buffer): void {

packages/client/lib/client/index.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,7 @@ describe('Client', () => {
357357
testUtils.testWithClient('undefined and null should not break the client', async client => {
358358
await assert.rejects(
359359
client.sendCommand([null as any, undefined as any]),
360-
'ERR unknown command ``, with args beginning with: ``'
360+
TypeError
361361
);
362362

363363
assert.equal(

packages/client/lib/client/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { CommandOptions, commandOptions, isCommandOptions } from '../command-opt
99
import { ScanOptions, ZMember } from '../commands/generic-transformers';
1010
import { ScanCommandOptions } from '../commands/SCAN';
1111
import { HScanTuple } from '../commands/HSCAN';
12-
import { extendWithCommands, extendWithModulesAndScripts, transformCommandArguments, transformCommandReply } from '../commander';
12+
import { extendWithCommands, extendWithModulesAndScripts, transformCommandArguments, transformCommandReply, transformLegacyCommandArguments } from '../commander';
1313
import { Pool, Options as PoolOptions, createPool } from 'generic-pool';
1414
import { ClientClosedError, DisconnectsClientError, AuthError } from '../errors';
1515
import { URL } from 'url';
@@ -304,7 +304,7 @@ export default class RedisClient<M extends RedisModules, S extends RedisScripts>
304304
callback = args.pop() as ClientLegacyCallback;
305305
}
306306

307-
this.#sendCommand(args.flat())
307+
this.#sendCommand(transformLegacyCommandArguments(args))
308308
.then((reply: RedisCommandRawReply) => {
309309
if (!callback) return;
310310

packages/client/lib/client/multi-command.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import COMMANDS from './commands';
22
import { RedisCommand, RedisCommandArguments, RedisCommandRawReply, RedisModules, RedisPlugins, RedisScript, RedisScripts } from '../commands';
33
import RedisMultiCommand, { RedisMultiQueuedCommand } from '../multi-command';
4-
import { extendWithCommands, extendWithModulesAndScripts } from '../commander';
4+
import { extendWithCommands, extendWithModulesAndScripts, transformLegacyCommandArguments } from '../commander';
55
import { ExcludeMappedString } from '.';
66

77
type RedisClientMultiCommandSignature<C extends RedisCommand, M extends RedisModules, S extends RedisScripts> =
@@ -54,7 +54,7 @@ export default class RedisClientMultiCommand {
5454
#legacyMode(): void {
5555
this.v4.addCommand = this.addCommand.bind(this);
5656
(this as any).addCommand = (...args: Array<any>): this => {
57-
this.#multi.addCommand(args.flat());
57+
this.#multi.addCommand(transformLegacyCommandArguments(args));
5858
return this;
5959
};
6060
this.v4.exec = this.exec.bind(this);

packages/client/lib/client/socket.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import * as tls from 'tls';
44
import { RedisCommandArguments } from '../commands';
55
import { ConnectionTimeoutError, ClientClosedError, SocketClosedUnexpectedlyError, AuthError, ReconnectStrategyError } from '../errors';
66
import { promiseTimeout } from '../utils';
7-
import encodeCommand from './RESP2/encoder';
7+
88
export interface RedisSocketCommonOptions {
99
connectTimeout?: number;
1010
noDelay?: boolean;
@@ -222,7 +222,7 @@ export default class RedisSocket extends EventEmitter {
222222
throw new ClientClosedError();
223223
}
224224

225-
for (const toWrite of encodeCommand(args)) {
225+
for (const toWrite of args) {
226226
this.#writableNeedDrain = !this.#socket.write(toWrite);
227227
}
228228
}

packages/client/lib/commander.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,10 @@ export function transformCommandArguments<T>(
8989
};
9090
}
9191

92+
export function transformLegacyCommandArguments(args: Array<any>): Array<any> {
93+
return args.flat().map(x => x?.toString?.());
94+
}
95+
9296
export function transformCommandReply(
9397
command: RedisCommand,
9498
rawReply: RedisCommandRawReply,

0 commit comments

Comments
 (0)