Skip to content
This repository was archived by the owner on Feb 12, 2024. It is now read-only.

Commit b5ea76a

Browse files
fix: cache preloaded CIDs (#3363)
We 'preload' most CIDs we interact with on the network. In some cases this can mean preloading the same CID over and over again which is not necessary. This PR adds a LRU cache to the preloader with a default size of 1000. The cache is used to avoid re-preloading the same CID over and over again until it drops out of the cache. We use a cache that will evict CIDs over time to have some sort of upper bound on memory usage. Fixes #3307 Co-authored-by: Vasco Santos <[email protected]>
1 parent a542882 commit b5ea76a

File tree

2 files changed

+45
-14
lines changed

2 files changed

+45
-14
lines changed

packages/ipfs-core/src/preload.js

+18-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ const CID = require('cids')
66
const shuffle = require('array-shuffle')
77
const AbortController = require('native-abort-controller')
88
const preload = require('./runtime/preload-nodejs')
9+
/** @type {typeof import('hashlru').default} */
10+
// @ts-ignore - hashlru has incorrect typedefs
11+
const hashlru = require('hashlru')
912

1013
const log = Object.assign(
1114
debug('ipfs:preload'),
@@ -14,12 +17,14 @@ const log = Object.assign(
1417

1518
/**
1619
* @param {Object} [options]
17-
* @param {boolean} [options.enabled]
18-
* @param {string[]} [options.addresses]
20+
* @param {boolean} [options.enabled = false] - Whether to preload anything
21+
* @param {string[]} [options.addresses = []] - Which preload servers to use
22+
* @param {number} [options.cache = 1000] - How many CIDs to cache
1923
*/
2024
const createPreloader = (options = {}) => {
2125
options.enabled = Boolean(options.enabled)
2226
options.addresses = options.addresses || []
27+
options.cache = options.cache || 1000
2328

2429
if (!options.enabled || !options.addresses.length) {
2530
log('preload disabled')
@@ -34,6 +39,9 @@ const createPreloader = (options = {}) => {
3439
let requests = []
3540
const apiUris = options.addresses.map(toUri)
3641

42+
// Avoid preloading the same CID over and over again
43+
const cache = hashlru(options.cache)
44+
3745
/**
3846
* @param {string|CID} path
3947
* @returns {Promise<void>}
@@ -46,6 +54,14 @@ const createPreloader = (options = {}) => {
4654
path = new CID(path).toString()
4755
}
4856

57+
if (cache.has(path)) {
58+
// we've preloaded this recently, don't preload it again
59+
return
60+
}
61+
62+
// make sure we don't preload this again any time soon
63+
cache.set(path, true)
64+
4965
const fallbackApiUris = shuffle(apiUris)
5066
let success = false
5167
const now = Date.now()

packages/ipfs-core/test/preload.spec.js

+27-12
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,19 @@ describe('preload', () => {
2626
after(() => cleanup())
2727
afterEach(() => MockPreloadNode.clearPreloadCids())
2828

29+
it('should not preload content multiple times', async function () {
30+
this.timeout(50 * 1000)
31+
const { cid } = await ipfs.add(uint8ArrayFromString(nanoid()), { preload: false })
32+
33+
await all(ipfs.cat(cid))
34+
await MockPreloadNode.waitForCids(cid)
35+
36+
// should not preload the second time
37+
await MockPreloadNode.clearPreloadCids()
38+
await all(ipfs.cat(cid))
39+
await expect(MockPreloadNode.waitForCids(cid)).to.eventually.be.rejectedWith('Timed out waiting for CIDs to be preloaded')
40+
})
41+
2942
it('should preload content added with add', async function () {
3043
this.timeout(50 * 1000)
3144
const res = await ipfs.add(uint8ArrayFromString(nanoid()))
@@ -112,7 +125,7 @@ describe('preload', () => {
112125
}, {
113126
path: 'dir0/file2',
114127
content: uint8ArrayFromString(nanoid())
115-
}], { wrapWithDirectory: true }))
128+
}], { wrapWithDirectory: true, preload: false }))
116129

117130
const wrappingDir = res.find(file => file.path === '')
118131
expect(wrappingDir).to.exist()
@@ -147,12 +160,12 @@ describe('preload', () => {
147160

148161
const [parent, link] = await Promise.all([createNode(), createNode()])
149162

163+
await MockPreloadNode.clearPreloadCids()
150164
const cid = await ipfs.object.patch.addLink(parent.cid, {
151165
Name: 'link',
152166
Hash: link.cid,
153167
Tsize: link.node.size
154168
})
155-
156169
await MockPreloadNode.waitForCids(cid)
157170
})
158171

@@ -171,27 +184,31 @@ describe('preload', () => {
171184
}]
172185
})
173186

187+
await MockPreloadNode.clearPreloadCids()
174188
const cid = await ipfs.object.patch.rmLink(parentCid, { name: 'link' })
175189
await MockPreloadNode.waitForCids(cid)
176190
})
177191

178192
it('should preload content added with object.patch.setData', async function () {
179193
this.timeout(50 * 1000)
180194
const originalCid = await ipfs.object.put({ Data: uint8ArrayFromString(nanoid()), Links: [] })
195+
await MockPreloadNode.clearPreloadCids()
181196
const patchedCid = await ipfs.object.patch.setData(originalCid, uint8ArrayFromString(nanoid()))
182197
await MockPreloadNode.waitForCids(patchedCid)
183198
})
184199

185200
it('should preload content added with object.patch.appendData', async function () {
186201
this.timeout(50 * 1000)
187202
const originalCid = await ipfs.object.put({ Data: uint8ArrayFromString(nanoid()), Links: [] })
203+
await MockPreloadNode.clearPreloadCids()
188204
const patchedCid = await ipfs.object.patch.appendData(originalCid, uint8ArrayFromString(nanoid()))
189205
await MockPreloadNode.waitForCids(patchedCid)
190206
})
191207

192208
it('should preload content retrieved with object.get', async function () {
193209
this.timeout(50 * 1000)
194-
const cid = await ipfs.object.new({ preload: false })
210+
const cid = await ipfs.object.put({ Data: uint8ArrayFromString(nanoid()), Links: [] }, { preload: false })
211+
await MockPreloadNode.clearPreloadCids()
195212
await ipfs.object.get(cid)
196213
await MockPreloadNode.waitForCids(cid)
197214
})
@@ -205,13 +222,15 @@ describe('preload', () => {
205222
it('should preload content retrieved with block.get', async function () {
206223
this.timeout(50 * 1000)
207224
const block = await ipfs.block.put(uint8ArrayFromString(nanoid()), { preload: false })
225+
await MockPreloadNode.clearPreloadCids()
208226
await ipfs.block.get(block.cid)
209227
await MockPreloadNode.waitForCids(block.cid)
210228
})
211229

212230
it('should preload content retrieved with block.stat', async function () {
213231
this.timeout(50 * 1000)
214232
const block = await ipfs.block.put(uint8ArrayFromString(nanoid()), { preload: false })
233+
await MockPreloadNode.clearPreloadCids()
215234
await ipfs.block.stat(block.cid)
216235
await MockPreloadNode.waitForCids(block.cid)
217236
})
@@ -228,39 +247,35 @@ describe('preload', () => {
228247
const obj = { test: nanoid() }
229248
const opts = { format: 'dag-cbor', hashAlg: 'sha2-256', preload: false }
230249
const cid = await ipfs.dag.put(obj, opts)
250+
await MockPreloadNode.clearPreloadCids()
231251
await ipfs.dag.get(cid)
232252
await MockPreloadNode.waitForCids(cid)
233253
})
234254

235255
it('should preload content retrieved with files.ls', async () => {
236-
const res = await ipfs.add({ path: `/t/${nanoid()}`, content: uint8ArrayFromString(nanoid()) })
256+
const res = await ipfs.add({ path: `/t/${nanoid()}`, content: uint8ArrayFromString(nanoid()) }, { preload: false })
237257
const dirCid = res.cid
238-
await MockPreloadNode.waitForCids(dirCid)
239258
await MockPreloadNode.clearPreloadCids()
240259
await all(ipfs.files.ls(`/ipfs/${dirCid}`))
241260
await MockPreloadNode.waitForCids(`/ipfs/${dirCid}`)
242261
})
243262

244263
it('should preload content retrieved with files.ls by CID', async () => {
245-
const res = await ipfs.add({ path: `/t/${nanoid()}`, content: uint8ArrayFromString(nanoid()) })
264+
const res = await ipfs.add({ path: `/t/${nanoid()}`, content: uint8ArrayFromString(nanoid()) }, { preload: false })
246265
const dirCid = res.cid
247-
await MockPreloadNode.waitForCids(dirCid)
248-
await MockPreloadNode.clearPreloadCids()
249266
await all(ipfs.files.ls(dirCid))
250267
await MockPreloadNode.waitForCids(dirCid)
251268
})
252269

253270
it('should preload content retrieved with files.read', async () => {
254-
const { cid } = await ipfs.add(uint8ArrayFromString(nanoid()))
255-
await MockPreloadNode.waitForCids(cid)
271+
const { cid } = await ipfs.add(uint8ArrayFromString(nanoid()), { preload: false })
256272
await MockPreloadNode.clearPreloadCids()
257273
await ipfs.files.read(`/ipfs/${cid}`)
258274
await MockPreloadNode.waitForCids(`/ipfs/${cid}`)
259275
})
260276

261277
it('should preload content retrieved with files.stat', async () => {
262-
const { cid: fileCid } = await ipfs.add(uint8ArrayFromString(nanoid()))
263-
await MockPreloadNode.waitForCids(fileCid)
278+
const { cid: fileCid } = await ipfs.add(uint8ArrayFromString(nanoid()), { preload: false })
264279
await MockPreloadNode.clearPreloadCids()
265280
await ipfs.files.stat(`/ipfs/${fileCid}`)
266281
await MockPreloadNode.waitForCids(`/ipfs/${fileCid}`)

0 commit comments

Comments
 (0)