Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

multiuser hash (BerkelyDB?)

2 views
Skip to first unread message

Blars Blarson

unread,
Apr 12, 2004, 4:03:13 PM4/12/04
to
I've got a fairly large (about 10 MB, 150000 entries) tied hash using
DB_File for perment storage. For performance reasons not directly
related to the hash, I need to rewrite so multiple processes will
access the hash at the same time. The only operations needed are read
and read/modify/write. (The one process that goes through the entire
hash will lock the entire hash.) While it would be nice to be able to
have exclusive access to only a small portion of the hash, it would be
acceptable to lock the entire hash for the short period of time the
read/modify/write takes. Opening and closing the file for each access
is unacceptable.

I'd think this would be a fairly common requirement, but I havn't
found any documentation on how to do so. BerkeleyDB has some more
advanced features than DB_File, but the file locking is undocumented
and the documentation of the underlying database is confusing.

Shared memory lacks the permenance I need.

Is there any better documentation on record locking with the BereleyDB
module, or is there another module more suitable? DBI could do it,
but SQL is a clunky mismatch compared to a tied hash.

If it matters, this is a Debian Linux Woody system (perl 5.6.1) with
some backports from unstable.

--
Blars Blarson bla...@blars.org
http://www.blars.org/blars.html
With Microsoft, failure is not an option. It is a standard feature.

Malcolm Dew-Jones

unread,
Apr 12, 2004, 11:01:48 PM4/12/04
to
Blars Blarson (bla...@blars.org) wrote:
: I've got a fairly large (about 10 MB, 150000 entries) tied hash using

I was told by a guy from sleepy cat that the Berkeley database will (by
default) automatically lock the entries you are accessing so that
individual entries do not need additional locking.

To be specific, if two processes both do a

$tied_hash{'X'}=$some_value

at the "same time", or otherwise access the database, then the values will
be correctly set without corruption of the database.

I do not know 100% that this is true, though after some simple tests with
BerkeleyDB it seamed reasonable to believe him.

HOWEVER, you must use the BerkeleyDB module, not DB_File. It definitely
isn't always true when using DB_File. Multiple processes accessing a
shared tied hash tied using DB_File can produce a corrupt database.

Blars Blarson

unread,
Apr 13, 2004, 5:39:20 AM4/13/04
to
In article <407b...@news.victoria.tc.ca>,

Malcolm Dew-Jones <yf...@vtn1.victoria.tc.ca> wrote:
>I was told by a guy from sleepy cat that the Berkeley database will (by
>default) automatically lock the entries you are accessing so that
>individual entries do not need additional locking.
>
>To be specific, if two processes both do a
>
> $tied_hash{'X'}=$some_value
>
>at the "same time", or otherwise access the database, then the values will
>be correctly set without corruption of the database.

As I said originally, I need to do read/modify/write operations.

$foo = $tied_hash{'X'};
$tied_hash{'X'} = func($foo);

The record needs to be locked betwen the first operation starting and
the second completing, otherwise two processes could be trying to
modify the same value.

Heiko Klein

unread,
Apr 15, 2004, 6:56:45 AM4/15/04
to Blars Blarson
You seem to have some other documentation of DB_File, so I append the
'Locking' paragraph of my DB_File manpage. I've still be using the 'old
no longer recommended way' without any problems, but I think I have to
update soon.

Heiko


HINTS AND TIPS
Locking: The Trouble with fd

Until version 1.72 of this module, the recommended technique for
lock-
ing DB_File databases was to flock the filehandle returned from the
"fd" function. Unfortunately this technique has been shown to be
funda-
mentally flawed (Kudos to David Harris for tracking this down).
Use it
at your own peril!

The locking technique went like this.

$db = tie(%db, 'DB_File', '/tmp/foo.db', O_CREAT|O_RDWR, 0666)
|| die "dbcreat /tmp/foo.db $!";
$fd = $db->fd;
open(DB_FH, "+<&=$fd") || die "dup $!";
flock (DB_FH, LOCK_EX) || die "flock: $!";
...
$db{"Tom"} = "Jerry" ;
...
flock(DB_FH, LOCK_UN);
undef $db;
untie %db;
close(DB_FH);

In simple terms, this is what happens:

1. Use "tie" to open the database.

2. Lock the database with fd & flock.

3. Read & Write to the database.

4. Unlock and close the database.

Here is the crux of the problem. A side-effect of opening the
DB_File
database in step 2 is that an initial block from the database
will get
read from disk and cached in memory.

To see why this is a problem, consider what can happen when two pro-
cesses, say "A" and "B", both want to update the same DB_File
database
using the locking steps outlined above. Assume process "A" has
already
opened the database and has a write lock, but it hasn't actually
updated the database yet (it has finished step 2, but not
started step
3 yet). Now process "B" tries to open the same database - step 1
will
succeed, but it will block on step 2 until process "A" releases the
lock. The important thing to notice here is that at this point
in time
both processes will have cached identical initial blocks from the
database.

Now process "A" updates the database and happens to change some
of the
data held in the initial buffer. Process "A" terminates,
flushing all
cached data to disk and releasing the database lock. At this
point the
database on disk will correctly reflect the changes made by process
"A".

With the lock released, process "B" can now continue. It also
updates
the database and unfortunately it too modifies the data that was
in its
initial buffer. Once that data gets flushed to disk it will
overwrite
some/all of the changes process "A" made to the database.

The result of this scenario is at best a database that doesn't
contain
what you expect. At worst the database will corrupt.

The above won't happen every time competing process update the same
DB_File database, but it does illustrate why the technique
should not
be used.

Safe ways to lock a database

Starting with version 2.x, Berkeley DB has internal support for
lock-
ing. The companion module to this one, BerkeleyDB, provides an
inter-
face to this locking functionality. If you are serious about locking
Berkeley DB databases, I strongly recommend using BerkeleyDB.

If using BerkeleyDB isn't an option, there are a number of modules
available on CPAN that can be used to implement locking. Each one
implements locking differently and has different goals in mind.
It is
therefore worth knowing the difference, so that you can pick the
right
one for your application. Here are the three locking wrappers:

Tie::DB_Lock
A DB_File wrapper which creates copies of the database file for
read access, so that you have a kind of a multiversioning
concur-
rent read system. However, updates are still serial. Use for
databases where reads may be lengthy and consistency
problems may
occur.

Tie::DB_LockFile
A DB_File wrapper that has the ability to lock and unlock the
database while it is being used. Avoids the
tie-before-flock prob-
lem by simply re-tie-ing the database when you get or drop
a lock.
Because of the flexibility in dropping and re-acquiring the
lock
in the middle of a session, this can be massaged into a system
that will work with long updates and/or reads if the
application
follows the hints in the POD documentation.

DB_File::Lock
An extremely lightweight DB_File wrapper that simply flocks a
lockfile before tie-ing the database and drops the lock
after the
untie. Allows one to use the same lockfile for multiple
databases
to avoid deadlock problems, if desired. Use for databases where
updates are reads are quick and simple flock locking
semantics are
enough.

Matt S Trout

unread,
Apr 20, 2004, 2:47:22 PM4/20/04
to
In article <hw3s5...@blars.org>, Blars Blarson <bla...@blars.org> wrote:
>In article <407b...@news.victoria.tc.ca>,
>Malcolm Dew-Jones <yf...@vtn1.victoria.tc.ca> wrote:
>>I was told by a guy from sleepy cat that the Berkeley database will (by
>>default) automatically lock the entries you are accessing so that
>>individual entries do not need additional locking.
>>
>>To be specific, if two processes both do a
>>
>> $tied_hash{'X'}=$some_value
>>
>>at the "same time", or otherwise access the database, then the values will
>>be correctly set without corruption of the database.
>
>As I said originally, I need to do read/modify/write operations.
>
>$foo = $tied_hash{'X'};
>$tied_hash{'X'} = func($foo);
>
>The record needs to be locked betwen the first operation starting and
>the second completing, otherwise two processes could be trying to
>modify the same value.

There are three locking wrappers for DB_File on CPAN, but they all seem to
be file-at-a-time. If something that coarse is acceptable, try that. If not,
consider having a set of lockfiles corresponding to the key (e.g. lock/X) and
possibly using a flock in addition; I'd suggest doing this with links but a
link isn't *actually* an atomic operation on Linux (don't ask) so this may
not be sufficient.

Alternatively, since keys %hash will always return the same order for a given
hash on a given machine, treat each key as its index in the hash and have an
array of locks in shared memory.
--
Bring me my etherkiller; Oh clouds unfold! / Bring me the magic smoke of desire
I shall not cease from mental fight / Nor shall my LART rest in my hand
Till we have buried the bodies / Of all the lusers in all this land
-- rpg, ASR [ My homepage is http://www.trout.me.uk ]

0 new messages