Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mongo db java driver - resource management with client

Tags:

java

mongodb

Background

I have a remote hosted server thats running java vm with custom server code for multiplayer real-time quiz game. The server deals with matchmaking, rooms, lobbies etc. I'm also using a Mongo db on same space which holds all the questions for mobile phone quiz game.

This is my first attempt at such a project and although I'm competent in Java my mongo skill are novice at best.

Client Singleton

My server contains static singleton of mongo client:

     public class ClientSingleton 
  {

  private static ClientSingleton uniqueInstance;
  // The MongoClient class is designed to be thread safe and shared among threads. 
  // We create only 1 instance for our given database cluster and use it across
  // our application.
  private MongoClient mongoClient;
  private MongoClientOptions options;
  private MongoCredential credential;

  private final String password = "xxxxxxxxxxxxxx";
  private final String host = "xx.xx.xx.xx";
  private final int port = 38180;

  /**
    * 
  */
  private ClientSingleton() 
{
    // Setup client credentials for DB connection (user, db name & password)
    credential =  MongoCredential.createCredential("XXXXXX", "DBName", password.toCharArray());
    options = MongoClientOptions.builder()
            .connectTimeout(25000)
            .socketTimeout(60000)
            .connectionsPerHost(100)
            .threadsAllowedToBlockForConnectionMultiplier(5)
            .build();
    try 
    {
        // Create client (server address(host,port), credential, options)
        mongoClient = new MongoClient(new ServerAddress(host, port), 
                Collections.singletonList(credential),
                options);
    } 
    catch (UnknownHostException e) 
    {
        e.printStackTrace();
    }
}

  /**
   * Double checked dispatch method to initialise our client singleton class
   * 
   */
  public static ClientSingleton getInstance()
  {
    if(uniqueInstance == null)
    {
        synchronized (ClientSingleton.class)
        {
            if(uniqueInstance == null)
            {
                uniqueInstance = new ClientSingleton();
            }
        }
    }
    return uniqueInstance;
  }

  /**
   * @return our mongo client
   */
  public MongoClient getClient() {
    return mongoClient;
  }
 }

Notes here:

Mongo client is new to me and I understand failure to properly utilise connection pooling is one major “gotcha” that greatly impact Mongo db performance. Also creating new connections to the db is expensive and I should try and re-use existing connections. I've not left socket timeout and connect timeout at defaults (eg infinite) if connection hangs for some reason I think it will get stuck forever! I set number of milliseconds the driver will wait before a connection attempt is aborted, for connections made through a Platform-as-a-Serivce (where server is hosted) it is advised to have a higher timeout (e.g. 25 seconds). I also set number of milliseconds the driver will wait for a response from the server for all types of requests (queries, writes, commands, authentication, etc.). Finally I set threadsAllowedToBlockForConnectionMultiplier to 5 (500) connection in, a FIFO stack, awaiting their turn on the db.

Server Zone

Zone gets a game request from client and receives the meta data string for quiz type. In this case "Episode 3". Zone creates room for user or allows user to join room with with that property.

Server Room

Room then establishes db connection to mongo collection for the quiz type:

// Get client & collection
mongoDatabase = ClientSingleton.getInstance().getClient().getDB("DBName");
mongoColl = mongoDatabase.getCollection("GOT");

// Query mongo db with meta data string request
queryMetaTags("Episode 3");

Notes here:

Following a game or I should say after an room idle time the room get destroyed - this idle time is currently set to 60 mins. I believe that if connections per host is set to 100 then while this room is idle then it would be using valuable connection resources.

Question

Is this a good way to manage my client connections? If I have several hundred concurrently connected games and each accessing the db to pull the questions then maybe following that request free up the client connection for other rooms to use? How should this be done? I'm concerned about possible bottle necks here!

Mongo Query FYI

    // Query our collection documents metaTag elements for a matching string
// @SuppressWarnings("deprecation")
public void queryMetaTags(String query)
{
    // Query to search all documents in current collection
    List<String> continentList = Arrays.asList(new String[]{query});
    DBObject matchFields = new 
       BasicDBObject("season.questions.questionEntry.metaTags", 
      new BasicDBObject("$in", continentList));
    DBObject groupFields = new BasicDBObject( "_id", "$_id").append("questions", 
       new BasicDBObject("$push","$season.questions"));
    //DBObject unwindshow = new BasicDBObject("$unwind","$show");
    DBObject unwindsea = new BasicDBObject("$unwind", "$season");
    DBObject unwindepi = new BasicDBObject("$unwind", "$season.questions");
    DBObject match = new BasicDBObject("$match", matchFields);
    DBObject group = new BasicDBObject("$group", groupFields); 
    @SuppressWarnings("deprecation")
    AggregationOutput output = 
    mongoColl.aggregate(unwindsea,unwindepi,match,group);

    String jsonString = null;
    JSONObject jsonObject = null;
    JSONArray jsonArray = null;
    ArrayList<JSONObject> ourResultsArray = new ArrayList<JSONObject>();

    // Loop for each document in our collection
    for (DBObject result : output.results()) 
    {       
        try 
        {
            // Parse our results so we can add them to an ArrayList
            jsonString = JSON.serialize(result);             
            jsonObject = new JSONObject(jsonString);
            jsonArray = jsonObject.getJSONArray("questions");

            for (int i = 0; i < jsonArray.length(); i++)
            {
                // Put each of our returned questionEntry elements into an ArrayList
                ourResultsArray.add(jsonArray.getJSONObject(i));
            }                
        } 
        catch (JSONException e1) 
        {
            e1.printStackTrace();
        }
    }   
    pullOut10Questions(ourResultsArray);
}
like image 352
TripVoltage Avatar asked Aug 07 '26 04:08

TripVoltage


1 Answers

The way I've done this is to use Spring to create a MongoClient Bean. You can then autowire this bean wherever it is needed.

For example:

MongoConfig.java

import com.mongodb.MongoClient;
import com.mongodb.MongoClientURI;
import com.tescobank.insurance.telematics.data.connector.config.DatabaseProperties;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.net.UnknownHostException;

@Configuration
public class MongoConfig {

    private @Autowired DatabaseProperties properties;

    @Bean
    public MongoClient fooClient() throws UnknownHostException {
        return mongo(properties.getFooDatabaseURI());
    }

}

Class Requiring Mongodb connection:

@Component
public class DatabaseUser {

    private MongoClient mongoClient;

    ....

    @Autowired
    public DatabaseUser(MongoClient mongoClient) {
         this.mongoClient = mongoClient;
    }
}

Spring will then create the connection and wire it where required. What you've done seems very complex and perhaps tries to recreate the functionality you would get for free by using a tried and test framework such as Spring. I'd generally try to avoid the use of Singletons too if I could avoid it. I've had no performance issues using Mongodb connections like this.

like image 120
robjwilkins Avatar answered Aug 09 '26 19:08

robjwilkins



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!