Use MongoDB as an Redis in memory database

  • 2020-05-14 05:20:09
  • OfStack

The basic idea

The use of MongoDB as an in-memory database (in-memory database), that is, not having MongoDB save data to disk at all, is of growing interest. This usage is extremely useful for the following applications:

Write-intensive caching before the slow RDBMS system Embedded system An PCI compatible system that does not require persistent data Unit tests that require a lightweight database with data that can be easily erased (unit testing)

It would be elegant if this one slice could be implemented: we would be able to take advantage of MongoDB's query/retrieval capabilities smartly without involving disk operations. As you probably know, disk IO (especially random IO) is the bottleneck 99% of the time, and if you want to write data, disk operations are unavoidable.

One of MongoDB's cool design decisions is that she can handle read and write requests to data in disk files using in-memory mapped files (memory-mapped file). That said, MongoDB doesn't treat RAM and disk any differently, but instead treats the file as a huge array, accesses the data in bytes, and leaves the rest to the operating system (OS)! It was this design decision that enabled MongoDB to run on RAM without any modifications.

Implementation method

This is done by using a special type of file system called tmpfs. In Linux it looks like the regular file system (FS) 1, except that it is completely in RAM (unless it is larger than RAM, it can also swap, which is very useful!). . I have 32GB RAM on my server, so let's create a 16GB tmpfs:

# mkdir /ramdata
# mount -t tmpfs -o size=16000M tmpfs /ramdata/
# df
Filesystem           1K-blocks      Used Available Use% Mounted on
/dev/xvde1             5905712   4973924    871792  86% /
none                  15344936         0  15344936   0% /dev/shm
tmpfs                 16384000         0  16384000   0% /ramdata

Next, start MongoDB with the appropriate Settings. To reduce the amount of wasted RAM, smallfiles and noprealloc should be set to true. Since it is now based on RAM, doing so will not degrade performance at all. It makes no sense to use journal again, so you should set nojournal to true.

dbpath=/ramdata
nojournal = true
smallFiles = true
noprealloc = true

Once you start MongoDB, you'll find that it works very well, and the files in the file system appear as expected:

# mongo
MongoDB shell version: 2.3.2
connecting to: test
> db.test.insert({a:1})
> db.test.find()
{ "_id" : ObjectId("51802115eafa5d80b5d2c145"), "a" : 1 } # ls -l /ramdata/
total 65684
-rw-------. 1 root root 16777216 Apr 30 15:52 local.0
-rw-------. 1 root root 16777216 Apr 30 15:52 local.ns
-rwxr-xr-x. 1 root root        5 Apr 30 15:52 mongod.lock
-rw-------. 1 root root 16777216 Apr 30 15:52 test.0
-rw-------. 1 root root 16777216 Apr 30 15:52 test.ns
drwxr-xr-x. 2 root root       40 Apr 30 15:52 _tmp

Now let's add some data and verify that 1 works perfectly. Let's create an document of 1KB and add it to MongoDB 4 million times:

> str = ""
> aaa = "aaaaaaaaaa"
aaaaaaaaaa
> for (var i = 0; i < 100; ++i) { str += aaa; } > for (var i = 0; i < 4000000; ++i) { db.foo.insert({a: Math.random(), s: str});}
> db.foo.stats()
{
        "ns" : "test.foo",
        "count" : 4000000,
        "size" : 4544000160,
        "avgObjSize" : 1136.00004,
        "storageSize" : 5030768544,
        "numExtents" : 26,
        "nindexes" : 1,
        "lastExtentSize" : 536600560,
        "paddingFactor" : 1,
        "systemFlags" : 1,
        "userFlags" : 0,
        "totalIndexSize" : 129794000,
        "indexSizes" : {
                "_id_" : 129794000
        },
        "ok" : 1
}


It can be seen that the average size of document is 1136 bytes, and the data takes up a total space of 5GB. The index size above _id is 130MB. Now we need to verify one very important thing: is there any duplication of data in RAM? Is there one copy in MongoDB and one in the file system? Remember that MongoDB does not cache any data in her own process, her data is only cached in the file system cache. So let's clear 1 file system cache and see what else is in RAM:

# echo 3 > /proc/sys/vm/drop_caches 
# free
             total       used       free     shared    buffers     cached
Mem:      30689876    6292780   24397096          0       1044    5817368
-/+ buffers/cache:     474368   30215508
Swap:            0          0          0

As you can see, of the 6.3GB RAM that has been used, 5.8GB is used for file system caching (buffers, buffer). Why y is there still a 5.8GB file system cache on the system even after all caches have been cleared? The reason is that Linux is so smart that she doesn't store duplicate data in tmpfs and the cache. That's great! That means you only have one copy of data at RAM. Now let's visit all document under 1 and verify that the usage of RAM does not change under 1:

> db.foo.find().itcount()
4000000 # free
             total       used       free     shared    buffers     cached
Mem:      30689876    6327988   24361888          0       1324    5818012
-/+ buffers/cache:     508652   30181224
Swap:            0          0          0
# ls -l /ramdata/
total 5808780
-rw-------. 1 root root  16777216 Apr 30 15:52 local.0
-rw-------. 1 root root  16777216 Apr 30 15:52 local.ns
-rwxr-xr-x. 1 root root         5 Apr 30 15:52 mongod.lock
-rw-------. 1 root root  16777216 Apr 30 16:00 test.0
-rw-------. 1 root root  33554432 Apr 30 16:00 test.1
-rw-------. 1 root root 536608768 Apr 30 16:02 test.10
-rw-------. 1 root root 536608768 Apr 30 16:03 test.11
-rw-------. 1 root root 536608768 Apr 30 16:03 test.12
-rw-------. 1 root root 536608768 Apr 30 16:04 test.13
-rw-------. 1 root root 536608768 Apr 30 16:04 test.14
-rw-------. 1 root root  67108864 Apr 30 16:00 test.2
-rw-------. 1 root root 134217728 Apr 30 16:00 test.3
-rw-------. 1 root root 268435456 Apr 30 16:00 test.4
-rw-------. 1 root root 536608768 Apr 30 16:01 test.5
-rw-------. 1 root root 536608768 Apr 30 16:01 test.6
-rw-------. 1 root root 536608768 Apr 30 16:04 test.7
-rw-------. 1 root root 536608768 Apr 30 16:03 test.8
-rw-------. 1 root root 536608768 Apr 30 16:02 test.9
-rw-------. 1 root root  16777216 Apr 30 15:52 test.ns
drwxr-xr-x. 2 root root        40 Apr 30 16:04 _tmp
# df
Filesystem           1K-blocks      Used Available Use% Mounted on
/dev/xvde1             5905712   4973960    871756  86% /
none                  15344936         0  15344936   0% /dev/shm
tmpfs                 16384000   5808780  10575220  36% /ramdata

Sure enough! :)

What about copy (replication)?

Since the data in RAM is lost when the server is restarted, you may want to use replication. Automatic failover (failover) can be obtained with a standard copy set (replica set) and data reading capability can be improved (read capacity). If a server is restarted, it can read from another server in the same replica set to rebuild its own data (resynchronization, resync). This is fast enough even with large amounts of data and indexes, as the indexing operations are all done in RAM :)

One important point is that the write operation writes to a special collection called oplog, which is located in the local database. By default, its size is 5% of the total data volume. In my case, oplog would have 5% of 16GB, which is 800MB. In case of doubt, it is safe to use the option oplogSize to select a fixed size for oplog. If the alternate server goes down for more than oplog, it will have to be resynchronized. To set its size to 1GB, go like this:

oplogSize = 1000

What about sharding (sharding)?

Now that you have all the query capabilities of MongoDB, how do you use it to implement a large service? You can use sharding as much as you like to implement a large scalable in-memory database. Configuring the servers (which hold block allocations) is still a disk-based solution, because the number of activities on these servers is small and it is not fun to rebuild the cluster from scratch.
Matters needing attention

RAM is a scarce resource, and in this case you want the entire data set to fit into RAM. Although tmpfs has the ability to use disk switching (swapping), the performance degradation will be significant. To take full advantage of RAM, you should consider:

The storage bucket is normalized using the usePowerOf2Sizes option Periodically run the compact command or resynchronize the nodes (resync) The design of schema should be fairly standardized (to avoid large Numbers of larger document)

conclusion

Baby, you can now use MongoDB as an in-memory database and use all of its features! Well, the performance should be pretty amazing: I was able to get 20K writes per second in tests with a single thread/core, and add as many cores as you want.


Related articles: