DyBASE is easy to use and provide high performance. It is intended to be used in applications which needs to deal with persistent data in more sophisticated way than load/store object tree provided by standard serialization mechanism. Although DyBASE is very simple, it provides fault tolerant support (ACID transactions) and concurrent access to the database.
The main advantage of DyBASE is tight integration with programming language. There is no gap between database and application data models - DyBASE directly stores language objects. So there is no need in packing/unpacking code, which has to be written for traditional relational databases. Also DyBASE (unlike many other OODBMS) requires no special compiler or preprocessor. And still it is able to provide a high level of transparency.
Persistent
base class is considered as persistent capable. It is automatically made
persistent and stored in the storage when it is referenced from some other persistent object and
store
method of that object was invoked. So there is no need (but it is possible) to explicitly
assign object to the storage.
The storage has one special root object. Root object is the only persistent object accessed in the special
way (using getRootObject
method). All other persistent objects are accessed in normal way:
either by traversing by references from another persistent objects or using indices.
Unlike many other OODBMS, there can be only one root in the storage. If you need to have several named roots,
you should create Index
object and use it as root object.
Which classes are persistent capable depends on the particular language API.
In most of them they must be derived it from Persistent
class.
This makes impossible to store "foreign" classes in the storage. This is the cost of easy use of DyBASE and lack of any specialized preprocessors or compilers. In Python it is possible to make persistent arbitrary class,
but it is still more convenient to derive it from Persistent
.
DyBASE supports the following basic types:
Type | Description | Size | Related C++ type |
---|---|---|---|
Boolean | boolean type | 1 | bool |
Integer | 32-bit integer type | 4 | int |
Long integer | 64-bit integer type | 8 | long long |
Real | 64-bit floating point type | 8 | double |
String | String with counter | N | char* |
Array | One dimensional array with components of any of the described type | - | - |
Unfortunately it is not possible to detect if object is changed or not without saving old state of the object and performing field-by-field comparison with new state of the object. But overhead of such solution (both space and CPU) is very high. In DyBASE it is responsibility of programmer to save object in the storage. It can be done by Persistent.store
or Persistent.modify
methods.
Persistent.store
method writes object in the storage as well
as all objects referenced from this object which are not yet persistent. So if you create a tree of objects and assign reference to the root of this tree to some persistent object X, it is only necessary to
invoke store()
method in this object X. But then if you update one of the elements in this tree,
you should invoke store()
individually for each such element (X.store()
will
NOT now automatically save referenced objects).
Persistent.modify
method mark object is modified but doesn't immediately write it to the storage.
All objects marked as modified will be stored to the storage during transaction commit (store
method
will be invoked for each modified object). So using modify
method is preferable if object
is updated multiple times within transaction. In this case instead of storing it several times, it will
be stored only once at the end.
When object is loaded from the storage, DyBASE will also try to recursively load all objects referenced from this object. So as a result all cluster of referenced object will be loaded, references between then will be correctly set and so programmer can access these object and traverse from one object to another without explicit checks whether object is loaded or not.
What is bad with this approach? If all objects in the storage are accessible from the rot by references (no indices are used),
then loading root object will cause loading of all objects from the database to the memory.
If the number of object is very large, it can take a significant amount of time and cause exhaustion of memory in application.
In DyBASE it is possible to stop recursive loading for particular objects.
This is done either by redefinition of recursiveLoading
method and returning false
,
either (in Python API) by setting __nonrecursive__
attribute). Objects referenced from the objects
with disables recursive loading should be explicitly loaded using Persistent.load
method.
Before object is loaded, you should not access any of its components.
Also it is important to notice that indices always load member objects on demand (i.e. Dybase does not perform automatic loading of all objects in the containers). Since access to the container members is always performed through methods of the container class, programmer will never notice it - container methods will always return loaded object(s).
Sometimes not all fields of persistent object need to be saved in the storage. Some of them are
transient. DyBASE use the following convention to distinguish persistent and transient fields:
all fields which name ends with "_" are considered to be transient and are not saved in the storage.
You can initialize such fields when object is loaded from the storage in onLoad()
method
which is invoked for any persistent object after loading it from the storage (i.e. after restoring values of all persistent
fields).
So summarizing all above, proposed mechanism is convenient and easy to use because it doesn't require programmer
to explicitly load any referenced object and from another point of view it is flexible above by providing programmer control
on object loading. Only those classes (usually containers) which explicitly control loading of their
members (by overloading recursiveLoading
to return false
value) should be aware
calling Persistent.load
method.
load
method and
overloading recursiveLoading
to stop recursion are needed.
Unfortunately it is still not possible to detect modification of the object.
Although some languages make it possible to redefine object attribute setter method,
it will not help to detect modification of self components inside instance method.
So explicit invocation of modify
or store
methods is needed.
Although delegator mechanism provides more convenient and transparent API, it has its own drawbacks:
This is why by default delegators support is switch off in all APIs.
Index
class which make it possible to associate persistent object with
key of any supported scalar or string type.
Indices are created in DyBASE using Storage.createXXXIndex
method,
where XXX
specifies type of the key. You can place in the index only keys with the same type as specified
at index creation time (so it is not possible to create index of strings and place integer key in it).
Index is implemented using B+Tree algorithm, because B+Tree is the most efficient structure
for disk based databases. Methods of Index
class allows to add, remove and
locate objects by key. It is possible to perform search either specifying exact key value either specifying range of key values
(high or low boundary or both of them can be skipped or can be declared as been exclusive or inclusive).
So it is possible to perform the following types of search:
commit, rollback
or close
methods.Commit of transaction cause synchronous write of changed pages to the disk. Synchronous write (application is blocked until data is really flushed to the disk) is very expensive operation. Average positioning time for modern disks is about 10ms, so them are usually not able to perform in one second more than 100 writes in random places. As far as transaction usually consists of several updated pages, it leads to average performance about 10 transaction commits per second.
Performance can be greatly increased if you minimize number of commits (larger transactions). DyBASE is using shadow mechanism for transaction implementation. When object is changed first time during transaction, shadow of the object is created and original object is kept unchanged. If object is updated multiple times during transaction, shadow is create only once. Because of using shadows, DyBASE does not need a transaction log file. So in DyBASE long transaction can not cause transaction log overflow as in most of other DBMSes. Quite the contrary, if you do not call commit at all, DyBASE works as DBMS without transaction support, adding almost no overhead of supporting transactions.
The only drawback of long transactions is possibility to loose a lot of changes in case of fault. DyBASE will preserve consistency of the database, but all changes made since list commit will be lost.
dybase.h
file.
API of particular programming language consists of two parts:
dybase.h
. And most of the interface is implemented in language itself.
So all such things as object caching, recursive loading, investigating object fields are implemented in
target language and not in C. The main advantages of such decision is flexibility (it is easier to implement different
strategies at this level) and convenience (using language extension API is usually not convenient an error prone).
The only disadvantage is worse performance because interpreted languages are usually not as fast as C and certainly
implementation of the whole API in C will lead to better performance because no interpretation overhead is present here).
But I think that pro in this case is more significant than its contra.Below is the description of classes and method present in API of all languages. The next section describes specific of implementation for particular language.
modify
method was invoked for the object within current transaction, False otherwise
Null
if object is not persistent.
sharedLock
and exclusiveLock
methods.
So programmer should set proper lock before accessing the object in multi-threaded application.Storage.makeObjectPersistent
method.
nowait
- optional parameter specifying whether request should
wait until lock is available or fail if lock can not be granted immediately.
true
if lock is successfully grantedfalse
if lock can not be granted within specified time
sharedLock
and exclusiveLock
methods.
So programmer should set proper lock before accessing the object in multi-threaded application.Storage.makeObjectPersistent
method.
nowait
- optional parameter specifying whether request should
wait until lock is available or fail if lock can not be granted immediately.
true
if lock is successfully grantedfalse
if lock can not be granted within specified time
key
- key with type matching with type of the index
value
- persistent capable object to be associated with this key. This object is automatically made persistent
(if it isn't persistent yet).
key
- key with type matching with type of the index
value
- persistent capable object to be associated with this key. This object is automatically made persistent (if it isn't persistent yet).
key
- key with type matching with type of the index
value
- optional reference to the persistent object removed from the index. If index is unique, this parameter can be skipped.
key
- key with type matching with type of the index
low
- low boundary for key value, if Null than there is no low boundary.
lowInclusive
- if low boundary is inclusive or not
high
- high boundary for key value, if Null than there is no high boundary.
highInclusive
- if high boundary is inclusive or not
low
- low boundary for key value, if Null than there is no low boundary.
lowInclusive
- if low boundary is inclusive or not
high
- high boundary for key value, if Null than there is no high boundary.
highInclusive
- if high boundary is inclusive or not
ascent
- iteration order: if true
, then objects will be traversed in key ascending order
pagePoolSize
- database page pool in bytes (larger page pool usually leads to better performance)
objectCacheSize
- this parameter is used only by some of languages API and specify maximal number
of objects in application object cache. Not all languages APIs maintain such cache.
path
- path to the database file
Storage.getRootObject
method and traverse objects from the root.
root
- new storage root object which is automatically made persistent.
obj
- persistent object
obj
- object to be made persistent, if it is already persistent = method as no effect.
Persistent.store()
method.
storeObject(obj)
obj
- stored object.
Persistent.modify()
method.
modifyObject(obj)
obj
- modified object.
Persistent.load()
method.
obj
- loaded object.
unique
- whether duplicated keys are allowed or not (by default not allowed)
unique
- whether duplicated keys are allowed or not (by default not allowed)
unique
- whether duplicated keys are allowed or not (by default not allowed)
unique
- whether duplicated keys are allowed or not (by default not allowed)
unique
- whether duplicated keys are allowed or not (by default not allowed)
Persistent.deallocate
method. But when you are using garbage collector,
you should be careful with keeping references to the persistent objects in local variables. If there are no references
to such object from other persistent object (so it is not reachable from root object), then garbage collector
can deallocate such object. If you then try to access or update this object using reference stored
in local variable, you will get on error.
maxAllocatedDelta
- delta between total size of allocated and deallocated object since last GC
or storage opening
sharedLock
and exclusiveLock
methods.
So programmer should set proper lock before accessing the object in multithreaded application.
If object is concurrently accessed by several threads in read-only mode, then explicit locking
of this object is not needed, because language API provides consistent retrieving of objects itself.
obj
- locked object
nowait
- optional parameter specifying whether request should
wait until lock is available or fail if lock can not be granted immediately.
true
if lock is successfully grantedfalse
if lock can not be granted within specified time
sharedLock
and exclusiveLock
methods.
So programmer should set proper lock before accessing the object in multithreaded application.
obj
- locked object
nowait
- optional parameter specifying whether request should
wait until lock is available or fail if lock can not be granted immediately.
true
if lock is successfully grantedfalse
if lock can not be granted within specified time
obj
- unlocked object
Python provides long integer type (64-bit integer) but it has not separate boolean type (boolean values are treated as integers).
In Python instance variables are not declared at all and are attached to to the object when first time assigned. So objects of the same class can have different sets of instance variables. But this is not a problems for DyBASE. But because of this feature of Python it is not required to derive persistent capable object from Persistent (although it is still more convenient to do it, because in this case you can use methods derived fro Persistent class).
Unlike other languages stop of recursive loading is done not by redefinition of recursiveLoading
method
but by the assignment __nonrecursive__
attribute to the object.
Python makes it possible to redefine getter/setter methods for attributes.
Using this facility delegators for persistent object can be implemented.
Instead of recursive loading of object cluster, it is possible to create delegators for the persistent
object and load objects on demand. Python API provides PersistentDelegator
class which catch method attribute access and load object from the database if needed.
This class also redefine comparison method to treat as equal two different delegators referencing
the same OID. The following things will not work with delegator:
isinstance
operator (it can return class of delegator instead of actual object class)
Persistent
class (Persistent
class defines special redefine stand quality comparison operator to correctly handle delegators)
To enable usage of delagators you should path True
to useDelegators
parameter of Storage
constructor (default value is False
).
Ruby provides weak references but its implementation is so inefficient, that by default it is switched off.
To enable weak references, set USE_WEAK_REFERENCES=true
in dybase.rb.
Also Ruby support delegates: classes which redirect (delegate) methods to some other class.
With delegate classes there is no need in recursiveLoading
method - objects
are loaded on demand. So it is much more convenient then common DyBase model of loading object.
But there are also some significant drawbacks of delegates:
self
from some method, if will not be equal to the object from which method was invoked:
class MyRoot<Persistent def me return self end end root = db.getRootObject itIsMe = (root == root.me) # !!! false
PersistentDelegator
.
USE_DELEGATOR = false
in dybase.rb.Ruby has normal (mark-and-sweep) garbage collector so it is not suffer from cyclic references. It support big numbers but not long (64 bit) integers.
In PHP 4.3.x reference counter field has short type. It means that PHP is not able to correctly
handle objects which are referenced from more than 65535 places. As far as each persistent
object contains reference to the storage object, this limitation means that there can not be more
than 65535 persistent object loaded from the database to the memory at each moment of time.
You have to periodically invoke Storage.resetHash
method to remove
persistent objects from the cache. Violation of this rule cause unpredictable behavior of the program
(corruption of memory and sometimes segmentation faults).
PHP doesn't support weak references at all. Also it has no long (64-bit) integers.
PHP 4.* doesn't provide any primitives for working in multithreaded environment. So locking mechanisms are not supported by PHP API.
__class__
field:
my-persistent-object: make persistent [ __class__: 'my-persistent-object ... ]
object! integer! decimal! string! block! hash!
. Values of string, block and hash types are stored as part of referencing them object.
So if one block is referenced by two objects, then in database two copies of this block will be stored.
And after reloading of these objects, there will be two independent blocks in memory.
Also DyBASE doesn't store current position in the block. So the block next [1 2 3 4 5]
will be stored as [2 3 4 5]
.
I run the same simple test implemented in C++, Java, C-Sharp, PHP, Python, Ruby and Rebol. This test contains three steps:
Time of execution of each step is measured. Number of records in each case is the same - 100000. Page pool size in all cases is 32Mb, which is enough to hold all records in memory. All test were executed at the same computer: P4-1800, 256Mb RAM, Win2k. I am using MS Visual.NET 2003 C++ compiler, Java JDK 1.4, Python 2.3.2, PHP 4.3., Ruby 1.8.0 and Rebol/View 1.2.10.3.1. I divide time of each step by number of iteration and produce the following results:
Color | Language | Database |
---|---|---|
1 | C++ | GigaBASE |
2 | Java | PERST |
3 | C-Sharp | PERST |
4 | Ruby | DyBase |
5 | Python | DyBase |
6 | PHP | DyBase |
7 | Rebol | DyBase |
1 | 147929 |
2 | 23010 |
3 | 15591 |
6 | 5000 |
5 | 4632 |
7 | 3389 |
4 | 2083 |
1 | 15676 |
2 | 8844 |
3 | 8713 |
5 | 3558 |
4 | 2777 |
6 | 2702 |
7 | 2173 |
1 | 12434 |
2 | 9819 |
3 | 5902 |
4 | 3571 |
6 | 2570 |
5 | 2272 |
7 | 1818 |
DyBASE performs cyclic scanning of bitmap pages. It keeps identifier
of current bitmap page and current position within the page. Each time
when allocation request arrives, scanning of the bitmap starts from the
current position.
When last allocated bitmap page is scanned, scanning continues from the
beginning (from the first bitmap page) and until current position.
When no free space is found after full cycle through all bitmap pages,
new bulk of memory is allocated. Size of extension is maximum of
size of allocated object and extension quantum. Extension quantum is parameter
of database, specified in constructor. Bitmap is extended to be able to map
additional space. If virtual space is exhausted and no more
bitmap pages can be allocated, then OutOfMemory
error
is reported.
Allocation memory using bitmap provides high locality of references (objects are mostly allocated sequentially) and also minimizes number of modified pages. Minimization of number of modified pages is significant when commit operation is performed and all dirty pages should be flushed on the disk. When all cloned objects are placed sequentially, number of modified pages is minimal and so transaction commit time is also reduced. Using extension quantum also helps to preserve sequential allocation. Once bitmap is extended, objects will be allocated sequentially until extension quantum will be completely used. Only after reaching the end of the bitmap, scanning restarts from the beginning searching for holes in previously allocated memory.
To reduce number of bitmap pages scans, DyBASE associates descriptor with each page, which is used to remember maximal size of the hole on the page. Calculation of maximal hole size is performed in the following way: if object of size M can not be allocated from this bitmap pages, then maximal hole size is less than M, so M is stored in the page descriptor if previous value of descriptor is large than M. For next allocation of object of size greater or equal than M, we will skip this bitmap page. Page descriptor is reset when some object is deallocated within this bitmap page.
Some database objects (like hash table pages) should be aligned on page boundary to provide more efficient access. DyBASE memory allocator checks requested size and if it is aligned on page boundary, then address of allocated memory segment is also aligned on page boundary. Search of free hole will be done faster in this case, because DyBASE increases step of current position increment according to the value of alignment.
To be able to deallocate memory used by object, DyBASE needs to keep somewhere information about object size. DyBASE memory allocator deals with two types of objects - normal table records and page objects. All table records are prepended by record header, which contains record size and pointer of L2-list linking all records in the table. So size of the table record object can be extracted from record header. Page objects always occupies the whole database page are are allocated at the positions aligned on page boundary. Page objects has no headers. DyBASE distinguish page objects with normal object by using special marker in object index.
When object is modified first time, it is cloned (copy of the object is created) and object handle in current index is changed to point to newly created object copy. And shadow index still contains handle which points to the original version of the object. All changes are done with the object copy, leaving original object unchanged. DyBASE marks in special bitmap page of the object index, which contains modified object handle.
When transaction is committed, DyBASE first checks if size of object index was increased during current transaction. If so, it also reallocates shadow copy of object index. Then DyBASE frees memory for all "old objects", i.e. objects which was cloned within transaction. Memory can not be deallocated before commit, because we wants to preserve consistent state of the database by keeping cloned object unchanged. If we deallocate memory immediately after cloning, new object can be allocated at the place of cloned object and we loose consistency. As far as memory deallocation is done in DyBASE by bitmap using the same transaction mechanism as for normal database objects, deallocation of object space will require clearing some bits in bitmap page, which also should be cloned before modification. Cloning bitmap page will require new space for allocation the page copy, and we can reuse space of deallocated objects. And it is not acceptable due to the reason explained above - we will loose database consistency. That is why deallocation of object is done in two steps. When object is cloned, all bitmap pages used for marking objects space, are also cloned (if not not cloned before). So when transaction is committed, we only clear bits in bitmap pages and no more requests for allocation memory can be generated at this moment.
After deallocation of old copies, DyBASE flushes all modified pages on disk to synchronize content of the memory and disk file. After that DyBASE changes current object index indicator in database header to switch roles of the object indices. Now object index, which was current becomes shadow, and shadow index becomes current. Then DyBASE again flushes modified page (i.e. page with database header) on disk, transferring database to new consistent state. After that DyBASE copies all modified handles from new object index to object index which was previously shadow and now becomes current. At this moment contents of both indices is synchronized and DyBASE is ready to start new transaction.
Bitmap of modified object index pages is used to minimize time of committing transaction. Not the whole object index, but only its modified pages should be copied. After committing of transaction bitmap is cleared.
When transaction is explicitly aborted by Storage.rollback
method, shadow object index is copied back to the current index, eliminating
all changes done by aborted transaction. After the end of copying,
both indices are identical again and database state corresponds to the moment
before the start of current transaction.
Allocation of object handles is done by free handles list. Header of the list is also shadowed and two instances of list headers are stored in database header. Switch between them is done in the same way as switch of object indices. When there are no more free elements in the list, DyBASE allocates handles from the unused part of new index. When there is no more space in the index, it is reallocated. Object index is the only entity in database whose is not cloned on modification. Instead of this two copies of object index are always used.
There are some predefined OID values in DyBASE. OID 0 is reserved as invalid object identifier. OID starting from 1 are reserved for bitmap pages. Number of bitmap pages depends on database maximum virtual space. For one terabyte virtual space, 8 Kb page size and 64 byte allocation quantum, 32K bitmap pages are required. So 32K handles are reserved in object index for bitmap. Bitmap pages are allocated on demand, when database size is extended. So OID of first users object will be 0x8002.
Recovery procedure is trivial in DyBASE. There are two instances of
object index, one of which is current and another corresponds to
consistent database state. When database is opened, DyBASE checks database
header to detect if database was normally closed. If not
(dirty
flag is set in database header), then DyBASE performs
database recovery. Recovery is very similar to rollback of transaction.
Indicator of current index in database object header is used to
determine index corresponding to consistent database state and object handles
from this index are copied to another object index, eliminating
all changes done by uncommitted transaction. As far as the only action
performed by recovery procedure is copying of objects index (really only
handles having different values in current and shadow indices are copied to
reduce number of modified pages) and size of object index is small,
recovery can be done very fast.
Fast recovery procedure reduces "out-of-service" time of application.
The table below summarize pro features of DyBASE:
lib/dybase.lib
lib/dybasedll.dll
python/pythonapi.dll
python/dybase.py
php/php_dybaseapi.dll
php/dybase.php
ruby/rubyapi.dll
ruby/dybase.rb
If you want to rebuild these libraries yourself, you should run make.bat
(which invokes MS nmake
for makefile.mvc
) in src
directory and compile.bat
in each language API
directory.
You make have to change first paths to language home directory in compile.bat
scripts.
At Unix you should run make
in each directory (GCC compiler and GNU make is expected).
Also paths to language installation directory may need to be adjusted in makefiles.
The easiest way to learn DyBASE API is to look at the examples. Directory of each language contains three examples:
To run these example and your own application do not forget to include dybase\lib
in PATH
.
Speed of accessing data at the disk is several times slower than speed of access data in main memory.
That is why caching of data is the key to increase database performance. DyBASE is using
pool of pages to optimize access to the disk. Page pool size can be specified in Storage.open
method (by default it is 4Mb). Usually increasing page pool leads to better performance. But you should
notice the following things:
php.ini
file).
database.h
file which has influence on
initial and maximal database size. If you want to change them, you will have to rebuild DyBASE.
Below is detailed description of this parameters:
Parameter | Default value | Description |
---|---|---|
dbDatabaseOffsetBits | 32 | Number of bits in offset within database file. This parameter limit the maximal database size.
Default value 32 restrict database to 1Gb. Increasing this parameter will also increase initial database
size. DyBASE is using bitmap to allocate space in the database. Each bitmap page has its own ID
which are reserved in objects index. When database is created, DyBASE reserve in object index space for
identifiers of ALL bitmap pages needed to map database virtual space (defined by dbDatabaseOffsetBits ).
Number of bitmap pages needed to map the whole database can be calculated as 2 ** (dbDatabaseOffsetBits-32)) .
Actually index will be two times larger, because it should contain some other elements and when index is reallocated its
size is always doubled. Each entry in object index is 8 byte long. There are two indices (active and shadow).
So to estimate initial size of the database, you should multiply value of the expression above by 32.
|
dbDefaultInitIndexSize | 10*1024 | Initial object index size. Object index is increased on demand. Reallocation of index is expensive operation and so to minimize number of such reallocations, object index size is always doubled. Specifying larger initial index size allows to reduce number of future reallocations and so a little bit increase performance (certainly if your application allocates such number of object). But it also leads to larger initial size of database file. With default value of this parameter, initial database size is about 50Kb. |
dbDefaultExtensionQuantum | 512*1024 | Database extension quantum.
Memory is allocate by scanning bitmap. If there is no large enough hole, then database is extended by the value of
dbDefaultExtensionQuantum . Increasing the value of this parameters leads to less frequent
rescanning of allocation bitmap from the very beginning. It leads to faster allocation speed and better locality of
reference for created objects (because there is more chances that them will be allocated sequentially).
From the other side it leads to less efficient memory usage. Reducing the value of this parameter force reusing
of existed holes in memory allocation bitmap.
|
Now some hints how to increase DyBASE performance and reduce size of used main memory. If you database performs a lot of updates of persistent data, then the main limiting factor is speed of writing changes to the disk. Especially synchronous write to the disk performed by commit. If you will do commit after each update, then average speed will be about 10 updates per second (this estimation is based on the assumption than average disk access time is about 10msec and each transaction commit usually requires writing about 10 pages in random places in the file). But it is possible to dramatically increase update performance if you group several updates in one transactions. DyBASE is creating shadow of the object when it is first time updated inside transaction. If object will be updated once in N transactions, then N shadows will be created. If object will be updated N times inside one transaction, then shadow will be created only once. It explains advantage of having one large transaction.
But if you will perform update of large number of objects in one transaction and for each updated object shadow is created, then it leads to significant increase of database file size. If you update each object in the database inside one transaction, database size will be almost doubled! And if you perform each update in separate transaction, then size of database will be almost unchanged (because space of allocated shadow objects will be reused in this case). So the best approach is to perform commit after 100 or 1000 updates, it will reduce overhead of each commit and save database size.
If your persistent object form tree or graph where all objects can be accessed by reference from the root object, then once you will load root object in main memory and store reference to it in some variable, GC will never be able to collect any instance of loaded persistent object (because it will be accessible from the root object). So when you application access more and more objects from the storage, at some moment of time all of them will have to be loaded in main memory. It can cause space exhaustion. To prevent this situation you should avoid to store in variables references to container objects which contain references to a large number of members. Especially it is true for storage root object. In this case GC is able to do it work and throw out from the memory objects which are not used at this moment (to which there are no references from local variable). But it is important to say that objects accessible though the index by key can be normally deallocated by garbage collector. So in this case special care is not needed.
I will provide e-mail support and help you with development of DyBASE applications.
Look for new version at my homepage | E-Mail me about bugs and problems