Customize mgo upsert operation

128 views
Skip to first unread message

İnanç Gümüş

unread,
Aug 16, 2017, 12:35:34 PM8/16/17
to mgo-users
Hi,

I've a game analytics rest API which stores the average performance statistics of the players. When a new statistic arrives, I want to update the existing game record in Mongodb by merging the new delta onto the existing document. I'm storing the past analytics data as well. So that, I can return data like the player's stats are decreasing or increasing since the game's last update.

The problem is: When I want to upsert my new game data into Mongodb with mgo, it overwrites all of a player's stats array. Actually, this is expected. I know how to fix it if I can modify my document that mgo tries to upsert into Mongodb.

Question: How can I customize mgo upsert behaviour? So that I can add a $push operator in front of Player.Stats to prevent Mongodb erasing the stats array inside the document.

Some Solutions: I've tried some solutions myself before. Like, encoding/decoding Game struct into bson.M to customize it. However, I found it cumbersome and messy. If there's no other way, I'd use it.

Blocks: I don't want to hand-write all of my structs fields with bson.M, just to use a $pushoperator on one field. Because there are dozens of fields, that would be error-prone and will increase my code complexity.

Example:


// Assume that, this is an existing game in Mongodb:
existingGame := Game{
ID: 1,
Name: "Existing game",
// The game has just one player
Players: []Player{
// The player has some stats. The newest one is 2.0.
{"foo", []{3.5, 2.0}},
}
}

// This is a new request coming to my API
// I want to upsert this into the existing Game
newGame := Game{
ID: 1,
Players: []Player{
// As expectedly, this will reset player foo's stats to 3.5
//
// After upserting, I want it to be as: 
//
// []{3.5, 2.0, 5.0}
//
// in Mongodb
{"foo", []{5.0}},
}
}

func (db *Store) Merge(newGame *Game) error {
sess := db.session.Copy()
defer sess.Close()

col := sess.DB("foo").C("games")
// I want to modify newGame here to add a $push operator
// into a new `bson.M` or `bson.D` to make mgo to upsert
// my new delta without resetting the player stats
_, err := col.UpsertId(newGame.ID, newGame)

return err
}

type Game struct {
ID int `bson:"_id"`
Name string
Players []Player `bson:",omitempty"`
// ...I omitted other details for simplicity here...
}

type Player struct {
Name string
// I want to keep the previous values of stats
// So, that's why I'm using an array here
Stats []float64
// ...
}




Luke

unread,
Aug 17, 2017, 7:25:09 AM8/17/17
to mgo-users
Hi,

I would do this outside of Mongo, for example:
  1. load previous game
  2. merge loaded game with the exisiting one in Go
  3. upsert the game
or
  1. use find and modify and modify only specific properties, like Stats
  2. if the game was not found, insert it
Also, it is a good practice to pass a pointer to the mgo.DB to the method that works with database, so I would have something like:
type Game struct {...}

func
(g *Game) Merge(newGame *Game) error {
...
}

func
(g *Game) SaveInDB(db *mgo.DB) error { //or use Session instead of DB so you can copy it and close
...
}

func
(f *Game) MergeInDB(db *mgo.DB, newGame *Game) error {
...
}




İnanç Gümüş

unread,
Aug 23, 2017, 5:33:19 AM8/23/17
to mgo-...@googlegroups.com
Hi,

The problem is, load & merge & upsert and the 2nd way (findandmodify
then insert) are open to race-condition issues.
> --
> You received this message because you are subscribed to a topic in the
> Google Groups "mgo-users" group.
> To unsubscribe from this topic, visit
> https://groups.google.com/d/topic/mgo-users/7PxFUYOtpT4/unsubscribe.
> To unsubscribe from this group and all its topics, send an email to
> mgo-users+...@googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.
Reply all
Reply to author
Forward
0 new messages