Comdb2 was designed to run easily out of the box with minimal setup. Databases just need to know the names of their peers to form a cluster. Applications just need to know the name of the database and the application tier to connect. This flexibility requires a little bit of configuration on the application machine side.

Applications have 3 options for discovering where databases live. In order of increasing complexity and improved flexibility, they are:

  • Pass location information as part of the cdb2_open call
  • Configure database locations on a per-database basis
  • Set up a meta database that stores that information

This document covers all 3 options. Experience of running databases at Bloomberg shows us that running a database containing cluster setup information for other database, and setting up a DNS entry to bootstrap the location of this meta-database is the most flexible and resilient option.

Passing location information

The type argument to cdb2_open will treat any string that begins with ‘@’ as a list of hostnames, not the name of a tier. This makes it possible to use alternative means of looking up database information in addition to configuration files and comdb2db. The rest of the string as interpreted as a comma-separated list of node specifications. For example

@machine:port=123:dc=ZONE1,machine2:port=456:dc=ZONE2

This specifies 2 machines (machine and machine2). Each machine optionally takes a colon separated list of options. The only options currently supported are the port number (port=) and data center (dc=). See room affinity for how the dc option is used. The port number, if omitted, will be queried from the specified machines.

Client configuration files

Destinations

The easiest option is to set up a configuration file that contains a list of machines where the database lives. The downside is that moving databases to a different set of machines becomes a bit more difficult (see clustering for more on that topic).

The Comdb2 client API looks for configuration files in 2 places:

  • $COMDB2_ROOT/etc/cdb2/config/comdb2db.cfg
  • $COMDB2_ROOT/etc/cdb2/config.d/$dbname.cfg

$COMDB2_ROOT can bet set as an environment variable. It defaults to /opt/bb. The config file format is simple:

dbname machine1 machine2 machine3

comdb2_config: option option_value
comdb2_config: option2 option_value2
...

Empty lines and lines starting with ‘#’ are ignored.

The configuration file can contain the name of the database, and which machines it lives on for the current application tier (eg: beta, production). Using "default" for type in the connect string passed to cdb2_open will use these machines as the destination. The database will query a machine in the list for cluster information and learn what the rest of the machines are, so a partial list will work. This comes in handy if a database ever needs to move.

In addition to database information, the config file can also contain settings that control how the API behaves. Each line containing a setting starts with comdb2_config. The settings are detailed below.

Application settings

default_type

This establishes the tier for the current machine. If specified in the global config file ($COMDB2_ROOT/etc/cdb2/config/comdb2db.cfg) this will establish a default value for all databases on this machine. In a per-database config file (COMDB2_ROOT/etc/cdb2/config.d/$dbname.cfg) it sets the default for the specified database. The value is a freeform string. Example values may be “dev”, “alpha”, “beta”, “prod”, etc. Applications that use "default" as the type argument to cdb2_open will look for machine configuration for this tier (see comdb2db for configuring multiple tiers).

room

The argument is a freeform string. cdb2_open fetches cluster information from the database on connecting. This information includes which “room” (data center, availability zone, etc.) the machine is in. cdb2_open will prefer to connect to database machines in the same room as the current machine to reduce latency. Room affinity of Comdb2 machines is configured in comdb2db.

pmuxport

cdb2_open needs to learn what port the database listens on. To do that, it talks to a service called pmux that runs on database machines. This configures what port pmux is listening on. The default is 5101.

connect_timeout

This sets the timeout for connecting to databases. The argument should be a number, in milliseconds. The default is 100ms. When an attempt to connect to a node takes longer than this, the API will abort the attempt, and try another machine in the cluster.

comdb2db_timeout

Similar to connect_timeout, but sets the timeout on querying comdb2db for cluster information. The API will try other available machines in turn if the cluster request fails.

comdb2dbname

This specifies the name of the meta database that contains information about locations of other databases. The default is comdb2db, and we’ll call it comdb2db in the rest of this document.

tcpbufsz

Expects an integer argument. This set the size of the receive buffer for database connections. The default is unset and will make the API use the OS default.

dnssuffix

As an alternative to specifying the location of comdb2db in a configuration file, it can be configured via DNS. If the location of comdb2db isn’t present in config files, cdb2api will try to resolve the following names:

  • If no default_type is set, comdb2db.dnssuffix
  • If default_type is set, type-comdb2db.dnssuffix

For example, for this configuration file:

comdb2_config: comdb2dbname metadb
comdb2_config: dnssuffix dyndns.example.com
comdb2_config: room prod

Comdb2 API will try to find the Comdb2 configuration database (called metadb) on machines returned by resolving the hostname prod-metadb.dyndns.example.com.

lib

This expects a path to a shared object file, which will be loaded into the application memory.

Note that the API must be compiled with WITH_DL_LIBS and the application must link with -ldl.

The API looks for cdb2_lib_init() in the shared object file. The function takes no arguments and returns void. It will be called immediately after the shared object is loaded.

comdb2db

Comdb2db manages 3 types of resources: databases, machines, and clusters. At the minimum, it should contains the tables/fields listed below. It can be extended with additional data. For example, at Bloomberg, comdb2db contains database size information and quotas, Comdb2 binaries for easy deployment, etc.

cdb2_open runs the following query to get a list of machines where a database runs:

   select M.name, D.dbnum, M.room 
      from 
        machines M join 
        databases D 
   where 
      M.cluster IN 
         (select cluster_machs
          from clusters 
          where 
            name=@dbname and 
            cluster_name=@cluster) and 
      D.name=@dbname 
   order by (room = @room) desc

Where dbname is the name argument to cdb2_open, cluster is the type argument, and room is the machine room (set with the room config file option).

Databases

This table just contains a database name and number. The number is only present for record keeping (it had a different purpose in older iterations of Comdb2). This table can be extended to contain whatever database specific information is relevant for your organization.

schema {
    cstring name[32]
    int     dbnum      dbstore=0
}

keys
{
    "KEY_NAME"  = name
}

Machines

The machines table contains information on machine clusters set up to host Comdb2 databases. A machine is a part of a cluster. Clusters have a name.

schema
{
    // Name of machine 
    cstring     name[32]

    // Name of the comdb2 cluster that this machine is part of e.g. "C"
    cstring     cluster[32]

    // Name of the room that this machine is part of e.g. "NY"
    cstring     room[10]
}

keys
{
       "KEY_NAME"     = name
   dup "KEY_CLUSTER"  = cluster
}

Clusters

The clusters table defines what cluster constitutes a given tier for a given database. cluster_machs refers to a group of machines (cluster field) in the machines table. The name field is the name of the tier.

schema
{
    // Database name
    cstring     name[32]

    // A name for this tier.  Common choices are "test", "alpha", "beta",
    // "prod".
    cstring     cluster_name[32]

    // The cluster machines that this database lives on e.g. "beta" or "E"
    // (this refers to the actual machines and must exist in table machines)
    cstring     cluster_machs[32]
}

keys
{
    // Primary key
    "KEY_NAME_CLUSTER" = name + cluster_name

    // For "what databases are on this machine" type queries
    "KEY_MACHS_NAME_CLUSTER" = cluster_machs + name + cluster_name
}

Example

Let’s say you have a database called somedb. You need to set up a development instance, a beta instance and a production instance. It’s going to run on devdb1, devdb2, devdb3 for the dev tier, betadb1, betadb2, betadb3 for the beta tier, and proddb1, proddb2, proddb3 for the production tier. Let’s say each machine is in one of three availability zones (r1, r2, r3).

First, we’ll set up the machines. We are going to define 3 clusters, called prod1 (our first production cluster), beta1 (our first beta cluster) and dev1 (our first development cluster).

insert into machines(name, cluster, room) values('proddb1', 'prod1', 'r1')
insert into machines(name, cluster, room) values('proddb2', 'prod1', 'r2')
insert into machines(name, cluster, room) values('proddb3', 'prod1', 'r3')

insert into machines(name, cluster, room) values('betadb1', 'beta1', 'r1')
insert into machines(name, cluster, room) values('betadb2', 'beta1', 'r2')
insert into machines(name, cluster, room) values('betadb3', 'beta1', 'r3')

insert into machines(name, cluster, room) values('devdb1', 'dev1', 'r1')
insert into machines(name, cluster, room) values('devdb2', 'dev1', 'r2')
insert into machines(name, cluster, room) values('devdb3', 'dev1', 'r3')

Now, we create the record for the database.

insert into databases(name) values('somedb')

Finally, we define the tiers for this database.

insert into clusters(name, cluster_name, cluster_machs) values('somedb', 'dev', 'dev1')
insert into clusters(name, cluster_name, cluster_machs) values('somedb', 'beta', 'beta1')
insert into clusters(name, cluster_name, cluster_machs) values('somedb', 'prod', 'prod1')

Now any machine that’s configured for comdb2db access can call out to ‘somedb’. Configuring the default_type setting on the machine will allow all applications to refer to the correct tier by passing “default” to cdb2_open.

cdb2sockpool

cdb2sockpool is a connection pooler program. If it’s running, cdb2_api will get existing connections from it instead of establishing a connection to the database. cdb2_close will donate a connection to the socket pool.
It listens on a UNIX socket for requests from applications. It’ll also read commands from /tmp/msgtrap.sockpool, which can be used to query it for information or change settings.

Commands

exit

Shuts down cdb2sockpool. Running programs will not be able to donate connections back to the pool, but will continue working.

stat clnt

Display statistics about connected applications.

stat pool

Display statistics about pooled connections.

stat port

Display statistics about cached database port information.

stat

Display general statistics.

closeall

Closes all available connections.

purgeports

Forgets all cached port information.

dumphints

Displays cached information.

sethint

Takes a “type string” (see output of dumphints for examples) and a port number. Sets the port number remembered for the given input string.