Cht-core: WebWorker service is terrible for performance

Created on 7 Feb 2018  Â·  13Comments  Â·  Source: medic/cht-core

TODO

Pull requests:

Observed

WebWorkerSrv is terrible for performance

  1. browse to your favourite medic project
  2. log in as a restricted user
  3. pop the following in your javascript console:
function log(...args) {
  console.log(new Date(), ...args);
}

function benchmark(db, opts) {
  if(!opts) opts = {};
  opts.include_docs = true;

  const allDocsStart = Date.now();
  let allIds;

  return db.allDocs(opts)
    .then(res => {
      log(`[worker: ${opts.worker}] DONE: allDocs`, Date.now() - allDocsStart, 'ms');
      allIds = res.rows.map(r => r.id);
    })
// This code was useful in demonstrating the allDocs({ keys }) bug fixed in PouchDB 6.4
//    .then(() => {
//      const manualStart = Date.now();
//      return db
//        .allDocs(opts)
//        .then(res => res.rows.filter(row => allIds.includes(row.id)))
//        .then(() => log(`[worker: ${opts.worker}] DONE: manual`, Date.now() - manualStart, 'ms'));
//    })
    .then(() => {
      const filteredStart = Date.now();
      opts.keys = allIds;
      return db
        .allDocs(opts)
        .then(() => log(`[worker: ${opts.worker}] DONE: filtered`, Date.now() - filteredStart, 'ms'));
    });
}

medicWorkerDb = angular.element(document.body).injector().get('DB')();
normalDb = new PouchDB(medicWorkerDb.name);

let iter = 0;
Promise.resolve()

  .then(() => log(++iter, 'BENCHMARK FOR: normalDb'))
  .then(() => benchmark(normalDb))

  .then(() => log(iter, 'BENCHMARK FOR: medicWorkerDb'))
  .then(() => benchmark(medicWorkerDb))

  .then(() => log(++iter, 'BENCHMARK FOR: normalDb'))
  .then(() => benchmark(normalDb))

  .then(() => log(iter, 'BENCHMARK FOR: medicWorkerDb'))
  .then(() => benchmark(medicWorkerDb))

  .then(() => log(++iter, 'BENCHMARK FOR: normalDb'))
  .then(() => benchmark(normalDb))

  .then(() => log(iter, 'BENCHMARK FOR: medicWorkerDb'))
  .then(() => benchmark(medicWorkerDb))

  .then(() => log(++iter, 'BENCHMARK FOR: normalDb'))
  .then(() => benchmark(normalDb))

  .then(() => log(iter, 'BENCHMARK FOR: medicWorkerDb'))
  .then(() => benchmark(medicWorkerDb))

  .then(() => log(++iter, 'BENCHMARK FOR: normalDb'))
  .then(() => benchmark(normalDb))

  .then(() => log(iter, 'BENCHMARK FOR: medicWorkerDb'))
  .then(() => benchmark(medicWorkerDb))

  .catch(log);
  1. gawp at the results

worker-pouch is mildly helpful for performance

  1. browse to https://medic.github.io/atp/pouchdb-6.4.2/
  2. pop the following in your javascript console:
user = ?;
localDbName = ?; // re-use this to prevent re-replication every iteration
db = new PouchDB(localDbName);

function benchmark(db, opts) {
  if(!opts) opts = {};
  opts.include_docs = true;

  const allDocsStart = Date.now();
  let allIds;

  return db.allDocs(opts)
    .then(res => {
      console.log(`[worker: ${opts.worker}] DONE: allDocs`, Date.now() - allDocsStart, 'ms');
      allIds = res.rows.map(r => r.id);
    })
    .then(() => {
      const manualStart = Date.now();
      return db
        .allDocs(opts)
        .then(res => res.rows.filter(row => allIds.includes(row.id)))
        .then(() => console.log(`[worker: ${opts.worker}] DONE: manual`, Date.now() - manualStart, 'ms'));
    })
    .then(() => {
      const filteredStart = Date.now();
      opts.keys = allIds;
      return db
        .allDocs(opts)
        .then(() => console.log(`[worker: ${opts.worker}] DONE: filtered`, Date.now() - filteredStart, 'ms'));
    });
}

let iter = 0;
db.replicate.from(`http://${user}:pass@localhost:5988/medic`, {
    doc_ids: [`org.couchdb.user:${user}`],
    timeout: 1000*60*10,
  })
  .then(() => console.log('db replication complete.'))

  .then(() => console.log(++iter, 'MAIN THREAD:'))
  .then(() => benchmark(db))

  .then(() => console.log(iter, 'WORKER THREAD:'))
  .then(() => benchmark(db, { worker:'worker' }))

  .then(() => console.log(++iter, 'MAIN THREAD:'))
  .then(() => benchmark(db))

  .then(() => console.log(iter, 'WORKER THREAD:'))
  .then(() => benchmark(db, { worker:'worker' }))

  .then(() => console.log(++iter, 'MAIN THREAD:'))
  .then(() => benchmark(db))

  .then(() => console.log(iter, 'WORKER THREAD:'))
  .then(() => benchmark(db, { worker:'worker' }))

  .then(() => console.log(++iter, 'MAIN THREAD:'))
  .then(() => benchmark(db))

  .then(() => console.log(iter, 'WORKER THREAD:'))
  .then(() => benchmark(db, { worker:'worker' }))

  .then(() => console.log(++iter, 'MAIN THREAD:'))
  .then(() => benchmark(db))

  .then(() => console.log(iter, 'WORKER THREAD:'))
  .then(() => benchmark(db, { worker:'worker' }))

  .catch(console.log);
  1. scratch your head a bit

Conclusion

Our WebWorker wrapper is having a terrible effect on PouchDB's performance.

Recommendation

Scientific Approach

  1. test this with in-use version(s) of PouchDB and record the results
  2. test this on crosswalk on relevant handsets
  3. create tickets for specific improvements

Slapdash Approach

I suspect the scientific approach will lead us here anyway.

  1. upgrade all supported project branches to latest PouchDB
  2. modify static/js/services/web-worker.js to return an unadulterated WebWorker instance in browsers which support it
1 - High Performance

Most helpful comment

I've got an idea on how to test. Will work on it on Monday. In the meantime, I had some performance concerns initially but I think my Y4 was just having a slow day earlier this week and it now seems pretty much the same as before. I'll have to test on a user with a lot more data to confirm.

All 13 comments

I'm pretty sure our wrapper is only necessary for really old versions of Chrome (including Chrome 30) which didn't support logging from within a WebWorker and once we bump our support we can drop the wrapper altogether.

worker-pouch is mildly helpful for performance

NB: the real benefit to worker-pouch is multithreading so the rest of the app can keep processing rules, responding to user actions, etc while pouch does its thing. This aspect isn't captured by your testing setup.

modify static/js/services/web-worker.js to return an unadulterated WebWorker instance in browsers which support it

I like this solution. That way we give people the option to use a crosswalk version of the android app to get the performance improvement. Then we can remove the adulterated webworker altogether when we bump browser requirements in 3.0.

  • Tecno Y4
  • 20k docs
  • local network

no-worker first

Running the benchmark code as above:

no worker

allDocs({ keys: all_db_ids }): 100 seconds

worker

allDocs({ keys: all_db_ids }): does not complete

worker first

Running the benchmark code, but with the worker-pouch version first:

worker

allDocs({ keys: all_db_ids }): app crashes

no worker

didn't get this far

modify static/js/services/web-worker.js to return an unadulterated WebWorker instance in browsers which support it

Actually I'd say this is already happening - polyfilling global.console inside the worker is done sensibly - only if required, and only missing methods are stubbed.

Samsung J1 Mini Play

  • 20,000 docs
  • connecting to webapp/api over wifi

PouchDB 6.1.2

screen shot 2018-02-13 at 15 25 58

--> _CRASH_

With only normal DB:

screen shot 2018-02-13 at 15 38 10

Conclusion

With PouchDB 6.1.2, WebWorkerService should not be used. If planning to use worker-pouch, then the JS should probably be loaded directly from a file URL rather than a blob (this currently seems to be the major difference between _our_ worker-couch usage, and that at https://medic.github.io/atp/pouchdb-6.4.2/).

PouchDB 6.4.3

Results for this version of Pouch seem consistent with for 6.1.2:

screen shot 2018-02-13 at 19 38 16

My conclusion is that we should stop using a WebWorker for PouchDB because the benefits are intangible, and the performance overhead is unbearable.

Pull requests:

On Chrome desktop, results with the WebWorker are much worse, but inconsistently so:

screen shot 2018-02-13 at 19 30 15

Perhaps the worker is given a lower priority than the main JS thread.

Huawei MediaPad T1 Tablet

1 — 32496a8a8edc48e4d0b58693c1703d44bc94c925 (master/2.15.x/6.4.3/WebWorker)

screen shot 2018-02-14 at 17 01 28

...finally medic-android crashed.

2 — d1461d39cf496100223f5569f73f1f294fd77f58 (no-web-worker/2.15.x/6.4.3/PouchDB on main thread)

(n.b. log output readsmedicWorkerDb when it really means the db returned from the Angular DB service)

screen shot 2018-02-14 at 17 13 56

Merged and backported to 2.14.x. When acceptance testing make sure performance is acceptable on Y4s (or similar) with restricted users with access to tens of thousands of docs.

Do we have a test instance with tens of thousands of docs.?

No, but that would be _very_ useful!

I've got an idea on how to test. Will work on it on Monday. In the meantime, I had some performance concerns initially but I think my Y4 was just having a slow day earlier this week and it now seems pretty much the same as before. I'll have to test on a user with a lot more data to confirm.

I've successfully tested on Y4. I upgraded a clone of the MoH Siaya instance and was able to load all of the docs on my Tecno (it took a long time to load initially but it made it). Once the app opened, warming up for the first time took a while too, but after warming up, everything was working at an acceptable pace. I then hard-closed and re-opened. It took about 8.5 minutes to re-open the app and less than 30 seconds to load any of the pages for the first time. I consider this acceptable performance for now and am moving this to ready.

Was this page helpful?
0 / 5 - 0 ratings