• flinthub

    https://www.flinthub.top/thread/1
    FlintHub(原名 SoSite)是一套轻量级 PHP 社区系统,提供论坛(帖子/回复/版块)、博客、用户中心、后台管理、插件系统等完整能力。它最核心的特点是:彻底剥离 MySQL,全部数据由纯 SQLite 分片引擎(SplitDB)承载,从"单库单表"升级为"分片 + 索引 + 缓存"的架构,在保留 PHP 极简部署优势的同时,解决了大数据量下的性能问题。

    有兴趣的可以看一下,暂时不发布,本人比较懒,没有精力去做服务😁

    主楼
  • bbs1org

    支持一下,鼓励发布啊。做好了,为啥不发布

    彻底剥离 MySQL,全部数据由纯 SQLite 分片引擎(SplitDB)承载,从"单库单表"升级为"分片 + 索引 + 缓存"的架构,在保留 PHP 极简部署优势的同时,解决了大数据量下的性能问题

    这个可以展开说说吗

    #1
  • flinthub

    这个我发布在github上了,从设计白皮书到落地实测代码,不过这个splitdb代码,现在只是用在我的程序上,没法做成通用的。
    (白皮书)https://github.com/flinthub-top/SplitDB-DOCS
    (SplitDB代码仓库)https://github.com/flinthub-top/SplitDB-CORE
    你有兴趣可以看看先。

    #2
  • flinthub

    二、核心架构:SplitDB 纯 SQLite 分片引擎

    2.1 数据分层

    data/
    ├── meta/                        # 元数据层(SQLite 库)
    │   ├── business.sqlite          # 非分片业务:用户/版块/设置/博客/私信/附件/标签/投票
    │   ├── main_index.sqlite        # 帖子主索引 topic_index(19.2万行) + 回复定位索引 reply_index(16万行)
    │   ├── search/                  # 搜索索引季度分文件 search_{YYYYQn}.sqlite(4 文件,235 万行)
    │   ├── global_id.sqlite         # 全局 ID 生成器(topic/reply 原子取号游标)
    │   ├── sessions.sqlite          # 会话存储(SQLite handler)
    │   └── task_queue/queue_{0..2}.sqlite  # 异步任务队列(三队列打散锁竞争)
    ├── bucket/active/{季度}/{0..31}.sqlite  # 帖子/回复真相源(季度时序 + ID2 哈希分桶)
    ├── bucket/archive/              # 18 个月以上冷数据归档桶
    └── extern/{年}/{季}/{桶}/        # 正文外置:存量 .bin/.idx 偏移索引,新帖 .txt(读取自动回退)

    2.2 分片与路由规则(ShardRouter)

    • 一级分区:季度时序分区(YYYYQn,如 2026Q3),新数据写入当前季度;
    • 二级分区:哈希分桶(默认 32 桶),写入路由 = ID % 桶数量;
    • 读取永远以 main_index.bucket_path 为准,不再二次哈希;未来季度可平滑升级 32 → 64 → 128 桶,历史数据零迁移。

    2.3 写入与读取链路

    写入 = global_id 原子取号 → 季度分区 + ID2 哈希 → extern 正文原子写(tmp+rename)
         → 桶 topic/reply 真相源 → main_index 索引行(最终一致性,队列可选)
    读取 = 列表/分页走 main_index(索引行含 bucket_path)→ 详情读桶 + extern(.bin/.idx 优先,缺失回退 .txt)
    搜索 = 自研 bigram 倒排索引(季度分文件,SearchIndexStore)

    2.4 引擎关键组件(app/SplitDB/)

    组件职责亮点
    DBFactorySQLite 连接池单进程 LRU 最大 8 句柄,统一 6 条 PRAGMA(WAL/busy\_timeout),禁持久连接
    IDGenerator全局 ID 生成BEGIN IMMEDIATE 写锁事务原子取号(兼容无 RETURNING 的 SQLite 3.33)
    ShardRouter分片路由季度 + 哈希双级分区,路径推导唯一依据
    ExternStorage正文外置存储tmp+rename 原子写;存量 .bin/.idx 偏移索引(fseek 定位,懒加载);编辑后作废 .idx 条目回退 .txt(修复虚拟主机编辑不生效)
    Queue / QueueConsumer异步任务队列三队列打散锁竞争;CAS 抢占/超时恢复/重试上限;sync/cron/cli 三模式
    Schema初始化与种子幂等 bootstrap(目录树 + 8 库 + 22 表 + 默认种子)
    ViewCounter浏览计数APCu 内存计数 + shutdown 批量落库;无 APCu 静默降级为直写 UPDATE(不崩站)
    SearchIndexStore搜索索引季度分文件存储,与 business 库物理隔离(P21 瘦身 306MB → 19MB)

    2.5 事务与一致性

    • 桶写入用原始 SQL exec('BEGIN')/exec('COMMIT');business 库用 beginTransaction()/commit(),二者不混用;
    • 正文外置红线:先原子写 extern → 再写索引行,绝不允许"索引导入成功但正文丢失"(迁移故障注入已验证);
    • 计数延迟合并:统计/浏览数先走内存/APCu 计数,请求结束时批量写回,把写放大钉死在量化边界。
    #3
  • bbs1org

    支持技术研究。我让ai分析了下,她说:这些零件都是在尽量减轻写打架,但没有从根上干掉 SQLite 本身的短板。就像把一间屋子拆成好多小房间,吵架抢位置的概率变小了,但每个小房间同一时间还是只能有一个人往里写东西,人挤爆了照样排队卡壳。同一个分片 db 文件,同一时刻依旧只能有 1 个写事务。WAL 只是读写互不阻塞,写‑写之间依然互斥。一堆请求同时往同一个分片发帖,还是排队 busy。拿 ID 本身就是一次写;大量并发抢 ID,会在同一个库文件上抢锁,出现等待。只是保证不出错,不能变快。

    老奶奶总结版

    后生啊,这套东西把能想到的小聪明全都用上了:东西分开装、重活往后挪、小账先记在脑子攒一堆再记账。
    可是每家小本子(每个 db 文件),同一时间只能一个人往上面写字。大家偏偏都抢着往同一本本子写的时候,照样挤成一团。能扛得住中等热闹,但是扛不住人山人海挤爆场子。

    #4
  • ldsdf

    哈哈你的ai是会总结的

    #5
  • bbs1org

    老奶奶专用

    #6
  • flinthub

    哈哈,你的ai只看到了表面。

    #7
  • flinthub

    我让我的AI写了一个反驳你的,哈哈:

    反驳:单写者是引擎事实,但“扛不住人山人海”不是它的推论

    一、先接住他对的部分,避免抬杠

    他说对了三件事:

    1. 单个 SQLite 文件同一时刻只有一个写事务;
    2. WAL 只解决“读不挡写”,不解决“写挡写”;
    3. 全局取 ID 本身也是一次写。

    这三条在任何 SQLite 项目里都成立,SplitDB 没有假装它们不存在——但 “单文件串行”是单文件的属性,不是系统的吞吐上限。系统写吞吐 = 并行可写文件数 × 单文件速率 × 事务薄厚度。他的论证缺的正是后面这三个乘数。

    二、逐条对质

    ①“一堆请求同时往同一个分片发帖,还是排队”——“同时往同一个分片发帖”这个前提在设计上就不成立

    新帖落盘路径:全局取号 → id % 桶数 路由。ShardRouter::bucket() / bucketForWrite()(app/SplitDB/ShardRouter.php)把帖子按 id 哈希进 32 个(config.php SPLITDB_BUCKET_SIZE = 32)独立桶文件 bucket/active/{季度}/{桶}.sqlite,且只有当前季度可写,历史季度零写入。同时涌入的 N 个发帖,id 不同 → 桶号不同 → 落在不同文件 → 各自持独立写锁并行提交。两人同时写同一本小本子的概率被哈希摊到 1/32,而不是他表述里的 100%。

    ②“拿 ID 本身就是一次写,大量并发抢 ID 会在同一个库文件上抢锁”——是写,但写在哪、写得有多薄,决定它是不是瓶颈

    IDGenerator 文件头写得很清楚:基于 global_id.sqlite 的 id_generator 表(app/SplitDB/IDGenerator.php)。抢 ID 的锁发生在一个刻意独立、单行、无业务数据的微型文件上,与分片桶文件是两套互不干扰的串行点:抢 ID 不占桶文件的写锁,写桶文件也不用再碰 ID 锁。每请求代价 = 一次单行 BEGIN IMMEDIATE + SELECT/UPDATE,微秒级持锁;竞争失败走 busy_timeout = 5000ms + 最多 3 次 5–20ms 随机退避重试,结果是有界等待,不报错、不丢号。

    ③“WAL 只是读写互不阻塞,写-写依然互斥”——对,所以设计不是消灭单写者,而是压低持锁时长与写请求密度

    写-写互斥的伤害 = 密度 × 持锁时长,两条都被拆了:

    • 降密度:32 桶扇出(见①)+ 扩容(见⑤);
    • 降持锁时长:DBFactory::PRAGMAS 统一强制 journal_mode = WAL; synchronous = NORMAL; cache_size = -20000; temp_store = MEMORY(app/SplitDB/DBFactory.php)——WAL 让提交走追加式日志,synchronous = NORMAL 免去每事务全量 fsync,单行事务的持锁窗口是微秒级;白皮书红线同时明令“禁止长事务、长快照”。
    • 把写挪出锁路径:
    • 正文这类大 payload 不进 SQLite,落 extern/{年}/{Q}/{桶}/{id}.txt 文件(ShardRouter::externRel()),桶文件只剩元数据小行;
    • 浏览量这类高频写是 APCu 内存计数 + 30s 节流、单事务批量落库(ViewCounter::inc()/flush(),落库成功才扣内存、不丢计数),与白皮书红线“禁止 HTTP 线程实时更新浏览量”一致;
    • 搜索索引等重活走队列异步(app/SplitDB/Queue.php),不占请求热路径。

    ④“人挤爆照样排队”——先问“人山人海”是读还是写

    论坛流量里读占绝对大头(游客刷首页、列表、热帖)。这套系统里游客列表页连 SQLite 都不进:PageCache::serve()/render()(app/Helpers/PageCache.php)把首页/论坛列表渲染成静态 HTML 文件缓存直接命中。真正写库的只有发帖/回帖,且被①的扇出摊到 32 个文件上。排队窗口存在,但既不无限(busy_timeout 有界)也不集中(32 路扇出)——要等“单桶写速率顶穿单文件吞吐”才轮到锁成瓶颈,而那是下一个机制处理的:

    ⑤ 扩容机

    BucketAutoScaler::updateBucketSize()/emergencyExpand()(app/SplitDB/BucketAutoScaler.php)支持 32 → 64 → 128,且 ShardRouter::bucketForWrite() 保证扩容后新帖只进新增桶编号段、旧桶不再收新写,读按 bucket_path 零迁移。人再多,是“多开几本小本子”,不是“排队等同一本”。

    三、老奶奶版本还给他

    小本子一次只能一个人写——对。但现在是 32 本小本子同时能写:账房每次只记一行(薄事务),大件货堆仓库不进账本(正文外置),门口看热闹的挤不进账房(页面缓存),账房最忙的活儿交给后屋伙计夜里做(异步队列)。真把 32 本写满了,就再开 64 本、128 本,旧本子从此不再加新账。

    四、诚实的边界(主动把话说到位)

    1. 单文件单写者是 SQLite 引擎事实,SplitDB 没有也不可能“从根上消灭”它——任何数据库的写路径最终都有一个全局串行点:InnoDB 的 redo log 刷盘、PostgreSQL 的 WAL 同样是单点 fsync。工程问题是串行点是否落在热路径上:正文不走它、浏览数不走它、游客读不走它、同秒写请求被 32 路摊开。
    2. 全系统唯一“每写必过”的真串行点是 global_id.sqlite 的单行取号——这是刻意的取舍(唯一性 + 散列确定性),它单行、独立文件、微秒级,离单机 PHP-FPM 的写速率上限还差两个数量级;真到那天,代码注释里已预留演进空间(ID 段预取 / 雪花取号)。
    3. 如果“人山人海”指同一瞬间对同一主题/同一行的更新(如千万人同时回同一帖),任何数据库都必须串行——这是语义串行,不是架构缺陷,不能拿来证明分片无用。SplitDB 白皮书自带“不适用场景”与红线(禁止网页同步索引、禁止实时更新浏览量、禁止长事务),说明它对“能扛什么、不扛什么”有明确边界:读多写多、写可散列的社区论坛恰在适用域内。

    一句话总结:他描述的是 SQLite 的引擎说明书;SplitDB 反驳的是“所以系统会卡死”这个结论——引擎特性不等于系统瓶颈,中间隔着扇出、薄事务、外置、缓存、异步、扩容这六道设计。

    #8
  • bbs1org

    ai妥协了:SQLite 单文件 “单写者” 是硬件级说明书特性,但不能直接等价为 SplitDB‑CORE 系统瓶颈。中间隔了哈希扇出、极薄事务、外置大文本、内存批处理、异步队列、动态桶扩容六层工程手段。它的短板不在单机写分散论坛场景,而在于无法多机分布式横向扩展。

    如此说来,单机部署,你设计的SplitDB是个非常好的数据库技术方案!

    #14
  • 123456789

    大佬,我觉得你可以考虑设计下。支持sql数据库,1千万主题或者1亿主题。应该可以采用分表处理吧。虽然很多人达不上这么多内容,但有利于缓解性能焦虑。另外支持很多人大批量采集。😅

    #15
  • 123456789

    这个方案剥除了搜索索引,绝了,否则内容多了,第一个崩溃的就是硬盘。😅

    #16
  • 123456789

    这也是程序很大的吸引点,代码小,内容大,功能强,还只需sql,绝了😅

    #17
  • bbs1org

    1亿主题的时候:mysql做水平分表,或者冷热分离、存档。实在不行,上多台服务器。都有很成熟的方案,别焦虑了

    #18
  • 123456789

    我说的sqlite,那还要修改程序,你直接优化程序自动的不行吗。😀

    #19
  • bbs1org

    SplitDB 不错啊。让 @sunkille 弄个 SplitDB 驱动的bbs1org版本吧

    #20
  • 123456789

    那算了,我只用你这个程序😅

    #21
  • flinthub

    我的splitdb的驱动不好搞啊,适配性太差。

    #23
  • Moo

    不错不错,基础审美很好啊👌 赞

    #9
  • Maser

    这个也很好看

    #10
  • bbs1org

    说明卧虎藏龙

    #11
  • elm

    好棒啊,偏geek风,即有论坛又有博客,还适合小主机,期待😌

    #12
  • 桃子大人

    有点东西,支持支持

    #13
  • george

    6666666

    #22
  • flinthub

    这个帖子好热闹,感谢楼上各位的点评和支持。

    #24
  • zaiwai

    不错不错,来支持一下。

    #25
  • x

    相当可以,高手在民间

    #26
  • dcve

    社区大佬越来越多了

    #27

发表回复

登录后回复