diff --git a/packages/memcache-client/README.md b/packages/memcache-client/README.md index 34dc1ff..2e860ec 100644 --- a/packages/memcache-client/README.md +++ b/packages/memcache-client/README.md @@ -166,6 +166,7 @@ const options = { lifetime: 100, // TTL 100 seconds cmdTimeout: 3000, // command timeout in milliseconds connectTimeout: 8000, // connect to server timeout in ms + noDelay: true, // whether to enable TCP_NODELAY on connections compressor: require("custom-compressor"), logger: require("./custom-logger"), Promise, @@ -185,6 +186,7 @@ const client = new MemcacheClient(options); - `ignoreNotStored` - **_optional_** If set to true, then will not treat `NOT_STORED` reply from any store commands as error. Use this for [Mcrouter AllAsyncRoute] mode. - `lifetime` - **_optional_** Your cache TTL in **_seconds_** to use for all entries. DEFAULT: 60 seconds. +- `noDelay` - **_optional_** Whether to enable `TCP_NODELAY` on connections to decrease latency. DEFAULT: false - `cmdTimeout` - **_optional_** Command timeout in milliseconds. DEFAULT: 5000 ms. - If a command didn't receive response before this timeout value, then it will cause the connection to shutdown and returns Error. - `connectTimeout` - **_optional_** Custom self connect to server timeout in milliseconds. It's disabled if set to 0. DEFAULT: 0 diff --git a/packages/memcache-client/src/lib/client.ts b/packages/memcache-client/src/lib/client.ts index 5ae4d66..39d4cb3 100644 --- a/packages/memcache-client/src/lib/client.ts +++ b/packages/memcache-client/src/lib/client.ts @@ -144,12 +144,11 @@ export class MemcacheClient extends EventEmitter { ): Promise { return this.send( (socket) => { - socket?.write(data); - if (options && options.noreply) { - socket?.write(" noreply\r\n"); - } else { - socket?.write("\r\n"); + let line = data; + if (options?.noreply) { + line += " noreply"; } + socket?.write(`${line}\r\n`); }, options, callback @@ -318,10 +317,11 @@ export class MemcacheClient extends EventEmitter { (options as Partial)?.compress === true ); const bytes = Buffer.byteLength(packed.data); - const msg = `${cmd} ${key} ${packed.flag} ${lifetime} ${bytes}${casUniq}${noreply}\r\n`; - socket?.write(msg); - socket?.write(packed.data); - socket?.write("\r\n"); + socket?.write(Buffer.concat([ + Buffer.from(`${cmd} ${key} ${packed.flag} ${lifetime} ${bytes}${casUniq}${noreply}\r\n`), + Buffer.isBuffer(packed.data) ? packed.data : Buffer.from(packed.data), + Buffer.from("\r\n"), + ])); }; return this._callbackSend(_data, options, callback) as unknown as Promise; diff --git a/packages/memcache-client/src/lib/connection.ts b/packages/memcache-client/src/lib/connection.ts index 14cc875..f8e7644 100644 --- a/packages/memcache-client/src/lib/connection.ts +++ b/packages/memcache-client/src/lib/connection.ts @@ -342,6 +342,10 @@ export class MemcacheConnection extends MemcacheParser { } _setupConnection(socket: Socket): void { + if (this.client?.options?.noDelay) { + socket.setNoDelay(true); + } + socket.on("data", this.onData.bind(this)); socket.on("end", () => { diff --git a/packages/memcache-client/src/test/spec/client.spec.ts b/packages/memcache-client/src/test/spec/client.spec.ts index c90519f..d82ff02 100644 --- a/packages/memcache-client/src/test/spec/client.spec.ts +++ b/packages/memcache-client/src/test/spec/client.spec.ts @@ -14,7 +14,7 @@ import Path from "path"; import { text1, text2, poem1, poem2, poem3, poem4, poem5 } from "../data/text"; import NullLoger from "../../lib/null-logger"; import memcached, { MemcachedServer } from "memcached-njs"; -import { AddressInfo } from "net"; +import { AddressInfo, Socket } from "net"; describe("memcache client", function () { process.on("unhandledRejection", (e) => { @@ -279,6 +279,40 @@ describe("memcache client", function () { }); }); + it("should not enable TCP_NODELAY by default", async () => { + await startSingleServer(); + + const _setNoDelay = Socket.prototype.setNoDelay; + const mockNoDelay = jest.fn(); + + try { + Socket.prototype.setNoDelay = mockNoDelay; + const x = new MemcacheClient({ server: server }); + await x.set("foo", "bar"); + } finally { + Socket.prototype.setNoDelay = _setNoDelay; + } + + expect(mockNoDelay).not.toHaveBeenCalled(); + }); + + it("should enable TCP_NODELAY when options.noDelay is true", async () => { + await startSingleServer(); + + const _setNoDelay = Socket.prototype.setNoDelay; + const mockNoDelay = jest.fn(); + + try { + Socket.prototype.setNoDelay = mockNoDelay; + const x = new MemcacheClient({ server: server, noDelay: true }); + await x.set("foo", "bar"); + } finally { + Socket.prototype.setNoDelay = _setNoDelay; + } + + expect(mockNoDelay).toHaveBeenCalled(); + }); + const testMulti = (maxConnections: number = 1, done: () => void) => { const key1 = "text1维基百科"; const key2 = "blah"; diff --git a/packages/memcache-client/src/types.ts b/packages/memcache-client/src/types.ts index 01f7c33..068a2bd 100644 --- a/packages/memcache-client/src/types.ts +++ b/packages/memcache-client/src/types.ts @@ -43,6 +43,7 @@ export type MemcacheClientOptions = { server: ServerDefinition | SingleServerEntry | MultipleServerEntry | string; ignoreNotStored?: boolean; lifetime?: number; + noDelay?: boolean; cmdTimeout?: number; connectTimeout?: number; keepDangleSocket?: boolean;