Showing Posts From

性能

看 free -h 别只盯 free 那列:available 才是真

服务器一查内存: $ free -h total used free shared buff/cache available Mem: 14Gi 10Gi 146Mi 0.0Ki 4.2Gi 4.1Gi Swap: 0B 0B 0B看到 free: 146Mi 就觉得"内存要爆了"——不对,Linux 从来不闲着,它会把空闲内存拿去做各种缓存。 各列到底是什么列 含义total 总内存used 已被进程占用free 完全空闲,谁都没在用(少不代表不好)shared tmpfs / shared memory 占用buff/cache 内核用作 page cache / dentry 缓存available 实际还能分配给程序的内存(这个才是真)关键:buff/cache 是弹性的——一旦有程序需要内存,内核会立刻回收 cache 让出来。所以真正衡量"还剩多少"的指标是 available,不是 free。 上面例子里 available = 4.1Gi,说明还能舒服再开 4G 内存的程序,没问题。 buff/cache 具体缓存什么Page cache:读过的文件内容缓存在内存里,下次读同一文件秒回 Dentry cache:目录项缓存,让 ls / find 之类操作更快 Inode cache:文件元数据(大小、时间戳)缓存 Buffers:块设备 IO 缓冲这些都是性能加速——把内存"用起来"总比闲着好。 手动清 cache 极少用得上,但知道一下: sync # 先把脏页刷盘 echo 3 > /proc/sys/vm/drop_caches # 清所有 cache1 = 清 page cache 2 = 清 dentry + inode 3 = 全清清完 free -h 里 free 会瞬间变大——但系统 IO 性能会立刻下降,因为所有文件读要重新从磁盘拉。生产上除非做基准测试,不然不要清。 Swap = 0 的风险 上面的例子 Swap: 0B。开 swap 有一个好处:内存暂时不够时,把不活跃页面换到磁盘,避免 OOM Killer 杀进程。 没 swap 的情况,某个进程突然吃到 15G 内存,系统会直接触发 OOM Killer: Killed process 12345 (python) Killed process 23456 (mysqld)杀谁看 oom_score(内核给每个进程算一个"该杀分数"),基本是"占内存多、优先级低"的先中招。 加个 swap(4G) sudo fallocate -l 4G /swapfile sudo chmod 600 /swapfile sudo mkswap /swapfile sudo swapon /swapfile验证: free -h # Swap: 4.0Gi ...开机自动挂载: echo '/swapfile swap swap defaults 0 0' | sudo tee -a /etc/fstabSwap 大小经验:内存 < 2G:swap 是内存的 2 倍 内存 2-8G:swap 等于内存 内存 > 8G:swap 2-4G 或者干脆不开(数据库服务器)找谁在吃内存 RSS 排序(真实占用物理内存): ps aux --sort=-rss | head -20或者 top 里按 M 排。列 RES才是实际占用,VIRT(虚拟内存)意义不大。 看某个进程详情: cat /proc/<PID>/status | grep -E 'VmRSS|VmSize'看具体是哪部分: pmap -x <PID> | tail -1MySQL / Redis / Java 类"吃内存大户" 这些程序的内存占用大多是配置定的:MySQL:innodb_buffer_pool_size,默认可能占 70% 内存 Redis:maxmemory,不配就吃到爆 Java:-Xmx 设堆大小,不设 JVM 会按机器算上生产前把这些参数对着物理内存卡好,不然多进程叠加轻松超总内存。 Linux OOM 前的预警 看 dmesg 有没有: sudo dmesg -T | grep -i "oom\|killed"Out of memory: Killed process 12345 (python) total-vm:15234567kB看到这个说明已经杀过了。生产上应该主动监控——node_exporter + Prometheus 采 node_memory_MemAvailable_bytes,跌破阈值发告警。 一句话总结 free -h 看的是 available,不是 free;buff/cache 是好东西不是问题。没 swap 的服务器建议加 4G 兜底,主要吃内存的程序(MySQL / Redis / Java)参数要卡死。

SQLite 大数据量优化:WAL 模式、批量事务、游标分页和索引策略

SQLite 单数据库文件理论上限约 281TB,实际几十 GB 均可正常运行,性能瓶颈通常来自配置和使用方式,而非 SQLite 本身。 必开:WAL 模式 PRAGMA journal_mode=WAL;默认的 DELETE journal 模式写入时会锁全库,WAL 模式允许读写并发,大幅提升写入性能。需要在每次连接时设置(或写入配置持久化)。 减少同步次数 -- 兼顾安全和性能(推荐) PRAGMA synchronous=NORMAL;-- 最高性能(断电可能丢最近几秒数据,非关键数据可用) PRAGMA synchronous=OFF;批量事务(最重要的优化) 单条 INSERT 性能极差,每次写入都会触发一次磁盘同步: // 错误写法:每条 INSERT 是独立事务 for (const item of items) { db.run("INSERT INTO logs VALUES (?)", item); }改为批量事务,性能提升 100-1000 倍: BEGIN; INSERT INTO logs VALUES (1, 'event_a', 1716800000); INSERT INTO logs VALUES (2, 'event_b', 1716800001); -- ... 批量写入 COMMIT;Node.js(better-sqlite3)示例: const insertMany = db.transaction((items) => { const stmt = db.prepare("INSERT INTO logs (id, type, ts) VALUES (?, ?, ?)"); for (const item of items) { stmt.run(item.id, item.type, item.ts); } });insertMany(items); // 一次事务写入全部Python(sqlite3)示例: conn.execute("BEGIN") conn.executemany("INSERT INTO logs VALUES (?, ?, ?)", items) conn.execute("COMMIT")建议每批 500-5000 条,批次过大会增加内存压力。 游标分页(替代 OFFSET) OFFSET 越大越慢,因为需要扫描并跳过前面所有行: -- 慢:OFFSET 100万,需要扫描 100万行 SELECT * FROM logs LIMIT 20 OFFSET 1000000;改用游标分页(Keyset Pagination): -- 快:直接从上次最后一条的 id 开始 SELECT * FROM logs WHERE id > ? LIMIT 20;记录上次最后一条的 id 作为下次查询的起始点,无论翻到多后面性能都一致。 索引策略 查看查询执行计划,确认是否走索引: EXPLAIN QUERY PLAN SELECT * FROM logs WHERE user_id = 123 AND created_at > 1716800000;原则:高频 WHERE 条件字段建索引 复合查询建复合索引(字段顺序与查询条件一致) 低选择度字段(如布尔值)单独建索引意义不大 写入密集型表控制索引数量,每个索引都会拖慢写入其他配置 -- 增大缓存(默认 -2000 即 2MB,建议改大) PRAGMA cache_size=-32000; -- 32MB-- 内存临时表(减少磁盘 I/O) PRAGMA temp_store=MEMORY;-- 预写 WAL 检查点 PRAGMA wal_autocheckpoint=1000;大字段(图片、视频、大 JSON、二进制 Blob)建议存文件系统,数据库只存文件路径,避免单数据库文件膨胀过快。

logTimingOnce 是啥 + 前端性能计时的正规做法

有人问 logTimingOnce 是 JS 内置什么函数——不是。这个名字不是浏览器标准 API,也不在 Node 内置里。多半是:某个项目里自己定义的工具函数 某个框架 / SDK 内部封装 被压缩混淆后的名字从名字看是 log + Timing + Once——只记录一次耗时日志。列几种可能的实现和前端性能计时的正规做法。 猜测的实现 版本 1:一次性计时器 function logTimingOnce(name, fn) { const start = performance.now(); const result = fn(); const end = performance.now(); console.log(`${name}: ${(end - start).toFixed(2)}ms`); return result; }// 用法 const data = logTimingOnce("parseJSON", () => JSON.parse(bigStr));版本 2:同一 key 只打一次 const logged = new Set();function logTimingOnce(key, cost) { if (logged.has(key)) return; logged.add(key); console.log(`[timing] ${key}: ${cost}ms`); }// 用法 const start = performance.now(); initWebGL(); logTimingOnce("webgl-init", performance.now() - start); // 只打一次大型 SPA / 游戏 / WebGL / AI 前端里这种模式常见——首屏耗时、初始化时间只统计一次。 想知道具体项目里 logTimingOnce 是哪种,直接: console.log(logTimingOnce.toString());正规姿势:performance.now Date.now() 精度只到毫秒,且系统时钟被调整还会跳(NTP 校时、用户改时间)。计时永远用 performance.now(): const start = performance.now(); // ... 做事 const cost = performance.now() - start; console.log(cost); // 精度到微秒(0.001ms)单调时钟,不会倒退,浏览器和 Node 都支持。 User Timing API:让 DevTools 看得见 上面的 console.log 输出到控制台,看着乱。用 User Timing API 把标记打到 Performance 面板: performance.mark("webgl-init-start"); initWebGL(); performance.mark("webgl-init-end");performance.measure( "webgl-init", "webgl-init-start", "webgl-init-end" );之后 Chrome DevTools → Performance 面板里能看到 webgl-init 这条竖线,跟其它内置事件(LCP、FCP)在一起。 拿测量结果: const [entry] = performance.getEntriesByName("webgl-init"); console.log(entry.duration);PerformanceObserver:订阅性能事件 要监听所有 measure(比如上报到日志系统): const observer = new PerformanceObserver(list => { for (const entry of list.getEntries()) { console.log(entry.name, entry.duration); // 上报到你的日志服务 } }); observer.observe({ entryTypes: ["measure"] });也能监听浏览器内置事件: // LCP(Largest Contentful Paint) new PerformanceObserver(list => { const last = list.getEntries().pop(); console.log("LCP:", last.startTime); }).observe({ type: "largest-contentful-paint", buffered: true });常用性能指标一览指标 什么意思 怎么拿FCP (First Contentful Paint) 首次有内容渲染 paint entryTypeLCP (Largest Contentful Paint) 最大内容渲染 largest-contentful-paintFID (First Input Delay) / INP 首次 / 最长交互延迟 event、first-inputCLS (Cumulative Layout Shift) 布局偏移 layout-shiftTTFB (Time to First Byte) 首字节时间 navigation entryType想一次性拿 Web Vitals,直接用 Google 的库: npm install web-vitalsimport { onCLS, onLCP, onINP } from "web-vitals"; onCLS(console.log); onLCP(console.log); onINP(console.log);顺带:JS 生成 UUID 原对话里还问了 UUID,顺便说下——现代浏览器和 Node 直接用: crypto.randomUUID(); // "3f6c2a6f-8d7d-4f9c-b7d5-3fcb52c8a1aa"比自己写的 Math.random 版本安全(用 CSPRNG)、代码短。老浏览器不支持时才需要 polyfill。 一句话总结 logTimingOnce 是项目自定义函数,不是标准 API。真正测性能用 performance.now() 做基础,performance.mark/measure 让 DevTools 看得见,PerformanceObserver 采集上报。UUID 直接 crypto.randomUUID()。