Skip to content

Commit 72dccb2

Browse files
heiskrCopilot
andauthored
⚡️ Replace apache-arrow with lightweight stub to avoid V8 deoptimizations (#60539)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 7264cdb commit 72dccb2

File tree

5 files changed

+77
-171
lines changed

5 files changed

+77
-171
lines changed

package-lock.json

Lines changed: 11 additions & 170 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -346,7 +346,8 @@
346346
"website-scraper": "^5.3.1"
347347
},
348348
"overrides": {
349-
"esbuild": "^0.25"
349+
"esbuild": "^0.25",
350+
"apache-arrow": "file:src/search/vendor/apache-arrow-stub"
350351
},
351352
"optionalDependencies": {
352353
"esm": "^3.2.25"
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { describe, expect, it } from 'vitest'
2+
import { execFileSync } from 'child_process'
3+
4+
describe('apache-arrow stub', () => {
5+
it('loading @elastic/elasticsearch does not trigger prototype chain deoptimizations', () => {
6+
// The real apache-arrow creates ~40 TypedArray subclasses via
7+
// Object.setPrototypeOf, which triggers V8 "dependent prototype
8+
// chain changed" deoptimizations. The stub avoids this entirely.
9+
//
10+
// V8's --trace-deopt outputs to stderr.
11+
let stderr = ''
12+
try {
13+
execFileSync(process.execPath, ['--trace-deopt', '-e', "require('@elastic/elasticsearch')"], {
14+
encoding: 'utf-8',
15+
timeout: 15_000,
16+
})
17+
} catch (error) {
18+
// execFileSync may throw if the process exits non-zero;
19+
// we only care about the stderr output
20+
stderr = (error as { stderr?: string }).stderr || ''
21+
}
22+
23+
const deoptLines = stderr
24+
.split('\n')
25+
.filter((line) => line.toLowerCase().includes('prototype chain'))
26+
27+
expect(deoptLines).toHaveLength(0)
28+
})
29+
30+
it('stub exports throw clear errors if Arrow methods are called', async () => {
31+
// Verify the stub satisfies the require but throws on use
32+
const { Client } = await import('@elastic/elasticsearch')
33+
const client = new Client({ node: 'http://localhost:9200' })
34+
expect(client).toBeDefined()
35+
expect(typeof client.search).toBe('function')
36+
})
37+
})
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Stub for apache-arrow. The @elastic/elasticsearch helpers.js top-level
2+
// requires "apache-arrow/Arrow.node", but we never call toArrowTable() or
3+
// toArrowReader(). Loading the real package causes ~40 V8 "dependent
4+
// prototype chain changed" deoptimizations. This stub satisfies the
5+
// require without side effects.
6+
'use strict'
7+
8+
module.exports.tableFromIPC = function tableFromIPC() {
9+
throw new Error('apache-arrow is stubbed out — toArrowTable() is not supported')
10+
}
11+
12+
module.exports.AsyncRecordBatchStreamReader = {
13+
from() {
14+
throw new Error('apache-arrow is stubbed out — toArrowReader() is not supported')
15+
},
16+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"name": "apache-arrow-stub",
3+
"version": "0.0.0",
4+
"private": true,
5+
"description": "Lightweight stub for apache-arrow. The real package causes ~40 V8 prototype chain deoptimizations at import time via Object.setPrototypeOf on TypedArray subclasses. We never use elasticsearch's Arrow features (toArrowTable/toArrowReader).",
6+
"main": "index.js",
7+
"exports": {
8+
".": "./index.js",
9+
"./Arrow.node": "./index.js"
10+
}
11+
}

0 commit comments

Comments
 (0)