Optimizing MongoDB: Lessons Learned at Localytics

44
Optimizing MongoDB: Lessons Learned at Localytics Benjamin Darfler MongoBoston - September 2011

description

MongoDB Optimizations done at Localytics to improve throughput while reducing cost.

Transcript of Optimizing MongoDB: Lessons Learned at Localytics

Page 1: Optimizing MongoDB: Lessons Learned at Localytics

Optimizing MongoDB:

Lessons Learned at Localytics

Benjamin DarflerMongoBoston - September 2011

Page 2: Optimizing MongoDB: Lessons Learned at Localytics

Introduction

• Benjamin Darflero @bdarflero http://bdarfler.como Senior Software Engineer at Localytics

• Localyticso Real time analytics for mobile applicationso 100M+ datapoints a dayo More than 2x growth over the past 4 monthso Heavy users of Scala, MongoDB and AWS

• This Talko Revised and updated from MongoNYC 2011

Page 3: Optimizing MongoDB: Lessons Learned at Localytics

MongoDB at Localytics

• Use caseso Anonymous loyalty informationo De-duplication of incoming data

• Scale todayo Hundreds of GBs of data per shardo Thousands of ops per second per shard

• Historyo In production for ~8 monthso Increased load 10x in that timeo Reduced shard count by more than a half

Page 4: Optimizing MongoDB: Lessons Learned at Localytics

Disclaimer

These steps worked for us and our dataWe verified them by testing early and often 

You should too

Page 5: Optimizing MongoDB: Lessons Learned at Localytics

Quick Poll

• Who is using MongoDB in production?

• Who is deployed on AWS?

• Who has a sharded deployment?o More than 2 shards?o More than 4 shards?o More than 8 shards?

Page 6: Optimizing MongoDB: Lessons Learned at Localytics

Optimizing Our Data

Documents and Indexes

Page 7: Optimizing MongoDB: Lessons Learned at Localytics

Shorten Names

Before{super_happy_fun_awesome_name:"yay!"}

After{s:"yay!"}

• Significantly reduced document size

Page 8: Optimizing MongoDB: Lessons Learned at Localytics

Use BinData for uuids/hashes

Before{u:"21EC2020-3AEA-1069-A2DD-08002B30309D"}

After{u:BinData(0, "...")}

• Used BinData type 0, least overhead• Reduced data size by more then 2x over UUID• Reduced index size on the field

Page 9: Optimizing MongoDB: Lessons Learned at Localytics

Override _id

Before{_id:ObjectId("..."), u:BinData(0, "...")}

After {_id:BinData(0, "...")}

• Reduced data size• Eliminated an index• Warning: Locality - more on that later

Page 10: Optimizing MongoDB: Lessons Learned at Localytics

Pre-aggregate

Before{u:BinData(0, "..."), k:BinData(0, "abc")}{u:BinData(0, "..."), k:BinData(0, "abc")}{u:BinData(0, "..."), k:BinData(0, "def")}

After{u:BinData(0, "abc"), c:2}{u:BinData(0, "def"), c:1}

• Actually kept data in both forms• Fewer records meant smaller indexes

Page 11: Optimizing MongoDB: Lessons Learned at Localytics

Prefix Indexes

Before{k:BinData(0, "...")} // indexed

After{p:BinData(0, "...") // prefix of k, indexeds:BinData(0, "...") // suffix of k, not indexed}

• Reduced index size• Warning: Prefix must be sufficiently unique• Would be nice to have it built in - SERVER-3260

Page 12: Optimizing MongoDB: Lessons Learned at Localytics

Sparse Indexes

Create a sparse indexdb.collection.ensureIndex({middle:1}, {sparse:true});

Only indexes documents that contain the field{u:BinData(0, "abc"), first:"Ben", last:"Darfler"}{u:BinData(0, "abc"), first:"Mike", last:"Smith"}{u:BinData(0, "abc"), first:"John", middle:"F", last:"Kennedy"}

• Fewer records meant smaller indexes• New in 1.8

Page 13: Optimizing MongoDB: Lessons Learned at Localytics

Upgrade to {v:1} indexes

• Upto 25% smaller• Upto 25% faster• New in 2.0• Must reindex after upgrade

Page 14: Optimizing MongoDB: Lessons Learned at Localytics

Optimizing Our Queries

Reading and Writing

Page 15: Optimizing MongoDB: Lessons Learned at Localytics

You are using an index right?

Create an indexdb.collection.ensureIndex({user:1});

Ensure you are using itdb.collection.find(query).explain();

Hint that it should be used if its notdb.collection.find({user:u, foo:d}).hint({user:1});

• I've seen the wrong index used beforeo open a bug if you see this happen

Page 16: Optimizing MongoDB: Lessons Learned at Localytics

Only as much as you need

Beforedb.collection.find();

Afterdb.collection.find().limit(10);db.collection.findOne();

• Reduced bytes on the wire• Reduced bytes read from disk• Result cursor streams data but in large chunks

Page 17: Optimizing MongoDB: Lessons Learned at Localytics

Only what you need

Beforedb.collection.find({u:BinData(0, "...")});

Afterdb.collection.find({u:BinData(0, "...")}, {field:1});

• Reduced bytes on the wire• Necessary to exploit covering indexes

Page 18: Optimizing MongoDB: Lessons Learned at Localytics

Covering Indexes

Create an indexdb.collection.ensureIndex({first:1, last:1});

Query for data only in the indexdb.collection.find({last:"Darfler"}, {_id:0, first:1, last:1});

• Can service the query entirely from the index• Eliminates having to read the data extent• Explicitly exclude _id if its not in the index• New in 1.8

Page 19: Optimizing MongoDB: Lessons Learned at Localytics

Prefetch

Beforedb.collection.update({u:BinData(0, "...")}, {$inc:{c:1}});

Afterdb.collection.find({u:BinData(0, "...")});db.collection.update({u:BinData(0, "...")}, {$inc:{c:1}});

• Prevents holding a write lock while paging in data• Most updates fit this pattern anyhow• Less necessary with yield improvements in 2.0

Page 20: Optimizing MongoDB: Lessons Learned at Localytics

Optimizing Our Disk

Fragmentation

Page 21: Optimizing MongoDB: Lessons Learned at Localytics

Inserts

doc1

doc2

doc3

doc4

doc5

Page 22: Optimizing MongoDB: Lessons Learned at Localytics

Deletes

doc1

doc2

doc3

doc4

doc5

doc1

doc2

doc3

doc4

doc5

Page 23: Optimizing MongoDB: Lessons Learned at Localytics

Updates

doc1

doc2

doc3

doc4

doc5

doc1

doc2

doc3

doc4

doc5

doc3

Updates can be in place if the document doesn't grow

Page 24: Optimizing MongoDB: Lessons Learned at Localytics

Reclaiming Freespace

doc1

doc2

doc6

doc4

doc5

doc1

doc2

doc3

doc4

doc5

Page 25: Optimizing MongoDB: Lessons Learned at Localytics

Memory Mapped Files

doc1

doc2

doc6

doc4

doc5

}}

page

page

Data is mapped into memory a full page at a time 

Page 26: Optimizing MongoDB: Lessons Learned at Localytics

Fragmentation

RAM used to be filled with useful dataNow it contains useless space or useless data

Inserts used to cause sequential writesNow inserts cause random writes

Page 27: Optimizing MongoDB: Lessons Learned at Localytics

Fragmentation Mitigation

• Automatic Padding o MongoDB auto-pads recordso Manual tuning scheduled for 2.2

• Manual Paddingo Pad arrays that are known to growo Pad with a BinData field, then remove it

• Free list improvement in 2.0 and scheduled in 2.2

Page 28: Optimizing MongoDB: Lessons Learned at Localytics

Fragmentation Fixes

• Repairo db.repairDatabase(); o Run on secondary, swap with primaryo Requires 2x disk space

• Compacto db.collection.runCommand( "compact" );o Run on secondary, swap with primaryo Faster than repairo Requires minimal extra disk spaceo New in 2.0

• Repair, compact and import remove padding

Page 29: Optimizing MongoDB: Lessons Learned at Localytics

Optimizing Our Keys

Index and Shard

Page 30: Optimizing MongoDB: Lessons Learned at Localytics

B-Tree Indexes - hash/uuid key

Hashes/UUIDs randomly distribute across the whole b-tree

Page 31: Optimizing MongoDB: Lessons Learned at Localytics

B-Tree Indexes - temporal key

Keys with a temporal prefix (i.e. ObjectId) are right aligned

Page 32: Optimizing MongoDB: Lessons Learned at Localytics

Migrations - hash/uuid shard key

Chunk 1k: 1 to 5

Chunk 2k: 6 to 9

Shard 1                                                Shard 2

Chunk 1k: 1 to 5

{k: 4, …}

{k: 8, …}

{k: 3, …}

{k: 7, …}

{k: 5, …}

{k: 6, …}

{k: 4, …}

{k: 3, …}

{k: 5, …}

Page 33: Optimizing MongoDB: Lessons Learned at Localytics

Hash/uuid shard key

• Distributes read/write load evenly across nodes• Migrations cause random I/O and fragmentation

o Makes it harder to add new shards• Pre-split

o db.runCommand({split:"db.collection", middle:{_id:99}});

• Pre-moveo db.adminCommand({moveChunk:"db.collection", find:{_id:5}, to:"s2"});

• Turn off balancero db.settings.update({_id:"balancer"}, {$set:{stopped:true}}, true});

Page 34: Optimizing MongoDB: Lessons Learned at Localytics

Migrations - temporal shard key

Chunk 1k: 1 to 5

Chunk 2k: 6 to 9

Shard 1                                                Shard 2

Chunk 1k: 1 to 5

{k: 3, …}

{k: 4, …}

{k: 5, …}

{k: 6, …}

{k: 7, …}

{k: 8, …}

{k: 3, …}

{k: 4, …}

{k: 5, …}

Page 35: Optimizing MongoDB: Lessons Learned at Localytics

Temporal shard key

• Can cause hot chunks• Migrations are less destructive

o Makes it easier to add new shards• Include a temporal prefix in your shard key 

o {day: ..., id: ...}• Choose prefix granularity based on insert rate

o low 100s of chunks (64MB) per "unit" of prefixo i.e. 10 GB per day => ~150 chunks per day

Page 36: Optimizing MongoDB: Lessons Learned at Localytics

Optimizing Our Deployment

Hardware and Configuration

Page 37: Optimizing MongoDB: Lessons Learned at Localytics

Elastic Compute Cloud

• Noisy Neighboro Used largest instance in a family (m1 or m2)

• Used m2 family for mongodso Best RAM to dollar ratio

• Used micros for arbiters and config servers 

Page 38: Optimizing MongoDB: Lessons Learned at Localytics

Elastic Block Storage

• Noisy Neighboro Netflix claims to only use 1TB disks

• RAID'ed our diskso Minimum of 4-8 diskso Recommended 8-16 diskso RAID0 for write heavy workloado RAID10 for read heavy workload

Page 39: Optimizing MongoDB: Lessons Learned at Localytics

Pathological Test

• What happens when data far exceeds RAM?o 10:1 read/write ratioo Reads evenly distributed over entire key space

Page 40: Optimizing MongoDB: Lessons Learned at Localytics

One Mongod

Index out  of RAM

Index in RAM

• One mongod on the hosto  Throughput drops more than 10x

Page 41: Optimizing MongoDB: Lessons Learned at Localytics

Many Mongods

Index out of RAM

Index in RAM

• 16 mongods on the hosto Throughput drops less than 3xo Graph for one shard, multiply by 16x for total

Page 42: Optimizing MongoDB: Lessons Learned at Localytics

Sharding within a node

• One read/write lock per mongod o Ticket for lock per collection - SERVER-1240o Ticket for lock per extent - SERVER-1241

• For in memory work loado Shard per core

• For out of memory work loado Shard per disk

• Warning: Must have shard key in every queryo Otherwise scatter gather across all shardso Requires manually managing secondary keys

• Less necessary in 2.0 with yield improvements

Page 43: Optimizing MongoDB: Lessons Learned at Localytics

Reminder

These steps worked for us and our dataWe verified them by testing early and often 

You should too

Page 44: Optimizing MongoDB: Lessons Learned at Localytics

Questions?

@bdarflerhttp://bdarfler.com