Tech Tip: The Building Blocks of 4D.
PRODUCT: 4D | VERSION: 13.1 | PLATFORM: Mac & Win
Published On: September 28, 2012
The GET MEMORY STATISTICS (GMS) command can be used to measure different categories of memory allocation in the 4D database cache. Selector 2 returns the following categories (among others):
- Used Mem
- Free Mem
- Nb Big Blocks
- Nb Used Big Blocks
- Nb Free Big Blocks
- Nb Pages containing small blocks
- Nb Small Blocks
- Nb Used Small Blocks
- Nb Free Small Blocks
This Tech Tip covers the meaning and usage of "Big Blocks" and "Small Blocks".
Blocks are generically a unit of storage within the 4D cache. They are akin to normal memory allocation units but these are managed by 4D, not the OS.
A Big Block starts at 1 MB. Everything under 1 MB is a Small Block. But this is not, in fact, the important disctinction. The important point is that Big Blocks contain Small Blocks. Said another way, Small Blocks are always allocated within a Big Block. Most importantly, Big Blocks are not used for cache object storage, they are used only to house Small Blocks; Small Blocks house the actual cache objects.
Since 4D 11.8/12.2 the 4D cache is divided into 4 equally-sized parts that might be referred to as "Super Big Blocks". When 4D needs to make space in the cache it purges one of these 4 units, so a purge is always at least 25% of the cache. Single unit purges occur in a round robin fashion so the same unit is not excessively purged. If purging 25% of the cache is not enough, 4D purges additional units (50%, 75%, 100%). Under normal circustances with an adequately sized cache, the used cache value should fluctuate between 75% and 100%, indicating that 4D only needs to purge 25% at a time.
Inside these Super Big Blocks are Big Blocks, and inside those are Small Blocks. Small Blocks are the smallest "allocation unit" in 4D's cache.
Note: as far as the OS is cocerned the cache is completely occupied, there is no free space. You cannot use OS tools to monitor 4D's cache usage, you must use GMS.
While Big Blocks may be allocated 1 at a time, Small Blocks are allocated in "groups" (think of it as an array) and each Small Block in a given group is the same size.
If 4D needs a Small Block and there are none free, it uses free space in a Big Block to create a new array of Small Blocks (all of the same size). If there is no empty space in any of the Big Blocks, another Big Block is created. If no free space exists in the cache for a new Big Block, 4D performs a purge. If there's not enough free space for a new Big Block even after a purge, the cache is too small.
4D maintains a kind of array of blocks organized by size. This list contains available and used blocks of each size. This is similar to the information returned by GMS (though GMS is less granular).
Note that cache objects need not fill an entire Small Block but, like any other allocation unit, a Small Block is considered occupied whether or not it is full. For example a very small object, say 16 bytes, sitting in a Small Block that is 32 KB occupies that entire block. The remainder of the 32 KB is not accessible (this is fragmentation; it is a natural part of any software).
Why is this important?
- In order to create new objects in the cache, 4D needs free Small Blocks, or it needs to be able to create a group of new ones.
- In order to create new Small Blocks, 4D needs Big Blocks with free space, or room in the cache to create new Big Blocks.
- In order to create new Big Blocks, the cache needs to be adequately large or be able to be purged.
- Purging can be prevented by "locked" objects as described in this Tech Note.
Imagine a Big Block that contains only one remaining group of Small Blocks, which contains only one used Small Block (say 16 bytes). As long as this small object is locked by 4D, the Big Block cannot be purged, so we have a memory fragmentation. In other words as long as there is a single "locked" object within a Big Block, the entire Big Block is locked. Objects are never moved from one Block in another, even if they are the only remaining object.
So how do you avoid this? Well, 4D has already done a lot of work to avoid fragmentation (the round-robin purging of the cache is one aspect of this). Perhaps the simplest answer is use 4D Server 64-bit and avoid the problem altogether by allocating sufficient cache such that the problem never occurs.
How do you analyze this type of problem? GMS is the key. Using the data from GMS and logging it over time can produce a graph like this:
This graph tracks the used cache of a database over ~6 days of uptime. For the first 2-3 days everything looks normal, when the server needs more space in the cache it purges one of the 25% units. Afterwards the memory is so fragmented that it starts to purge 50%, then 75%, and then 100% every time.
This problem can occur quite easily and IS NOT a bug. For example imagine a system like this:
- 200 users
- 6 processes each (1,200 total on the server)
- 250 tables
This means at a given moment there could be 300,000 "current records" (per-process, per-table) each of which are non-purgable. Incidentally this particular problem should be resolved by using REDUCE SELECTION or UNLOAD RECORD to make the objects purgable when not in use.
GMS is indeed a powerful tool and helps really understand how 4D's cache works.