Implementation Plan: MsgPack Package Database Indexes
Date: 2026-01-29
Status: Planned
Target: Coral v0.2
Overview
Implement the MsgPack-based package database indexes that were planned as the key feature of Coral v0.2. Currently src/core/pkgdb.reef is entirely stubs that fall back to slow file scanning. This causes:
coral list- O(N) TOML file readscoral owner- O(N*M) full scan of all packages' file lists (CRITICAL bottleneck)coral info- O(1) but requires TOML parsing
Target Performance
| Command | Current | With MsgPack |
|---|---|---|
coral list (100 pkgs) |
~500ms | ~10ms |
coral owner /usr/bin/vim (50K files) |
~2000ms | ~1ms |
coral info vim |
~50ms | ~5ms |
File Format Specifications
packages.db (MsgPack + Base64)
Structure:
{
"version": 1,
"generated": "2026-01-29T...",
"package_count": N,
"packages": [
{ "name": "vim", "version": "9.1", "release": 1,
"description": "...", "installed_date": "...", "file_count": 423 },
...
]
}
files.db (MsgPack + Base64)
Structure:
{
"version": 1,
"generated": "2026-01-29T...",
"file_count": M,
"files": {
"usr/bin/vim": "vim",
"usr/lib/libz.so": "zlib",
...
}
}
Note: Base64 encoding required because Reef's file.readFile()/file.writeFile() work with strings.
Implementation Phases
Phase 1: Foundation (in pkgdb.reef)
Add new types:
type PackagesIndex = struct
version: int
generated: string
package_count: int
entries: [PkgIndexEntry]
loaded: bool
end PackagesIndex
type FilesIndex = struct
version: int
generated: string
file_count: int
file_paths: [string] // Sorted for binary search
owners: [string] // Parallel array - owner at same index
loaded: bool
end FilesIndex
Add helper functions:
base64_encode(buf: [int], length: int): stringbase64_decode(encoded: string, buf: [int]): intestimate_packages_buffer_size(pkg_count: int): intestimate_files_buffer_size(file_count: int): int
Phase 2: Serialization
Implement MsgPack serialization:
fn serialize_packages_db(root: string): [int]
// 1. Get list of installed packages from database module
// 2. Allocate buffer based on count estimate
// 3. Pack map header (4 keys: version, generated, package_count, packages)
// 4. Pack array of package entries
// Returns packed buffer
end serialize_packages_db
fn serialize_files_db(root: string): [int]
// 1. Get all packages, collect all files with owners
// 2. Sort file paths for binary search later
// 3. Allocate buffer
// 4. Pack map header + files map
// Returns packed buffer
end serialize_files_db
Implement rebuild_indexes_rooted():
fn rebuild_indexes_rooted(root: string): bool
// 1. Serialize packages.db, base64 encode, write file
// 2. Serialize files.db, base64 encode, write file
// 3. Return success
end rebuild_indexes_rooted
Phase 3: Deserialization
Implement loading functions:
fn load_packages_index(root: string): PackagesIndex
// 1. Read base64 string from packages.db file
// 2. Decode to binary buffer
// 3. Unpack MsgPack structure
// 4. Return populated PackagesIndex
end load_packages_index
fn load_files_index(root: string): FilesIndex
// 1. Read base64 string from files.db file
// 2. Decode to binary buffer
// 3. Unpack MsgPack map into parallel arrays
// 4. Return populated FilesIndex (already sorted from serialize)
end load_files_index
fn binary_search_file(index: FilesIndex, path: string): string
// O(log N) binary search in sorted file_paths array
// Return owner or empty string
end binary_search_file
Phase 4: Update Public API
Replace stub implementations:
fn list_packages(names: [string], versions: [string], max_count: int): int
let index = load_packages_index("")
if not index.loaded
return fallback_list_packages(names, versions, max_count)
end if
// Copy from index to arrays
end list_packages
fn find_file_owner(file_path: string): string
let index = load_files_index("")
if not index.loaded
return database.find_owner(file_path) // Fallback
end if
return binary_search_file(index, normalize_path(file_path))
end find_file_owner
fn get_indexed_package(name: string): PkgIndexEntry
let index = load_packages_index("")
if not index.loaded
return fallback_get_indexed_package(name)
end if
// Search index for package by name
end get_indexed_package
Add rooted variants:
find_file_owner_rooted(file_path: string, root: string): string
Phase 5: Command Integration
install.reef - After registering package:
// Rebuild indexes after install
pkgdb.rebuild_indexes_rooted(root_path)
remove.reef - After unregistering package:
// Rebuild indexes after remove
pkgdb.rebuild_indexes_rooted(root_path)
owner.reef - Use indexed lookup:
// Change from database.find_owner() to pkgdb.find_file_owner()
Files to Modify
| File | Changes |
|---|---|
src/core/pkgdb.reef |
Main implementation - add types, serialization, deserialization, binary search |
src/commands/install.reef |
Call rebuild_indexes_rooted() after registration |
src/commands/remove.reef |
Call rebuild_indexes_rooted() after unregistration |
src/commands/owner.reef |
Use pkgdb.find_file_owner() instead of database.find_owner() |
Key Implementation Details
Base64 Encoding (required for file I/O)
Reef's encoding.base64 module exists in reef-stdlib. Use:
import encoding.base64
let encoded = base64.base64_encode(binary_string)
let decoded = base64.base64_decode(encoded)
Issue: base64 module works with strings, but MsgPack produces [int] buffer. Need conversion helpers.
MsgPack Usage (from reef-stdlib/encoding/msgpack.reef)
import encoding.msgpack
// Packing
let buf = msgpack.msgpack_alloc_buffer(size)
let pos = 0
pos = pos + msgpack.msgpack_pack_map_header(4, buf, pos)
pos = pos + msgpack.msgpack_pack_string("version", buf, pos)
pos = pos + msgpack.msgpack_pack_int(1, buf, pos)
// ... etc
// Unpacking
let map_len = msgpack.msgpack_unpack_map_len(buf, offset)
offset = offset + msgpack.msgpack_value_size(buf, offset)
// ... iterate through map entries
Buffer to String Conversion
Need helper to convert [int] buffer to string for base64:
fn buffer_to_string(buf: [int], length: int): string
mut result = ""
mut i = 0
while i < length
result = result + chr(buf[i])
i = i + 1
end while
return result
end buffer_to_string
Verification
Test Commands
# Initialize database (should create packages.db and files.db)
sudo coral --root /tmp/coral-chroot db init
ls -la /tmp/coral-chroot/var/lib/coral/db/
# Verify files exist
file /tmp/coral-chroot/var/lib/coral/db/packages.db
file /tmp/coral-chroot/var/lib/coral/db/files.db
# Build and install a test package
coral build core/zlib
sudo coral --root /tmp/coral-chroot install zlib
# Verify index updated
sudo coral --root /tmp/coral-chroot db status
# Test fast list
time sudo coral --root /tmp/coral-chroot list
# Test fast owner lookup
time sudo coral --root /tmp/coral-chroot owner /usr/lib/libz.so
# Test rebuild from scratch
sudo rm /tmp/coral-chroot/var/lib/coral/db/*.db
sudo coral --root /tmp/coral-chroot db rebuild
Performance Validation
coral listwith 100 packages should complete in <50mscoral ownershould complete in <10ms regardless of file count- Index files should be created after install/remove
Design Decisions
-
Full Rebuild on Changes: Each install/remove triggers a complete index rebuild. Simpler, always consistent, acceptable performance for typical package counts (<500 packages).
-
Trust the Index: Commands trust the index is correct. If indexes get out of sync (e.g., manual edits to installed/ directory), user runs
coral db rebuildto fix. -
No Caching: Reload index from disk on each command invocation. Keeps implementation simple and avoids stale memory issues.