Slide 1

Slide 1 text

Multi-Master Replication with MongoDB Rick Copeland @rick446 Tuesday, December 4, 12

Slide 2

Slide 2 text

The Client Problem MongoDB (factory floor) MongoDB (cloud) App Server App Server App Server App Server Camera Camera Camera Camera Frame & Feature Data (mostly) Inspection Metadata (mostly) • Cameras save frame images to GridFS • Inspections, measurements, etc. defined by metadata in MongoDB • Access locally + from the web Tuesday, December 4, 12

Slide 3

Slide 3 text

The Client Problem MongoDB (factory floor) MongoDB (cloud) App Server App Server App Server App Server Camera Camera Camera Camera Frame & Feature Data (mostly) Inspection Metadata (mostly) • Frames must still be saved, inspections performed • Tolerate being inaccessible from the cloud Tuesday, December 4, 12

Slide 4

Slide 4 text

Put the Primary in the Cloud? MongoDB (factory floor) MongoDB (cloud) App Server App Server App Server App Server Camera Camera Camera Camera Frame & Feature Data (mostly) Inspection Metadata (mostly) Primary • Pros: • More conventional • Additional seconaries in the cloud • Cons: • Dependent on internet • Saving frame data up an ADSL connection??!! Tuesday, December 4, 12

Slide 5

Slide 5 text

Put the Primary in the Factory? MongoDB (factory floor) MongoDB (cloud) App Server App Server App Server App Server Camera Camera Camera Camera Frame & Feature Data (mostly) Inspection Metadata (mostly) Primary • Pros: • Frame data saves fast • Resilient to outages • Cons: • Backups / durability? • Seems wrong, somehow... Tuesday, December 4, 12

Slide 6

Slide 6 text

But what if.... MongoDB (factory floor) MongoDB (cloud) App Server App Server App Server App Server Camera Camera Camera Camera Frame & Feature Data (mostly) Inspection Metadata (mostly) Primary Primary • Pros: • Frame data saves fast • Resilient to outages • Backups / durability in cloud • Cons: • But MongoDB doesn’t do multi-master replication Tuesday, December 4, 12

Slide 7

Slide 7 text

Subverting Replication Internals: Bending the Oplog to your Will Tuesday, December 4, 12

Slide 8

Slide 8 text

Replication: the Oplog { "ts" : Timestamp(1317653790000, 2), "h" : -6022751846629753359, "op" : "i", "ns" : "confoo.People", "o" : { "_id" : ObjectId("4e89cd1e0364241932324269"), "first" : "Rick", "last" : "Copeland” } } Tuesday, December 4, 12

Slide 9

Slide 9 text

Replication: the Oplog { "ts" : Timestamp(1317653790000, 2), "h" : -6022751846629753359, "op" : "i", "ns" : "confoo.People", "o" : { "_id" : ObjectId("4e89cd1e0364241932324269"), "first" : "Rick", "last" : "Copeland” } } Insert Tuesday, December 4, 12

Slide 10

Slide 10 text

Replication: the Oplog { "ts" : Timestamp(1317653790000, 2), "h" : -6022751846629753359, "op" : "i", "ns" : "confoo.People", "o" : { "_id" : ObjectId("4e89cd1e0364241932324269"), "first" : "Rick", "last" : "Copeland” } } Insert Collection name Tuesday, December 4, 12

Slide 11

Slide 11 text

Replication: the Oplog { "ts" : Timestamp(1317653790000, 2), "h" : -6022751846629753359, "op" : "i", "ns" : "confoo.People", "o" : { "_id" : ObjectId("4e89cd1e0364241932324269"), "first" : "Rick", "last" : "Copeland” } } Insert Collection name Object to insert Tuesday, December 4, 12

Slide 12

Slide 12 text

Step 1: Build Triggers def run(self): while True: yield self.checkpoint spec = dict(ts={'$gt': self.checkpoint}) q = self._oplog.find( spec, tailable=True, await_data=True) found=False for op in q.sort('$natural'): found = True self.checkpoint = op['ts'] for callback in self._callbacks.get( (op['ns'], op['op']), []): callback(**op) if found: sleep(0) else: sleep(1) Tuesday, December 4, 12

Slide 13

Slide 13 text

Step 1: Build Triggers def run(self): while True: yield self.checkpoint spec = dict(ts={'$gt': self.checkpoint}) q = self._oplog.find( spec, tailable=True, await_data=True) found=False for op in q.sort('$natural'): found = True self.checkpoint = op['ts'] for callback in self._callbacks.get( (op['ns'], op['op']), []): callback(**op) if found: sleep(0) else: sleep(1) Find ops Tuesday, December 4, 12

Slide 14

Slide 14 text

Step 1: Build Triggers def run(self): while True: yield self.checkpoint spec = dict(ts={'$gt': self.checkpoint}) q = self._oplog.find( spec, tailable=True, await_data=True) found=False for op in q.sort('$natural'): found = True self.checkpoint = op['ts'] for callback in self._callbacks.get( (op['ns'], op['op']), []): callback(**op) if found: sleep(0) else: sleep(1) Find ops Call triggers Tuesday, December 4, 12

Slide 15

Slide 15 text

Step 2: Build the replication MMM_REPL_FLAG = '__mmm' def trigger(ts, h, op, ns, o, o2=None, b=False): if op == 'i': if o.get(MMM_REPL_FLAG) == self.id: return o.setdefault(MMM_REPL_FLAG, src_id) collection.insert(o) elif op == 'u': upsert = b if any(k.startswith('$') for k in o): # With modifiers, check & update setters setters = o.setdefault('$set', {}) else: # Without modifiers, update doc directly setters = o if setters.get(MMM_REPL_FLAG) == self.id: return setters.setdefault(MMM_REPL_FLAG, src_id) collection.update(o2, o, upsert) elif op == 'd': justOne = b collection.remove(o) Tuesday, December 4, 12

Slide 16

Slide 16 text

Step 2: Build the replication MMM_REPL_FLAG = '__mmm' def trigger(ts, h, op, ns, o, o2=None, b=False): if op == 'i': if o.get(MMM_REPL_FLAG) == self.id: return o.setdefault(MMM_REPL_FLAG, src_id) collection.insert(o) elif op == 'u': upsert = b if any(k.startswith('$') for k in o): # With modifiers, check & update setters setters = o.setdefault('$set', {}) else: # Without modifiers, update doc directly setters = o if setters.get(MMM_REPL_FLAG) == self.id: return setters.setdefault(MMM_REPL_FLAG, src_id) collection.update(o2, o, upsert) elif op == 'd': justOne = b collection.remove(o) Inserts Tuesday, December 4, 12

Slide 17

Slide 17 text

Step 2: Build the replication MMM_REPL_FLAG = '__mmm' def trigger(ts, h, op, ns, o, o2=None, b=False): if op == 'i': if o.get(MMM_REPL_FLAG) == self.id: return o.setdefault(MMM_REPL_FLAG, src_id) collection.insert(o) elif op == 'u': upsert = b if any(k.startswith('$') for k in o): # With modifiers, check & update setters setters = o.setdefault('$set', {}) else: # Without modifiers, update doc directly setters = o if setters.get(MMM_REPL_FLAG) == self.id: return setters.setdefault(MMM_REPL_FLAG, src_id) collection.update(o2, o, upsert) elif op == 'd': justOne = b collection.remove(o) Inserts Updates Tuesday, December 4, 12

Slide 18

Slide 18 text

Step 2: Build the replication MMM_REPL_FLAG = '__mmm' def trigger(ts, h, op, ns, o, o2=None, b=False): if op == 'i': if o.get(MMM_REPL_FLAG) == self.id: return o.setdefault(MMM_REPL_FLAG, src_id) collection.insert(o) elif op == 'u': upsert = b if any(k.startswith('$') for k in o): # With modifiers, check & update setters setters = o.setdefault('$set', {}) else: # Without modifiers, update doc directly setters = o if setters.get(MMM_REPL_FLAG) == self.id: return setters.setdefault(MMM_REPL_FLAG, src_id) collection.update(o2, o, upsert) elif op == 'd': justOne = b collection.remove(o) Inserts Updates Deletes Tuesday, December 4, 12

Slide 19

Slide 19 text

Step 3 Tuesday, December 4, 12

Slide 20

Slide 20 text

But be careful if you use it.... Tuesday, December 4, 12

Slide 21

Slide 21 text

Caveats • Conflicts? • Performance? • Reliability? • Support? Tuesday, December 4, 12

Slide 22

Slide 22 text

github.com/rick446/mmm Tuesday, December 4, 12

Slide 23

Slide 23 text

Client Results Tuesday, December 4, 12

Slide 24

Slide 24 text

Questions? MongoDB MultiMaster: http://github.com/rick446/mmm Rick Copeland @rick446 http://arborian.com Tuesday, December 4, 12