Mongo db java driver - resource management with client
Background
I have a remote hosting server running a java vm with custom server code for real-time multiplayer game. The server works with matchmaking, rooms, lobby, etc. I am also using Mongo db in the same place where all the questions for the mobile phone quiz are stored.
This is my first attempt at such a project, and although I'm competent in Java, my mongo skill is a beginner at best.
Singleton client
My server contains a static singleton of a 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:
The Mongo client is new to me, and I understand that failing to use the connection pool correctly is one of the main "gotchas" that greatly impact the performance of Mongo db. Also making new connections to db is expensive and I have to try and reuse existing connections. I didn't leave the socket timeout and the default connection timeout (like infinite), if the connection hangs for some reason I think it will get stuck forever! I have set the number of milliseconds that the driver will wait before the connection attempt is aborted, for connections made through the as-server platform (where the server is hosted), a higher timeout is recommended (eg 25 seconds). I also set the number of milliseconds,the driver will wait for a response from the server for all types of requests (requests, records, commands, authentication, etc.). Finally, I set threadAllowedToBlockForConnectionMultiplier to 5 (500) connections, on the FIFO stack, waiting for them to be enabled on the db.
Server zone
The zone receives a game request from the client and receives a metadata string for the quiz type. In this case, "Episode 3". The zone creates space for the user or allows the user to join a room with this property.
Server room
The room then makes a db connection to the 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:
After the game or should I say that after the downtime in the room the room collapses - that downtime is currently set to 60 minutes. I believe that if the connections to the host are set to 100, then while this room is down it will use up valuable connection resources.
Question
Is this a good way to manage my client connections? If I have several hundred concurrently linked games and each access to the db to pull questions, then maybe after this request free up the client connection for other rooms to use? How to do it? I'm worried 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);
}
source to share
The way I did it was to use Spring to create MongoClient Bean. Then you can auto-install this bean wherever 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 route it where needed. What you have done seems to be very complex and is perhaps trying to recreate the functionality you get for free using a tried and tested framework like Spring. I've tried to avoid using Singletons altogether if I could avoid it. I had no performance issues using Mongodb connections like this.
source to share