-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathfetch-by-id.mjs
executable file
·196 lines (160 loc) · 4.93 KB
/
fetch-by-id.mjs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
import { waitForIdle } from './wait-for-idle.mjs';
import { getTestData } from './test-data.mjs';
import {
log,
logResults,
clear,
prepareTestDatabase,
compareQueryResults,
} from './utils.mjs';
const MS_PER_DAY = 1000 * 60 * 60 * 24;
const EXP_FACTOR = 0.00225;
const output = document.querySelector('output');
export async function runFetchByIdTest({ numCards, numNotes }) {
clear(output);
const queryResults = [];
try {
log(
`Generating test data for ${numCards} card(s) and ${numNotes} note(s)...`
);
const testData = getTestData({ numCards, numNotes });
const idsToFetch = getIdsToFetch(testData.cards);
log('Running overdueness test...');
queryResults.push(await runViewTest(testData, idsToFetch));
queryResults.push(await runIdTest(testData, idsToFetch));
log('Re-running all tests a second time in reverse order...');
await runIdTest(testData, idsToFetch);
await runViewTest(testData, idsToFetch);
log('Done.');
} catch (e) {
log(e.message, 'error');
}
compareQueryResults(queryResults);
}
function getIdsToFetch(cards) {
// We typically would fetch a whole review at once which would be, say, 20
// cards. But just to exercise the function a little more, let's try a review
// of 100 cards.
const numIds = Math.min(cards.length, 100);
const result = new Set();
while (result.size < numIds) {
const index = Math.floor(Math.random() * cards.length);
const id = cards[index]._id.substr('card-'.length);
result.add(id);
}
// Add a couple of missing cards
result.add('definitely-not-an-id');
result.add('neither-is-this');
return [...result];
}
// 1. With separate combined view (current setup)
async function runViewTest(testData, idsToFetch) {
// Prep
log('1. Lookup by view', 'heading');
log('Preparing database...');
const db = await prepareTestDatabase(testData);
await waitForIdle();
log('Running test...');
const reviewTime = new Date();
// Run test
const startTime = performance.now();
await db.put({
_id: `_design/cards`,
views: {
cards: {
map: `function(doc) {
if (!doc._id.startsWith('progress-')) {
return;
}
emit(doc._id, {
_id: 'card-' + doc._id.substr('progress-'.length),
progress: {
level: doc.level,
due: doc.due,
},
});
}`,
},
},
});
await db.query('cards', { limit: 0 });
const indexCreationTimeMs = performance.now() - startTime;
const timeResults = [];
let queryResult;
for (let i = 0; i < 5; i++) {
const runStartTime = performance.now();
const keys = idsToFetch.map(id => `progress-${id}`);
const result = await db.query('cards', {
keys,
include_docs: true,
});
queryResult = result.rows.map(row => {
// TODO: This doesn't actually work. Despite what the docs say, query
// doesn't return error objects for missing keys.
if (row.doc && !row.deleted && !row.error) {
return {
...row.doc,
progress: row.value.progress,
};
} else {
return { status: 'missing' };
}
});
timeResults.push(performance.now() - runStartTime);
}
const durationMs = performance.now() - startTime;
// Clean up
await db.destroy();
log(`Index creation took ${indexCreationTimeMs}ms`);
logResults(durationMs, timeResults);
return queryResult;
}
// 2. Looking up by ID twice and pairing them up
async function runIdTest(testData, idsToFetch) {
// Prep
log('2. Lookup by ID twice', 'heading');
log('Preparing database...');
const db = await prepareTestDatabase(testData);
await waitForIdle();
log('Running test...');
const reviewTime = new Date();
// Run test
const startTime = performance.now();
const timeResults = [];
let queryResult = [];
for (let i = 0; i < 5; i++) {
const runStartTime = performance.now();
queryResult = [];
const progressKeys = idsToFetch.map(id => `progress-${id}`);
const progressResult = await db.allDocs({
include_docs: true,
keys: progressKeys,
});
const cardKeys = idsToFetch.map(id => `card-${id}`);
const cardResult = await db.allDocs({
include_docs: true,
keys: cardKeys,
});
if (progressResult.rows.length !== cardResult.rows.length) {
throw new Error(
`Got mismatched number of card records (progress: ${progressResult.rows.length} vs cards: ${cardResult.rows.length})`
);
}
for (const [i, progressRow] of progressResult.rows.entries()) {
if (!progressRow.doc) {
queryResult.push({ status: 'missing' });
continue;
}
queryResult.push({
...cardResult.rows[i].doc,
progress: { level: progressRow.doc.level, due: progressRow.doc.due },
});
}
timeResults.push(performance.now() - runStartTime);
}
const durationMs = performance.now() - startTime;
// Clean up
await db.destroy();
logResults(durationMs, timeResults);
return queryResult;
}