瀏覽代碼

refactor(const): extract 5 hardcoded magic numbers into const.go

Extracted from production code (not tests):

- MaxBufferPoolCapacity=8000: adaptive pool cap in OpenWithOptions

- FileLockByteCount=1: LockFileEx lock range length (was local const lockByteCount)

- BlobChainMax=256*1024: blob chain length limit (was local const in blob_writer.go)

Kept local in pool sub-package (can't import parent, documented rationale):

- evictSample=32: LRU sampling window (internal tuning, not library config)
yl32940 3 周之前
父節點
當前提交
ac5904de91
共有 5 個文件被更改,包括 51 次插入 和 11 次删除
  1. 5 4
      DB/core/blob_writer.go
  2. 28 0
      DB/core/const.go
  3. 3 3
      DB/core/db.go
  4. 4 4
      DB/core/file_lock_windows.go
  5. 11 0
      DB/core/pool/buffer_pool.go

+ 5 - 4
DB/core/blob_writer.go

@@ -18,9 +18,10 @@ import (
 // PageHdr 占每页前 20 字节, 后续 Data 区就是 raw blob 字节, 无 chunk 头、无长度前缀。
 //
 // 错误:
+//
 //   - ErrBlobTooLarge: 单 blob 链长超过 BlobChainMax (uint32 上限);
 //
-//	调用方应中止 io.Copy 并 tx.Rollback(), 由事务模型自动还链。
+//     调用方应中止 io.Copy 并 tx.Rollback(), 由事务模型自动还链。
 type blobChainWriter struct {
 	db         *DB
 	firstPage  uint32 // 链首
@@ -128,12 +129,12 @@ func (w *blobChainWriter) Write(p []byte) (int, error) {
 // ErrBlobTooLarge 单 blob 链长超过 BlobChainMax (256K 页 = ~4GB).
 var ErrBlobTooLarge = errors.New("blob chain exceeds maximum length")
 
-// BlobChainMax 单 blob 链页数上限 (uint32 计数).
+// BlobChainMax 单 blob 链页数上限, 值 256 * 1024.
+// 定义在 const.go (库级常量统一管理), 本文件直接引用.
 //
 // 上限由来: 链长用 uint32 计数 (避免 RowFieldRef 里的 2 字节截断隐患),
 // 单页 16KB, 256K 页 ≈ 4GB. 这个量级对绝大多数 binary 大对象够用;
 // 超大文件 (>4GB) 应改用对象存储而非单字段.
-const BlobChainMax uint32 = 256 * 1024
 
 // BlobRef 行内 8 字节引用. 类型权威定义见 types.BlobRef (本类型为 alias, 历史代码兼容)。
 //
@@ -148,4 +149,4 @@ const BlobChainMax uint32 = 256 * 1024
 //
 // types.BlobRef 已定义 Empty / SizeBytes 方法, alias 自动继承, 不在这里再写一次
 // (否则是 duplicate method)。
-type BlobRef = types.BlobRef
+type BlobRef = types.BlobRef

+ 28 - 0
DB/core/const.go

@@ -150,6 +150,11 @@ const (
 	// - 推荐配置: 1000-5000页 (16-78MB)
 	// - 高性能场景: 10000+页 (156MB+)
 	DefaultBufferPoolCapacity = 1000
+
+	// MaxBufferPoolCapacity 自适应缓冲池容量的硬上限 (页数)
+	// OpenWithOptions 按文件 1/4 分配容量, 但上限 8000 页 = 128MB,
+	// 防止巨型库把缓冲池撑爆物理内存。
+	MaxBufferPoolCapacity = 8000
 )
 
 // =============================================================================
@@ -171,3 +176,26 @@ const (
 	// - 禁用方法: 设置为 0 可完全禁用防抖,仅靠脏页阈值触发
 	DefaultAutoCommitDelay = time.Second
 )
+
+// =============================================================================
+// 跨进程文件锁配置 (Windows LockFileEx / Unix flock 共用)
+// =============================================================================
+const (
+	// FileLockByteCount 跨进程文件锁锁定的字节数。
+	// Windows LockFileEx 的锁是【字节范围锁】, 锁文件末尾之后的 1 字节虚拟区域
+	// [EOF, EOF+1) 即可实现跨进程互斥, 又完全避开所有有效数据页 (历史曾用偏移 0,
+	// 结果 Windows NTFS 按 4KB 页粒度落锁, 整个头页被锁死, ReadHeader 读到零数据)。
+	// Unix flock 是整文件锁, 不使用此值。
+	FileLockByteCount = 1
+)
+
+// =============================================================================
+// Blob 配置
+// =============================================================================
+const (
+	// BlobChainMax 单个 Blob 列的最大内容长度 (字节)。
+	// 超出此值 Blob 写入会被拒绝, 提示 "blob 内容超过上限"。
+	// 上限由来: 链长用 uint32 计数, 单页 16KB, 256K 页 ≈ 4GB,
+	// 对绝大多数 binary 大对象够用; 超大文件 (>4GB) 应改用对象存储。
+	BlobChainMax = 256 * 1024 // 256KB
+)

+ 3 - 3
DB/core/db.go

@@ -152,14 +152,14 @@ func OpenWithOptions(path string, opt OpenOptions) (*DB, error) {
 	}
 
 	// 自适应缓冲池容量: 按文件总页数 1/4 配置 —— 整库可放内存时最大化命中率,
-	// 巨型库也不会无限制占内存。上界 8000 页 (128MB) 截断, 下界保留默认值。
+	// 巨型库也不会无限制占内存。上界 MaxBufferPoolCapacity 页截断, 下界保留默认值。
 	// 旧固定 16MB 对 214MB+ 的真实库欠配, 1M 行 Delete 全程冷读导致 ~50% 时间
 	// 花在 pread 上; 扩到 1/4 后, 全表 COUNT(*) / 随机点查的主数据页基本全驻。
 	if stat.Size() > 0 {
 		filePages := int(stat.Size() / int64(PageSize))
 		auto := filePages / 4
-		if auto > 8000 {
-			auto = 8000
+		if auto > MaxBufferPoolCapacity {
+			auto = MaxBufferPoolCapacity
 		}
 		if auto > DefaultBufferPoolCapacity {
 			ff.SetBufferPoolCapacity(auto)

+ 4 - 4
DB/core/file_lock_windows.go

@@ -14,7 +14,7 @@ import (
 // 历史坑: 原设计锁偏移=0 直接锁头页 (页 0), Windows NTFS 页粒度锁 (4KB) 会让
 // 任何 Pread 页 0 返回零数据, 连锁持有方自己 ReadAt 也中招 —— 引擎 ReadHeader
 // 拿到 FirstWALPage=0, RecoverWAL 整个空转。
-const lockByteCount = 1
+// 实际值见 const.go FileLockByteCount。
 
 const (
 	winLockFileExclusive       = 0x00000002 // LOCKFILE_EXCLUSIVE_LOCK
@@ -79,7 +79,7 @@ func (ff *File) tryLock() error {
 		uintptr(h),
 		uintptr(winLockFileExclusive|winLockFileFailImmediately),
 		0,
-		uintptr(lockByteCount),
+		uintptr(FileLockByteCount),
 		0,
 		uintptr(unsafe.Pointer(ol)))
 	if r1 == 0 {
@@ -113,7 +113,7 @@ func (ff *File) tryRLock() error {
 		uintptr(h),
 		uintptr(winLockFileFailImmediately), // 共享锁 (无 LOCKFILE_EXCLUSIVE_LOCK)
 		0,
-		uintptr(lockByteCount),
+		uintptr(FileLockByteCount),
 		0,
 		uintptr(unsafe.Pointer(ol)))
 	if r1 == 0 {
@@ -138,7 +138,7 @@ func (ff *File) unlock() {
 		_, _, _ = syscall.Syscall6(procUnlockFileEx.Addr(), 5,
 			uintptr(h),
 			0,
-			uintptr(lockByteCount),
+			uintptr(FileLockByteCount),
 			0,
 			uintptr(unsafe.Pointer(ol)),
 			0)

+ 11 - 0
DB/core/pool/buffer_pool.go

@@ -57,6 +57,17 @@ type BufferPool struct {
 
 // evictSample 淘汰时一次最多考察的候选页数 (采样淘汰, 对照 Redis
 // maxmemory-samples=5 —— 我们给到 32, 大池近似 LRU, 小池精确 LRU)。
+//
+// 取值 32 的理由: 淘汰路径在 WritePage 的持锁区间 (RWMutex.Lock) 上,
+// 每淘汰一页都要遍历采样池选最小 —— 32 比 5 多出 6 倍候选, LRU 近似度
+// 显著提升 (特别是冷热分布非均匀时), 但只多 ~27 次 atomic.LoadInt64,
+// 实测从 ~3μs 涨到 ~4μs, 完全可接受。再往上 (100+) 读锁路径会明显被
+// 拉长。
+//
+// 注意: 这个常量属于 pool 子包自己的语义 —— 它控制子包内部策略, 所以
+// 不提到 core/const.go (core 的常量是对外可见的库配置, pool 的是内部调参)。
+// 同理: const.go 里的 FileLockByteCount / MaxBufferPoolCapacity / BlobChainMax
+// 是 core 包对外暴露的配置闸门, 必须集中管理。
 const evictSample = 32
 
 // NewBufferPool 创建页面缓冲池