Cocoli DB - File Format ======================= :: cocoli = { db-header , db-state , [record] } db-header = { /* 8 octets */ db-magic , db-version } db-magic = "cocoli" db-version = 0 :: Word16 /* 21 octets */ db-state = clean-state | writing-state | compacting-state clean-state = { 0 :: Word8 , dummy 20 octets } writing-state = { 1 :: Word8 , last-file-size-when-the-db-was-clean :: Word64 , dummy 12 octets } compacting-state = { /* 21 octets */ 2 :: Word8 , move-from :: Word64 , move-to :: Word64 , amount :: Word32 } record = hole | pair | necrology hole = { /* at a minimum of 9 octets */ 0 :: Word8 , hole-size :: Word32 , content :: [Word8] , hole-size :: Word32 } pair = { 1 :: Word8 , pair-size :: Word32 , key-size :: Word32 , value-size :: Word32 , key :: [Word8] , value :: [Word8] , padding :: [Word8] , pair-size :: Word32 } necrology = { 2 :: Word8 , necrology-size :: Word32 , key :: [Word8] , necrology-size :: Word32 } ---------- Operations ---------- Creating DB ~~~~~~~~~~~ 1. Create an empty file. 2. Write a DB header and clean-state. Opening DB ~~~~~~~~~~ 1. Open the DB file. 2. See if the DB header is correct. 3. See if the file is at least 29 octets long. 4. Read the db-state and redo any interrupted operations. Inserting a pair ~~~~~~~~~~~~~~~~ 1. Write the current file size to the writing-state. 2. Write "1" to the beginning of db-state. 3. Append a new pair to the end of file. 4. Write "0" to the beginning of db-state. Removing a pair ~~~~~~~~~~~~~~~ 1. Write the current file size to the writing-state. 2. Write "1" to the beginning of db-state. 3. Append a new necrology to the end of file. 4. Write "0" to the beginning of db-state. Finding a pair ~~~~~~~~~~~~~~ 1. Seek to the end of file. 2. Find the first pair with the given key. Compaction ~~~~~~~~~~ 1. Find the first contiguous holes from the beginning of file, turning any inactive pairs and necrologies into holes at the same time. 2. Find the first active pair from the beginning of file. 3. If the contiguous holes is bigger than the active pair, move the pair to the beginning of holes. 4. If the contiguous holes is smaller than the active pair, move the pair to the end of file, then move it again to the beginning of holes. Moving a pair ~~~~~~~~~~~~~ 1. Write move-from, move-to, and amount to the compacting-state. 2. Write "2" to the db-state. 3. Copy the pair to the new location, eliminating padding octets if any. If the destination hole is at least 9 octets larger than the pair, turn the surplus into a new hole. If the surplus isn't large enough to be a hole, pad the pair to fill the surplus. 4. Turn the original pair into a hole by writing "0" to the beginning of pair. 5. Write "0" to the db-state. 6. If the original pair was at the end of file, truncate the file to remove the hole. Recovering from a writing-state ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1. Truncate the file to the size information in the writing-state. 2. Write "0" to the db-state. Recovering from a compacting-state ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1. Copy the pair to the new location as the same way as the move operation. Be aware that the original pair might already be turned into a hole. 2. Turn the original pair into a hole. 3. Write "0" to the db-state. 4. If the original pair was at the end of file, truncate the file to remove the hole. ----------- Concurrency ----------- Any operations involving db-state changes can not be performed simultaneously. It's possible to find a pair while inserting or removing another pair. But it's impossible to do the compaction in that situation, because the compaction process needs to tell active pairs from inactive ones. Finding a pair while doing the compaction is impossible, because the compaction process may move an active pair back and forth.