Summary
LadybugDB 0.16.1 crashes at runtime on kernels built with CONFIG_PAGE_SIZE_16KB=y (e.g., the official Raspberry Pi OS kernel for RPi 4/5). The buffer manager calls madvise(addr, 4096, MADV_DONTNEED) with 4 KB alignment, but on 16 KB page kernels madvise returns EINVAL for any address or length not aligned to the kernel page size (16 KB). This causes a fatal RuntimeError after approximately 10,000–15,000 write operations.
System Information
OS: Raspberry Pi OS (Debian bookworm), aarch64
Kernel: 6.18.29+rpt-rpi-2712 (official RPi kernel, CONFIG_PAGE_SIZE_16KB=y)
Python: 3.13
Package: ladybug==0.16.1
ladybug-0.16.1-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl
Key kernel configuration:
CONFIG_PAGE_SIZE_16KB=y # confirmed via /boot/config-$(uname -r)
$ getconf PAGE_SIZE
16384
Minimal Reproduction
import ladybug as lb
db = lb.Database("/tmp/test.lbug", buffer_pool_size=256*1024*1024)
conn = lb.Connection(db)
conn.execute("CREATE NODE TABLE T(id STRING, PRIMARY KEY(id))")
# Fails at approximately i=12,000–15,000 (varies with node data size)
for i in range(20000):
conn.execute(f"MERGE (:T {{id: 'node_{i}_{'x'*100}'}})")
Error:
RuntimeError: Buffer manager exception:
Releasing physical memory associated with a frame failed with error code -1: Invalid argument.
Root Cause
The buffer manager allocates its pool as:
mmap(NULL, 1073741824, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_NORESERVE, -1, 0)
When evicting a 4 KB frame, it calls:
madvise(frame_addr, 4096, MADV_DONTNEED) → EINVAL on 16KB-page kernels
On Linux with CONFIG_PAGE_SIZE_16KB=y, the kernel page size is 16384 bytes. The madvise(2) syscall requires both addr and length to be aligned to the kernel page size. With 4 KB database frames, only every 4th frame happens to be 16 KB-aligned, so the vast majority of eviction calls fail.
Python-level verification (run on affected system):
import ctypes, mmap
libc = ctypes.CDLL("libc.so.6", use_errno=True)
# 4KB-misaligned: fails on 16KB-page kernel
m = mmap.mmap(-1, 4096)
buf = (ctypes.c_char * 4096).from_buffer(m)
addr = ctypes.addressof(buf)
ctypes.set_errno(0)
ret = libc.madvise(ctypes.c_void_p(addr), ctypes.c_size_t(4096), ctypes.c_int(4)) # MADV_DONTNEED=4
print(ret, ctypes.get_errno()) # → -1 22 (EINVAL)
# 16KB-aligned: succeeds
m2 = mmap.mmap(-1, 16384)
buf2 = (ctypes.c_char * 16384).from_buffer(m2)
addr2 = ctypes.addressof(buf2)
ctypes.set_errno(0)
ret2 = libc.madvise(ctypes.c_void_p(addr2), ctypes.c_size_t(16384), ctypes.c_int(4))
print(ret2, ctypes.get_errno()) # → 0 0 (Success)
Pattern of Failure
strace -e madvise shows LadybugDB calling madvise with 4096-byte frames:
madvise(0x7ffedbc51000, 4096, MADV_DONTNEED) = -1 EINVAL (Invalid argument)
The failure occurs consistently after frame ~1025, which is the point at which the buffer pool fills and the first eviction is attempted.
Workaround Attempted
An LD_PRELOAD shim converting 16 KB-misaligned MADV_DONTNEED/MADV_FREE calls to no-ops suppresses the initial exception, but LadybugDB 0.16.1 then hits a secondary error:
RuntimeError: Buffer manager exception: Unable to allocate memory! The buffer pool is full and no memory could be freed!
This means the eviction path in 0.16.1 requires that madvise actually succeed and release physical memory. The no-op approach does not work for the current version.
Suggested Fix
In the buffer manager's frame-release code, query the actual kernel page size at startup and round the madvise call up to the kernel page granule:
// Before:
madvise(frame_addr, KUZU_PAGE_SIZE, MADV_DONTNEED);
// After:
static const size_t kernel_page = sysconf(_SC_PAGE_SIZE);
if (KUZU_PAGE_SIZE >= kernel_page) {
// frame size >= page size: call is well-aligned by construction
madvise(frame_addr, KUZU_PAGE_SIZE, MADV_DONTNEED);
} else {
// frame size < page size: round addr down, length up
uintptr_t aligned_addr = (uintptr_t)frame_addr & ~(kernel_page - 1);
size_t aligned_len = kernel_page; // covers at least one frame
madvise((void*)aligned_addr, aligned_len, MADV_DONTNEED);
}
Alternatively, if KUZU_PAGE_SIZE < sysconf(_SC_PAGE_SIZE), skip the madvise call entirely and accept that physical memory is not immediately returned (lower memory pressure matters less than correctness). A startup warning could inform users of the configuration mismatch.
Impact
All LadybugDB users on:
- Raspberry Pi 4/5 running the official Raspberry Pi OS kernel
- Any Linux system with
CONFIG_PAGE_SIZE_16KB=y or CONFIG_PAGE_SIZE_64KB=y
Raspberry Pi 5 is increasingly popular for AI/ML edge deployments. The RPi5 official kernel uses 16 KB pages for performance reasons, following Apple Silicon and other modern aarch64 SoCs. This bug makes LadybugDB entirely unusable on that platform for any workload that exceeds the buffer pool capacity.
Summary
LadybugDB 0.16.1 crashes at runtime on kernels built with
CONFIG_PAGE_SIZE_16KB=y(e.g., the official Raspberry Pi OS kernel for RPi 4/5). The buffer manager callsmadvise(addr, 4096, MADV_DONTNEED)with 4 KB alignment, but on 16 KB page kernelsmadvisereturnsEINVALfor any address or length not aligned to the kernel page size (16 KB). This causes a fatalRuntimeErrorafter approximately 10,000–15,000 write operations.System Information
Key kernel configuration:
Minimal Reproduction
Error:
Root Cause
The buffer manager allocates its pool as:
When evicting a 4 KB frame, it calls:
On Linux with
CONFIG_PAGE_SIZE_16KB=y, the kernel page size is 16384 bytes. Themadvise(2)syscall requires bothaddrandlengthto be aligned to the kernel page size. With 4 KB database frames, only every 4th frame happens to be 16 KB-aligned, so the vast majority of eviction calls fail.Python-level verification (run on affected system):
Pattern of Failure
strace -e madviseshows LadybugDB callingmadvisewith 4096-byte frames:The failure occurs consistently after frame ~1025, which is the point at which the buffer pool fills and the first eviction is attempted.
Workaround Attempted
An
LD_PRELOADshim converting 16 KB-misalignedMADV_DONTNEED/MADV_FREEcalls to no-ops suppresses the initial exception, but LadybugDB 0.16.1 then hits a secondary error:This means the eviction path in 0.16.1 requires that
madviseactually succeed and release physical memory. The no-op approach does not work for the current version.Suggested Fix
In the buffer manager's frame-release code, query the actual kernel page size at startup and round the
madvisecall up to the kernel page granule:Alternatively, if
KUZU_PAGE_SIZE < sysconf(_SC_PAGE_SIZE), skip themadvisecall entirely and accept that physical memory is not immediately returned (lower memory pressure matters less than correctness). A startup warning could inform users of the configuration mismatch.Impact
All LadybugDB users on:
CONFIG_PAGE_SIZE_16KB=yorCONFIG_PAGE_SIZE_64KB=yRaspberry Pi 5 is increasingly popular for AI/ML edge deployments. The RPi5 official kernel uses 16 KB pages for performance reasons, following Apple Silicon and other modern aarch64 SoCs. This bug makes LadybugDB entirely unusable on that platform for any workload that exceeds the buffer pool capacity.