Seva Software

 

What is Aruna DB?

Last Updated: 10/3/2001

A_BTree.rb

Purpose

License

Description

Dependencies

Details

Limitations

Performance Considerations

To Do

Usage

Class Methods

Instance Methods

Testing

A_Debug Usage

 

Purpose:

This is an n'ary tree or b*tree written in Ruby. This class quickly stores and retrieves lots of key/value pairs (also called records). ArunaDB uses this class for the A_Table and A_Index classes to read and write records. ArunaDB is a database server written in Ruby. Records are stored in pages (see a_buffer.html for a description of the class used for storing btree pages). Each page can hold many records. Each page is sorted for fast binary searching. The pages are written to disk such that 1 row can be found in 1 million by reading as few as 3 pages in the btree (* the actual number of pages read may vary depending how big the key is and how large the page size is). A good discussion about n'ary trees or b*trees can be found at: http://perl.plover.com/BTree/article.html

 

Description:

This class is thread safe. Many threads can read and write to the same btree at the same time. Only one process may access a btree at a time. This class uses A_Catalog.rb to remember information about the btree so it can be reopened by name only. This class uses A_FileStore.rb for all file I/O. A filestore allows many btrees to reside in one file and one btree to span many files. This allows you to spread a large btree across many physically different disk drives. Note: A record, a row, and a key/value pair all describe the same thing.

Here are the main features:

 

Details:

The btree uses stacks when iterating. When you start iterating over rows in the tree, a stack is created and populated with one page for each level in the tree. This is a working stack and reused as much as possible. The stack is always memory resident and is used is such that you can traverse forward or backward through the rows in the btree at any time. As you iterate over the rows in the btree, the stack is updated by reading new pages only as needed. This is technique very flexible and very efficient. There are two stacks for each btree. One stack is used by the iterator methods such as each(), each_reverse(), and each_duplicate(), and another stack is used by methods that use iterators such as find(), insert(), update(), and delete(). This approach allows you to find() rows in the btree while you are iterating over rows in the btree.

This btree class provides a mostly balanced btree. By mostly balanced, I mean, I tried to get the best performance while keeping the unused space in the btree to a minimum. As a result, here are rules that were followed:

Locking Strategies:

 

Dependencies:

 

Limitations:

 

Performance Considerations:

BTree and FileStore Considerations:

 

To Do:

 

Usage:

require "A_FileStore"

# we need a system catalog so let's create one

A_Catalog.use('./test.ctl')

# create a filestore

A_FileStore.create('test', 1024, './filename.fs)

# create a btree

btree = A_BTree.new('btree_test2', 'test', false, 1, 1)

# insert a few rows, the keys are integers, the values are an array

btree.insert(10, [0, 0])

btree.insert(14, [4444, 3333])

btree.insert(11, [1111, 1111])

#Print the rows

btree.each {|key, value| print key, value.a_show(), "\n"}

# delete some row (10, [0, 0]) while iterating

btree.each(10, 10, true) {|key, value| btree.delete_row() if (value==[0, 0])}

# drop the btree

A_BTree.drop('btree_test2')

A_FileStore.drop('test') # drop the filestore and delete it's files

 

Class Methods:

A_BTree.connect(name)

Connects you to an existing btree. Return an A_BTree object for you to use.

A_BTree.drop(name)

Drops the btree from the default system catalog (the catalog currently in use).

A_BTree.exists?(name)

Returns true if this btree exists in the default system catalog (the catalog currently in use), otherwise returns false.

A_BTree.new (name, file_store_name=nil, unique=nil, node_page_size=1, leaf_page_size=1, key_pack_string=nil, value_pack_string=nil, check_values=nil)

A_BTree.rename(oldname, newname)

Renames a btree.

A_BTree.version()

Returns the current version of the btree. The most current version is 0.90.

 

Instance Methods:

[](key)

Alias for find(). Returns the value for the key.

[]=(key, value)

If key exists in the btree, it's value is updated. If key does not exist, it is inserted.

bof()

Short for Beginning Of File. Returns true if any iterator method or read_prev is at the beginning of the file (you read the first key/value pair), otherwise returns nil.

bof?()

Alias for bof.

clear()

Alias for delete_all.

close()

Close this btree.

check_values()

Returns true if the extra key checking is turned on. Otherwise returns nil.

delete(key, value=nil)

Delete this key/value pair from this btree. If value == nil, then all rows matching the key are deleted. Using value allows you to delete one row when the key is not unique.

delete_all()

Delete all elements in this B-Tree.

delete_row()

Delete the current row in the btree. This works in conjunction with each(), each_key(), each_value(), or each_duplicate(). For example:

Each(){|key, value| delete_row() if (value == 22)}

disconnect()

Alias for close.

drop()

Drop this btree. All elements are deleted and the btree is remove from the system catalog.

each(min_key=nil, max_key=nil, grab_write_lock=nil)

Yield(key, value) each key/value pair in the btree. The min_key and max_key parameters are a bit unusual. The btree uses these values to narrow down how many pages have to be read to retrieve the values you are looking for. If both values are nil, then all rows in the table are iterated over. If you are looking to iterate over a range of keys, then set the min_key to smallest key value and the max_key the largest. Using min_key and max_key values could really improve your performance.

each_duplicate(min_key=nil, max_key=nil, grab_write_lock=nil)

Yield(key, value, counter) each key/value pair in the btree where the key is duplicated (exists more than once). Yielding "counter" allows you to easily delete duplicate by using "delete_row() if (counter > 0)" as you are iterating over each_duplicate. The min_key and max_key parameters are a bit unusual. The btree uses these values to narrow down how many pages have to be read to retrieve the values you are looking for. If both values are nil, then all rows in the table are iterated over. If you are looking to iterate over a range of keys, then set the min_key to smallest key value and the max_key the largest. Using min_key and max_key values could really improve your performance.

each_key(min_key=nil, max_key=nil, grab_write_lock=nil)

Yield(key) each key in the btree. The min_key and max_key parameters are a bit unusual. The btree uses these values to narrow down how many pages have to be read to retrieve the values you are looking for. If both values are nil, then all rows in the table are iterated over. If you are looking to iterate over a range of keys, then set the min_key to smallest key value and the max_key the largest. Using min_key and max_key values could really improve your performance.

min_key - if you provide a min key, iterating starts where key >= min_key. This allows you reduce the number of pages that have to be read get the specific key(s) you are looking for.

each_leaf(min_key=nil, max_key=nil)

Yield(page_pos, i) each leaf page in the tree. A stack is used to make iterating easier, faster, and allow parent pages to be updated when needed. The min_key and max_key parameters are a bit unusual. The btree uses these values to narrow down how many pages have to be read to retrieve the values you are looking for. If both values are nil, then all rows in the table are iterated over. If you are looking to iterate over a range of keys, then set the min_key to smallest key value and the max_key the largest. Using min_key and max_key values could really improve your performance.

each_value(min_key=nil, max_key=nil, grab_write_lock=nil)

Yield(value) each value in the btree. The min_key and max_key parameters are a bit unusual. The btree uses these values to narrow down how many pages have to be read to retrieve the values you are looking for. If both values are nil, then all rows in the table are iterated over. If you are looking to iterate over a range of keys, then set the min_key to smallest key value and the max_key the largest. Using min_key and max_key values could really improve your performance.

eof()

Short for End Of File. Returns true if any iterator method or read_next is at the end of the file (you read the last key/value pair), otherwise returns nil.

eof?()

Alias for eof.

exists?(key, value=nil)

Returns true if this key exists in the btree, returns false when the key does not exist. If value is provided, then returns true if this key/value pair exists in the btree. Return false when this key/value pair does not exist.

export(fp)

Writes all key/value pairs in the tree to data_fp. Also wraps the btree data with strings so the import process knows when to stop reading key/value pairs. This allows you to put more than one btree in the file. This process Marshal.dumps all the key/value pairs to the file.

fp - the File pointer of an opened file. You must open the file first.

fetch(key, default=nil)

Alias for find().

fetch_last(key, default=nil)

Alias for find_last().

filestore_name()

Returns the name of the filestore where this btree resides.

find(key, default=nil)

Returns the value for the first element in this btree that matches key. Returns default if key is not found.

find_last(key, default=nil)

Returns the value for the last element in this btree that matches key. Returns default if key is not found.

flush_updates()

If there is a pending update, this will write the updated page to the disk. Under normal circumstances, you don't need to call this method because the iterators will automatically call this when needed. Call this only if you are iterating and call update_row() or delete_row() and you want the update or delete written immediately to disk.

has_key?(key, value=nil)

Alias for exists?().

import(fp, percent_full=95)

Load the data into the btree from fp. The data must have been previously exported.

fp - the File pointer of an opened file. You must open the file first.

include?(key, value=nil)

Alias for exists?().

insert(key, value)

Insert a new record (key/value pair) into this Btree.

key?(key, value=nil)

Alias for exists?().

leaf_page_size()

Returns the size of leaf pages in blocks.

load_start()

Prepare for loading. Loading is a quick way to load data into a btree. The data must be sorted by the key before you start loading. There are three steps, load_start(), load_row(for each row), load_finish(). This grabs the read and write locks.

load_finish()

This completes the load process by writing all unwritten pages. I releases the read and write locks.

load_row(key, value, percent_full=95)

This is called for each key/value pair you want to add to the btree. The data must be sorted by the key before you start calling this method. This is 3-4 times faster than calling insert.

Percent_full - this indicates how full the tree will be after all rows are loaded. The larger the number the less unused space. The smaller the number the more unused space. If there will be a lot of inserts to the btree, then a smaller number will reduce the number of page splits. 95% works well for trees with few inserts/updated/deletes. 80% works well for btree that heavily updated and inserted into.

lock()

Grab the lock on this btree. There is a unique lock for each btree. This is unique to the each btree not each object used for this btee.

name()

Returns the name of this btree.

nitems()

Returns the number of key/value pairs in the tree.

node_page_size()

Returns the size of node pages in blocks.

pages_accessed()

This allows you to see how efficient the tree is. It returns the # of pages read after calling any of the methods that iterate over rows such as delete(), update(), each(), each_value(), each_duplicate, etc.

read_end(release_write_lock=nil)

Read_first(), read_last(), read_next(), read_prev(), read_end() allow you move forward and backward through the key/value pairs in the btree. All iterator methods call these methods. If you call read_first() or read_last(), you must call read_end(). Example:

btree.read_first()

begin

while(!btree.eof)

btree.read_next()

end

ensure

btree.read_end()

end

read_first(min_key=nil, max_key=nil, grab_write_lock=nil)

Read_first(), read_last(), read_next(), read_prev(), read_end() allow you move forward and backward through the key/value pairs in the btree. All iterator methods call these methods. If you call read_first() or read_last(), you must call read_end(). See read_end for an example.

read_last(min_key=nil, max_key=nil, grab_write_lock=nil)

Read_first(), read_last(), read_next(), read_prev(), read_end() allow you move forward and backward through the key/value pairs in the btree. All iterator methods call these methods. If you call read_first() or read_last(), you must call read_end(). See read_end for an example.

read_next(max_key=nil)

Read_first(), read_last(), read_next(), read_prev(), read_end() allow you move forward and backward through the key/value pairs in the btree. All iterator methods call these methods. If you call read_first() or read_last(), you must call read_end(). See read_end for an example.

read_prev(min_key=nil)

Read_first(), read_last(), read_next(), read_prev(), read_end() allow you move forward and backward through the key/value pairs in the btree. All iterator methods call these methods. If you call read_first() or read_last(), you must call read_end(). See read_end for an example.

refresh()

This will force a re-read of all pages used by this btree. You should never need to call this method. This method is called internally when an error occurs to make sure pending inserts and updates are cleared.

rename(newname)

Rename this btree to newname.

reverse_each(min_key=nil, max_key=nil, grab_write_lock=nil)

Yield(key, value) each key/value pair in the btree.

reverse_each_key(min_key=nil, max_key=nil, grab_write_lock=nil)

Yield(key) each key in the btree.

reverse_each_value(min_key=nil, max_key=nil, grab_write_lock=nil)

Yield(value) each value in the btree.

set_check_values=(value)

To turn on extra key checking on in the btree, set this to true (i.e. set_check_values = true). To turn off extra key checking in the btree, set this to nil. When set to true, a lot of extra checking is performed on the btree to make sure that inserts, updates, and deletes are working properly. I only use this when testing the btree. Setting this to true will hurt the performance of the btree.

show(report_type='A', prefix='')

Prints information about the btree to standard out.

'A' will print a one line summary of the btree and its contents

'S' will print 'A' plus information on each node in the tree. All leafs under the same node are all printed together as one page. This is a good condensed view of the btree.

'L' will print a very detailed view of the tree. The details of each leaf are printed. This will produce a lot of output when the tree has a lot of key/value pairs.

size()

Returns the number of blocks used by this btree.

synchronize()

Works just like Mutex.synchronize for the lock on this btree. There is a unique lock for each btree. This is unique to the each btree not each object used for this btee.

to_s()

Returns the content of show('A').

truncate()

Alias for delete_all(). Removes all key/value pairs from the btree.

unique()

Returns true if the keys in this btree are unique, otherwise returns nil.

unique=(value)

Set to true if the keys in this btree are unique. Set to false or nil if the keys in this btree are not unique. This value is also provided when you create the btee. This method allows you to change the unique value. You can turn this on and off as needed. Keep in mind that if this value is changed from false to true you could have duplicate keys in the btree. In other words, you can set this to true even if there are duplicate keys in the btree. You need to manually remove any duplicate values.

unlock()

Release the lock on this btree. There is a unique lock for each btree. This is unique to the each btree not each object used for this btee.

update(key, new_value, old_value=nil)

Update the value for key in this btree with new_value. If the key is not unique, you can provide and old_value to update only one key/value pair in the tree. If old_value == nil, then all rows matching key will be updated with new_value.

update_row(value)

Update the value for the current row in the btree. This works in conjunction with each(), each_key(), each_value(), or each_duplicate(). For example:

Each(){|key, value| update_row() if (value == 22)}

 

Testing: 

a_btree_tst.rb - this script tests the basic functionality of the btree. It's great for seeing this class in action.

a_btree_tst_detail.rb - this script tests the as much of the functionality as possible. It makes sure everything works.

a_btree_tst_perf.rb - this script runs performance tests.

a_btree_tst_threads.rb - this script makes sure this class is thread safe.

a_btree_tst_merge.rb - this script runs a lot of detailed test to make sure merging and splitting page works correctly in all conditions. These tests can take days to complete.

 

A_Debug Usage:

20 - prints high level stuff like create, open, drop, etc

21 - loading rows - global btree locking

22 -

23 - insert/update/delete/find/exists

24 - stack related stuff

25 - create/drop btree pages

26 - not used

27 - read/write btree pages

28 - btree read/write locking

29 - searching

See a_debug for more details.