tag:blogger.com,1999:blog-18508356.post244206574615048453..comments2026-02-13T11:24:21.556-05:00Comments on Just a little Python: MongoDB Pub/Sub with Capped CollectionsRick Copelandhttp://www.blogger.com/profile/11612114223288841087noreply@blogger.comBlogger22125tag:blogger.com,1999:blog-18508356.post-14971796051460860382015-10-09T10:49:55.115-04:002015-10-09T10:49:55.115-04:00No, the sources are for something different. I'...No, the sources are for something different. I've restarted the mongodb server and the ids have changed in the middle.Victorhttps://www.blogger.com/profile/03730310438852689932noreply@blogger.comtag:blogger.com,1999:blog-18508356.post-4020702694656243312015-10-09T10:38:50.845-04:002015-10-09T10:38:50.845-04:00Looking at the sources (I could really read them, ...Looking at the sources (I could really read them, but; https://github.com/mongodb/libbson/blob/master/src/bson/bson-oid.c#L104):<br /><br />> The bson_oid_t generated by this function is not guaranteed to be globally unique. Only unique within this context. It is however, guaranteed to be sequential.Victorhttps://www.blogger.com/profile/03730310438852689932noreply@blogger.comtag:blogger.com,1999:blog-18508356.post-67472694597769685652015-10-09T10:25:16.222-04:002015-10-09T10:25:16.222-04:00Ah yes, you are right about the rollover.Ah yes, you are right about the rollover.Victorhttps://www.blogger.com/profile/03730310438852689932noreply@blogger.comtag:blogger.com,1999:blog-18508356.post-76543397233720306232015-10-09T10:20:59.019-04:002015-10-09T10:20:59.019-04:00With the oplog, the filtering happens on the serve...With the oplog, the filtering happens on the server (you query the oplog for only the events you're interested in), so it doesn't actually consume more resources than the approach in this blog post. So you'd do something like db.oplog.rs.find({'ns': 'my-db.messages', 'ts': {'$gt': last_ts_field_I_handled}}).sort('$natural)<br /><br />For rollover, keep in mind that, while the counter may not be *thread* local, it is certainly *machine* local, and is not a shared counter with the clients. Thus we can ignore them entirely for this analysis. So consider two ObjectIds generated in the same second *on the server*. In this case, the first one is [current_time, server_machine_id, server_pid, 0xffffff], and the second one is [current_time, server_machine_id, server_pid, 0x000000]. So you get rollover, and order inversion. Now, you may have a slow event stream, which would reduce the likelihood of this, but it 50% of your events occur in the same second as the previous event, this inversion will happen, on average, every 48 mln events, with higher likelihood if you have a faster flow of events. (And the approach in this blog post can generate 1000s of events per second, so you *can* get inversions almost every every 28 mln events).<br />Rick Copelandhttps://www.blogger.com/profile/11612114223288841087noreply@blogger.comtag:blogger.com,1999:blog-18508356.post-7772194777577636322015-10-09T10:08:34.480-04:002015-10-09T10:08:34.480-04:00> This means that if you tail the oplog you'...> This means that if you tail the oplog you'll have to filter out operations that you don't care about.<br /><br />That's what I meant. Filtering out also takes resources. And IIRC oplog is a capped collection with a limited size. So it could be filled with other records with enough intensivity.<br /><br />> It only requires 2 documents in the same second that happen to have the sequence numbers 0xffffff and 0x000000<br /><br />Rollover doesn't affect the order, because it's not within a second. Object id = current second + machine id + process id + counter.<br />In order that object_id_1 > object_id_2 while object object_id_1 was generated earlier than object_id_2, the overflow must happen during one second, which is possible only when during the same second 16 millions ids were generated (while most of the inserted documents have ids generated on clients).Victorhttps://www.blogger.com/profile/03730310438852689932noreply@blogger.comtag:blogger.com,1999:blog-18508356.post-3329454704611228232015-10-09T09:49:19.013-04:002015-10-09T09:49:19.013-04:00For the oplog, I think you have things backwards. ...For the oplog, I think you have things backwards. Inserts into messages (as well as all other collections in your database) causes new operation documents to be inserted into the local.oplog.rs collection. (This means that if you tail the oplog you'll have to filter out operations that you don't care about.) <br /><br />And order inversion doesn't require 16 mln documents per second. It only requires 2 documents in the same second that happen to have the sequence numbers 0xffffff and 0x000000, which can happen once ever 16 mln documents (which is not unrealistic over the course of weeks or months.) And of course, the first rollover will occur at a random time after the counter is initialized, since it's initialized with a random value.<br /><br />Thanks for the comments!Rick Copelandhttps://www.blogger.com/profile/11612114223288841087noreply@blogger.comtag:blogger.com,1999:blog-18508356.post-86489959744735260802015-10-09T09:42:16.851-04:002015-10-09T09:42:16.851-04:00The reply button under your comment doesn't wo...The reply button under your comment doesn't work, so I reply here.<br />I meant that local oplog.rs records insertions not only into messages collection, but also into other collections? Or not?<br /><br />> which would lead to an order inversion if two events in the same second spanned the rollover<br /><br />I thought of this, but this means over 16 mln of documents per second, which is unrealistic. Anyway, I think using server-generated ids suits my case, where sequence within a second is not so important (although on my server machine identifier and process id are the same for inserted records).<br />Victorhttps://www.blogger.com/profile/03730310438852689932noreply@blogger.comtag:blogger.com,1999:blog-18508356.post-49623456055093984452015-10-09T09:30:52.038-04:002015-10-09T09:30:52.038-04:00Oh, and also regarding the local database and repl...Oh, and also regarding the local database and replication: all operations on all your non-local databases are automatically appended to the oplog, so there's no dedicated databases needed for that approach. (Operations in the oplog specify both the database and collection to which they pertain.)Rick Copelandhttps://www.blogger.com/profile/11612114223288841087noreply@blogger.comtag:blogger.com,1999:blog-18508356.post-19273900189163950642015-10-09T09:29:18.251-04:002015-10-09T09:29:18.251-04:00As for server-side ObjectIds being monotonic, ther...As for server-side ObjectIds being monotonic, there are two possible issues. One is counter rollover. At some point your sequence number will contain 0xfffffe, 0xffffff, 0x000000, 0x000001..., which would lead to an order inversion if two events in the same second spanned the rollover. Secondly, I haven't read the MongoDB server code here, but it's certainly possible that a different "machine ID" or "process ID" is used for different threads within the server to eliminate locking, which could lead to consistent order inversion within a second. The same page you referenced did contain a warning not to assume monotonicity (though it didn't explicitly state whether server-side monotonicity is guaranteed).<br /><br />As for the local database, that's a database present on all MongoDB nodes that is never replicated to the rest of the set. That's why the oplog is located in the local database (replicating the oplog would cause a circular replication dependency). And while using the oplog for pub/sub does require running a replica set (in most cases you should probably be doing this in production anyway), it does not require any secondaries. For instance, in development and testing I run a single-node replica set (no secondaries) for just this purpose.Rick Copelandhttps://www.blogger.com/profile/11612114223288841087noreply@blogger.comtag:blogger.com,1999:blog-18508356.post-31854621550411269822015-10-09T09:12:54.099-04:002015-10-09T09:12:54.099-04:00Having server side generated object ids, no counte...Having server side generated object ids, no counters are needed, so there is no race condition (as I see it). Also not every installation is run in master mode having an oplog.Victorhttps://www.blogger.com/profile/03730310438852689932noreply@blogger.comtag:blogger.com,1999:blog-18508356.post-90889581032746129182015-10-09T09:09:38.265-04:002015-10-09T09:09:38.265-04:00I am using this to generate the _id on the server ...I am using this to generate the _id on the server side:<br /> messages.database.eval('db.messages.insert({})'.format(json.dumps(<br /> {'data': message, 'channel': channel}, ensure_ascii=False)))<br /><br />I've seen your code using oplog, but I found it too complex. Does it have to use a dedicated database if you are reading `local`?<br /><br />> I would *not*, however, assume that server-generated _id fields are guaranteed to be monotonically increasing. <br /><br />I would (http://docs.mongodb.org/manual/reference/object-id/), ObjectId contains "a 3-byte counter, starting with a random value", which is, I guess, incremented upon each new object id.<br /><br />Thanks for the article and your comments!Victorhttps://www.blogger.com/profile/03730310438852689932noreply@blogger.comtag:blogger.com,1999:blog-18508356.post-88960164735674593992015-10-09T09:03:12.020-04:002015-10-09T09:03:12.020-04:00eval() could be used to generate new dates on the ...eval() could be used to generate new dates on the server, I suppose, but it suffers from having to eval() Javascript, which causes a speed penalty. It's also not guaranteed that dates are unique when they are generated (multi-client again), so you'd need a unique index to take that into account.<br /><br />Tailing the oplog fixes those issues and more, though. A big issue I discovered when doing using the approach in this blog post is that there is a gap between when the sequence number is generated and when the event is inserted, so you can have a race condition where client A generates a sequence number, client B generates a sequence number, client B inserts an event, server handles client B's event and re-queries the server, updating its internal counter, then client A inserts its event. In this case, client A's event will never be handled because the internal 'tail' of the capped collection has already proceeded beyond client A's sequence number.<br /><br />Additionally, it's much faster to just insert into a collection and count on the oplog to get updated by the replication machinery rather than perform the 2 separate operations of a) generate sequence number and b) insert into oplog.<br /><br />Thanks for the comments!Rick Copelandhttps://www.blogger.com/profile/11612114223288841087noreply@blogger.comtag:blogger.com,1999:blog-18508356.post-39634502866443903962015-10-09T08:53:54.593-04:002015-10-09T08:53:54.593-04:00Good point on the _id field. I would *not*, howeve...Good point on the _id field. I would *not*, however, assume that server-generated _id fields are guaranteed to be monotonically increasing. <br /><br />As an aside, I've actually abandoned this approach in favor of using the oplog directly (just create insertion or update events in some collection and the get a monotonically increasing "ts" field in the oplog automatically)Rick Copelandhttps://www.blogger.com/profile/11612114223288841087noreply@blogger.comtag:blogger.com,1999:blog-18508356.post-34440674551408313002015-10-09T08:53:11.465-04:002015-10-09T08:53:11.465-04:00And this allowed me to evaluate an expression on t...And this allowed me to evaluate an expression on the server side:<br />db.eval('db.messages.insert({ts: new Date()})')Victorhttps://www.blogger.com/profile/03730310438852689932noreply@blogger.comtag:blogger.com,1999:blog-18508356.post-4256734019248977892015-10-09T08:50:11.330-04:002015-10-09T08:50:11.330-04:00According to the docs http://docs.mongodb.org/manu...According to the docs http://docs.mongodb.org/manual/reference/method/db.collection.insert/#id-field <br />> Most drivers create an ObjectId and insert the _id field, but the mongod will create and populate the _id if the driver or application does not.<br /><br />As for server side generated dates, you can do:<br />Victorhttps://www.blogger.com/profile/03730310438852689932noreply@blogger.comtag:blogger.com,1999:blog-18508356.post-20336929964787784212015-10-09T08:25:10.178-04:002015-10-09T08:25:10.178-04:00Neither dates nor ObjectIDs are server-side genera...Neither dates nor ObjectIDs are server-side generated in MongoDB, so there's no guarantee that events are consumed in the same order they're produced.Rick Copelandhttps://www.blogger.com/profile/11612114223288841087noreply@blogger.comtag:blogger.com,1999:blog-18508356.post-24151131212887727032015-10-09T01:02:41.341-04:002015-10-09T01:02:41.341-04:00Why not using server side generated Date or Object...Why not using server side generated Date or ObjectId?Victorhttps://www.blogger.com/profile/03730310438852689932noreply@blogger.comtag:blogger.com,1999:blog-18508356.post-7424084371524936112013-06-14T17:37:12.684-04:002013-06-14T17:37:12.684-04:00Hi Mike,
Thanks for the comment. In the case of t...Hi Mike,<br /><br />Thanks for the comment. In the case of the slow consumer, it is possible to lose data. Blocking the producer is certainly an approach, but in my case I was able to size the capped collection to handle the occasional slow consumer, and in general I have a large number of consumers for each producer. Rick Copelandhttps://www.blogger.com/profile/11612114223288841087noreply@blogger.comtag:blogger.com,1999:blog-18508356.post-78441026163495739582013-06-04T10:01:02.162-04:002013-06-04T10:01:02.162-04:00Rick,
This is a great post, thanks, but it leave...Rick, <br /><br />This is a great post, thanks, but it leaves me wondering one thing: What happens in the case of the classic slow consumer? E.g. what if I can insert records into the capped collection faster than I'm able to get them to the consumers, and capped collection fills up. Won't I simply lose data in that case? Or is there a way I'm not seeing to "block" the producer temporarily?<br /><br />Thanks again for the post<br /><br />MikeAnonymoushttps://www.blogger.com/profile/03377801811387316872noreply@blogger.comtag:blogger.com,1999:blog-18508356.post-66750952677567168032013-04-16T21:52:24.325-04:002013-04-16T21:52:24.325-04:00yaps, you are right in a multi client scenario mon...yaps, you are right in a multi client scenario mongoIds can possibly overlap.<br /> <br />Thanks for replying and your post.<br /><br />Regards Nick MilonNickhttps://www.blogger.com/profile/06695334163724983162noreply@blogger.comtag:blogger.com,1999:blog-18508356.post-32527988882175679372013-04-16T15:45:34.427-04:002013-04-16T15:45:34.427-04:00Thanks for the comment, Nick!
The reason I'm ...Thanks for the comment, Nick!<br /><br />The reason I'm using a manually generated sequence number is that ObjectIds aren't actually sequence numbers (ObjectIds generated on 2 different clients can come in "out of order" for instance). What I need for this is a way to ensure that I never handle a message twice; an always-increasing sequence number is a fairly inexpensive way to do that.<br /><br />Thanks again for the comment!Rick Copelandhttps://www.blogger.com/profile/11612114223288841087noreply@blogger.comtag:blogger.com,1999:blog-18508356.post-86026377319736939702013-04-16T15:36:18.821-04:002013-04-16T15:36:18.821-04:00Nice writing, I have a similar implementation and ...Nice writing, I have a similar implementation and find it extremely usufull since it saves me the trouble of maintenance for one more application (REDIS) in the stack, performance is not bad either.<br />The only thing I missed is hacking of "ts" field although I use 'oplog_replay' when i read from RS oplog.<br /><br />And one question ..... why not use a mongo Id object for filling 'ts' field instead of creating your own sequence ? <br />Nickhttps://www.blogger.com/profile/06695334163724983162noreply@blogger.com