Skip to content

Commit 85ef35d

Browse files
committed
fix(data): oom crash in stats sync
1 parent 251177d commit 85ef35d

2 files changed

Lines changed: 26 additions & 19 deletions

File tree

infra/stats.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,9 @@ export const statSync = new sst.aws.Service("StatsSyncService", {
185185
cluster: lakeCluster,
186186
architecture: "arm64",
187187
cpu: "0.25 vCPU",
188-
memory: "0.5 GB",
188+
// 0.5 GB caused an OOM crash loop: every restart immediately re-ran the 4 Athena
189+
// stats queries (~$5/pass) every ~5 minutes instead of hourly.
190+
memory: "2 GB",
189191
image: {
190192
context: ".",
191193
dockerfile: "packages/stats/server/Dockerfile",

packages/stats/core/src/athena.ts

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -116,28 +116,33 @@ const poll: (
116116
const results: (
117117
client: AwsAthenaClient,
118118
queryExecutionId: string,
119-
nextToken?: string,
120119
) => Effect.Effect<AthenaData[], AthenaQueryError> = Effect.fn("Athena.results")(function* (
121120
client: AwsAthenaClient,
122121
queryExecutionId: string,
123-
nextToken?: string,
124122
) {
125-
const result = yield* Effect.tryPromise({
126-
try: () =>
127-
client.send(
128-
new GetQueryResultsCommand({
129-
QueryExecutionId: queryExecutionId,
130-
NextToken: nextToken,
131-
MaxResults: ATHENA_PAGE_SIZE,
132-
}),
133-
),
134-
catch: (cause) => new AthenaQueryError({ message: "Failed to read Athena stats results", queryExecutionId, cause }),
135-
})
136-
const columns = result.ResultSet?.ResultSetMetadata?.ColumnInfo?.map((item) => item.Name ?? "") ?? []
137-
const rows = (result.ResultSet?.Rows ?? []).slice(nextToken ? 0 : 1).map((row) => rowData(columns, row))
138-
139-
if (!result.NextToken) return rows
140-
return [...rows, ...(yield* results(client, queryExecutionId, result.NextToken))]
123+
// Accumulate pages iteratively; recursive spreads copied every previously
124+
// fetched row per page and blew up memory on large result sets.
125+
const rows: AthenaData[] = []
126+
let nextToken: string | undefined
127+
while (true) {
128+
const result = yield* Effect.tryPromise({
129+
try: () =>
130+
client.send(
131+
new GetQueryResultsCommand({
132+
QueryExecutionId: queryExecutionId,
133+
NextToken: nextToken,
134+
MaxResults: ATHENA_PAGE_SIZE,
135+
}),
136+
),
137+
catch: (cause) =>
138+
new AthenaQueryError({ message: "Failed to read Athena stats results", queryExecutionId, cause }),
139+
})
140+
const columns = result.ResultSet?.ResultSetMetadata?.ColumnInfo?.map((item) => item.Name ?? "") ?? []
141+
// The first page starts with the header row.
142+
for (const row of (result.ResultSet?.Rows ?? []).slice(nextToken ? 0 : 1)) rows.push(rowData(columns, row))
143+
if (!result.NextToken) return rows
144+
nextToken = result.NextToken
145+
}
141146
})
142147

143148
function rowData(columns: string[], row: Row): AthenaData {

0 commit comments

Comments
 (0)