Skip to content

Commit 4fd549e

Browse files
authored
fix: inefficient traversal and intermediate allocations (#1164)
Signed-off-by: ferhat elmas <elmas.ferhat@gmail.com>
1 parent de0af4b commit 4fd549e

10 files changed

Lines changed: 355 additions & 198 deletions

File tree

src/http/routes/s3/commands/put-object.ts

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -240,26 +240,25 @@ export default function PutObject(s3Router: S3Router) {
240240
}
241241

242242
function fieldsToObject(fields: MultipartFields) {
243-
return Object.keys(fields).reduce(
244-
(acc, key) => {
245-
const field = fields[key]
246-
if (Array.isArray(field)) {
247-
return acc
248-
}
243+
const acc: Record<string, string> = {}
249244

250-
if (!field) {
251-
return acc
252-
}
245+
for (const key in fields) {
246+
if (!Object.prototype.hasOwnProperty.call(fields, key)) {
247+
continue
248+
}
253249

254-
if (
255-
field.type === 'field' &&
256-
(typeof field.value === 'string' || field.value === 'number' || field.value === 'boolean')
257-
) {
258-
acc[field.fieldname.toLowerCase()] = field.value
259-
}
250+
const field = fields[key]
251+
if (Array.isArray(field) || !field) {
252+
continue
253+
}
260254

261-
return acc
262-
},
263-
{} as Record<string, string>
264-
)
255+
if (
256+
field.type === 'field' &&
257+
(typeof field.value === 'string' || field.value === 'number' || field.value === 'boolean')
258+
) {
259+
acc[field.fieldname.toLowerCase()] = field.value
260+
}
261+
}
262+
263+
return acc
265264
}

src/http/routes/s3/index.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,11 +99,15 @@ export default async function routes(fastify: FastifyInstance) {
9999
const headers = output.headers
100100

101101
if (headers) {
102-
Object.keys(headers).forEach((header) => {
102+
for (const header in headers) {
103+
if (!Object.prototype.hasOwnProperty.call(headers, header)) {
104+
continue
105+
}
106+
103107
if (headers[header]) {
104108
reply.header(header, headers[header])
105109
}
106-
})
110+
}
107111
}
108112
return reply.status(output.statusCode || 200).send(output.responseBody)
109113
} catch (e) {

src/internal/testing/generators/array.ts

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,3 @@ export async function eachParallel<T>(times: number, fn: (index: number) => Prom
66

77
return Promise.all(promises)
88
}
9-
10-
// export function pickRandomFromArray<T>(arr: T[]): T {
11-
// return arr[Math.floor(Math.random() * arr.length)]
12-
// }
13-
14-
// export function pickRandomRangeFromArray<T>(arr: T[], range: number): T[] {
15-
// if (arr.length <= range) {
16-
// return arr
17-
// }
18-
19-
// const result = new Set<T>()
20-
// while (result.size < range) {
21-
// result.add(pickRandomFromArray(arr))
22-
// }
23-
24-
// return Array.from(result)
25-
// }

src/storage/backend/s3/adapter.test.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {
22
DeleteObjectsCommand,
33
GetObjectCommand,
44
HeadObjectCommand,
5+
ListObjectsV2Command,
56
PutObjectCommand,
67
S3Client,
78
} from '@aws-sdk/client-s3'
@@ -163,6 +164,53 @@ describe('S3Backend', () => {
163164
})
164165
})
165166

167+
describe('list', () => {
168+
test('filters listed keys by cutoff date and strips the requested prefix', async () => {
169+
mockSend.mockResolvedValue({
170+
Contents: [
171+
{
172+
Key: 'tenant/bucket/old.txt',
173+
LastModified: new Date('2024-01-01T00:00:00.000Z'),
174+
Size: 12,
175+
},
176+
{
177+
Key: 'tenant/bucket/new.txt',
178+
LastModified: new Date('2024-01-03T00:00:00.000Z'),
179+
Size: 34,
180+
},
181+
{
182+
Key: 'tenant/bucket/no-date.txt',
183+
Size: 56,
184+
},
185+
{
186+
LastModified: new Date('2024-01-01T00:00:00.000Z'),
187+
Size: 78,
188+
},
189+
],
190+
NextContinuationToken: 'next-page',
191+
})
192+
193+
const backend = createBackend()
194+
195+
await expect(
196+
backend.list('test-bucket', {
197+
prefix: 'tenant/bucket',
198+
beforeDate: new Date('2024-01-02T00:00:00.000Z'),
199+
})
200+
).resolves.toEqual({
201+
keys: [{ name: 'old.txt', size: 12 }],
202+
nextToken: 'next-page',
203+
})
204+
205+
expect(mockSend).toHaveBeenCalledTimes(1)
206+
expect(mockSend.mock.calls[0][0]).toBeInstanceOf(ListObjectsV2Command)
207+
expect(mockSend.mock.calls[0][0].input).toMatchObject({
208+
Bucket: 'test-bucket',
209+
Prefix: 'tenant/bucket',
210+
})
211+
})
212+
})
213+
166214
describe('deleteObjects', () => {
167215
test('chunks DeleteObjectsCommand payloads to the S3 key limit', async () => {
168216
mockSend.mockResolvedValue({

src/storage/backend/s3/adapter.ts

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -393,26 +393,27 @@ export class S3Backend implements StorageBackendAdapter {
393393
StartAfter: options?.startAfter,
394394
})
395395
const data = await this.client.send(command)
396-
const keys =
397-
data.Contents?.filter((ele) => {
398-
if (options?.beforeDate) {
399-
if (ele.LastModified && ele.LastModified < options.beforeDate) {
400-
return ele.Key as string
401-
}
402-
return false
403-
}
404-
return ele.Key
405-
}).map((ele) => {
406-
if (options?.prefix) {
407-
return {
408-
// remove prefix and leading slash if present
409-
name: (ele.Key as string).replace(options.prefix, '').replace(/^\//, ''),
410-
size: ele.Size as number,
411-
}
412-
}
413-
414-
return { name: ele.Key as string, size: ele.Size as number }
415-
}) || []
396+
const keys: { name: string; size: number }[] = []
397+
398+
for (const ele of data.Contents || []) {
399+
if (!ele.Key) {
400+
continue
401+
}
402+
403+
if (options?.beforeDate && (!ele.LastModified || ele.LastModified >= options.beforeDate)) {
404+
continue
405+
}
406+
407+
if (options?.prefix) {
408+
keys.push({
409+
// remove prefix and leading slash if present
410+
name: ele.Key.replace(options.prefix, '').replace(/^\//, ''),
411+
size: ele.Size as number,
412+
})
413+
} else {
414+
keys.push({ name: ele.Key, size: ele.Size as number })
415+
}
416+
}
416417

417418
return {
418419
keys,

0 commit comments

Comments
 (0)