The Pinnacle Server

Overview

Since RC21 is a relational database management system (RDBMS) with full commit and rollback, multi-user operations must be handled by a Server that maintains concurrency and data integrity. This server is a program that

†††††††† receives request messages from client programs

†††††††† performs the requested actions

†††††††† sends a message that returns results to the requesting client program

In addition, the server is responsible for handling row and table locking.

RC21 client programs have a C++ API identical to the RC21 single-user C++ API, with the addition of several calls to connect to the server and to lock rows and tables. Certain single-user RC21 functions are missing in the multi-user library. This is because they perform actions that are reserved for the server. Client programs are linked with an alternate library that encodes function calls into messages that are sent to the server via TCP/IP.

Server Name

Each server has a servername. The syntax for servername is host:portnumber. Host is a tcpip hostname OR an ip address OR the special string "localhost". Portnumber is a 16-bit unsigned number that you may choose, but that must not conflict with port assignments of other services (like FTP, HTTP, etc.) on your server machine between. The following are valid server names:

Localhost:16000
vtdb10.vermontdatabase.com:16000
206.25.78.58:16000

Concurrency

In RC21 all changes performed by a client program are private until a Commit is performed. At that point, the changes become visible to other clients of the database. This is achieved by logging all database-changing operations performed by each client. When a client commits, the server looks around to see if any other clients have been modifying any overlapping areas of the database. If a given client does have changes to the same database pages as the client that just commited, this client must roll forward his own changes to the new version of the commited database, making a private copy of the new pages for the client. This way, changes to the database are integrated with private, tentative copies of the database prior to commit. This is handled automatically by the server. Thus, a client may discover that if he re-reads a non-locked field, the value has changed. This does not usually have any practical results in a record-update environment. However, if an instantaneous picture of the database at a certain point in time is desired, the database should either be copied using Copy, or the relevant tables should be locked using LockTable. In fact, the entire database could be locked with the LockDB function. Needless to say, it is inappropriate for a single client to lock a table or database for a long period of time as this would totally stall other users of the same table/database.

The C++ API

Connecting to the Server

Several instances of the RC21 server may be running on a network - each with its own distinct set of database files. Each server has a servername by which it is addressed. In order to perform database operations, the client program must first do a Connect, specifying the desired server by name. The Connect function will return a connection-handle which is used to access databases on the server named in the connect. When all operations are complete, the client program should do a Disconnect, signalling to the server that it is done, so that the server may free the resources allocated on behalf of the client. Thus, the correct RC21 program will have:

Connect (servername);
...
Database x(
normal RC21 operations
...
Disconnect ();

Locking

The RC21 Server provides a locking facility that prevents clients from interfering with each other's updates. The locking facility is embodied in the following declarations that are found in the header file RC21.h.

 
typedef enum {LockWait, LockNoWait} LockMode;
typedef enum {LockOK, LockTryAgain, Deadlock, AwaitingLock} LockReturn;
 
LockReturn DB_LockRow (DBTAB t, LockMode m);
LockReturn DB_LockTable (DBTAB t, LockMode m);
LockReturn DB_LockDB (DB db, LockMode m);
LockReturn DB_LockSchema (DB db, LockMode m);
 
 
LockReturn Table::LockRow (LockMode m);// lock the current row
LockReturn Table::LockTable (LockMode m); // lock the entire table
LockReturn Database::Lock (LockMode m); // lock the entire database
LockReturn Database::LockSchema (LockMode m); // lock the schema

A lock may be applied to a row, a table, to the schema (only one user may modify the schema at a time), or to an entire database. When an entity is locked by a client, no other client may lock the same entity or an overlapping entity. A locked entity therefore may be safely read, updated, and written without fear that another client will perform a conflicting database update. The special function LockSchema is provided so that schema operations like AddTable, AddColumn, etc., may be performed by a client without compromising the integrity of the data dictionary. Strictly speaking, there is no reason why schema operations may not be performed concurrently by several users, but the resulting complexity of the server code was a greater cost than seemed warranted. So, if you need temporary tables, columns, etc., simply perform a LockSchema followed by the schema operations desired. Then, perform a Commit. When all is done, perform a LockSchema, delete the temporary tables and columns using DeleteTable, etc., and do a Commit. It is an error to attempt to modify the schema without first locking it.

Lock Operation

To prevent concurrent updates to the same row, perform the following sequence of operations:

1.†††††††† Position to the desired row in the table you wish to update;

2.†††††††† Perform a Table::LockRow;

3.†††††††† Read the field(s) that will be updated;

4.†††††††† Write the field(s) that will be updated;

5.†††††††† Repeat the last four steps as necessary for the transaction;

6.†††††††† Perform a Database::Commit; this will commit the changes and free the locks.

The Lock calls are of two varieties - waiting and non-waiting. Waiting calls do not return until the desired row/table can be locked. Non-waiting calls return immediately with a return code indicating success or failure to lock. The following snippet of code increments all the values in a column one row at a time:

 
DBTAB t;
ForAllRows (t)
†† {
†† t->LockRow (LockWait);†† // lock current row
†† i << t; // read the integer
†† t << ++i;// store back the incremented value
†† Database(t)->Commit();// commit the changes in the database
†† }

Or, if we prefer, we could write:

 
DBTAB t;
t->LockTable();
ForAllRows (t)
†† {
†† i << t; // read the integer
†† t << ++i;// store back the incremented value
†† }
Database(t)->Commit();// commit the changes in the database

Sometimes we need to know immediately if the desired row may be locked:

 
if (t->LockRow (LockNoWait) == LockTryAgain)
†† {
†† cout << "This row currently locked -- do you wish to wait? " <<
†††††††††††††† endl;
†† /* etc. */
†† }

Deadlocks

If it is necessary to lock more than one row in the span of a transaction (all updates between commits), it is possible for a so-called deadlock to occur.

Let's say Client A locks row 1 of a table; Client B locks row 2; then Client A attempts to lock row 2, with wait; so, Client A is waiting for row 2; meanwhile, Client B attempts to lock row 1 with wait.We now have a deadlock situation. Client A cannot proceed because it is waiting for something that Client B owns. Client B cannot proceed because it is waiting for something that Client A owns.

RC21 handles this by cancelling one of the transactions - in effect doing a rollback, so that the other can proceed. The client with the oldest lock will prevail. The client that gets rolled back will have to proceed with the transaction all over again. This implies that certain C/C++ code may need to be written to handle a passive rollback.

Server Operation

The RC21 server is invoked by executing the program presrvr.

presrvr is a program that is typically run in the background or as a daemon. In Windows/NT, it is run as a program.

presrvr takes several command-line arguments. These are preceeded by a slash or a dash and an identifying letter. They may be in any order and all arguments except "/lfilename" are optional Here are the particulars (the following text is taken from a help file that is display when presrvr is invoked without any arguments):

presrvr /lp1/pp2/cp3/tp4 /vp5 /up6 /e

where ..

p1 (l)†† is the full path of a file for the logical name table. (see below.)

p2 (p)is the number of pages for the shared buffer; this is the same as the parameter in DB_Open, except the number can be much larger.

p3 (c)is "commit interval." This is the spacing between actual physical commits. If not specified, then every time a client commits, there will be a commit for that database. Specifying a commit interval can save lots of disk i/o at the expense of lost transactions should the server go down. However, the full atomicity of a two-phase commit is maintained.

p4 (t) is the size in pages of the ram-resident transaction buffer for each userís database. This is an area where transactions are queued prior to commit. When the area is overflowed, the transactions are placed in a stream file, which is less efficient. The default is 1024 pages.

<p5> (v)††††††††† enables transaction logging. All transactions against this server will be logged into a file called <p5>. Subsequently, these transactions may be run against an identical set of databases, producing identical results.

<p6> (u)†††††††† runs an existing transaction file against the server. <p6> is the transaction file.

(e)†††††† echo RC21 function calls before they are executed

The logical name table is a text file that contains 2 or more lines; The first line has three elements: the server name, the pagesize of all databases that this server will handle, and the secret_word for this server instance.

Each server must have a unique name. The client addresses a particular server (which serves a group of databases) using this name in the DB_Connect function.

All databases served by a given server must have the same physical pagesize. This is the pagesize specified when the database is created by DB_Create. This parameter is optional, however. If it is not specified, it will default to the value specified in the environment variable DB_PAGESIZE. If DB_PAGESIZE is not specified, it will default to a value #define'd (DEFAULT_PAGESIZE) in the server program. As shipped, this is defined as 512.

The secret_word is a password to be used by the system administrator to perform certain privileged operations on the server using the spin utility (see below).

All the other lines in the logical name file contain pairs - logical name followed by space followed by actual path. Clients always refer to database files by their logical names in the DB_Open.

Server Utilities

The operator of a database server will frequently have a requirement for the following:

†††††††† display of current server status

†††††††† cancel a client (in case the client program is hung due to faulty programming or whatever)

†††††††† orderly shutdown of the server (which implies removal of temporary files, physical commit, and so forth)

These actions are acomplished by invoking the program spin. spin is a client program that performs the functions of the standard single-user RC21 utility program. In addition, spin requests the server to perform the special functions enumerated above. spin is invoked as a command with one or more arguments. The general format of the command line is as follows:

 
spin servername[:secret_word] other parameters like RC21.exe...

Where servername is the servername specified for the particular invocation of presrvr. secret_word is optional; it must be supplied if the user will execute any privileged commands. The privileged commands that may be executed within spin are finish, quit, or kill. The server-oriented commands are as follows:

report: display a report showing the server parameters, the users connected, the database(s) being accessed, number of commits, i/o's, time of last commit, whether opened for read or read/write, and so forth.

finish: request he server to perform an orderly shutdown. Obviously this is a privileged command and may be entered only if the proper secret_word was entered on the command line that invoked spin. The server will accept no more DB_Connect's and will wait 'til the last client DB_Disconnect's. Then it will do a physical commit to all open databases, remove all temporary files, and exit to the operating system.

quit: Also privileged - the server will perform the same actions as in finish above except that clients will be terminated immediately. Any data that has not been commited by the client will be lost.

kill, the next parameter will be taken as a client-id (as displayed by report). The named client will be cancelled immediately.

Dummy Server Functions

The following functions are supported only in client-server operation: DB_Connect, DB_Disconnect, the DB_Lockxxx functions. These functions are implemented by empty stubs in the non-client-server libraries so you won't get link errors if you reference them in single-user applications. This helps in multi-platform code maintenance.