Skip to content

Performance issue with the escapeLiteral utility #3194

@mrmlnc

Description

@mrmlnc

Hello,

In my application, I use a field with the jsonb type. The size of this field can be more than 1000 characters. There can be many text-based columns in the table.

I've noticed that insert queries with long strings take a long time. My investigation led me to the escapeLiteral function. This function iterates over the entire line and builds it again.

Here are some measurements (node@20) showing the growth of operation time depending on the length of the string:

Input string length Function execution time
11 0.053ms
110 0.065ms
1100 0.11ms
11_000 0.554ms
110_000 4.946ms
var input = "hello world".repeat(10000)

console.time('\nperf_escape_literal')
utils.escapeLiteral(input)
console.timeEnd('\nperf_escape_literal')

console.dir(input.length)

An example of a simplified string that is written to the jsonb column. We also have users with a large amount of data (the string will be longer x2…10).

[
 {"type": "local", "action": "Open projects page", "expectation": ""},
 {"type": "local", "action": "Click on button \"Create new project\"", "expectation": "You are being redirected to the new project form"},
 {"type": "local", "action": "Fill the form", "expectation": "Project code should be generated automatically"},
 {"type": "local", "action": "Click on \"Create project\" button", "expectation": "You are being redirected to repository page with no cases or suites"},
 {"hash": "1_4", "type": "shared", "action": "Do action 0", "version": 23, "expectation": "Action 0 is indicated as 0.13738214216469524 on the ui", "sharedStepsGroupId": 1},
 {"hash": "1_4", "type": "shared", "action": "Swipe 0.4240689256483676", "version": 23, "expectation": "The sun is shining with the color temperature of 0.8792701581028319", "sharedStepsGroupId": 1},
 {"hash": "1_4", "type": "shared", "action": "Rotate at 0.3493667640291416 degrees", "version": 23, "expectation": "Wind speed is changed to 0 m/s", "sharedStepsGroupId": 1}
]

perf_escape_literal: 0.104ms
input_length: 1014

At the moment, these are small numbers, but escaping is called for each text-based column. In total, these are significant numbers. For example, when adapting the solution below, we got an speed-up from ~950ms to ~550ms for some insert queries.

I found a solution for this problem: Dollar-Quoted string. It eliminates the need to construct a string each time, saving time.

const escapeLiteral = function (str) {
  if (str.length > 100) return `$__pg__$${str}$__pg__$`
  
  return escapeLiteralOld(str)
}

I believe it would be beneficial to use the previous solution for strings that are less than 50 or 100 characters, as it would reduce the amount of data sent over the network for the sake of the smaller strings.

Here are the measurements for this solution:

Input string length Function execution time
11 0.051ms
110 0.056ms
1100 0.05ms
11_000 0.051ms
110_000 0.047ms

Of course, I am able to replace the current implementation of the function within the ORM being used in the project. However, I believe it would be beneficial to explore options for "native" optimization for all users.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions