Skip to content

Commit 212138e

Browse files
committed
refactor(perfs): implement lazy sort processor (fixed-size buffer algo)
1 parent c6d54d2 commit 212138e

2 files changed

Lines changed: 58 additions & 16 deletions

File tree

src/query.ts

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ export function query<T extends GenericObject, P extends QueryParams<T>>(
55
data: T[],
66
params: P,
77
): QueryResult<T, P> {
8-
let result = Array.from(lazyQuery(data, params))
8+
let result: Iterable<T> = lazyQuery(data, params)
99

1010
if (params.sort)
11-
result = sortedQuery(result, params.sort)
11+
result = lazySortedQuery(result, params.sort)
1212

13-
return paginateQuery(result, params)
13+
return paginateQuery(Array.from(result), params)
1414
}
1515

1616
function* lazyQuery<T extends GenericObject>(data: T[], params: QueryParams<T>): Generator<T> {
@@ -59,24 +59,59 @@ function matchesFilters<T extends GenericObject>(item: T, filters?: (QueryFilter
5959
})
6060
}
6161

62-
function sortedQuery<T extends GenericObject>(data: T[], sortOptions?: QueryParams<T>['sort']): T[] {
63-
if (!sortOptions)
64-
return data
62+
function* lazySortedQuery<T extends GenericObject>(
63+
data: Iterable<T>,
64+
sortOptions?: QueryParams<T>['sort'],
65+
): Generator<T, void, undefined> {
66+
if (!sortOptions) {
67+
yield * data
68+
return
69+
}
6570

6671
const sortArray = Array.isArray(sortOptions) ? sortOptions : [sortOptions]
67-
return data.slice().sort((a, b) => {
72+
const buffer: T[] = []
73+
const compare = (a: T, b: T) => {
6874
for (const { key, dir, parser } of sortArray) {
69-
const parserHandler = typeof parser === 'function' ? parser : (v: any) => parser === 'number' ? Number(v) : parser === 'boolean' ? Boolean(v) : parser === 'string' ? String(v) : v
75+
const parserHandler = typeof parser === 'function'
76+
? parser
77+
: (v: any) =>
78+
parser === 'number'
79+
? Number(v)
80+
: parser === 'boolean'
81+
? Boolean(v)
82+
: parser === 'string' ? String(v) : v
7083
const aParsed = parserHandler(getObjectProperty(a, key)) ?? null
7184
const bParsed = parserHandler(getObjectProperty(b, key)) ?? null
72-
7385
if (aParsed !== bParsed) {
7486
const comparison = (aParsed < bParsed) ? -1 : 1
7587
return dir === 'asc' ? comparison : -comparison
7688
}
7789
}
7890
return 0
79-
})
91+
}
92+
93+
for (const item of data) {
94+
buffer.push(item)
95+
if (buffer.length >= 1000)
96+
break
97+
}
98+
99+
buffer.sort(compare)
100+
101+
while (buffer.length > 0) {
102+
yield buffer[0]
103+
buffer.shift()
104+
105+
for (const item of data) {
106+
let insertIndex = buffer.findIndex(bufferItem => compare(item, bufferItem) < 0)
107+
if (insertIndex === -1) {
108+
insertIndex = buffer.length
109+
}
110+
buffer.splice(insertIndex, 0, item)
111+
if (buffer.length >= 1000)
112+
break
113+
}
114+
}
80115
}
81116

82117
function paginateQuery<T extends GenericObject, P extends QueryParams<T>>(data: T[], params: P): QueryResult<T, P> {

test/index.test.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,22 @@ for (const fixture of fixtures) {
4141
describe('performance check', () => {
4242
// VAL BETWEEN 1 & 100
4343
const getValue = () => Math.floor(Math.random() * 100)
44-
const items = Array.from({ length: 1000000 }, (_, i) => ({ id: i, name: `Item ${i}`, value: getValue(), other: [], address: { city: 'New York', country: 'USA' } }))
44+
const items = Array.from({ length: 1000000 }, (_, i) => ({
45+
id: i,
46+
name: `Item ${i}`,
47+
value: getValue(),
48+
other: [],
49+
address: { city: 'New York', country: 'USA' },
50+
age: Math.floor(Math.random() * 100),
51+
}))
4552
it('query 1M rows - paginate + sort + search + filter in less than 500ms', () => {
4653
const start = performance.now()
4754
query(items, {
48-
limit: 10,
49-
sort: {
50-
key: 'address.country',
51-
dir: 'asc',
52-
},
55+
limit: 100,
56+
sort: [
57+
{ key: 'age', dir: 'asc' },
58+
{ key: 'name', dir: 'asc' },
59+
],
5360
search: {
5461
value: 'Item 1',
5562
keys: ['name'],

0 commit comments

Comments
 (0)