@ssb-graphql/whakapapa
Version:
GraphQL types and resolvers for the ssb-whakapapa plugin
284 lines (239 loc) • 7.18 kB
JavaScript
const test = require('tape')
const { promisify: p } = require('util')
const { join } = require('path')
const { createWhakapapaTree } = require('ahau-fixtures')
const pull = require('pull-stream')
const Pushable = require('pull-pushable')
const paraMap = require('pull-paramap')
const flatMap = require('pull-flatmap')
const TestBot = require('../test-bot')
const SIZE = 5000 // how many profiles!
const DIVISOR = 5000 / 100
const LOG_PROGESS = true
test('loadWhakapapaLinks', async t => {
const { ssb, apollo } = await TestBot({
path: join(__dirname, '../db'),
startUnclean: true
})
const view = await findOrCreateWhakapapa(ssb, SIZE)
.catch(err => {
console.error('error in findOrCreateWhakapapa')
throw err
})
console.log({ view })
let width = 20
// write link lookup for children via existing API
console.time(`Load iteratively (width ${width})`)
const links1 = await loadWhakapapaIteratively(apollo, view.focus, width)
console.timeEnd(`Load iteratively (width ${width})`)
console.log('----')
// width = 10
// console.time(`Load iteratively (width ${width})`)
// const links1b = await loadWhakapapaIteratively(apollo, view.focus, width)
// console.timeEnd(`Load iteratively (width ${width})`)
// console.log('----')
// t.deepEqual(
// links1.sort(childLinkComparator),
// links1b.sort(childLinkComparator),
// 'links are same regardless of how width parallel process is'
// )
width = 20
console.time(`Load parallel (width ${width})`)
const links2 = await loadWhakapapaParallel(apollo, view.focus, width)
console.timeEnd(`Load parallel (width ${width})`)
console.log('----')
console.time('Load in one go')
const links3 = await loadWhakapapaInOneGo(apollo, view.key)
console.timeEnd('Load in one go')
console.log('----')
console.time('Load in one go (now cached!)')
const links4 = await loadWhakapapaInOneGo(apollo, view.key)
console.timeEnd('Load in one go (now cached!)')
console.log('----')
t.deepEqual(
links1.sort(childLinkComparator),
links2.sort(childLinkComparator),
'whakapapaView.links.childLinks are the same'
)
t.deepEqual(
links2.sort(childLinkComparator),
links3.sort(childLinkComparator),
'whakapapaView.links.childLinks are the same'
)
t.deepEqual(
links3.sort(childLinkComparator),
links4.sort(childLinkComparator),
'whakapapaView.links.childLinks are the same'
)
ssb.close()
t.end()
})
async function findOrCreateWhakapapa (ssb, size) {
const views = await ssb.whakapapa.view.list({})
.catch(err => {
console.error('error in whakapapa.view.list')
throw err
})
console.log(views.map(view => view.name))
let view = views.find(view => view.name === `bulk-${size}`)
if (view) return view
console.log('no existing view...')
const groupId = await findOrCreateTribe(ssb)
.catch(err => {
console.error('error in findOrCreateTribe')
throw err
})
console.log({ groupId })
// build a large whakapapa tree, then measure link lookup
// 1. with recurssive loadFamilyOfPerson
// 2. with single backend lookup!
console.log('starting whakapapa build')
const viewId = await createWhakapapaTree(ssb, groupId, SIZE)
.catch(err => {
console.error('error in createWhakapapaTree')
throw err
})
console.log({ viewId })
while (!view) {
view = await ssb.whakapapa.view.get(viewId)
.catch(err => console.error(err.message))
if (view) continue
console.log('not ready')
await new Promise(resolve => setTimeout(resolve, 1000))
}
console.log('here', view)
return view
}
async function findOrCreateTribe (ssb) {
const [groupId] = await p(ssb.tribes.list)({})
.catch(err => { throw err })
if (groupId) return groupId
const tribe = await p(ssb.tribes.create)({})
.catch(err => { throw err })
return tribe.groupId
}
async function loadWhakapapaIteratively (apollo, focus, width = 5) {
let links = []
let queue = [focus]
const QUERY = `query ($profileId: String!, $extended: Boolean) {
loadFamilyOfPerson(id: $profileId, extended: $extended) {
childLinks {
parent
child
relationshipType
}
}
}`
while (queue.length) {
const parentIds = queue.slice(0, width)
queue = queue.slice(width)
const parentChildLinks = await Promise.all(
parentIds.map(profileId => apollo.query({
query: QUERY,
variables: {
profileId,
extended: true
}
}))
)
const childLinks = parentChildLinks
.map(result => result.data.loadFamilyOfPerson.childLinks)
.filter((link, _, i) => link.parent === queue[i])
.flatMap(arr => arr)
links = links.concat(childLinks)
queue = queue.concat(childLinks.map(link => link.child))
if (LOG_PROGESS) logProgress(links, queue)
}
return links
}
async function loadWhakapapaParallel (apollo, focus, width = 10) {
const links = []
const queue = Pushable()
queue.push(focus)
const QUERY = `query ($profileId: String!, $extended: Boolean) {
loadFamilyOfPerson(id: $profileId, extended: $extended) {
childLinks {
parent
child
relationshipType
}
}
}`
let count = 0
return new Promise((resolve, reject) => {
pull(
queue,
paraMap(
(parentId, cb) => {
apollo.query({
query: QUERY,
variables: {
profileId: parentId,
extended: true
}
})
.catch(err => cb(err))
.then(res => {
const childLinks = res.data.loadFamilyOfPerson.childLinks
.filter(link => link.parent === parentId)
if (LOG_PROGESS && ++count % width === 0) logProgress(links, queue.buffer)
cb(null, childLinks)
})
},
width
),
flatMap(arr => arr),
pull.through(link => queue.push(link.child)),
pull.take(SIZE - 1),
pull.drain(
link => {
links.push(link)
},
err => {
if (err) reject(err)
else resolve(links)
}
)
)
})
}
async function loadWhakapapaInOneGo (apollo, viewId) {
const QUERY = `query ($viewId: String!) {
whakapapaView(id: $viewId) {
links {
childLinks {
parent
child
relationshipType
}
}
}
}`
const result = await apollo.query({
query: QUERY,
variables: {
viewId
}
})
.catch(console.error)
return result.data.whakapapaView.links.childLinks
}
function logProgress (links, queue) {
const barWidth = Math.ceil(queue.length / DIVISOR)
console.log([
'links: ' + leftPad(links.length),
'queue: ' + leftPad(queue.length) + ' ' + new Array(barWidth).fill('▦').join('')
].join(', '))
}
function leftPad (str, width = 3) {
let output = String(str)
while (output.length < width) {
output = ' ' + output
}
return output
}
function childLinkComparator (a, b) {
const A = (a.parent + a.child)
const B = (b.parent + b.child)
return A < B ? -1 : 1
}