惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Stack Overflow Blog
Stack Overflow Blog
J
Java Code Geeks
Last Week in AI
Last Week in AI
人人都是产品经理
人人都是产品经理
博客园 - 【当耐特】
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
C
Check Point Blog
月光博客
月光博客
腾讯CDC
Engineering at Meta
Engineering at Meta
博客园 - Franky
Vercel News
Vercel News
D
Docker
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
F
Fortinet All Blogs
Microsoft Security Blog
Microsoft Security Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
雷峰网
雷峰网
Google DeepMind News
Google DeepMind News
Martin Fowler
Martin Fowler
GbyAI
GbyAI
B
Blog
Hugging Face - Blog
Hugging Face - Blog
T
Tailwind CSS Blog

V2EX

我用 AI 写代码,但终端管理反而成了累赘——于是我做了 codux [调研] 各位在公司都用什么 ide 和 agent 写代码? 老运维 share 一个运维平台 看到有公司考核 token 指标,很好奇大家上个月的 AI 账单是多少 GLM-Coding 调用持续报错: z.ai 的 Lite 套餐几乎无法使用,官方 Pro/Max 是否稳定? 现在还有什么渠道可以稳定安全地使用 Claude 吗? 上海漕河泾内推,本组有 2 个 hc,一个后端,一个前端,预算都是 20k 左右,不打卡,氛围好 如果 V2EX 上有一组不永久保存聊天记录(比如只保存 7 天或者 24 小时)的聊天室,那么会开启哪些有用或者有趣的可能? gemini cli 貌似挂了,一直返回 403 第一次在自媒体上赚到钱 收集了最近在使用的低价 GPT, Gemini,邮箱等 AI 会员的小店合集 讨论个大实话:现在企业还在说 AI 编程提效 20%, 30%的,真的太落后,没用懂 AI。因为包括很多前沿公司,已经狂奔到提效 200%-500%的情况 [招聘][远程][币安] 前端/后端/QA/iOS/Android 至少 3 年以上经验 目前有大量 HC 欢迎投递 Chatgpt Pro 用量用不完的可以开这些设置 面试的时候好像遇到钓鱼了,给各位避个坑 cursor 年续费 22 号到期, 自动续费是否还是老的计次套餐呢 被两件破事毁掉的一下午,琐碎的内耗消磨人的精力 使用 Planet 存储 Codex 的会话或者重要信息 如果业务部门领导不要你开发功能,而是要求你教会它用 claude code 开发功能,你会怎么做? 分享一个 MacOS 接绿联 CM818 USB 转 DP 转接器使用感受 我的 HR 朋友 10 年老 Java ,非全大专,大家帮忙看看简历 开源了一个 AI 口语练习工具,音素级发音评分,完全免费可自部署 V2EX 上有哪些你觉得很有趣、印象深刻的妹纸? 字节为啥不出个国内版 Vercel? 有在大马的朋友吗? 问个运营商问题 你们在有领导的公司大群发过的最大胆的消息是什么 公司裁员,目前没有工作。想试试摆摊,做一个移动鲜啤打酒车 我的硬盘 Memblaze Pblaze 5 Linux 下不识别,给 Linux 内核提交了补丁, AI 说有望被合并 只有我一个人觉得 codex 不好用?
SQLite 开启 WAL 后,我的同步服务吞吐量提升了 3 倍
AhFei · 2026-06-07 · via V2EX

SQLite 默认未开启 WAL ,这会显著限制并发性能。

PlanTodo 是一个计划管理软件,最近我为它的同步服务编写了性能测试,经过实测,仅开启 WAL 就让同步服务的吞吐量便提升至原来的 3 倍,

其实关于 SQLite 性能优化的文章早有珠玉在前,比如 Optimal SQLite settings for DjangoOptimizing SQLite for servers ,所以本篇文章并没有独创性,只是为了让更多人了解 SQLite 的性能以及分享一个真实的性能测试用例

PlanTodo 同步服务性能测试

性能测试分为三个:

  1. oo_upload (one user, one device for a user, only upload) ,就是一个用户一台设备仅上传
  2. oo_download 就是仅下载
  3. oo_cross 是上传和下载交错进行

oo_upload 和 oo_download 是为了查看上传和下载场景下的极限性能,是为了将来专门优化时用来参考的。而 oo_cross 则较为贴近真实使用场景:用户的某个设备上传几个更新,另一个设备被触发下载;因此可以拿它计算服务器能承受的用户量。

如果你不想看下面具体的测试数据,这里简单展示了吞吐量的变化:

  1. oo_upload ,18027 -> 61682 ,是原来的 3.42 倍
  2. oo_download ,17082 -> 49635 ,是原来的 2.90 倍
  3. oo_cross ,17085 -> 44203 ,是原来的 2.58 倍

一般查询是比写入要快的,因此下载应该比上传快,但 PlanTodo 的同步服务却反了过来,说明有很大的优化空间。


下面是开启 WAL 前的测试数据:

+ just -f services/sync/justfile headless_oo_upload --less-output
============================================================
Performance Summary for test_oo_upload
============================================================
Requests      : 18,027
Failures      : 0
Failure Rate  : 0.00%

Average RT    : 78.14 ms
P50           : 78 ms
P95           : 110 ms
P99           : 130 ms
Max           : 272.81 ms

Endpoints
------------------------------------------------------------
POST /v1/sync/delta
  Requests=18,011  Avg=78.1ms  P95=110ms  P99=130ms  Max=272.8ms

POST /v1/clients
  Requests=8  Avg=76.2ms  P95=110ms  P99=110ms  Max=109.9ms

GET /v1/sync/full
  Requests=8  Avg=57.2ms  P95=120ms  P99=120ms  Max=120.3ms


+ just -f services/sync/justfile headless_oo_download --less-output
============================================================
Performance Summary for test_oo_download
============================================================
Requests      : 17,082
Failures      : 0
Failure Rate  : 0.00%

Average RT    : 82.63 ms
P50           : 82 ms
P95           : 110 ms
P99           : 130 ms
Max           : 370.33 ms

Endpoints
------------------------------------------------------------
POST /v1/sync/delta
  Requests=16  Avg=172.6ms  P95=370ms  P99=370ms  Max=370.3ms

GET /v1/sync/delta?cursor=1780662404990&limit=100
  Requests=2,087  Avg=83.4ms  P95=110ms  P99=130ms  Max=316.1ms

GET /v1/sync/delta?cursor=1780662403978&limit=100
  Requests=2,102  Avg=83.3ms  P95=110ms  P99=130ms  Max=325.5ms


+ just -f services/sync/justfile headless_oo_cross --less-output
============================================================
Performance Summary for test_oo_cross
============================================================
Requests      : 17,085
Failures      : 0
Failure Rate  : 0.00%

Average RT    : 83.05 ms
P50           : 82 ms
P95           : 120 ms
P99           : 150 ms
Max           : 245.89 ms

Endpoints
------------------------------------------------------------
GET /v1/sync/delta?cursor=1780662760888&limit=100
  Requests=1  Avg=245.9ms  P95=250ms  P99=250ms  Max=245.9ms

GET /v1/sync/delta?cursor=1780662760900&limit=100
  Requests=1  Avg=245.4ms  P95=250ms  P99=250ms  Max=245.4ms

GET /v1/sync/delta?cursor=1780662758760&limit=100
  Requests=1  Avg=228.2ms  P95=230ms  P99=230ms  Max=228.2ms

下面是开启 WAL 之后的测试数据:

+ just -f services/sync/justfile headless_oo_upload --less-output
============================================================
Performance Summary for test_oo_upload
============================================================
Requests      : 61,682
Failures      : 0
Failure Rate  : 0.00%

Average RT    : 22.30 ms
P50           : 22 ms
P95           : 32 ms
P99           : 41 ms
Max           : 74.82 ms

Endpoints
------------------------------------------------------------
POST /v1/clients
  Requests=8  Avg=33.8ms  P95=48ms  P99=48ms  Max=48.1ms

POST /v1/sync/delta
  Requests=61,666  Avg=22.3ms  P95=32ms  P99=41ms  Max=74.8ms

GET /v1/sync/full
  Requests=8  Avg=20.7ms  P95=32ms  P99=32ms  Max=32.5ms


+ just -f services/sync/justfile headless_oo_download --less-output
============================================================
Performance Summary for test_oo_download
============================================================
Requests      : 49,635
Failures      : 0
Failure Rate  : 0.00%

Average RT    : 28.03 ms
P50           : 28 ms
P95           : 38 ms
P99           : 46 ms
Max           : 305.64 ms

Endpoints
------------------------------------------------------------
POST /v1/sync/delta
  Requests=16  Avg=127.0ms  P95=310ms  P99=310ms  Max=305.6ms

GET /v1/sync/delta?cursor=1780663066610&limit=100
  Requests=6,109  Avg=28.4ms  P95=38ms  P99=46ms  Max=246.0ms

GET /v1/sync/delta?cursor=1780663068640&limit=100
  Requests=6,064  Avg=28.3ms  P95=38ms  P99=46ms  Max=95.8ms


+ just -f services/sync/justfile headless_oo_cross --less-output
============================================================
Performance Summary for test_oo_cross
============================================================
Requests      : 44,203
Failures      : 0
Failure Rate  : 0.00%

Average RT    : 31.80 ms
P50           : 28 ms
P95           : 45 ms
P99           : 150 ms
Max           : 477.37 ms

Endpoints
------------------------------------------------------------
GET /v1/sync/delta?cursor=1780663421960&limit=100
  Requests=1  Avg=477.4ms  P95=480ms  P99=480ms  Max=477.4ms

GET /v1/sync/delta?cursor=1780663421963&limit=100
  Requests=1  Avg=475.9ms  P95=480ms  P99=480ms  Max=475.9ms

GET /v1/sync/delta?cursor=1780663421966&limit=100
  Requests=1  Avg=475.4ms  P95=480ms  P99=480ms  Max=475.4ms

每个测试只持续了 3 min ,因此数据量很小,在大数据量的情况下,性能可能会下降很多,这个会在将来补充。

在测试 oo_cross 里,3min 处理了 44203 个请求,也就是一秒 245 个。测试其实是不断在重复先上传再下载,而客户端也是上传下载成对出现,因此一秒能处理 122 ( 245 / 2 )台设备的请求,即便因为多用户、多设备带来的其他压力,至少能稳定在一秒 100 个请求。

考虑到,真实使用下,平均要几分钟到十几分钟才会更新一次内容,取 10 分钟一次的话,一个同步服务极限下能支撑 100 x 60 x 10 ,6 万个设备正常使用。

如何开启 WAL

不管是什么 ORM ,开启的方式都是一样的,就是在连接数据库后,执行一次 PRAGMA journal_mode=WAL

开启了 WAL 后,在原本的 SQLite 数据库文件那里,会多出两个 .db-shm.db-wal 的文件,可以以此判断是否成功开启。

SQLAlchemy

from sqlalchemy import create_engine, event

engine = create_engine(
    DATABASE_URL,
    echo=False,
    connect_args={
        "timeout": 5,
    },
)

if engine.dialect.name == "sqlite":
    @event.listens_for(engine, "connect")
    def set_sqlite_pragma(dbapi_connection, _):
        cursor = dbapi_connection.cursor()

        # cursor.execute("PRAGMA foreign_keys=ON")
        cursor.execute("PRAGMA journal_mode=WAL")
        cursor.execute("PRAGMA synchronous=NORMAL")
        cursor.execute("PRAGMA temp_store=MEMORY")
        cursor.execute("PRAGMA cache_size=2000")
        cursor.execute("PRAGMA mmap_size=134217728")

        cursor.close()

PlanTodo 的同步服务就是使用 FastAPI + SQLAlchemy 开发的。由于同步服务的特殊性,这里没有开启外键约束。

Django

在项目的 settings.py 文件里,添加 init_command:

DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.sqlite3",
        "NAME": BASE_DIR / "db.sqlite3",
        "OPTIONS": {
            "init_command": (
                "PRAGMA foreign_keys = ON;"
                "PRAGMA journal_mode = WAL;"
                "PRAGMA synchronous = NORMAL;"
                "PRAGMA busy_timeout = 5000;"
                "PRAGMA temp_store = MEMORY;"
                "PRAGMA cache_size = 2000;"
                "PRAGMA mmap_size = 134217728;"
            ),
        },
    }
}

drift

在数据库类的 migration get 方法里,在 beforeOpen 这个回调里增加执行命令:

class PtdDatabase extends _$PtdDatabase {

  // 省略无关代码

  @override
  MigrationStrategy get migration {
    return MigrationStrategy(
      beforeOpen: (details) async {
        // 在每次打开数据库,正式使用之前,执行的命令
        await customStatement('PRAGMA journal_mode = WAL');
        await customStatement('PRAGMA synchronous = NORMAL');
        await customStatement('PRAGMA busy_timeout = 5000');
        await customStatement('PRAGMA temp_store = MEMORY');
        await customStatement('PRAGMA cache_size = -2000');
      },
    );
  }
}

原文链接: https://yanh.tech/2026/06/sqlite-performance-optimization/

版权声明:本博客所有文章除特別声明外,均为 AhFei 原创,采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 技焉洲 (yanh.tech)