복붙노트

[MONGODB] MongoDB를 사용하여 산화 마그네슘의 효율적인 페이징

MONGODB

MongoDB를 사용하여 산화 마그네슘의 효율적인 페이징

나는 검색하지 StackOverflow의에서가 아닌 다른 사이트에,하지 또는 mgo.v2를 사용하지 않고, 문제에 더 이동 솔루션을 발견했습니다. 이 Q & A 지식 공유 / 문서화의 정신에있다.

이제 MongoDB를이 이동 구조체와 모델링에서 우리는 사용자 컬렉션이 있다고 가정 해 봅시다 :

type User struct {
    ID      bson.ObjectId `bson:"_id"`
    Name    string        `bson:"name"`
    Country string        `bson:"country"`
}

우리는 몇 가지 기준에 따라 분류 및 목록 사용자에게 원하지만 예상 긴 결과 목록으로 인해 구현 페이징했다.

일부 쿼리의 결과를 페이징을 달성하기 위해, MongoDB를하고 mgo.v2 드라이버 패키지가 내장되어 지원 예컨대 Query.Skip ()와 Query.Limit ()의 형태로 :

session, err := mgo.Dial(url) // Acquire Mongo session, handle error!

c := session.DB("").C("users")
q := c.Find(bson.M{"country" : "USA"}).Sort("name", "_id").Limit(10)

// To get the nth page:
q = q.Skip((n-1)*10)

var users []*User
err = q.All(&users)

수 그냥 "마술"결과의 x 번째 문서에 점프, 모든 반복에 MongoDB를 같은 페이지 번호가 증가을 가지고 있지 않은 경우 그러나 이것은 느려지는 결과 문서와 생략 (반환되지 않음) 필요가있을 수 있음을 처음 X 건너 뜁니다.

MongoDB를 오른쪽 솔루션을 제공 : 쿼리가 인덱스에서 작동하는 경우 (이 인덱스에 대한 작업에있다), cursor.min는 ()에서 결과를 나열 시작하는 첫 번째 인덱스 항목을 지정하는 데 사용할 수 있습니다.

그것이 몽고 클라이언트를 사용하여 수행 할 수있는 방법이 스택 오버플로 대답 쇼 : 어떻게하여 MongoDB의 범위 쿼리를 사용하여 페이지 매김을 할까?

참고 : 위의 질의에 필요한 인덱스가 될 것이다 :

db.users.createIndex(
    {
        country: 1,
        name: 1,
        _id: 1
    }
)

한 가지 문제 그러나이있다 다음 mgo.v2 패키지는이 분을 지정 전혀 지원하지 않는다 ().

우리는 어떻게 mgo.v2 드라이버를 사용하여 MongoDB의 cursor.min () 기능을 사용하여 효율적으로 페이징을 달성 할 수있다?

해결법

  1. ==============================

    1.불행하게도 mgo.v2 드라이버가 cursor.min을 지정하는 API 호출을 제공하지 않습니다 ().

    불행하게도 mgo.v2 드라이버가 cursor.min을 지정하는 API 호출을 제공하지 않습니다 ().

    그러나 해결책이있다. mgo.Database 유형은 MongoDB의 명령을 실행하는 Database.Run () 메소드를 제공한다. 사용 가능한 명령 및 문서는 여기에서 찾을 수 있습니다 : 데이터베이스 명령

    MongoDB를 3.2부터 새로운 find 명령은 쿼리를 실행하는 데 사용할 수있는 사용할 수 있으며,이 결과를 나열 시작하는 첫 번째 인덱스 항목을 표시 최소 인수를 지정 지원합니다.

    좋은. 우리가해야 할 일은 각 배치 후 (페이지 문서) 쿼리를 실행하는 데 사용 된 인덱스 항목의 값을 포함해야 쿼리 결과의 마지막 문서에서 최소 문서를 생성 한 후 다음 배치입니다 (다음 페이지의 문서) 이전 쿼리의 실행이 최소의 인덱스 항목을 설정함으로써 획득 될 수있다.

    이 인덱스 항목 그러길의 전화를 지금 온 문자열로 인코딩 될 수에서 커서 결과와 함께 클라이언트로 전송하고 클라이언트가 다음 페이지를 원할 때, 그는이 커서 이후에 시작하는 결과를 원하는 말을 다시 커서를 보낸다 .

    이 명령은 다른 형태로 할 수 있습니다 실행,하지만 우리가 bson.D을 (이 보존이 bson.M 달리 주문) 사용합니다 있도록 명령 이름 (발견)는 마샬링 된 결과 먼저해야합니다 :

    limit := 10
    cmd := bson.D{
        {Name: "find", Value: "users"},
        {Name: "filter", Value: bson.M{"country": "USA"}},
        {Name: "sort", Value: []bson.D{
            {Name: "name", Value: 1},
            {Name: "_id", Value: 1},
        },
        {Name: "limit", Value: limit},
        {Name: "batchSize", Value: limit},
        {Name: "singleBatch", Value: true},
    }
    if min != nil {
        // min is inclusive, must skip first (which is the previous last)
        cmd = append(cmd,
            bson.DocElem{Name: "skip", Value: 1},
            bson.DocElem{Name: "min", Value: min},
        )
    }
    

    Database.Run (와 MongoDB를 찾기 명령을 실행 한 결과)은 다음의 방식으로 포착 할 수있다 :

    var res struct {
        OK       int `bson:"ok"`
        WaitedMS int `bson:"waitedMS"`
        Cursor   struct {
            ID         interface{} `bson:"id"`
            NS         string      `bson:"ns"`
            FirstBatch []bson.Raw  `bson:"firstBatch"`
        } `bson:"cursor"`
    }
    
    db := session.DB("")
    if err := db.Run(cmd, &res); err != nil {
        // Handle error (abort)
    }
    

    이제 우리는 결과를 가지고 있지만, 유형 [] bson.Raw의 조각이다. 그러나 우리는 유형 [] * 사용자의 조각에서 그것을 원한다. Collection.NewIter ()가 유용하게 활용됩니다. 우리가 보통 Query.All () 또는 Iter.All로 전달되는 타입으로 타입 [] bson.Raw의 값 (마샬링) 변형 할 수있다 (). 좋은. 자 살펴 봅시다:

    firstBatch := res.Cursor.FirstBatch
    var users []*User
    err = db.C("users").NewIter(nil, firstBatch, 0, nil).All(&users)
    

    우리는 이제 다음 페이지의 사용자를 가지고있다. 오직 한 가지 왼쪽 : 커서를 생성하는 우리가 이제까지 그것을 필요로한다 후속 페이지를 가져 오는 데 사용되는 :

    if len(users) > 0 {
        lastUser := users[len(users)-1]
        cursorData := []bson.D{
            {Name: "country", Value: lastUser.Country},
            {Name: "name", Value: lastUser.Name},
            {Name: "_id", Value: lastUser.ID},
        }
    } else {
        // No more users found, use the last cursor
    }
    

    이것은 모든 좋은,하지만 어떻게 우리가 반대의 문자열과 그에 cursorData을 변환합니까? 우리는 bson.Marshal ()와 base64 인코딩과 결합 bson.Unmarshal ()를 사용할 수있다; base64.RawURLEncoding의 사용은 우리에게 웹에 적합한 커서 문자열, 이스케이프없이 URL 쿼리에 추가 할 수 있습니다 하나를 제공 할 것입니다.

    다음은 구현 예는 다음과 같습니다

    // CreateCursor returns a web-safe cursor string from the specified fields.
    // The returned cursor string is safe to include in URL queries without escaping.
    func CreateCursor(cursorData bson.D) (string, error) {
        // bson.Marshal() never returns error, so I skip a check and early return
        // (but I do return the error if it would ever happen)
        data, err := bson.Marshal(cursorData)
        return base64.RawURLEncoding.EncodeToString(data), err
    }
    
    // ParseCursor parses the cursor string and returns the cursor data.
    func ParseCursor(c string) (cursorData bson.D, err error) {
        var data []byte
        if data, err = base64.RawURLEncoding.DecodeString(c); err != nil {
            return
        }
    
        err = bson.Unmarshal(data, &cursorData)
        return
    }
    

    그리고 우리는 마침내 우리의 효율을 가지고 있지만 너무 짧은되지 MongoDB가이 기능을 페이징 MGO. 읽어...

    수동 방법은 매우 긴 것입니다; 그것은 일반적으로 만든 자동화 할 수 있습니다. 이 github.com/icza/minquery이 그림으로 오는 곳이다 (공개 : 나는 저자 해요). 이 구성에 래퍼를 제공하고 커서를 지정할 수 있도록하는 MongoDB를 찾기 명령을 실행하고 쿼리를 실행 한 후, 당신이 새로운 커서가 결과의 다음 배치를 쿼리하는 데 사용하는 백업 할 수 있습니다. 랩퍼 mgo.Query 매우 유사한 MinQuery 타입 있지만 MinQuery.Cursor () 메소드를 통해 MongoDB에의 분 지정 지원한다.

    이 같은 minquery 외모를 사용하여 위의 솔루션 :

    q := minquery.New(session.DB(""), "users", bson.M{"country" : "USA"}).
        Sort("name", "_id").Limit(10)
    // If this is not the first page, set cursor:
    // getLastCursor() represents your logic how you acquire the last cursor.
    if cursor := getLastCursor(); cursor != "" {
        q = q.Cursor(cursor)
    }
    
    var users []*User
    newCursor, err := q.All(&users, "country", "name", "_id")
    

    그리고 그게 다야. newCursor는 커서가 다음 배치를 가져 오는 데 사용됩니다.

    참고 # 1 : 커서 필드의 이름을 제공해야합니다) (MinQuery.All를 호출 할 때,이에서 커서 데이터 (궁극적으로 커서 문자열)을 구축하는 데 사용됩니다.

    참고 # 2 : 부분적인 결과를 검색하는 경우 (MinQuery.Select를 사용하여 ()), 당신은 당신이 직접 사용하지 않는 경우에도 커서 (색인 항목)의 일부인 모든 필드를 포함해야 , 다른 MinQuery.All () 커서 필드의 모든 값을 가지고 있지 않습니다, 적절한 커서 가치를 창출 할 수 없기 때문.

    여기 minquery의 패키지 문서를 체크 아웃 : https://godoc.org/github.com/icza/minquery를, 오히려 짧고 희망 깨끗합니다.

  2. from https://stackoverflow.com/questions/40634865/efficient-paging-in-mongodb-using-mgo by cc-by-sa and MIT license