Apache::DBI::Cache(3)

πŸ“¦
version 0.08 Β· 2 versions
, in fedora 43

πŸ’– Apache::DBI::Cache(3) πŸ’–

okay bestie so this is the user-contributed Perl manual page for Apache::DBI::Cache, and trust, it's a whole vibe. let's get into it πŸ¦‹


✨ NAME ✨

Apache::DBI::Cache β€” it's a DBI connection cache, periodt πŸ’…


✨ SYNOPSIS ✨

use Apache::DBI::Cache debug=>3, bdb_env=>'/tmp/tmp',
                       plugin=>'Apache::DBI::Cache::mysql',
                       plugin=>['DBM', sub {...}, sub {...}],
                       ...;

that's it, that's the whole entrance. iconic πŸ‘‘


✨ DESCRIPTION ✨

so this module is the alternative to the famous Apache::DBI module. just like Apache::DBI does, it gives you persistent DBI connections β€” the connections stay alive instead of getting yeeted every time.

and the range?? immaculate. it works with mod_perl1, mod_perl2, and honestly even standalone. versatile queen 🌈

🌸 WHY ANOTHER MODULE FOR THE SAME? 🌸

ngl, Apache::DBI has a bunch of limitations. let's list them:

  • first of all, you can't get multiple connections with the same parameters. a super common scenario is using one connection to do transactions and another one to do simple lookups in the same database. with Apache::DBI, you're very likely to get the same connection when you actually meant to use different ones. tragic 😭
  • with Apache::DBI, all connections are reset at the end of a request. every single one. no cap.
  • Apache::DBI doesn't regard database-specific functions to cache handles more aggressively. like, a mysql DSN can look like
dbi:mysql:test:localhost:3306

or it can look like

dbi:mysql:host=localhost;db=test

both of these point to the same database, but for Apache::DBI they're two completely different things πŸ’€. Apache::DBI::Cache recognizes these two by means of a mysql plugin. slay.

  • the plugin even recognizes connections to different databases on the same mysql server as the same connection, and it issues a "USE database" command before returning the actual handle to the user. so with Apache::DBI::Cache the overall number of connections to a DB server can be dramatically reduced. the efficiency?? we love to see it πŸ’–

πŸ¦‹ HOW DOES IT WORK? πŸ¦‹

to decide whether to use Apache::DBI::Cache or not, it's essential to know how it actually works. buckle up.

  • same as Apache::DBI, Apache::DBI::Cache uses a hook provided by the DBI module to intercept DBI->connect() calls. and yes, Apache::DBI::Cache also maintains a cache of active handles.
  • when a new connection is requested and the cache is empty, a new connection is established and returned to the user. at this point it is not cached at all. this is where Apache::DBI::Cache differs from Apache::DBI β€” plot twist ✨
  • later, either disconnect is called on the handle, or it simply goes out of scope and the garbage collector calls a DESTROY method if one is provided. both of these events are intercepted by Apache::DBI::Cache. and only then is the handle put in the cache.
  • which means a handle is never really disconnected. $dbh->{Active} will always return true no matter how many times you call disconnect. also, you can prevent a handle from getting reused by simply not forgetting it. iykyk 😌

✨ USAGE ✨

different from Apache::DBI, Apache::DBI::Cache must be used, not required. that means its import function must be called.

when you're using it with mod_perl (versions 1.x or 2.x), the best place to do this is in a startup.pl or in a <Perl> section in the httpd.conf. go check the mod_perl documentation for more info bestie 🫢

after that, DBI->connect is called as usual. no special treatment needed. you just vibe.

when Apache::Status or Apache2::Status is used, Apache::DBI::Cache provides an extra menu item to show statistics on handles. and the loading order of Apache::Status and Apache::DBI::Cache?? irrelevant. doesn't matter, it just works πŸ’…

πŸ’œ FUNCTIONS πŸ’œ

import β€” the "use" parameter

parameters to the "use" statement are given in a "key => value" fashion.

use Apache::DBI::Cache debug=>3, logger=>sub {...},
                       plugin=>['driver', sub {}, sub {}],
                       plugin=>'Apache::DBI::Cache::mysql',
                       use_bdb=>1,
                       bdb_env=>'/tmp/mybdbenv',
                       bdb_memcache=>20*1024,
                       ...;
  • plugin β€” loads a plugin, see also "PLUGINS" below. the plugin can be specified as a 3-element array or by name. in the second case, import simply uses the module. this option can be given multiple times.
  • use_bdb, bdb_env and bdb_memcache β€” Apache::DBI::Cache can use BerkeleyDB as a shared memory implementation to maintain statistics for a group of processes instead of a single process. (yes the original says "BerleleyDB", we're being cute about it, moving on πŸ’€)
    • "use_bdb" specifies whether to use BerkeleyDB or not. if omitted, Apache::DBI::Cache will try to load and use BerkeleyDB. if that fails, it silently provides per-process statistics. if "use_bdb" is true, Apache::DBI::Cache dies if it cannot use BerkeleyDB. if "use_bdb" is false, per-process statistics are maintained and BerkeleyDB is not used.
    • "bdb_env" specifies a path to a directory where BerkeleyDB can put its temporary files. if omitted, /tmp/Apache::DBI::Cache is used. the parent directory of this directory must exist and be writeable. no exceptions bestie.
    • "bdb_memcache" specifies the size of the shared memory segment that is allocated by BerkeleyDB. depending on the number of handles in your configuration, a few kilobytes are enough. if omitted, 20 kB are used.
    • "bdb_env" and "bdb_memcache" can also be specified by the APACHE_DBI_CACHE_ENVPATH and APACHE_DBI_CACHE_CACHESIZE environment variables. options options options ✨
  • debug β€” set a debug level. under mod_perl this is almost irrelevant, see "logger" below.
  • logger β€” here a logger function can be specified. it is called with the message verbosity level as the first parameter. the remaining parameters are concatenated to build the actual message.

currently there are 2 verbosity levels used, 1 and 2. 0 is reserved for real errors. 1 mentions that the module has been initialized. 2 rattles off normal processing messages.

Apache::DBI::Cache provides 2 logger functions. one is controlled by the "debug" level setting (see above). a message is printed to STDERR if its level is equal or greater than the current debug level.

the other logger is used when running under mod_perl. it is mainly controlled by the Apache "LogLevel" setting. messages at level 0 are printed as $log->error, level 1 as $log->info, and level 2 as $log->debug. for level 2 messages, additionally the current debug level is checked to be greater or equal to 2.

  • delimiter β€” here the internal key delimiter can be changed. it defaults to "\1". changing it is necessary only when your DSN, username or password contain it, or to provide more readable debugging messages.

statistics

returns a reference to the statistics hash. if BerkeleyDB is used, it is tied to BerkeleyDB::Btree.

statistics_as_html

returns a reference to an array of HTML fragments. if mod_perl and Apache::Status or Apache::Status2 is used, the output of this function is shown under http://HOST/STATUS/URI?DBI_conn. cute right? 🌸

plugin( 'name', \&mangle, \&setup )

installs a new plugin, see "PLUGINS" below. if a plugin for the specified database type was already installed, it is returned as a 2-element list:

($old_mangle, $old_setup)=
  plugin( 'name', \&new_mangle, \&new_setup );

if called with a name only, the current plugin is returned:

($old_mangle, $old_setup)=plugin( 'name' );

to delete a plugin, call:

($old_mangle, $old_setup)=plugin( 'name', undef, undef );

connect_on_init

call this function multiple times with parameters you would pass to "DBI->connect" before calling Apache::DBI::Cache::init, i.e. in your "startup.pl". then "init" will establish all these connections. pre-loading the vibes ✨

init

this function is called once per child process to initialize Apache::DBI::Cache. if mod_perl is used, this is done automatically in a PerlChildInitHandler.

finish

this function must be called before a process is going to terminate. under mod_perl it is automatically called in a PerlChildExitHandler.

as of version 0.08, calling this function is optional. so you can relax a lil.

undef_at_request_cleanup( \$dbh1, \$dbh2, ... )

when an application uses global variables to store handles, they probably won't be reused, because a global variable is... well... global. this can be fixed by explicitly undefining these handles at request cleanup, or by using this function. it simply collects all handle references passed in between 2 calls to Apache::DBI::Cache::request_cleanup. when Apache::DBI::Cache::request_cleanup is called, all these handles are undefined. the first call to this function during a request cycle installs Apache::DBI::Cache::request_cleanup as a PerlCleanupHandler.

with mod_perl2 this requires the PerlOption "GlobalRequest" to be set:

PerlOption +GlobalRequest

in your httpd.conf.

request_cleanup

this is the PerlCleanupHandler. if Apache::DBI::Cache is used standalone, the application can call it from time to time.


✨ EXPORT ✨

nothing. nope. zero. it exports nothing πŸ’…


✨ DBI SUBCLASSING ✨

for a module like Apache::DBI::Cache, coping with DBI subclasses is complicated. there are 2 problems to solve.

first β€” make sure that our "disconnect" and "DESTROY" methods get called instead of the original ones. Apache::DBI::Cache solves this by inserting its own methods into the foreign class.

  • hence, if a subclass provides "disconnect" and "DESTROY" methods, they will never be called. this is ugly, but it works in most cases. we're being honest about it bestie 😭

second β€” to insert our methods into the subclass, we need to know its name. to create a subclassed DBI handle, you call either

DBI->connect( $dsn, $user, $passwd, {RootClass=>SUBCLASS} );

or

SUBCLASS->connect( $dsn, $user, $passwd, {} );

the first case is simple, since the attribute hash is passed to our connect method. unfortunately, the second case is not simple, since "SUBCLASS" is known only by DBI::connect. this is solved by searching the current call stack for the DBI::connect call. then we use its first parameter.

  • that works for me, but it's even uglier. if you encounter problems, don't hesitate to mail me. fr fr 🫢

✨ Class::DBI and Ima::DBI ✨

to make "Class::DBI" or "Ima::DBI" work with "Apache::DBI::Cache", see Apache::DBI::Cache::ImaDBI. that's the plug 🌈


✨ PLUGINS ✨

okay bestie, so plugins are how you customize the caching for specific database types πŸ’… They can do three iconic things: swap up the caching key, run database commands right before a handle gets handed back to you, or straight up block handle caching entirely for that database type. no cap.

There can only be one plugin per database type at a time. periodt. that's the rule.

A plugin signs itself up by calling Apache::DBI::Cache::plugin and passing 3 parameters 🫢 The first one is just the name of the database type. It matches the DBI driver name. So a MySQL plugin passes the string "mysql" because the matching DBI driver is called "DBD::mysql". Meanwhile a PostgreSQL plugin passes either "Pg" or "PgPP" depending on which driver is actually being used. iykyk 🌸

The 2nd and 3rd parameters are CODE references ✨ The first gets called just before a connection is chosen from the cache or newly established, and the second gets called after the connection is made, just before it's returned to the caller. The first function can mangle the connection parameters, the second performs additional setup steps. From here on I'm calling them mangle and setup πŸ’–

So a plugin registers itself like this:

Apache::DBI::Cache::plugin('Name', \&mangle, \&setup);

Normally it's implemented as its own separate module following this template β€” see Apache::DBI::Cache::mysql for example πŸ‘‘

package Apache::DBI::Cache::DRIVER;

use strict;

BEGIN {
  die "Please load Apache::DBI::Cache before"
    unless defined &Apache::DBI::Cache::plugin;

  ...

  Apache::DBI::Cache::plugin
      (
       'DRIVER',
       sub {},
       sub {}
      );
}

1;

🌈 Calling Conventions 🌈

  • mangle
($dsn, $user, $passwd, $attr, $ctx, $nocache)=
   mangle($dsn, $user, $passwd, $attr);

mangle gets called with basically the same parameters as the original call to DBI->connect πŸ’œ The dbi:DRIVER: prefix is stripped from the DSN. It's expected to return similar values, plus an arbitrary context that later gets passed to setup, and an optional nocache flag.

If $nocache is true, or if mangle returns an empty list, a brand new connection is made and the handle goes straight to the caller with no further processing. Also setup will not be called. A handle like that will not be cached on disconnect or DESTROY 🚫

mangle can change all the parameters. The MySQL plugin, for example, deletes the actual database name from the DSN, reformats it into a standard format, and adds the standard port if it was omitted. The database gets put in the context. slay πŸ’…

  • setup
$rc=setup($dbh, $dsn, $user, $passwd, $attr, $ctx);

The setup function performs extra setup steps on $dbh ✨ The MySQL plugin, for example, issues a USE database command using the database from the context.

A connection is considered dead if setup returns false πŸ’€


πŸ“ TODO πŸ“

  • periodically ping all cached handles
  • correct statistics when a process is finishes without calling finish()
  • redirect BerkeleyDB errors to logger

πŸ‘€ SEE ALSO πŸ‘€

  • Apache::DBI::Cache::mysql
  • Apache::DBI

πŸŽ€ AUTHOR πŸŽ€

Torsten Foertsch, <torsten.foertsch@gmx.net>

With suggestions from

Andreas Nolte < andreas dot nolte at bertelsmann dot de >

Dietmar Hanisch < dietmar dot hanisch at bertelsmann dot de > and

Ewald Hinrichs < ewald dot hinrichs at bertelsmann dot de >

πŸ’– SPONSORING πŸ’–

Sincere thanks to Arvato Direct Services (http://www.arvato.com/) for sponsoring this module and providing a test platform with several thousand DBI connections. iconic behavior honestly 🫢


Copyright (C) 2005-2006 by Torsten Foertsch

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.


and that's a wrap bestie πŸ¦‹ this page is from perl v5.42.0, dated 2025-07-25, and it's the Apache::DBI::Cache(3) manpage. go cache those handles responsibly, mwah πŸ’‹

πŸ“¦ where this page comes from