MongoDB Top Design Considerations
Having worked with mongodb for a while now I have come to know a few things about how exactly mongodb works and some things to consider as you come up with the design of your mongodb implementation. Here is a list of things to keep in mind as you start to decide how you are going to design your mongodb implementation.
1. Document Padding
Mongo adaptively learns if documents in a collection tend to grow, and if they do, it adds some padding so that the document has room to grow. This helps to prevent excessive movements on subsequent writes. This statistic is tracked separately for each collection.
When designing a schema its important to understand how your document is going to grow. Lets look at a blog example. Lets say you run a very successful blog and some blogs receive 1000s of comments while others receive little to no comments. In this example you don't want to embed your comments in the single json document you want to make a separate collection of comments and reference the blog by ID.
2. Indexes
By default all indexes should be created with background:true. Replica sets running version 1.8.x its important to remember the slaves ignore background:true and the index operation is a blocking operation, this is a concern only when secondary nodes are used to offload reads from the PRIMARY.
3. Write Concern
By default development should not code write concern into the application, it should be the operations team responsibility to work with the development team on the write concern requirement and implement it at the operations level (driver config file). The issue with write concern is that when improperly implemented all writes to a replica set could fail under maintenance conditions.
4. Schema Design Considerations
One company I worked for allowed for users to add "tags" to tasks, well tags could also be managed and renamed from the administrative interface. Well lets say you use the tag "red" in 1000s of tasks and you want to rename "red" to "blue" well you don't want to update 1000s of documents you want to make a collection called tags and reference the collection and update the identifier associated to the red tag to be blue, this will be a single update vs 1000s of updates.
5. OpLog
The oplog is a queryable collection in the database "local". In the /etc/mongodb.conf file there is an option "dbpath" mongodb will allocate 5% of the partition where dbpath lives for the oplog. The oplog size is configurable, and one big thing to remember about the oplog. If a delayed secondary is leveraged the oplog needs to be big enough to support transactions older than the configured delayed secondary time. So if the delayed secondary is one hour the oplog needs to be able to store at least an hours worth of data so the delayed secondary can stay in sync.
6. Backup And Recovery
mongoexport doesn't export the indexes.
7. Collections vs Databases
At one company I worked for we had something like 30,000 customers on the trial side of our application. During the design phase we discussed giving each "company / customer" their own database because on the production side we had much fewer companies like 2,000. We had a big discussion about collections or databases per customer. Here are some things to think about if you encounter the same situation:
One set of datafiles
By leveraging a single database you will only have one set of data files. Why is this important you ask? Well in mongodb when a database is created an initial 64MB file is created called database_name.1 as the dataset grows more datafiles are created database_name.2 (128MB), database_name.3 (512MB), database_name.4 (1024MB), database_name.5 (2046MB) the issue with a single database per customer is that there is a chance you will fill up to the 64MB limit pretty quickly and you don't want to be in a situation where you are growing datafiles for multiple databases multiple times a day. It isn't efficient from a performance standpoint or from a disk utilization standpoint.
Data Deletion
If a large customer requires data to be deleted and the customer has their own database the db files for that customer can simply be deleted, the database dropped and there is no impact to the rest of the data set. In a situation where there is a single database with multiple customers a disk compaction might need to be run to clean up any unused space in the data files.
db.stats()
If per customer it becomes easy to identify quickly stats can be easily pulled on a case by case basis. On the other hand it makes it hard to pull that information into a monitoring system if there are 10s of thousands of databases.
Sharding Considerations
It might not make sense to split out customers per database or even collection at all for sharding purposes. Sharding happens per collection and if you're Craigslist lets say you would want to have similar entities (like a post and a user) in their own collection since that is going to be the bulk of your data. In my previous company's example it might make sense to have a "tasks" collection and have all companies tasks in the same collection then it becomes a lot easier to shard that collection without having to worry about how you are going to shard a collection in company A's database or a collection in company B's collection.
Security
Another consideration is security. Users are created per database so if you want to leverage security and have hundreds or thousands of databases it will become a nightmare to actually manage users for each db.
8. Rolling Data File Compaction
In mongodb when a record is deleted from the database or even moved due to document padding issues there is empty space left in the data file. While mongodb does attempt to reuse this empty disk space it isn't always efficient at it.
You will want to either add the following to a monitoring solution or run every so often to check how efficiently mongodb is reusing disk space. This will report per database dataSize and storageSize. storageSize should always be bigger than dataSize but the difference should be less than 2GB when the difference between the two numbers grows larger than 2GB a rolling database compaction should be run.
db._adminCommand("listDatabases").databases.forEach(function (d) {
mdb = db.getSiblingDB(d.name);
printjson(mdb.stats())
})