Skip to content

Commit 1ad5025

Browse files
vvoHaroenv
vvo
andcommitted
fix(deletes): handle npm deletions
We had to change the way replicate is done, it was actually completely broken since we "fixed" the memory leak. The reasoning is to always use db.changes as an event emitter to allow: - getting docs from couchdb - avoiding memory leaks (when complete event is triggered, docs are already gone from memory) Co-authored-by: Haroen Viaene <[email protected]>
1 parent f620235 commit 1ad5025

File tree

3 files changed

+58
-22
lines changed

3 files changed

+58
-22
lines changed

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,11 @@ When the process starts with `seq=0`:
236236
* replicate registry changes since the current sequence
237237
* watch for registry changes continuously and replicate them
238238

239+
Replicate and watch are separated because:
240+
1. In replicate we want to replicate a batch of documents in a fast way
241+
2. In watch we want new changes as fast as possible, one by one. If watch was
242+
asking for batches of 100, new packages would be added too late to the index
243+
239244
## Tests
240245

241246
```sh

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,5 +75,8 @@
7575
},
7676
"engines": {
7777
"node": "9.4.0"
78+
},
79+
"jest": {
80+
"testPathIgnorePatterns": ["node_modules", "lib"]
7881
}
7982
}

src/index.js

Lines changed: 50 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import PouchDB from 'pouchdb-http';
66
import * as npm from './npm.js';
77
import log from './log.js';
88
import ms from 'ms';
9+
import cargo from 'async/cargo';
910
import queue from 'async/queue';
1011

1112
log.info('🗿 npm ↔️ Algolia replication starts ⛷ 🐌 🛰');
@@ -15,7 +16,6 @@ const defaultOptions = {
1516
include_docs: true, // eslint-disable-line camelcase
1617
conflicts: false,
1718
attachments: false,
18-
return_docs: false, // eslint-disable-line camelcase
1919
};
2020

2121
let loopStart = Date.now();
@@ -105,6 +105,7 @@ async function bootstrap(state) {
105105
.allDocs({
106106
...defaultOptions,
107107
...options,
108+
return_docs: false, // eslint-disable-line camelcase
108109
limit: c.bootstrapConcurrency,
109110
})
110111
.then(res => {
@@ -146,38 +147,57 @@ async function moveToProduction() {
146147
await client.deleteIndex(c.bootstrapIndexName);
147148
}
148149

149-
function replicate({ seq }) {
150+
async function replicate({ seq }) {
150151
log.info(
151152
'🐌 Replicate: Asking for %d changes since sequence %d',
152153
c.replicateConcurrency,
153154
seq
154155
);
155156

156-
return db
157-
.changes({
157+
const { seq: npmSeqToReach } = await npm.info();
158+
159+
return new Promise((resolve, reject) => {
160+
const changes = db.changes({
158161
...defaultOptions,
159162
since: seq,
160-
limit: c.replicateConcurrency,
161-
})
162-
.then(res =>
163-
saveDocs({ docs: res.results, index: mainIndex })
163+
batch_size: c.replicateConcurrency, // eslint-disable-line camelcase
164+
live: true,
165+
return_docs: true, // eslint-disable-line camelcase
166+
});
167+
168+
const q = cargo((docs, done) => {
169+
saveDocs({ docs, index: mainIndex })
170+
.then(() => infoChange(docs[docs.length - 1].seq, 1, '🐌'))
164171
.then(() =>
165172
stateManager.save({
166-
seq: res.last_seq,
173+
seq: docs[docs.length - 1].seq,
167174
})
168175
)
169-
.then(() => infoChange(res.last_seq, res.results.length, '🐌'))
170-
.then(() => {
171-
if (res.results.length < c.replicateConcurrency) {
172-
log.info('🐌 Replicate: done');
173-
return true;
176+
.then(({ seq: lastDocSeq }) => {
177+
if (lastDocSeq >= npmSeqToReach) {
178+
log.info('🐌 We reached the npm current sequence');
179+
changes.cancel();
174180
}
175-
176-
return replicate({
177-
seq: res.last_seq,
178-
});
179181
})
180-
);
182+
.then(done)
183+
.catch(done);
184+
}, c.replicateConcurrency);
185+
186+
changes.on('change', async change => {
187+
if (change.deleted === true) {
188+
await mainIndex.deleteObject(change.id);
189+
log.info(`🐌 Deleted ${change.id}`);
190+
}
191+
192+
q.push(change, err => {
193+
if (err) {
194+
reject(err);
195+
}
196+
});
197+
});
198+
changes.on('complete', resolve);
199+
changes.on('error', reject);
200+
});
181201
}
182202

183203
function watch({ seq }) {
@@ -191,7 +211,7 @@ function watch({ seq }) {
191211
since: seq,
192212
live: true,
193213
limit: undefined,
194-
return_docs: false, // eslint-disable-line camelcase
214+
return_docs: true, // eslint-disable-line camelcase
195215
});
196216

197217
const q = queue((change, done) => {
@@ -226,8 +246,16 @@ function watch({ seq }) {
226246
.catch(done);
227247
}, 1);
228248

229-
changes.on('change', change => {
230-
q.push(change);
249+
changes.on('change', async change => {
250+
if (change.deleted === true) {
251+
await mainIndex.deleteObject(change.id);
252+
log.info(`🛰 Deleted ${change.id}`);
253+
}
254+
q.push(change, err => {
255+
if (err) {
256+
reject(err);
257+
}
258+
});
231259
});
232260
changes.on('error', reject);
233261
});

0 commit comments

Comments
 (0)