globalsign / mgo

The MongoDB driver for Go
Other
1.97k stars 230 forks source link

Session Mode Clarification #154

Closed rwynn closed 6 years ago

rwynn commented 6 years ago

Thank for you maintaining mgo and adding new features. I had a question about setting the Mode of the session. I am using session.Copy() in a series of go routines to do work concurrently. I noticed that when I do this the number of connections goes up in mongostat. This looks good to me since I am trying to maximize throughput. However, if I follow the session.Copy() with a session.SetMode(mgo.Eventual, true), then I do not see the connection count increase in mongostat. I am trying to maximize read throughput and thought mgo.Eventual would be a faster mode to use, however I don't understand why this would not use a new socket. Is socket reuse the expected behavior in this case?

Without mgo.Eventual

insert query update delete getmore command flushes mapped vsize   res faults qrw arw net_in net_out conn set repl                time
    *0    *0     *0     *0       1     2|0       0        18.7G 15.0M      0 0|0 0|0   208b   32.3k   29 rs1  PRI Apr 25 14:34:39.109

With mgo.Eventual(refresh=true) added after the Copy, I only see about 5 conns instead of 29.

weiishann commented 6 years ago

Hi @rwynn ,

Are you running it against a replica set? Could you check the connection count on the other secondaries please?

From mgo doc: In the Eventual consistency mode reads will be made to any secondary in the cluster, if one is available, and sequential reads will not necessarily be made with the same connection.

rwynn commented 6 years ago

Hi @weiishann, I am running it against a single mongod instead, not a real replica set. Although I did call rs.initiate(), so I guess it's just a one node replicate set (which doesn't make any sense).

Anyway, I know this sort of setup is not how the Eventual Mode is supposed to run, since it is made to read from secondaries, however, I am still wondering why the difference in connection count, since even in the Eventual mode it should use the primary if a secondary is not available.

I guess what threw me is that I'm expecting to a new connection when (a) I copy the session or (b) I guess call SetMode with refresh = true, no matter what Mode the session is in. I'm seeing that happen with the default Mode, but not with Eventual.

domodwyer commented 6 years ago

Hi @rwynn

Would you be able to share your code? We're having a little trouble reproducing the issue.

Dom

rwynn commented 6 years ago

Hi @domodwyer

The client code and the line number I had a question about is here

https://github.com/rwynn/gtm/blob/master/gtm.go#L925

That function should be getting called from another named DirectReadPaged which first attempts to break a large collection into _id range segments which get read concurrently in multiple go routines in DirectReadSegment.

Specifically, I am trying to use this library to read a large 20 million document collection. When I change the line above from mgo.Nearest to mgo.Eventual, then I see far fewer connections in mongostat. I'm not sure if that is a bug or just a misunderstanding on my part.

A simple example of using this library can be found here

https://github.com/rwynn/gtm#usage https://github.com/rwynn/gtm#configuration

The DirectReadPaged function would then be triggered if db.users is large collection (as configured in the 2nd example link)

rwynn commented 6 years ago

A simple way to trigger is to run the following:

package main
import "github.com/globalsign/mgo"
import "github.com/rwynn/gtm"
func main() {
    session, err := mgo.Dial("localhost")
    if err != nil {
        panic(err)
    }
    defer session.Close()
    session.SetMode(mgo.Monotonic, true)
    ctx := gtm.Start(session, &gtm.Options{
        DirectReadNs: []string{"test.test"},
    })
    for {
        select {
        case <-ctx.ErrC:
            break
        case <-ctx.OpC:
            break
        }
    }
}

Where test.test is a large, multi-million document collection, and viewing the output of mongostat.

domodwyer commented 6 years ago

Hi @rwynn

There doesn't seem to be an issue as such so I'm going to close this, but feel free to re-open if you come across a problem.

Dom

rwynn commented 6 years ago

Hi @domodwyer,

Maybe this example is easier since it is a self contained program with output.

package main

import "github.com/globalsign/mgo"
import "github.com/globalsign/mgo/bson"
import "sync"
import "time"
import "fmt"

func printConns(wg *sync.WaitGroup, session *mgo.Session) {
    defer wg.Done()
    for {
        status := bson.M{}
        err := session.Run(bson.D{{"serverStatus", 1}}, &status)
        if (err != nil) {
            fmt.Printf("Error getting server status: %s\n", err)
        } else {
            fmt.Printf("Server connection status: %+v\n", status["connections"])
        }
        time.Sleep(time.Duration(5) * time.Second)
    }
}

func runQuery(wg *sync.WaitGroup, session *mgo.Session) {
    defer wg.Done()
    mySession := session.Copy()

    // TOGGLE FOLLOWING LINE AND COMPARE OUTPUT
    // WITH LINE = less connections
    // WITHOUT LINE = more connections

        //mySession.SetMode(mgo.Eventual, true)

    // END

    defer mySession.Close()
    for {
        col := mySession.DB("test").C("test")
        var results []bson.M
        col.Find(nil).Limit(1).All(&results)
        time.Sleep(time.Duration(5) * time.Second)
    }
}

func main() {
    wg := &sync.WaitGroup{}
    session, err := mgo.Dial("localhost")
    if err != nil {
        panic(err)
    }
    defer session.Close()
    session.SetMode(mgo.Monotonic, true)
    for i:=0; i<20; i++ {
        wg.Add(1)
        go runQuery(wg, session)
    }
    wg.Add(1)
    go printConns(wg, session)
    wg.Wait()
}

If you run and note output, then un comment the line that sets the eventual mode, run and compare output.

Notice the difference in the number of connections reported. If you can explain to me why this difference is correct that would be helpful to me, because I would expect both to be equivalent. Thanks!