Comparison of mgo and mongo-go-driver

Posted by kamurj on Wed, 11 Sep 2019 11:18:45 +0200

Comparison of mgo and mongo-go-driver

Introduction to Library

  • mgo: It's driven by MongoDB's Go language. It uses a simple API based on Go grammar to implement rich features and has been well tested. It's easy to use, the documents are enough, and it has been used in the early stage, but it's a pity that it's not maintained.

  • mongo-go-driver: Official driver, very low-level design, transfer from mgo is not easy, mainly using transactions;

Usage introduction

Connect to the database

  • mgo

    import ( 
    "gopkg.in/mgo.v2" 
    "gopkg.in/mgo.v2/bson" 
    )
    session, err := mgo.Dial("127.0.0.1:27017")
  • mongo-go-driver

    import (
    "go.mongodb.org/mongo-driver/mongo"
    "go.mongodb.org/mongo-driver/mongo/options"
    )
    
    client, err := mongo.NewClient(options.Client().ApplyURI("mongodb://localhost:27017"))

The connection between the two databases is very simple. The latter uses options, which can set the number of connections, connection time, socket time, timeout, etc.

Indexes

  • mgo

    func createUniqueIndex(collection string, keys ...string) {
        ms, c := Connect(setting.DatabaseSetting.DBName, collection)
        defer ms.Close()
    
        // Setting unique index for statistical tables
        index := mgo.Index{
            Key:        keys, // Index key
            Unique:     true, // unique index
            DropDups:   true, // When created after data exists, duplicate data is automatically deleted
            Background: true, // Write locks are not occupied for a long time
        }
        // Create index
        err := c.EnsureIndex(index)
        if err != nil {
            Logger.Error("EnsureIndex error", zap.String("error", err.Error()))
        }
    }
  • mongo-go-driver

    func createUniqueIndex(collection string, keys ...string) {
        db := DB.Mongo.Database(setting.DatabaseSetting.DBName).Collection(collection)
        opts := options.CreateIndexes().SetMaxTime(10 * time.Second)
    
        indexView := db.Indexes()
        keysDoc := bsonx.Doc{}
        
        // Composite index
        for _, key := range keys {
            if strings.HasPrefix(key, "-") {
                keysDoc = keysDoc.Append(strings.TrimLeft(key, "-"), bsonx.Int32(-1))
            } else {
                keysDoc = keysDoc.Append(key, bsonx.Int32(1))
            }
        }
    
        // Create index
        result, err := indexView.CreateOne(
            context.Background(),
            mongo.IndexModel{
                Keys:    keysDoc,
                Options: options.Index().SetUnique(true),
            },
            opts,
        )
        if result == "" || err != nil {
            Logger.Error("EnsureIndex error", zap.String("error", err.Error()))
        }
    }

mgo can construct composite index directly, and create index (addrTrxC,'address_id','asset_id') by passing in multiple parameters in sequence, but mongo-go-driver needs to do some processing by itself.

query

  • mgo

    func FindProNode() ([]DBNode, error) {
        var nodes []DBNode
        ms, c := Connect(setting.DatabaseSetting.DBName, superNodeC)
        defer ms.Close()
        err := c.Find(M{}).Sort("-vote_count").Limit(10).All(&nodes)
        if err != nil {
            return nil, err
        }
    
        return nodes, nil
    }
  • mongo-go-driver

    func FindNodes() ([]DBNode, error) {
        var nodes []DBNode
    
        c := Connect(setting.DatabaseSetting.DBName, superNodeC)
        ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
        defer cancel()
    
        opts := options.Find().SetSort(bsonx.Doc{{"vote_count", bsonx.Int32(-1)}})
        cursor, err := c.Find(ctx, M{}, opts)
        if err != nil {
            return nil, err
        }
    
        for cursor.Next(context.Background()) {
            var node DBNode
            if err = cursor.Decode(&node); err != nil {
                return nil, err
            } else {
                nodes = append(nodes, node)
            }
        }
    
        return nodes, nil
    }

When querying a single element, both drivers are convenient. However, when querying multiple elements, mongo-go-driver can not directly parse into an array. It needs to use cursor type to traverse and parse all data (troublesome, need to encapsulate itself).

insert

  • mgo

    // currency
    func Insert(db, collection string, docs ...interface{}) error {
        ms, c := Connect(db, collection)
        defer ms.Close()
        return c.Insert(docs...)
    }
    
    //insert
    func InsertNode(node DBNode) error {
        err := Insert(setting.DatabaseSetting.DBName, superNodeC, node)
        return err
    }
  • mongo-go-driver

    func Insert(db, collection string, docs ...interface{}) (*mongo.InsertManyResult, error) {
        c := Connect(db, collection)
        ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
        defer cancel()
    
        return c.InsertMany(ctx, docs)
    }
    
    
    func InsertNode(node DBNode) error {
        _, err := Insert(setting.DatabaseSetting.DBName, superNodeC, node)
        return err
    }

The difference in insertion is not very big either.

modify

  • mgo

    func UpsertNode(node DBNode) (*mgo.ChangeInfo, error) {
        findM := M{"pub_key": node.PubKey}
    
        ms, c := Connect(setting.DatabaseSetting.DBName, superNodeC)
        defer ms.Close()
    
        updateM := bson.M{"$inc": M{"vote_count": node.VoteCount},
            "$set": M{
                "name":            node.Name,
                "address":         node.Address,
                "first_timestamp": node.FirstTimestamp}}
        return c.Upsert(findM, updateM)
    }
  • mongo-go-driver

    func Update(db, collection string, query, update interface{}) (*mongo.UpdateResult, error) {
        c := Connect(db, collection)
        ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
        defer cancel()
        
        opts := options.Update().SetUpsert(true)
        return c.UpdateOne(ctx, query, update,opts)
    }

Both can update one mgo(update) mongo-go-driver(updateOne) and multiple mgo(updateAll) mongo-go-driver(updateMany); but when upsert, mgo can use upsert directly, and mongo-go-driver needs to set opts: = options. Update (). Setsert (true).

delete

  • mgo

    func Remove(db, collection string, query interface{}) error {
        ms, c := Connect(db, collection)
        defer ms.Close()
        return c.Remove(query)
    }
    
    func RemoveNode(pubKey string) error {
        findM := M{"pub_key": pubKey}
        err := Remove(setting.DatabaseSetting.DBName, superNodeC, findM)
        return err
    }
  • mongo-go-driver

    func Remove(db, collection string, query interface{}) (*mongo.DeleteResult, error) {
        c := Connect(db, collection)
        ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
        defer cancel()
        return c.DeleteOne(ctx, query)
    }
    
    
    func RemoveNode(pubKey string) error {
        findM := M{"pub_key": pubKey}
        _, err := Remove(setting.DatabaseSetting.DBName, superNodeC, findM)
        return err
    }
    

Delete is also relatively simple, roughly the same.

affair

  • mgo
    I won't support it
  • mongo-go-driver

    • Need to use SessionContext
    • Use SessionContext for all changes you need to query before making them.

      Because it is used in many places, it encapsulates a method.
      //The method to be implemented in this method is Exec's operator.
      
      type DBTransaction struct {
          Commit func(mongo.SessionContext) error
          Run    func(mongo.SessionContext, func(mongo.SessionContext, DBTransaction) error) error
          Logger *logging.Logger
      }
      
      func NewDBTransaction(logger *logging.Logger) *DBTransaction {
          var dbTransaction = &DBTransaction{}
          dbTransaction.SetLogger(logger)
          dbTransaction.SetRun()
          dbTransaction.SetCommit()
          return dbTransaction
      }
      
      func (d *DBTransaction) SetCommit() {
          d.Commit = func(sctx mongo.SessionContext) error {
              err := sctx.CommitTransaction(sctx)
              switch e := err.(type) {
              case nil:
                  d.Logger.Info("Transaction committed.")
                  return nil
              default:
                  d.Logger.Error("Error during commit...")
                  return e
              }
          }
      }
      
      func (d *DBTransaction) SetRun() {
          d.Run = func(sctx mongo.SessionContext, txnFn func(mongo.SessionContext, DBTransaction) error) error {
              err := txnFn(sctx, *d) // Performs transaction.
              if err == nil {
                  return nil
              }
              d.Logger.Error("Transaction aborted. Caught exception during transaction.",
                  zap.String("error", err.Error()))
      
              return err
          }
      }
      
      func (d *DBTransaction) SetLogger(logger *logging.Logger) {
          d.Logger = logger
      }
      
      func (d *DBTransaction) Exec(mongoClient *mongo.Client, operator func(mongo.SessionContext, DBTransaction) error) error {
          ctx, cancel := context.WithTimeout(context.Background(), 20*time.Minute)
          defer cancel()
      
          return mongoClient.UseSessionWithOptions(
              ctx, options.Session().SetDefaultReadPreference(readpref.Primary()),
              func(sctx mongo.SessionContext) error {
                  return d.Run(sctx, operator)
              },
          )
      }
      
      
      
      //Specific call
      func SyncBlockData(node models.DBNode) error {
          dbTransaction := db_session_service.NewDBTransaction(Logger)
      
          // Updates two collections in a transaction.
          updateEmployeeInfo := func(sctx mongo.SessionContext, d db_session_service.DBTransaction) error {
              err := sctx.StartTransaction(options.Transaction().
                  SetReadConcern(readconcern.Snapshot()).
                  SetWriteConcern(writeconcern.New(writeconcern.WMajority())),
              )
              if err != nil {
                  return err
              }
              err = models.InsertNodeWithSession(sctx, node)
              if err != nil {
                  _ = sctx.AbortTransaction(sctx)
                  d.Logger.Info("caught exception during transaction, aborting.")
                  return err
              }
      
              return d.Commit(sctx)
          }
      
          return dbTransaction.Exec(models.DB.Mongo, updateEmployeeInfo)
      }
      

summary

  • In turn, it's mainly official support transactions. When switching, many of the official use cases use bsonx. The bson used in mgo stepped on some pits, but you can also keep using bson.
  • Transactions also need to ensure that all statements executed by the whole transaction use transactions, not session Content.
  • Do as much encapsulation as possible

Topics: Database MongoDB Session github