redis-oplog icon indicating copy to clipboard operation
redis-oplog copied to clipboard

Highly efficient counter

Open theodorDiaconu opened this issue 7 years ago • 4 comments

If you want to have a simple count for your query, we should be able to use redis-oplog to catch added, updated, removed events, and update an ObservableCounter.

If we are here why not take another leap and provide a function that allows people to reduce to the value they want. A sort of map reducer. To think

theodorDiaconu avatar Nov 24 '17 00:11 theodorDiaconu

This would be great! Currently using this package, but the ~10 second polling interval can be a bit annoying: https://github.com/nate-strauser/meteor-publish-performant-counts

jasongrishkoff avatar Dec 27 '17 19:12 jasongrishkoff

using "INCR" and "DECR" should do a great job, in the worst case we could appeal to some lua...

I think the problem would be how to count the results for a certain query, i.e. count all documents that belong to a user instead of simply all documents.

Perhaps the same way we have a "channel" parameter we could have a "counter" parameter which would be the id for the counter we are affecting when doing the given insert/update/remove !

hems avatar Jan 12 '18 03:01 hems

Some users cases i can think of:

Let's say you have a query where you need a counter for all documents belonging to a user with status "done" and another counter for all documents belonging to a user with status "todo", so, later on, we could easily provide counters to "paginate" such tables.

Following / Followers: Every time a user follows another user a new document is created in a collection, once a user follow someone we should INCR the following count for a user and INCR the "followed" counter for the other user.

hems avatar Jan 12 '18 03:01 hems

Not sure how performant this really is, also it depends on server-side autoruns --- but our current hack to get Counters working in our transition to redis oplog is:

package.js

Package.describe({
  name: 'hive:counts',
  version: '0.1.1',
  summary: 'Publish counts',
});

Package.onUse(function(api) {
  api.versionsFrom('[email protected]');
  api.use(['random', 'peerlibrary:[email protected]'], 'server');
  api.addFiles('lib/server.js', 'server');
  api.addFiles('lib/client.js', 'client');
  api.export(['Counter']);
});

Server (lib/server.js):

const collection = new Meteor.Collection('counters-collection', { connection: null });

// Sets up an autorun for a counters-collection insert unique to this
// publication. Inserts a document on the first run, updates after that.
// Once the subscription is stopped, removes the item.
Counter = function(name, cursor, sub) {
  var _id = Random.id();
  var c = null;
  Tracker.autorun(function(computation) {
    c = computation;
    var count = cursor.count();
    if (computation.firstRun) {
      collection.insert({ _id: _id, name: name, count: count });
    } else {
      collection.update({ _id: _id }, { $set: { count: count } });
    }
  });
  sub.onStop(function() {
    if (c && c.stop) {
      c.stop();
      collection.remove({ _id: _id });
    }
  });
  return collection.find({ _id: _id });
};

Client (lib/client.js):

Counter = {};
Counter.collection = new Meteor.Collection('counters-collection');

Counter.get = function(name) {
  var doc = Counter.collection.findOne({ name: name });
  if (doc) {
    return doc.count;
  } else {
    return 0;
  }
};

Example usage on Server:

Meteor.publish('mySubscription', function mySubcription() {
  const subName = 'someUniqueCounterName';
  const self = this;

  const cursor = MyCollection.find({});
  const counter = new Counter(subName, cursor, self);

  return [counter, cursor];
});

Example usage on Client:

Meteor.subscribe('mySubscription', () => {
  Counter.get('someUniqueCounterName');
});

Again, just a hack to get it working. Haven't benchmarked yet since we're not using redis oplog in production yet but are working towards it.

etyp avatar Apr 12 '18 01:04 etyp