📜 ⬆️ ⬇️

How to already feel transactions in MongoDB

In the summer of 2018 (that is, right now, at the time of this writing), an incredible thing happened - honest ACID transactions were delivered to MongoDB . With the release of the fourth version of this document-oriented DBMS , it can be used for slightly more serious applications.


For those who are in the tank, in a nutshell: transactions allow us to make a series of changes in several documents and save them at once, or cancel all changes made as part of a transaction at once, if something went wrong, or the application failed .


Unfortunately, the developer is not so easy to use this super feature. Below I will tell you why, and what to do about it.


If you open the DBMS documentation on the Transactions section, we can see the following remark:


Multi-document transactions are available for replica sets only. Transactions for sharded clusters are scheduled for MongoDB 4.2

This tells us that a simple MongoDB server does not support transactions, only a cluster in the replica set mode. Support in sharded clusters will also be later, in version 4.2.


At the same time, a normal server will allow us to start a transaction, save it, or cancel it, but nothing can be done within it, something like this will be displayed:


WriteCommandError({ "ok" : 0, "errmsg" : "Transaction numbers are only allowed on a replica set member or mongos", "code" : 20, "codeName" : "IllegalOperation" }) 

Fortunately, everyone can run a MongoDB cluster consisting of one server. On my own machines on which I develop, I launch all the DBMS in docker containers. For example, starting a regular MongoDB server looks like this:


 docker run -v ~/mongo/:/data/db --name mongo --restart=always -p 27017:27017 -d mongo mongod --smallfiles 

Let's sort the startup keys:



How to run a simple server, I just cited for reference. Now let's figure out what needs to be done to start a server that supports transactions.


First of all, you should create a new network inside the docker, in which all the servers of our cluster will work. Yes, I wrote above that the server will be one, but the network must be created, otherwise it will not work.


 docker network create mongo-cluster 

Next, in the container startup parameters, you need to specify the use of the new network - net mongo-cluster , and also to transfer the parameter to the server, to work in the replica set mode : - replSet rs0 . Also, I intentionally lowered the key --restart = always , since I don’t always use MongoDB at work now and I don’t want it to start with the operating system.


 docker run -v ~/mongo/:/data/db --name mongo -p 27017:27017 -d mongo mongod --smallfiles --replSet rs0 

Great, the container is running, which we can verify by running the docker ps command and seeing something like this:


 docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 2292d7e0778b mongo "docker-entrypoint.s…" About a minute ago Up About a minute 0.0.0.0:27017->27017/tcp mongo 

Next, we need to initialize the cluster, to do this, enter the console of the running server, create the configuration of our cluster and perform the initialization:


 docker exec -it mongo mongo # output omited # > config = { "_id" : "rs0", "members" : [ { "_id" : 0, "host" : "mongo:27017" } ] } > rs.initiate(config) { "ok" : 1, "operationTime" : Timestamp(1531248932, 1), "$clusterTime" : { "clusterTime" : Timestamp(1531248932, 1), "signature" : { "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="), "keyId" : NumberLong(0) } } } rs0:SECONDARY> rs0:PRIMARY> 

Done! We got a cluster from one MongoDB server. Now you can check that everything works as expected.


 rs0:PRIMARY> session = db.getMongo().startSession() session { "id" : UUID("7eb81006-983f-4398-adc7-5ed23e027377") } rs0:PRIMARY> database = session.getDatabase("test") test rs0:PRIMARY> //    rs0:PRIMARY> database.col.insert({name: "1"}) WriteResult({ "nInserted" : 1 }) rs0:PRIMARY> database.col.insert({name: "2"}) WriteResult({ "nInserted" : 1 }) rs0:PRIMARY> database.col.insert({name: "3"}) WriteResult({ "nInserted" : 1 }) rs0:PRIMARY> database.col.insert({name: "4"}) WriteResult({ "nInserted" : 1 }) rs0:PRIMARY> // ,     rs0:PRIMARY> database.col.find({}) { "_id" : ObjectId("5b45026edc396f534f11952f"), "name" : "1" } { "_id" : ObjectId("5b450272dc396f534f119530"), "name" : "2" } { "_id" : ObjectId("5b450274dc396f534f119531"), "name" : "3" } { "_id" : ObjectId("5b450276dc396f534f119532"), "name" : "4" } rs0:PRIMARY> //   rs0:PRIMARY> session.startTransaction() rs0:PRIMARY> //    rs0:PRIMARY> database.col.update({name: "4"}, {name: "44"}) WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 }) rs0:PRIMARY> //   rs0:PRIMARY> database.col.find({}) { "_id" : ObjectId("5b45026edc396f534f11952f"), "name" : "1" } { "_id" : ObjectId("5b450272dc396f534f119530"), "name" : "2" } { "_id" : ObjectId("5b450274dc396f534f119531"), "name" : "3" } { "_id" : ObjectId("5b450276dc396f534f119532"), "name" : "44" } rs0:PRIMARY> //         ,    -: rs0:PRIMARY> // { "_id" : ObjectId("5b450276dc396f534f119532"), "name" : "4" } rs0:PRIMARY> //   rs0:PRIMARY> session.commitTransaction() rs0:PRIMARY> //   rs0:PRIMARY> database.col.find({}) { "_id" : ObjectId("5b45026edc396f534f11952f"), "name" : "1" } { "_id" : ObjectId("5b450272dc396f534f119530"), "name" : "2" } { "_id" : ObjectId("5b450274dc396f534f119531"), "name" : "3" } { "_id" : ObjectId("5b450276dc396f534f119532"), "name" : "44" } rs0:PRIMARY> //     rs0:PRIMARY> session.startTransaction() rs0:PRIMARY> database.col.update({name: "44"}, {name: "42"}) WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 }) rs0:PRIMARY> database.col.find({}) { "_id" : ObjectId("5b45026edc396f534f11952f"), "name" : "1" } { "_id" : ObjectId("5b450272dc396f534f119530"), "name" : "2" } { "_id" : ObjectId("5b450274dc396f534f119531"), "name" : "3" } { "_id" : ObjectId("5b450276dc396f534f119532"), "name" : "42" } rs0:PRIMARY> database.col.update({name: "1"}, {name: "21"}) WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 }) rs0:PRIMARY> database.col.find({}) { "_id" : ObjectId("5b45026edc396f534f11952f"), "name" : "21" } { "_id" : ObjectId("5b450272dc396f534f119530"), "name" : "2" } { "_id" : ObjectId("5b450274dc396f534f119531"), "name" : "3" } { "_id" : ObjectId("5b450276dc396f534f119532"), "name" : "42" } rs0:PRIMARY> session.commitTransaction() rs0:PRIMARY> //   rs0:PRIMARY> database.col.find({}) { "_id" : ObjectId("5b45026edc396f534f11952f"), "name" : "21" } { "_id" : ObjectId("5b450272dc396f534f119530"), "name" : "2" } { "_id" : ObjectId("5b450274dc396f534f119531"), "name" : "3" } { "_id" : ObjectId("5b450276dc396f534f119532"), "name" : "42" } rs0:PRIMARY> //   ,     rs0:PRIMARY> session.startTransaction() rs0:PRIMARY> database.col.update({name: "21"}, {name: "1"}) WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 }) rs0:PRIMARY> database.col.find({}) { "_id" : ObjectId("5b45026edc396f534f11952f"), "name" : "1" } { "_id" : ObjectId("5b450272dc396f534f119530"), "name" : "2" } { "_id" : ObjectId("5b450274dc396f534f119531"), "name" : "3" } { "_id" : ObjectId("5b450276dc396f534f119532"), "name" : "42" } rs0:PRIMARY> //   rs0:PRIMARY> session.abortTransaction() rs0:PRIMARY> database.col.find({}) { "_id" : ObjectId("5b45026edc396f534f11952f"), "name" : "21" } { "_id" : ObjectId("5b450272dc396f534f119530"), "name" : "2" } { "_id" : ObjectId("5b450274dc396f534f119531"), "name" : "3" } { "_id" : ObjectId("5b450276dc396f534f119532"), "name" : "42" } rs0:PRIMARY> // !     ! rs0:PRIMARY> 

Thus, without straining at all, you can try Mongov transactions right now without starting a multi-server cluster. I advise you to look into the documentation and read about the limitations of transactions. For example, that transactions "live" no more than 1 minute, if you do not have time to save the changes, they will be canceled.


PS: the purpose of this article is not to learn how to use a docker or work with Monga, but just a quick way to try new tools of this interesting DBMS.


')

Source: https://habr.com/ru/post/417131/


All Articles