Skip to content

Commit cbd1c85

Browse files
committed
feat(cu): reduce gql queries
1 parent a34766a commit cbd1c85

File tree

4 files changed

+92
-45
lines changed

4 files changed

+92
-45
lines changed

servers/cu/src/domain/lib/loadMessages.js

Lines changed: 55 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Transform } from 'node:stream'
22

3-
import { Rejected, Resolved, fromPromise, of } from 'hyper-async'
3+
import { Resolved, fromPromise, of } from 'hyper-async'
44
import { T, always, ascend, cond, equals, identity, ifElse, isNil, last, length, pipe, prop, reduce, uniqBy } from 'ramda'
55
import ms from 'ms'
66

@@ -9,6 +9,27 @@ import { findBlocksSchema, getLatestBlockSchema, loadBlocksMetaSchema, loadMessa
99

1010
export const toSeconds = (millis) => Math.floor(millis / 1000)
1111

12+
const makeRanges = (missing, blocks) => {
13+
if (missing.length === 0) return []
14+
const sorted = [...missing].sort((a, b) => a - b)
15+
16+
const ranges = []
17+
let current = [sorted[0]]
18+
19+
for (let i = 1; i < sorted.length; i++) {
20+
if (sorted[i] === sorted[i - 1] + 1) {
21+
current.push(sorted[i])
22+
} else {
23+
const maxTimestamp = blocks.find((block) => block.height === current[current.length - 1] + 1)?.timestamp - 1
24+
ranges.push({ min: current[0], max: current[current.length - 1], maxTimestamp })
25+
current = [sorted[i]]
26+
}
27+
}
28+
const maxTimestamp = blocks.find((block) => block.height === current[current.length - 1] + 1)?.timestamp - 1
29+
ranges.push({ min: current[0], max: current[current.length - 1], maxTimestamp })
30+
return ranges
31+
}
32+
1233
export function findMissingBlocksIn (blocks, { min, maxTimestamp }) {
1334
if (!blocks.length) return { min, maxTimestamp }
1435

@@ -37,16 +58,8 @@ export function findMissingBlocksIn (blocks, { min, maxTimestamp }) {
3758
return { min: maxBlock.height, maxTimestamp }
3859
}
3960

40-
/**
41-
* TODO:
42-
*
43-
* The purpose of this function to find the holes in the incremental sequence of block meta.
44-
* This impl returns one "large" hole to fetch from the gateway.
45-
*
46-
* An optimization would be to split the "large" hold into more reasonably sized "small" holes,
47-
* and fetch those. aka. more resolution and less data unnecessarily loaded from the gateway
48-
*/
49-
return { min: Math.min(...missing), maxTimestamp }
61+
const missingRanges = makeRanges(missing, blocks)
62+
return { missingRanges, maxTimestamp }
5063
}
5164

5265
export function mergeBlocks (fromDb, fromGateway) {
@@ -347,42 +360,40 @@ function reconcileBlocksWith ({ logger, loadBlocksMeta, findBlocks, saveBlocks,
347360
* between the minimum block, and the maxTimestamp
348361
*/
349362
.map((fromDb) => findMissingBlocksIn(fromDb, { min, maxTimestamp }))
350-
.chain((missingRange) => {
351-
if (!missingRange) return Resolved(fromDb)
352-
const latestBlocksMatch = missingRange.min === fromDb[fromDb.length - 1].height
353-
if (latestBlocksMatch) {
354-
logger('Latest blocks match at height %d. Checking Arweave for latest block', missingRange.min)
355-
return of()
356-
.chain(getLatestBlock)
357-
.chain((latestBlock) => {
358-
if (latestBlock === missingRange.min) {
359-
logger('Latest block matches missing range min height %d. Bypassing GQL call', missingRange.min)
360-
return Resolved(fromDb)
361-
}
362-
logger('Latest blocks do not match (arweave: %d, db: %d). Fetching missing blocks from gateway', latestBlock, missingRange.min)
363-
return Rejected(missingRange)
364-
})
365-
}
366-
return Rejected(missingRange)
367-
})
368-
.bichain((missingRange) => {
369-
if (!missingRange) return Resolved(fromDb)
370-
logger('Loading missing blocks within range of %j', missingRange)
363+
.chain((missing) => {
364+
if (!missing) return Resolved(fromDb)
371365

372366
/**
373-
* Load any missing blocks within the determined range,
374-
* from the gateway
367+
* Handle two cases:
368+
* 1. missing.missingRanges - array of ranges with gaps in the middle
369+
* 2. missing.min and missing.maxTimestamp - single range at the end
375370
*/
376-
return loadBlocksMeta(missingRange)
377-
/**
378-
* Cache any fetched blocks for next time
379-
*
380-
* This will definitely result in individual 409s for existing blocks,
381-
* but those shouldn't impact anything and are swallowed
382-
*/
383-
.chain((fromGateway) => saveBlocks(fromGateway).map(() => fromGateway))
384-
.map((fromGateway) => mergeBlocks(fromDb, fromGateway))
385-
}, Resolved)
371+
const ranges = missing.missingRanges || [{ min: missing.min, maxTimestamp: missing.maxTimestamp }]
372+
373+
/**
374+
* Load blocks for each missing range sequentially,
375+
* accumulating the results
376+
*/
377+
return ranges.reduce((acc, missingRange) => {
378+
return acc.chain((accumulated) => {
379+
logger('Loading missing blocks within range of %j', missingRange)
380+
381+
/**
382+
* Load any missing blocks within the determined range,
383+
* from the gateway
384+
*/
385+
return loadBlocksMeta(missingRange)
386+
/**
387+
* Cache any fetched blocks for next time
388+
*
389+
* This will definitely result in individual 409s for existing blocks,
390+
* but those shouldn't impact anything and are swallowed
391+
*/
392+
.chain((fromGateway) => saveBlocks(fromGateway).map(() => fromGateway))
393+
.map((fromGateway) => mergeBlocks(accumulated, fromGateway))
394+
})
395+
}, Resolved(fromDb))
396+
})
386397
})
387398
}
388399
}

servers/cu/src/effects/ao-block.test.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,9 @@ describe('ao-block', () => {
136136
test('load the block data across multiple pages', async () => {
137137
const loadBlocksMeta = loadBlocksMetaSchema.implement(loadBlocksMetaWith({
138138
fetch,
139+
gatewayCounter: {
140+
inc: () => {}
141+
},
139142
GRAPHQL_URLS,
140143
/**
141144
* Weird page size, so we know we are chopping off the excess
@@ -164,6 +167,9 @@ describe('ao-block', () => {
164167
errorCount++
165168
throw Error('Fetch error!')
166169
},
170+
gatewayCounter: {
171+
inc: () => {}
172+
},
167173
GRAPHQL_URLS,
168174
pageSize: 17,
169175
logger
@@ -183,6 +189,9 @@ describe('ao-block', () => {
183189
assert.equal(url, GRAPHQL_URLS[errorsCount % GRAPHQL_URLS.length])
184190
throw Error('Fetch error!')
185191
},
192+
gatewayCounter: {
193+
inc: () => {}
194+
},
186195
GRAPHQL_URLS,
187196
pageSize: 17,
188197
logger,
@@ -230,6 +239,9 @@ describe('ao-block', () => {
230239
fetch: () => {
231240
throw Error('Fetch error!')
232241
},
242+
gatewayCounter: {
243+
inc: () => {}
244+
},
233245
GRAPHQL_URLS,
234246
pageSize: 17,
235247
logger,
@@ -334,6 +346,9 @@ describe('ao-block', () => {
334346
})
335347
}
336348
},
349+
gatewayCounter: {
350+
inc: () => {}
351+
},
337352
GRAPHQL_URLS,
338353
pageSize: 5,
339354
logger,

servers/cu/src/effects/ao-process.test.js

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -749,6 +749,9 @@ describe('ao-process', () => {
749749

750750
return { data: { transactions: { edges } } }
751751
},
752+
gatewayCounter: {
753+
inc: () => {}
754+
},
752755
queryCheckpointGateway: async () => assert.fail('should not call if default gateway is successful'),
753756
loadTransactionData: async (id) => {
754757
assert.ok(id.includes('tx-123-'))
@@ -1320,6 +1323,9 @@ describe('ao-process', () => {
13201323
findRecordCheckpointBefore: async () => assert.fail('should not call if found in cache'),
13211324
address: async () => assert.fail('should not call if found in file checkpoint'),
13221325
queryGateway: async () => assert.fail('should not call if found in file checkpoint'),
1326+
gatewayCounter: {
1327+
inc: () => {}
1328+
},
13231329
queryCheckpointGateway: async () => assert.fail('should not call if file checkpoint'),
13241330
loadTransactionData: async (id) => {
13251331
assert.equal(id, 'tx-123')
@@ -1361,6 +1367,9 @@ describe('ao-process', () => {
13611367
address: async () => assert.fail('should not call if found in file checkpoint'),
13621368
queryGateway: async () => assert.fail('should not call if found in file checkpoint'),
13631369
queryCheckpointGateway: async () => assert.fail('should not call if file checkpoint'),
1370+
gatewayCounter: {
1371+
inc: () => {}
1372+
},
13641373
loadTransactionData: async (id) => {
13651374
assert.equal(id, 'tx-123')
13661375
return new Response(Readable.toWeb(Readable.from(zipped)))
@@ -1417,6 +1426,9 @@ describe('ao-process', () => {
14171426
}
14181427
}),
14191428
queryCheckpointGateway: async () => assert.fail('should not call if default gateway is successful'),
1429+
gatewayCounter: {
1430+
inc: () => {}
1431+
},
14201432
loadTransactionData: async (id) => {
14211433
assert.ok(id.includes('tx-123-'))
14221434
return new Response(Readable.toWeb(Readable.from(zipped)))
@@ -1786,7 +1798,10 @@ describe('ao-process', () => {
17861798
describe('saveCheckpointWith', () => {
17871799
const logger = createTestLogger({ name: 'saveCheckpointWith' })
17881800
const depsAll = {
1789-
logger
1801+
logger,
1802+
gatewayCounter: {
1803+
inc: () => {}
1804+
}
17901805
}
17911806

17921807
describe('should create an arweave checkpoint and file checkpoint', async () => {

servers/cu/src/effects/arweave.test.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ describe('arweave', () => {
1313
test('load transaction meta', async () => {
1414
const loadTransactionMeta = loadTransactionMetaSchema.implement(
1515
loadTransactionMetaWith({
16+
gatewayCounter: {
17+
inc: () => {}
18+
},
1619
fetch: (url, options) => {
1720
const body = JSON.parse(options.body)
1821
assert.deepStrictEqual(body.variables, {
@@ -41,6 +44,9 @@ describe('arweave', () => {
4144
const loadTransactionMeta = loadTransactionMetaSchema.implement(
4245
loadTransactionMetaWith({
4346
GRAPHQL_URL,
47+
gatewayCounter: {
48+
inc: () => {}
49+
},
4450
fetch: async (url, options) => {
4551
assert.equal(url, GRAPHQL_URL)
4652
const body = JSON.parse(options.body)

0 commit comments

Comments
 (0)