Trifle
GitHub
Trifle
Trifle::Stats / Drivers / Mongo
Learn in depth about Mongo driver implementation.

Mongo

Mongo driver uses mongo client gem to talk to database. It uses bulk_write and find operations for interacting with database.

Configuration

To be able to use Trifle::Stats::Driver::Mongo requires few arguments to be configured correctly.

driver (Mongo)

First required argument is a driver. You can configure driver directly using mongo gem;

require 'mongo'

Trifle::Stats.configure do |config|
  config.driver = Trifle::Stats::Driver::Mongo.new(
    Mongo::Client.new('mongodb://mongo:27017/stats')
  )
end

Or if you are using mongoid, you can reuse its client setup.

Trifle::Stats.configure do |config|
  config.driver = Trifle::Stats::Driver::Mongo.new(
    Mongoid.client(:default)
  )
end

collection_name: 'trifle_stats' (String)

You can specify collection name used to store Trifle::Stats data in MongoDB. You can pass collection_name: 'my_stats' keyword argument.

Trifle::Stats.configure do |config|
  config.driver = Trifle::Stats::Driver::Mongo.new(
    Mongo::Client.new('mongodb://mongo:27017/stats'),
    collection_name: 'my_stats'
  )
end

joined_identfier: true (Bool)

Mongo driver supports key as a joined value of key, range and at but also supports these attributes separately. You can specify it via optional joined_identifier: false keyword argument. It defaults to joined configuration as it performs better. Separating these into its attributes opens you to access data directly and use other tools to visualize the content.

Trifle::Stats.configure do |config|
  config.driver = Trifle::Stats::Driver::Mongo.new(
    Mongo::Client.new('mongodb://mongo:27017/stats'),
    joined_identifier: false
  )
end

expire_after: nil (Integer)

As MongoDB supports expiring indexes, it is possible to pass a expire_after: 60 keyword argument that will set expire_at attribute on each upserted document 60 seconds after at timestamp. As of now the expiration is basic - a simple number of seconds from the tracked timestamp.

Here are some usecases where it comes handy:

  • Using beam and scan where beamed metrics automatically expire after 15 seconds. expire_after: 15
  • Tracking only past 60 minutes of a performance metrics and discarding automatically everything that is older than that. expire_after: 60 * 60
Trifle::Stats.configure do |config|
  config.driver = Trifle::Stats::Driver::Mongo.new(
    Mongo::Client.new('mongodb://mongo:27017/stats'),
    expire_after: 60
  )
end

Setup

Mongo driver requires trifle_stats collection to be present. You can override collection_name when creating an instance of a driver. If you wish to modify joined identifier you need to pass the same argument in setup as well. For joined identifiers it creates index only for key and for separated identifiers it creates combined index for key, range and at. The downside of having keys separately is slightly (~5%-10%) slower performance due to combined index. Keep that in mind.

You can create collection and index on your own or simply call setup! class method that does this for you. It creates a collection and adds an unique index on a key attribute.

Below is a setup and configuration for a custom database and a collection name.

client = Mongo::Client.new('mongodb://mongo:27017/stats')
Trifle::Stats::Driver::Mongo.setup!(
  Mongo::Client.new('mongodb://mongo:27017/stats'),
  collection_name: 'my_stats',
  joined_identifiers: false
)

Trifle::Stats.configure do |config|
  config.driver = Trifle::Stats::Driver::Mongo.new(
    Mongo::Client.new('mongodb://mongo:27017/stats'),
    collection_name: 'my_stats',
    joined_identifiers: false
  )
end

Driver

You can either use your default Mongoid client, or pass in instance of custom Mongo client.

irb(main):001:0> client = Mongo::Client.new('mongodb://mongo:27017/stats')
=> #<Mongo::Client:0x2080 cluster=#<Cluster topology=Single[mongo:27017] servers=[#<Server address=mongo:27017 STANDALONE pool=#<ConnectionPool size=0 (0-5) used=0 avail=0 pending=0>>]>>
irb(main):002:0> driver = Trifle::Stats::Driver::Mongo.new(client)
=> #<Trifle::Stats::Driver::Mongo:0x000055949646adb8 @client=#<Mongo::Client:0x2080 cluster=#<Cluster topology=Single[mongo:27017] servers=[#<Server address=mongo:27017 STANDALONE pool=#<ConnectionPool size=0 (0-5) used=0 avail=0 pending=0>>]>>, @collection_name="trifle_stats", @separator=...

Interaction

Once you create instance of a driver, you can use it to set, inc or get your data.

irb(main):003:0> driver.get(keys: [['test', 'now']])
=> [{}]

irb(main):004:0> driver.inc(keys: [['test', 'now']], count: 1, success: 1, error: 0)
=> #<Mongo::BulkWrite::Result:0x00005573a9148d28 @results={"n_modified"=>0, "n_upserted"=>1, "n_matched"=>0, "n"=>1, "upserted_ids"=>[BSON::ObjectId('62aabc4dca4d709bbea3719f')]}>
irb(main):005:0> driver.get(keys: [['test', 'now']])
=> [{"count"=>1, "error"=>0, "success"=>1}]

irb(main):006:0> driver.inc(keys: [['test', 'now']], count: 1, success: 0, error: 1, account: { count: 1 })
=> #<Mongo::BulkWrite::Result:0x00005573a8dcde78 @results={"n_modified"=>1, "n_upserted"=>0, "n_matched"=>1, "n"=>1, "upserted_ids"=>[]}>
irb(main):007:0> driver.get(keys: [['test', 'now']])
=> [{"count"=>2, "error"=>1, "success"=>1, "account"=>{"count"=>1}}]

Performance

All good here! set and inc are executed in one query and each key in keys is executed as a one bulk write operation. get fetches all keys in a single query.