Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
85f89dc
fix
iaojnh May 20, 2026
9e58670
Merge branch 'main' into fix/exclude-arrow
iaojnh May 20, 2026
46bf9aa
Merge branch 'alibaba:main' into main
iaojnh May 21, 2026
d6547aa
Merge branch 'alibaba:main' into main
iaojnh May 25, 2026
c9a4d94
Merge branch 'alibaba:main' into main
iaojnh May 26, 2026
5bff8b6
Merge branch 'alibaba:main' into main
iaojnh May 29, 2026
10f1dee
Merge branch 'alibaba:main' into main
iaojnh Jun 1, 2026
ad6ac3a
Merge branch 'alibaba:main' into main
iaojnh Jun 2, 2026
5c045b0
Merge branch 'alibaba:main' into main
iaojnh Jun 3, 2026
570da53
Merge branch 'alibaba:main' into main
iaojnh Jun 8, 2026
d3fecea
Merge branch 'alibaba:main' into main
iaojnh Jun 9, 2026
4f07741
Merge branch 'alibaba:main' into main
iaojnh Jun 10, 2026
9e900c5
Merge branch 'alibaba:main' into main
iaojnh Jun 11, 2026
4761640
Merge branch 'alibaba:main' into main
iaojnh Jun 15, 2026
a842e22
Merge branch 'alibaba:main' into main
iaojnh Jun 15, 2026
8bb7e53
Merge branch 'alibaba:main' into main
iaojnh Jun 17, 2026
d9af524
Merge branch 'alibaba:main' into main
iaojnh Jun 22, 2026
973d233
Merge branch 'alibaba:main' into main
iaojnh Jun 24, 2026
43cb151
Merge branch 'alibaba:main' into main
iaojnh Jun 25, 2026
4685c96
Merge branch 'alibaba:main' into main
iaojnh Jun 30, 2026
a07dd9d
Merge branch 'alibaba:main' into main
iaojnh Jul 10, 2026
229dc57
Merge branch 'alibaba:main' into main
iaojnh Jul 13, 2026
9ac71b2
Merge branch 'alibaba:main' into main
iaojnh Jul 15, 2026
23a4354
Merge branch 'alibaba:main' into main
iaojnh Jul 17, 2026
a1ad5fc
Merge branch 'alibaba:main' into main
iaojnh Jul 20, 2026
ef12c90
Merge branch 'alibaba:main' into main
iaojnh Jul 23, 2026
95d9225
Merge branch 'alibaba:main' into main
iaojnh Jul 27, 2026
76e41c4
Merge branch 'alibaba:main' into main
iaojnh Jul 28, 2026
46777d4
Merge branch 'alibaba:main' into main
iaojnh Jul 29, 2026
492bd1d
Merge branch 'alibaba:main' into main
iaojnh Aug 3, 2026
25e0cb9
Merge branch 'alibaba:main' into main
iaojnh Aug 5, 2026
d2194e5
fix(db): preserve shared cache budget on column changes
iaojnh Aug 5, 2026
dc339b6
feat(storage): harden buffer pool and add read backend
iaojnh Aug 5, 2026
6d8f817
clang format
iaojnh Aug 5, 2026
2562ce2
refactor(io): remove duplicate libaio headers
iaojnh Aug 5, 2026
76098a0
fix(buffer): harden storage open and aio fallback
iaojnh Aug 5, 2026
71823b4
clang format
iaojnh Aug 5, 2026
6bb3627
fix(buffer): harden memory accounting and eviction safety
iaojnh Aug 5, 2026
9e5e209
fix(buffer): harden storage lifecycle and cache ownership
iaojnh Aug 6, 2026
4f63b5c
fix(buffer): stabilize diskann storage and aio lifecycle
iaojnh Aug 7, 2026
ee775bb
style(buffer): apply clang-format
iaojnh Aug 7, 2026
54be4e8
test(db): run collection test serially
iaojnh Aug 7, 2026
e8ff055
fix(buffer): reuse cached pages for legacy reads
iaojnh Aug 7, 2026
7ce6e7d
perf(hnsw): batch buffer storage reads under eviction
iaojnh Aug 7, 2026
efd4f61
Merge branch 'main' into codex/buffer-pool-core
iaojnh Aug 10, 2026
d246ebc
perf(buffer): add reclaimable aligned slabs
iaojnh Aug 10, 2026
5dfb1e6
style: apply clang-format
iaojnh Aug 10, 2026
f0fd2b7
feat(buffer): add io_uring batch reads
iaojnh Aug 10, 2026
12f3f60
perf(hnsw): protect buffer pool search hotspots
iaojnh Aug 10, 2026
8200583
feat(buffer): protect reused pages under memory pressure
iaojnh Aug 10, 2026
3a38124
perf(buffer): coalesce page loads and retain hot history
iaojnh Aug 10, 2026
30e86ad
perf(buffer): reduce fixed page table metadata
iaojnh Aug 10, 2026
afee5c0
perf(buffer): add pressure-aware admission control
iaojnh Aug 10, 2026
e41669e
perf(buffer): optimize DiskANN bypass reads
iaojnh Aug 10, 2026
7428479
perf(buffer): reduce hot page policy overhead
iaojnh Aug 10, 2026
8203a0f
perf(buffer): skip redundant hit path work
iaojnh Aug 10, 2026
8ab5b6f
refactor(buffer): simplify storage and cache internals
iaojnh Aug 11, 2026
17f3482
perf(buffer): colocate hot page metadata
iaojnh Aug 11, 2026
c9a130e
perf(buffer): reuse scratch arena for cross-page batch reads
iaojnh Aug 11, 2026
d63f609
fix(buffer): bypass macOS file cache
iaojnh Aug 11, 2026
6ad528d
feat: support memory-bounded HNSW writes
iaojnh Aug 12, 2026
84463a5
perf(buffer): optimize bounded HNSW writes
iaojnh Aug 12, 2026
cf5be95
fix(buffer): bound low-memory HNSW cache usage
iaojnh Aug 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
992 changes: 934 additions & 58 deletions src/ailego/buffer/block_eviction_queue.cc

Large diffs are not rendered by default.

2,787 changes: 2,548 additions & 239 deletions src/ailego/buffer/vector_page_table.cc

Large diffs are not rendered by default.

16 changes: 16 additions & 0 deletions src/ailego/io/iouring_def.h
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,22 @@ static inline void io_uring_prep_read(struct io_uring_sqe *sqe, int fd,
sqe->buf.personality = 0;
}

static inline void io_uring_prep_write(struct io_uring_sqe *sqe, int fd,
const void *buf, uint32_t nbytes,
uint64_t offset) {
sqe->opcode = IORING_OP_WRITE;
sqe->flags = 0;
sqe->ioprio = 0;
sqe->fd = fd;
sqe->off = offset;
sqe->addr = reinterpret_cast<uint64_t>(buf);
sqe->len = nbytes;
sqe->rw_flags = 0;
sqe->user_data = 0;
sqe->buf.buf_index = 0;
sqe->buf.personality = 0;
}

// ---------------------------------------------------------------------------
// End: struct and constant definitions from <linux/io_uring.h>
// ---------------------------------------------------------------------------
Expand Down
246 changes: 225 additions & 21 deletions src/ailego/io/iouring_loader.cc
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,22 @@

#include <sys/syscall.h> // syscall(), __NR_io_uring_setup
#include <unistd.h> // close()
#include <algorithm>
#include <array>
#include <cerrno>
#include <chrono>
#include <cstring>
#include <limits>
#include <thread>
#include <ailego/io/iouring_loader.h>
#include <zvec/ailego/logger/logger.h>

namespace zvec {
namespace core {
namespace ailego {

// Retry budget for draining in-flight requests when the kernel keeps
// returning EAGAIN/EBUSY (100 us sleep per retry, about one second total).
static constexpr size_t kIoUringDrainRetries = 10000;

bool IoUringRing::setup(uint32_t entries) {
struct io_uring_params params;
Expand Down Expand Up @@ -58,10 +67,10 @@ bool IoUringRing::setup(uint32_t entries) {
// --- mmap the three shared regions ---

// 1. SQ ring (includes head, tail, mask, entries, flags, dropped, array).
size_t sq_ring_sz =
sq_ring_size_ =
static_cast<size_t>(params.sq_off.array) + sq_entries_ * sizeof(uint32_t);
sq_ring_ptr_ = ::mmap(nullptr, sq_ring_sz, PROT_READ | PROT_WRITE, MAP_SHARED,
ring_fd_, IORING_OFF_SQ_RING);
sq_ring_ptr_ = ::mmap(nullptr, sq_ring_size_, PROT_READ | PROT_WRITE,
MAP_SHARED, ring_fd_, IORING_OFF_SQ_RING);
if (sq_ring_ptr_ == MAP_FAILED) {
LOG_ERROR("mmap SQ ring failed: %s", ::strerror(errno));
sq_ring_ptr_ = nullptr;
Expand All @@ -70,9 +79,9 @@ bool IoUringRing::setup(uint32_t entries) {
}

// 2. SQE array.
size_t sqes_sz = sq_entries_ * sizeof(struct io_uring_sqe);
sqes_size_ = sq_entries_ * sizeof(struct io_uring_sqe);
sqes_ptr_ = reinterpret_cast<struct io_uring_sqe *>(
::mmap(nullptr, sqes_sz, PROT_READ | PROT_WRITE, MAP_SHARED, ring_fd_,
::mmap(nullptr, sqes_size_, PROT_READ | PROT_WRITE, MAP_SHARED, ring_fd_,
IORING_OFF_SQES));
if (sqes_ptr_ == MAP_FAILED) {
LOG_ERROR("mmap SQEs failed: %s", ::strerror(errno));
Expand All @@ -82,10 +91,10 @@ bool IoUringRing::setup(uint32_t entries) {
}

// 3. CQ ring (includes head, tail, mask, entries, overflow, cqes[]).
size_t cq_ring_sz = static_cast<size_t>(params.cq_off.cqes) +
cq_entries_ * sizeof(struct io_uring_cqe);
cq_ring_ptr_ = ::mmap(nullptr, cq_ring_sz, PROT_READ | PROT_WRITE, MAP_SHARED,
ring_fd_, IORING_OFF_CQ_RING);
cq_ring_size_ = static_cast<size_t>(params.cq_off.cqes) +
cq_entries_ * sizeof(struct io_uring_cqe);
cq_ring_ptr_ = ::mmap(nullptr, cq_ring_size_, PROT_READ | PROT_WRITE,
MAP_SHARED, ring_fd_, IORING_OFF_CQ_RING);
if (cq_ring_ptr_ == MAP_FAILED) {
LOG_ERROR("mmap CQ ring failed: %s", ::strerror(errno));
cq_ring_ptr_ = nullptr;
Expand Down Expand Up @@ -139,25 +148,21 @@ bool IoUringRing::setup(uint32_t entries) {

void IoUringRing::teardown() {
if (sq_ring_ptr_ && sq_ring_ptr_ != MAP_FAILED) {
// We don't track the exact mmap size; munmap with a large enough size
// is safe because the kernel only unmaps what was actually mapped.
// However, to be correct we use the page-aligned size.
size_t sz = static_cast<size_t>(sq_entries_) * sizeof(uint32_t) + 4096;
::munmap(sq_ring_ptr_, sz);
::munmap(sq_ring_ptr_, sq_ring_size_);
}
if (sqes_ptr_ && sqes_ptr_ != MAP_FAILED) {
size_t sz = static_cast<size_t>(sq_entries_) * sizeof(struct io_uring_sqe);
::munmap(sqes_ptr_, sz);
::munmap(sqes_ptr_, sqes_size_);
}
if (cq_ring_ptr_ && cq_ring_ptr_ != MAP_FAILED) {
size_t sz =
static_cast<size_t>(cq_entries_) * sizeof(struct io_uring_cqe) + 4096;
::munmap(cq_ring_ptr_, sz);
::munmap(cq_ring_ptr_, cq_ring_size_);
}

sq_ring_ptr_ = nullptr;
sqes_ptr_ = nullptr;
cq_ring_ptr_ = nullptr;
sq_ring_size_ = 0;
sqes_size_ = 0;
cq_ring_size_ = 0;
sqes_ = nullptr;
cqes_ = nullptr;
sq_head_ = sq_tail_ = sq_ring_mask_ = sq_ring_entries_ = nullptr;
Expand Down Expand Up @@ -195,7 +200,206 @@ bool IoUringRing::ensure_staging(size_t bytes) {
return true;
}

} // namespace core
int IoUringRing::execute(int fd, const IoUringRead *read_reqs, size_t count) {
return execute_impl(fd, read_reqs, nullptr, count);
}

int IoUringRing::execute_writes(int fd, const IoUringWrite *write_reqs,
size_t count) {
return execute_impl(fd, nullptr, write_reqs, count);
}

int IoUringRing::execute_impl(int fd, const IoUringRead *read_reqs,
const IoUringWrite *write_reqs, size_t count) {
const bool is_write = write_reqs != nullptr;
if (!is_valid() ||
(count != 0 && ((read_reqs == nullptr) == (write_reqs == nullptr)))) {
return -1;
}
if (count == 0) {
return 0;
}

const size_t batch_size = std::min<size_t>(sq_entries_, kIoUringMaxBatch);
if (batch_size == 0) {
return -1;
}

for (size_t batch_start = 0; batch_start < count; batch_start += batch_size) {
const size_t n_ops = std::min(batch_size, count - batch_start);
std::array<size_t, kIoUringMaxBatch> slot_offsets{};
size_t staging_bytes = 0;
for (size_t j = 0; j < n_ops; ++j) {
const size_t req_idx = batch_start + j;
const uint64_t offset =
is_write ? write_reqs[req_idx].offset : read_reqs[req_idx].offset;
const uint64_t len =
is_write ? write_reqs[req_idx].len : read_reqs[req_idx].len;
const uint64_t expected_len =
is_write ? len
: (read_reqs[req_idx].expected_len == 0
? len
: read_reqs[req_idx].expected_len);
const void *buf =
is_write ? write_reqs[req_idx].buf : read_reqs[req_idx].buf;
if (buf == nullptr || len == 0 || expected_len > len ||
len > std::numeric_limits<uint32_t>::max() || offset % 512 != 0 ||
len % 512 != 0 ||
(!is_write && reinterpret_cast<uintptr_t>(buf) % 512 != 0)) {
return -1;
}
const size_t aligned_len =
(static_cast<size_t>(len) + kIoUringStagingAlign - 1) &
~(kIoUringStagingAlign - 1);
if (aligned_len < len ||
staging_bytes > std::numeric_limits<size_t>::max() - aligned_len) {
return -1;
}
slot_offsets[j] = staging_bytes;
staging_bytes += aligned_len;
}
if (!ensure_staging(staging_bytes)) {
return -1;
}

const unsigned tail = __atomic_load_n(sq_tail_, __ATOMIC_ACQUIRE);
const unsigned mask = *sq_ring_mask_;
for (size_t j = 0; j < n_ops; ++j) {
const unsigned idx = (tail + static_cast<unsigned>(j)) & mask;
const unsigned sqe_idx = sq_array_[idx];
struct io_uring_sqe *sqe = &sqes_[sqe_idx];
const size_t req_idx = batch_start + j;
if (is_write) {
const IoUringWrite &req = write_reqs[req_idx];
std::memcpy(staging_ + slot_offsets[j], req.buf, req.len);
io_uring_prep_write(sqe, fd, staging_ + slot_offsets[j],
static_cast<uint32_t>(req.len), req.offset);
} else {
const IoUringRead &req = read_reqs[req_idx];
io_uring_prep_read(sqe, fd, staging_ + slot_offsets[j],
static_cast<uint32_t>(req.len), req.offset);
}
sqe->user_data = req_idx;
}

__sync_synchronize();
__atomic_store_n(sq_tail_, tail + static_cast<unsigned>(n_ops),
__ATOMIC_RELEASE);

size_t submitted = 0;
size_t completed = 0;
bool all_ok = true;
auto reap_available = [&]() {
unsigned chead = *cq_head_;
const unsigned ctail = __atomic_load_n(cq_tail_, __ATOMIC_ACQUIRE);
const unsigned cq_mask = *cq_ring_mask_;
while (chead != ctail) {
struct io_uring_cqe *cqe = &cqes_[chead & cq_mask];
const size_t req_idx = static_cast<size_t>(cqe->user_data);
if (req_idx < batch_start || req_idx >= batch_start + n_ops) {
LOG_WARN("io_uring completion referenced unknown request: %zu",
req_idx);
all_ok = false;
} else {
const uint64_t offset =
is_write ? write_reqs[req_idx].offset : read_reqs[req_idx].offset;
const uint64_t len =
is_write ? write_reqs[req_idx].len : read_reqs[req_idx].len;
const uint64_t expected_len =
is_write ? len
: (read_reqs[req_idx].expected_len == 0
? len
: read_reqs[req_idx].expected_len);
const char *operation = is_write ? "write" : "read";
if (cqe->res < 0) {
LOG_WARN("io_uring %s failed: req=%zu, res=%d, offset=%lu",
operation, req_idx, cqe->res,
static_cast<unsigned long>(offset));
all_ok = false;
} else if (static_cast<uint64_t>(cqe->res) != expected_len) {
LOG_WARN("io_uring short %s: req=%zu, got=%d, expected=%lu",
operation, req_idx, cqe->res,
static_cast<unsigned long>(expected_len));
all_ok = false;
} else if (!is_write) {
const IoUringRead &req = read_reqs[req_idx];
const size_t slot = req_idx - batch_start;
std::memcpy(req.buf, staging_ + slot_offsets[slot], expected_len);
if (expected_len < len) {
std::memset(static_cast<char *>(req.buf) + expected_len, 0,
len - expected_len);
}
}
}
++chead;
++completed;
}
__atomic_store_n(cq_head_, chead, __ATOMIC_RELEASE);
};

while (completed < n_ops) {
reap_available();
if (completed >= n_ops) {
break;
}

const unsigned to_submit = static_cast<unsigned>(n_ops - submitted);
const int ret = static_cast<int>(syscall(
__NR_io_uring_enter, ring_fd_, to_submit, 1u, IORING_ENTER_GETEVENTS,
static_cast<void *>(nullptr), static_cast<size_t>(0)));
if (ret >= 0) {
submitted += static_cast<size_t>(ret);
continue;
}
if (errno == EINTR ||
((errno == EAGAIN || errno == EBUSY) && completed < submitted)) {
continue;
}

LOG_WARN(
"io_uring_enter failed; errno=%d, %s, submitted=%zu/%zu, "
"completed=%zu. draining before falling back to p%s",
errno, ::strerror(errno), submitted, n_ops, completed,
is_write ? "write" : "read");
__atomic_store_n(sq_tail_, tail + static_cast<unsigned>(submitted),
__ATOMIC_RELEASE);

size_t drain_retries = 0;
while (completed < submitted) {
reap_available();
if (completed >= submitted) {
break;
}
const int wait_ret = static_cast<int>(syscall(
__NR_io_uring_enter, ring_fd_, 0u, 1u, IORING_ENTER_GETEVENTS,
static_cast<void *>(nullptr), static_cast<size_t>(0)));
if (wait_ret >= 0 || errno == EINTR) {
continue;
}
if ((errno == EAGAIN || errno == EBUSY) &&
drain_retries++ < kIoUringDrainRetries) {
std::this_thread::sleep_for(std::chrono::microseconds(100));
continue;
}
LOG_ERROR(
"io_uring drain failed; errno=%d, %s. leaking the staging pool "
"and disabling io_uring for this context",
errno, ::strerror(errno));
abandon_staging();
teardown();
return -1;
}
return -1;
}

if (!all_ok) {
return -1;
}
}
return 0;
}

} // namespace ailego
} // namespace zvec

#endif // __linux__
Loading