Cover V11, I02

Article
Listing 1
Listing 2
Listing 3
Sidebar 1
Sidebar 2

feb2002.tar

Developing a MySQL Quota Daemon

Daniel Solin

One of the drawbacks of MySQL is the lack of a real quota system. This article will cover how our ISP was able to solve this problem and keep track of our customers' databases. With this completely automated solution, many hours of administration work was avoided.

Identifying the Problem

In the UNIX world, MySQL and PHP have become very popular for Web database development. The ISP that I work for has offered both MySQL database access as well as PHP support for its customers for quite a while. However, we've lacked an automated system that tracks our customers' databases so that they don't grow to an unreasonable size. The customers are offered a set of pre-defined MySQL packages, each package defining exactly how much space the customer is allowed to use on the MySQL server. However, the only way to be sure customers don't exceed their quota is to manually log into the server, check each database size, and compare the current size to the size that the customer has paid for. This obviously isn't the best solution.

We needed software that could track and automate this process for us. Our software criteria were:

  • Continuous updates -- We wanted the quota checks to be performed frequently to provide current information about each database.
  • Low footprint -- Because the quota software would be executed on the same server on which MySQL runs (which is already heavy), it was important for the quota software to require minimal CPU time.
  • Good communication -- The quota software should be able to contact the administrator via email when a problem occurs, and also when any problem is resolved.
  • Logging functionality -- A good logging functionality was required for reference.
  • Dumping database-specific information -- We let our customers use phpMyAdmin to administer their databases, and we wanted to also provide a status page to phpMyAdmin (http://phpwizard.net/projects/phpMyAdmin).
Finding a Solution

After considering these criteria, we worked toward a solution. We chose C as the programming language for speed and lowest footprint. We couldn't consider a cron job because we needed updates on an almost per-second basis. We instead created a UNIX daemon that resides in memory so that the host isn't forced to execute the program each time an update is needed. The communication, logging, and dumping functionalities would not be difficult, even in C, because they only require some basic file operations and an execution of Sendmail with the correct arguments. We considered further developing the mailing functions of the new program but weren't sure we could do that without devoting an unreasonable amount of work to it. Additionally, we wanted to receive the mail that was to be sent, and Sendmail provided a stable option. Everything that the daemon would do was to be logged into a logfile. For the interaction with phpMyAdmin, the daemon should write a simple HTML page for each database, informing the customer of the current quota status. After making these decisions, we were ready to begin coding.

Writing a UNIX Daemon

A few things must be done either manually or by the daemon() function (do a man daemon for details) in order to make a program a real UNIX daemon. I prefer to do these manually. By doing it manually, you can learn more about UNIX processes. Listing 1 (http://www.sysadminmag.com/code/) shows the main() function used in our MySQL quota daemon. See the sidebar for an explanation of Listing 1.

Implementing the Quota Checking

Each of our customer's MySQL databases is represented by a sub-directory of $MYSQL_BASE_DIR/data (or $MYSQL_BASE_DIR/var or something else, depending on your setup). The name of this sub-directory is always the same as the database name. The files in $MYSQL_BASE_DIR/data/$DB_NAME represent the tables you have in your database, so you get the actual database size by calculating the size of the $MYSQL_BASE_DIR/data/$DB_NAME directory. If you're on a system running a MySQL database server, and you know there is a database called "mydb" on the server, you can determine the size of this database by running du $MYSQL_BASE_DIR/data/mydb (changing $MYSQL_BASE_DIR to your real MySQL base installation directory). In my company's case, the database name is the same as the user who owns the database, so mydb would be owned by the user "mydb" (which is useful when implementing the database status Web page a little later). Listing 2 (http://www.sysadminmag.com/code/) shows the complete do_quota_file() function. See the sidebar for an explanation of Listing 2.

Dumping Database-Specific Information

As mentioned, we also wanted the quota daemon to dump information about each database to a file that could easily be implemented in phpMyAdmin. This information should be presented on a Web page anyway, so we put this information in HTML format. This functionality is handled by the function int gen_www_info( char *db_name, double size, double quota ), which is called in Listing 2 on line 212. It takes three arguments -- the name of the database about which to dump information, the current size, and quota for the database. Given this information, gen_ww_info() generates the database information, saves it to /var/lib/apache/htdocs/quota/$DB_NAME, and returns 0 if everything goes well. Listing 3 shows our implementation of this function (http://www.sysadminmag.com/code/).

The function is straightforward and simply takes the database name passed to it and saves some information in HTML format to the file /var/lib/apache/htdocs/quota/$DB_NAME. (Of course, you need to change this path to suit your system.)

The customer can log on to phpMyAdmin using a MySQL username and password. Because the username is the same as the database name, the name of the database is present in phpMyAdmin, stored in the $PHP_AUTH_NAME variable. The database status information is stored in a file with the same name as its corresponding database in /var/lib/apache/htdocs/quota, so the correct information file is used by implementing the following line in a suitable PHP script:

<?php include("quota/$PHP_AUTH_USER"); ?>
This will cause all information stored in quota/$PHP_AUTH_USER to be included in the HTML document.

Time for Testing

When you're ready to test this setup, you first must compile the daemon. Before the test will work, however, remove the code on line 114 of Listing 2 that tells the program to ignore the MySQL database ((!strcmp( dir_element-d_name, "mysql"))). Then tell the C compiler to build the daemon. On my Linux system, I use the GNU C compiler:

gcc -Wall lst01.c -o quotad
Before starting the daemon, confirm that it has enough permissions to perform all its tasks. You can run it as root the first time to see whether it works as expected and then run it as a normal user. If any problem occurs now, it has something to do with the permissions.

Start the daemon:

./quotad
The daemon should now go directly into the background, directing you to the command line. To see whether the deamon is running, you can run a tail -f /usr/local/mysql/logs/quota_log. If the daemon is running, this file should get updates every two seconds. To test whether the quota checking works, create a quota file for the MySQL database (/usr/local/mysql/quota), and add a single 1 to it. This will cause the quota daemon to react each time the /usr/local/mysql/data/mysql directory gets larger than 1 Mb. If you don't have many MySQL users and privileges defined, your MySQL database is probably not larger than 1 Mb. To fake an exceeded account, copy a file larger than 1 Mb to /usr/local/mysql/data/mysql. If everything works as expected, you should quickly receive an email to the address defined in lst02.c.

I received the following email:

Date: Sun, 18 Nov 2001 19:41:24 +0100
From: mysql@mysql
Message-Id: <200111181841.TAA09554@mysql>
To: daniel@solin.org
Subject: Reports from MyQuotaD regarding mysql

Hi there!

I am the MySQL-quota-daemon, and I just realized that the the user mysql has 
exceeded his/her MySQL-quota. The database is 129.80Mb, while the maximum 
allowed size for this account is 100.00Mb. Maybe we should contact the customer?

User/Database: mysql
Current Status: 1.29Mb
Allowed Size: 1.00Mb

All best,
MyQuotaD
The email report arrived just as we wanted it to and, with our automated system for adding new databases to this quota-checking facility, we'll soon be able to just wait for the emails to arrive.

Daniel Solin is a UNIX and Linux freak from cold Sweden. He is working with UNIX on one of Europe's largest ISPs in the daytime, and writing about UNIX at night. He can be contacted at: daniel@solin.org.