Let's start Scheme

2019-04-12

R6RS MongoDB with transaction

I've made a portable R6RS MongoDB library, I think, last year. Now I needed to support transaction so decided to implement it. Since MongoDB 4.0.0, it supports transactions. However, their document doesn't say which command to use or how. The only thing it mentions is I need to set up replica sets.

If you there's no documentation, then what you can do is reverse engineering. So, I've written a couple of scripts to set up MongoDB with transaction and proxy server which observes the commands.

The followings are the scripts I made to investigate:
The first one sets up the server which receives client command and sends to the MongoDB server. It does also dump both requests and responses. The second one sets up the docker network and instances of MongoDB with replica sets and executes the script files with mongo shell. Then the third one prints wire protocol commands in, sort of, a human-readable format.

With this investigation, I've figured out that I need to add lsid, txnNumber, autocommit and startTransaction. Okay, I've never seen them on the document, so I have no idea how these options works, but just followed the example. Then, here comes the transaction support.

How to use
This is an example of a transaction:
#!r6rs
(import (rnrs)
 (mongodb))

(define conn (make-mongodb-connection "localhost" 27017))
(define collection "txn")

(open-mongodb-connection! conn)

;; create the collection a head
(mongodb-database-run-command db `(("create" ,collection)))

(let* ((session (mongodb-session-start conn)) ;; start session
       (db (mongodb-session-database session "test"))) ;; create a database with session
  (guard (e (else (mongodb-session-abort-transaction session)))
    (mongodb-session-start-transaction! session) ;; start transaction
    ;; okay insert documents using usual database procedure
    ;; NB: has to be command, not other procedures...
    (mongodb-database-insert-command db collection
         '#((("id" 1) ("foo" "bar"))))
    (mongodb-database-insert-command db collection
         '#((("id" 2) ("foo" "bar"))))
    ;; and commit
    (mongodb-session-commit-transaction! session))
  ;; session must be end
  (mongodb-session-end! session))

(let* ((db (make-mongodb-database conn "test"))
       (r (mongodb-database-query db collection '())))
  (mongodb-query-result-documents r))
I haven't implement any utilities of transaction related procedures. So at this moment, you need to bare with low-level APIs.

How it works
Maybe you don't want to know, but it's nice to mention. When a session is created, then it also creates a session id. Then the database retrieved from the session adds the session id to query messages (OP_QUERY, not OP_MSG). Once mongodb-session-start-transaction! procedure is called, then it allocates transaction number and after this, the database also adds transaction information.

If the MongoDB server doesn't support the transaction, then the session automatically detects it and doesn't send any session or transaction related command.

And again, I'm not sure if I implemented correctly or not.

Once the official document of the transaction commands is written, I'll come back and review.

No comments:

Post a Comment