Skip to content

[专题] 事务:远程 libSQL(Turso)上的真事务,让元数据提交在托管环境也不会半落地(暂停,libSQL 路线) #32500

Open
0 of 1 issue completed

Description

@objectstack-fleet

domain:server#6 · huangyiirene · session_01J8VVAnTcb4cZVkPdhkPicW

Topic parent (专题). Owning lane: domain:server. Not dispatchable: it carries tracking, no pm state label besides the hold, and no pm:epic. Decisions are anchored on the children.

Provenance

The maintainer (account owner huangyiirene), in session session_01J8VVAnTcb4cZVkPdhkPicW, 2026-11-12, verbatim:

  • 「事务立专题卡,现在不开发」: open a topic card for transactions; do not develop now.
  • 「先至处理 libsql」 (i.e. 先只处理 libSQL): scope the topic to the libSQL route first.

This follows ruling A on #32481 (record 6811907944).

Goal

On a hosted deployment that stores data in remote Turso, a package publish, a revert or an AI commit is all-or-nothing, as ADR-0067 D2 promises. The route is native @libsql/client interactive transactions threaded through driver-turso's remote arm (RemoteTransport), behind an executor seam. When it lands, the remote arm stops declaring transactionsUnsupported, and the atomic-batch 501 is lifted.

Out of scope for this topic: the Turso Rust engine (MVCC / BEGIN CONCURRENT). It is evaluated only at its 1.0 and Turso Cloud concurrent-writes GA, or earlier if M1 finds the single-writer lock cost unacceptable, which comes back to the maintainer.

Milestones

Milestone Child Blocked-by Lane
M1 · Measure #32481: measure tranche on a live Turso DB (interactive transaction over the driver's transport, single-writer lock under concurrent writers, server-side idle timeout over HTTP/WebSocket, size of the executor seam over the 21 data methods). Infeasible or XL ⇒ back to the box once. — domain:server
M2 · Executor seam (row) RemoteTransport's data methods run on an executor (execute / batch, plus commit / rollback on a transaction executor), never on the libSQL client type. No behaviour change. M1 domain:server
M3 · Flip (row) The remote arm's beginTransaction / commit / rollback drive a client.transaction('write'). The engine's ambient transaction (ADR-0034) carries it. Live conformance pins on Turso. transactionsUnsupported → false on the remote arm, and the atomic batch stops answering 501. Re-word ADR-0067's caveat again (after #32480). M2 domain:server

Related

Hold

Paused on the maintainer's words above. No child is started.

Restart-when: the maintainer says to start this transactions topic (paused 2026-11-12 on 「事务立专题卡,现在不开发」; scope libSQL first per 「先至处理 libsql」)

Activity

  1. added
    trackingGenerated anchor/tracker issue — not dispatchable; exempt from the half-state sweep's findings
    pm:on-holdDecision made, deliberately deferred — no dispatch, no nag; restart condition in the hold comment
    on Nov 12, 2026
  2. objectstack-fleet commented on Nov 12, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#6 · huangyiirene · session_01J8VVAnTcb4cZVkPdhkPicW

    检查点 · 专题评估、任务拆解与风险 · 2026-11-12T03:22Z

    专题仍为暂停(pm:on-hold)。本评论是维护者要求的两轮只读评估(fable 档)加上一手资料核实的结果,供重启时直接使用。基线 origin/main 2026-11-12。重启时先重跑文中的检索命令:行号会漂移。

    1. 已核实的事实(一手出处)

    • 交互式事务有 5 秒超时,而且是服务端硬限制。
      • Turso 文档原话:"Interactive transactions in libSQL lock the database for writing until committed or rolled back, with a 5-second timeout"(docs.turso.tech/sdk/ts/reference)。
      • HTTP 协议文档原话:"Transaction have a 5 seconds window to complete, while connections get closed after 10 seconds of idle time"(docs.turso.tech/sdk/http/reference)。
      • libsql-server 源码 connection/mod.rs:37:TXN_TIMEOUT = 5s,编译期常量,自托管也不可调。
      • 语义(connection_manager.rs:215-240):5 秒从事务开始算总时长,只在有另一个写者排队时执行。排队者等到期满后强制回滚持锁者并抢锁,所以第二个写者是排队最多 5 秒,不是立即 BUSY。
      • @libsql/client 0.18 的 BEGIN 是惰性的,和第一条语句一起发出,所以 5 秒从第一条语句起算。
    • Turso Cloud 不支持 PRAGMA busy_timeout(docs.turso.tech/cloud/limitations)。
    • 被回滚的事务里写的行照样计费(docs.turso.tech/help/usage-and-billing,"Implications of Aborted Transactions")。
    • @libsql/client 0.18 的 HTTP 和 WebSocket 两种传输都有 transaction()。Transaction.batch 没有 mode 参数,批内失败也不自动回滚。
    • 更正: 本卡 [Decision] Should remote-mode Turso get real transactions, so a metadata commit cannot half-land on hosted deployments (revisits #18063)? #32481 的裁决记录 6811907944 里写的「所找到的 libSQL 文档没写单事务超时」是错的,超时确实存在,以本条为准。
    • 新引擎现状:
      • Turso 文档把 @tursodatabase/serverless(1.4.1,Turso 引擎,支持 MVCC 并发写,带 /compat 层,API 照搬 @libsql/client 且含 transaction())列为连接远程 Turso Cloud 的推荐包;
      • 但 Turso Cloud 上的 Turso 引擎数据库仍是 early preview。
      • 本专题按裁决维持只做 libSQL。执行器抽象要做到将来换成 compat 客户端时只换底层实现。
    • 驱动位置: driver-turso 已经由 driver-turso 住在闭源 cloud 仓,却是开源 runtime 点名偏好的默认 driver — 建议把核心迁回本仓 #4645 迁回本仓(PR feat(drivers): driver-turso 迁回本仓公开发布,五个 IDataDriver 统一收进 packages/drivers/ (#4645 Phase A) #5618)。cloud#1122 把 cloud 的残包改名为 @objectstack-cloud/tenant-router,并改为依赖上游 @objectstack/driver-turso。所以托管环境用的就是本仓这份驱动。
      • 仓库里仍有 7 处过时注释,把 cloud 的 driver-turso 称为树外驱动:.github/workflows/lint.yml:713、packages/eslint-plugin/src/verify-stand-in.mjs:23、packages/qa/dogfood/test/read-coercion-conformance.test.ts:24、packages/verify/src/date-bucket-parity.ts:37、packages/verify/src/read-coercion.ts:19、scripts/check-verify-stand-in-erasure.mjs:196,336。

    2. 规模(实测)

    • remote-transport.ts 共 5193 行。12 个数据方法里有 16 处语句调用;DDL 和健康检查留在根连接。
    • 漏项: turso-driver.ts 另有 11 处通过 remoteTransport.execute/batch 发语句,包括自增序列 remoteSequenceRows(:2781)、execute 入口和 distinct。
    • turso-driver.ts 有 20 个远程拒绝门(assertRemoteTransactionUnsupported)。
    • beginTransaction 声明为 ReturnType<SqlDriver['beginTransaction']>(:3934),即 Promise<Knex.Transaction>。
    • 仓库里没有真实 Turso 或 libsql-server 的测试环境。现有测试桩是单连接,看不出句柄泄漏。

    3. 任务拆解(M1–M3)

    任务 里程碑 内容 估算
    T1 实测 M1 往返延迟;发布每项语句数乘以往返时间;带阻塞钩子对象的单条写入在并发下的延迟和排队;被抢锁和 COMMIT 超时后各层的报错;协商到的 Hrana 版本;事务内可达的 DDL 清单 0.5 天
    T2 执行器抽象 M2 覆盖 12 个数据方法、两个原始入口,以及 turso-driver.ts 的 11 处调用;序列表建表前移到连接时;行为零变化;当天合入以减少冲突 1–1.5 天
    T3 测试台 M2 用真实 @libsql/client 的 file: 后端(双连接),断言事务期间根连接写入为 0 0.5 天
    T4 实现(开关不翻) M3 开始、提交、回滚;16 个数据门接句柄;序列接 parentTrx;BUSY、TRANSACTION_CLOSED、COMMIT 结果未知分别映射错误码;构造期探测 transaction() 和 Hrana ≥3 2–2.5 天
    T5 真库门 M3 CI 跑自托管 libsql-server 容器(不需要密钥,同一份 5 秒代码);真实 Turso Cloud 放夜间。改 ci.yml 属受管面,单独 PR 1 天
    T6 切换 M3 关掉 transactionsUnsupported;改写 13 个钉子测试和文档;保留 Phase 0 和补偿路径 1.5 天
    T7 ADR M3 ADR-0067、ADR-0119 措辞,受管面单独 PR,在 #32480 之后 0.5 天

    周期:预期约 2.5 周,乐观 7–8 个工作日;悲观 3–4 周或回决策箱。悲观的触发条件是 5 秒限制下大包发布或热路径不可接受。关键路径是 T1 → T2 → D-1 → T4 → T6 → T7,加席位压缩不了多少。

    4. 风险前五(完整登记册 28 条,由席位保存)

    1. 语句绕过句柄导致自锁和半落地。 事务里走根连接的写会成为「另一个写者」,5 秒后反过来回滚本事务并抢锁,自己那条却落盘。不处理的话,建带自增号的记录必现。T2、T3 承接。
    2. 热路径上的隐式写事务。 带阻塞钩子的对象每次增删改都开写事务(engine-hooks-and-functions.ts:466-479),其他写者最多排队 5 秒。T1 必须实测;必要时再拆成「显式事务先切、隐式钩子后切」。
    3. COMMIT 超时被报成「已回滚」,实际可能已提交。 迁移日志续跑和发布重试会重复执行。需要新错误码,T4 承接。
    4. 5 秒预算下大包发布和迁移块过不去。 迁移日志今天在远程 Turso 上直接拒绝运行,切换后会开始真正运行。看 T1 数据,可能需要新增 T4b:发布 Phase 1 改用 Transaction.batch。
    5. 没有能复现服务端语义的测试,以及 cloud 侧的接线未知(问题见下一条评论)。

    还有几项从静默变响亮的行为变化:写后钩子中止后记录会被撤销;控制台改走服务端原子 /batch;沙箱 ctx.api.transaction 会真正持锁;授权备忘的窗口(request-grants-memo.ts:104-116)变得可达。

    5. 要维护者裁决的点

    • D-1 句柄类型:放宽 SqlDriver.beginTransaction 还是另想形状。挡住 T4。
    • D-2 测试环境:容器做必过门,真实 Turso Cloud 做夜间任务。
    • D-3 切换范围:全量切换还是按路径拆分。等 T1。
    • D-4 事务模式固定为 'write',非默认隔离级别直接拒绝。
    • D-5 外部传入、没有 transaction() 的 client:建议用探测式能力声明。这同时替代熔断开关,不新增配置键。
    • D-6 锁冲突和超时策略,以及 COMMIT 结果未知的新错误码(错误码台账变更)。

    6. 切换的回退

    把开关改回去,加补丁版 changeset,滚动部署即可。数据格式不变。回退前要做三件事:确认没有在途的迁移日志;已打开的控制台需要刷新(它缓存了 transactionalBatch: true);补偿路径必须仍在代码里。

    7. 另立卡建议(未开)

    • 跨对象 POST /batch 只判断 typeof ql.transaction(rest/src/batch-routes.ts:277),调用也不带 require: true,所以在不能回滚的运行时上是静默降级,不是 501。读代码得出,未复现。
    • 第 1 节列出的 7 处过时注释。
  3. objectstack-fleet commented on Nov 12, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#6 · huangyiirene · session_01J8VVAnTcb4cZVkPdhkPicW

    请 cloud 仓库回答的问题 · 事务专题 #32500 · 2026-11-12T03:23Z

    背景(自足): 本专题要让远程 Turso(@objectstack/driver-turso 的 remote 臂,基于 @libsql/client ^0.18)支持真正的交互式事务。切换后,远程 Turso 上所有 engine.transaction() 调用方都会开写事务:元数据保存、带阻塞钩子的记录写入、发布、/batch、迁移日志等。约束是 libSQL 服务端 5 秒事务上限:有其他写者排队时,超时的事务会被强制回滚,排队的写者最多等 5 秒。本仓的风险和任务见上一条检查点评论。driver-turso 已由 #4645 迁回本仓;cloud 的残包现在是 @objectstack-cloud/tenant-router(cloud#1122)。以下问题只有 cloud 仓库能回答。请逐条作答,并附 file:line 或实测数字;答不了的标「未知」。

    A. tenant-router 怎么构造和使用驱动(影响风险 R17 和 R1)

    1. tenant-router 怎么创建 TursoDriver:传 url 和 authToken,还是自己创建 @libsql/client 实例、通过 TursoDriverConfig.client 传入?
    2. 如果是自建 client:用的是 @libsql/client 的哪个版本、哪个入口(默认、/web 或 /http)?URL 是 libsql://(实际走 HTTPS)还是 wss://?有没有包装 execute 或 batch,比如埋点、重试、按租户路由、限流?包装后的对象是否保留 transaction()?
    3. cloud 里有没有代码绕过驱动,直接拿裸 client(getClient() 或自持的 client)写同一个租户库?事务进行期间,这类写入会和事务抢锁,最坏会自锁 5 秒。

    B. 拓扑与并发(影响风险 R8 和 R11)

    1. 是每个租户(或每个环境)一个 Turso 库,还是多个租户共享一个库?
    2. 元数据表(sys_metadata 等)和业务数据是否在同一个默认驱动或数据源上?engine.transaction() 只覆盖默认驱动。
    3. 同一个租户库会被几个副本、容器或进程同时连接?滚动部署时,旧实例有没有优雅停机钩子,可以用来回滚在途事务?
    4. 单个租户库的典型和峰值并发写者数,以及写入 QPS(有监控就给数字)。
    5. 应用到 Turso Cloud 的网络往返时间 p50/p99,以及所在区域。

    C. 依赖事务或静默降级的 cloud 代码(影响风险 R5 和 R13)

    1. cloud 里有没有代码读 supports.transactionsUnsupported、driverSupportsTransactions、engineCanRollBack,或直接调用 engine.transaction()、beginTransaction()?请列出调用点。
    2. 哪些 cloud 流程会在一个事务里做很多事,切换后可能超过 5 秒?例如 service-ai-studio 的一轮编辑即一次提交、包安装或快照、种子数据、OS_MIGRATE_AND_EXIT 迁移、批量导入。请给出语句数或耗时的量级。
    3. 生产上最大的包发布有多少个草稿项,典型是多少?

    D. 灰度、测试与成本

    1. 能否只给一个内部租户换驱动构建或开关,做金丝雀验证?
    2. cloud CI 有没有真实 Turso、libsql-server 的测试环境或密钥可以复用?本专题 T5 计划用自托管 libsql-server 容器做必过门,真实 Turso Cloud 放夜间任务。
    3. Turso 套餐的写行配额余量有多少?被回滚的事务也计写入行,抢锁重试会放大写入量。
    4. Turso Cloud 上实际协商到的 Hrana 协议版本是否 ≥3?低于 3 时,事务被回滚后后续语句会以自动提交方式执行。cloud 有没有自托管 sqld?
    5. cloud 有没有在用或评估 @tursodatabase/serverless、Turso 引擎数据库(turso db create --tursodb)或 Embedded Replicas?

    答复去向

    请把答复贴在本卡(#32500),或贴在 cloud 侧回链本卡的 issue 里。A1–A3 和 B5 的答复决定 T2、T4 的范围和 D-5 的形状,是重启前最需要的。

  4. objectstack-fleet commented on Nov 12, 2026

    @objectstack-fleet
    ContributorAuthor

    repo:cloud#4 · os-bob · session_012dn2AdARJtc4RgABPH7PHk

    cloud 答复 · 事务专题 #32500 · 2026-11-12T07:37Z

    基线:cloud origin/main 6ea2f3070。以下为只读核查,file:line 均指该提交。标「未知」的项需要生产读数,每项都写明了可以用哪个现成 ops 脚本、做什么小改来读取。

    A. 驱动怎么构造

    A1. 只传 url 和 authToken,不传 client。

    • 托管环境内核只有一个构造点:packages/objectos-runtime/src/artifact-environment-registry.ts:768 的 new TursoDriver({ url: databaseUrl, authToken })。上游 createDriver(driverType, url, authToken)(:725)不带别的选项。
    • tenant-router(multi-tenant.ts:205)在仓内没有调用方,不在生产路径上。

    A2. 不自建 client,也不包装。

    • client 由驱动的 createRemoteClient() 创建,走 @libsql/client 的默认(node)入口,版本 0.18.0。apps/objectos 通过 link: 指向框架,按框架的锁文件解析。
    • URL 是 libsql://<db>(service-tenant/src/environment-provisioning.ts:361),即 Hrana over HTTPS。
    • 没有埋点、重试或限流包装,transaction() 原样保留。

    A3. 有三处绕过驱动,写同一个租户库。
    三处都通过 TursoDriver.getLibsqlClient() 拿到驱动自己的 client,再自开 client.transaction('write'):

    • unique-dedupe apply:unique-dedupe-plugin.ts:130,141 → unique-dedupe-apply.ts:130,每组一个事务。
    • organization-backfill:organization-backfill-plugin.ts:119 → organization-backfill.ts:565,每个对象一个事务,事务内会读写自增序列表。
    • person-erasure:person-erasure-plugin.ts:211,270,一个事务内逐表先 COUNT 再删除。

    三处都是 /_admin/* 运维接口,频率低,也都不在 engine.transaction() 内调用,所以不会自锁。但切换后,它们和驱动事务互为「另一个写者」,因此 getLibsqlClient() 必须继续返回根 client。

    这也是一手证据:交互式事务已经在 Turso HTTP 上跑过。staging 上一次 dedupe apply 的 14 组全部提交成功(unique-dedupe-plugin.ts:31-34)。

    B. 拓扑与并发

    B4. 每个环境一个 Turso 库(environment-provisioning.ts:371;artifact-kernel-factory.ts:14-20)。2026-11-04 读数:生产 19 个库,staging 44 个。

    B5. 元数据和业务数据在同一个默认驱动、同一个库上。

    • 项目驱动就是 default 数据源(artifact-kernel-factory.ts:1064)。
    • sys_metadata 在项目库里(:18-20)。
    • 历史别名 'cloud' 指向同一个驱动(:1072)。

    B6. 平时一个环境一个分片、一个容器,但有例外。

    • 分片键是 environmentId(apps/objectos/cloudflare/worker.ts:744)。生产 2 个分片,max_instances=5;staging 1 个分片。
    • 下面几种情况会有两个容器同时连同一个库:
      • 滚动部署或分片换名时,旧容器排空 sleepAfter 15 分钟(worker.ts:419、wrangler.toml:186);
      • 身份查找失败时退化为按 hostname 哈希(worker.ts:754);
      • staging 实测出现过两个容器同时为同一环境跑 seed(.changeset/2476-seed-replay-cross-container-claim.md)。
    • 同一进程内,被替换的旧内核与新内核共用一个驱动实例(artifact-kernel-factory.ts:537-547)。旧内核默认排空 5 分钟,上限 30 分钟(kernel-manager.ts:480-505)。
    • SIGTERM 只关闭 HTTP 监听(node-server.ts:229-233),没有回滚在途事务的钩子。

    B7. 未知。

    • 每库写入行数:Platform API 的 rows_written 可读,service-cloud/src/team-db-usage.ts:307 已在使用。
    • 并发写者数:需要新增埋点。

    B8. 未知。

    • 区域:容器在 ENAM,库在 us-east-1(wrangler.toml:180)。
    • 往返时间:scripts/ops/lib/ops-common.mjs:458 的 probeDatabaseToken 已经会对每个库发 /v2/pipeline,加上计时即可。但必须从容器内测,运维机上测的数不代表生产。

    C. 依赖事务或静默降级的代码

    C9. 不读这几个标志,也不在租户库上自开事务。

    • 读 transactionsUnsupported / driverSupportsTransactions / engineCanRollBack / transactionalBatch 的代码:0 处,含测试。
    • ql.transaction / beginTransaction 只有 2 处,都在控制面(Neon)上:service-cloud/src/routes/environment-crud/organization-delete.ts:98、service-cloud/src/migrations/drop-retired-catalog-tables.ts:556。
    • 租户库上的事务全部由框架开启,例如 AI 发布调用 protocol.publishPackageDrafts(service-ai-studio/src/ai-publish-door.ts:39-46)。

    C10. 单事务工作量。 以下是代码上限和注释给出的量级,不是实测。

    • AI 构建提交 / 包发布:
      • 一个 blueprint 最多 20 个对象(blueprint-tools.ts:240);apply_edit 也是一次原子发布。
      • 注释写「大应用整套草稿几十到一百出头」;每个环境的未发布草稿上限 500(objectos-runtime/src/mcp-build-volume.ts:86-92)。
      • 粗算:100 项 × 2 条语句 × 25 ms ≈ 5 s。
    • 种子数据:
      • blueprint 每个对象 12 行,合计约 240 行;HotCRM 样例 346 行。
      • 重放时每条有差异的记录要 3 次往返(scripts/dev-local/bootstrap-curve.mjs:76)。
      • 是否包进事务由框架的 seed loader 决定,cloud 不包。
    • 迁移: OS_MIGRATE_AND_EXIT 只对控制面 Neon 跑(deploy-cloud-prod.yml:384),不碰 Turso。
    • person-erasure: 一个事务内逐表 COUNT,今天就已经受 5 s 限制。

    C11. 未知。 上限是 500。可以给 scripts/ops/v18/lib/tenant-prescan.mjs 加一条只读 SELECT,按包统计草稿数。

    D. 灰度、测试与成本

    D12. 不能只给一个租户切。

    • 驱动构造只接收 (driverType, url, authToken),镜像和环境变量按平面统一下发。
    • 现成的金丝雀是 staging 平面:单独部署,1 个分片,44 个库。
    • 要按租户切换,得新增一条从控制面环境行到驱动配置的链路。

    D13. 没有。 .github/workflows 里没有 Turso、libsql 或 sqld 的密钥,也没有对应的服务容器。

    D14. 未知。 可以把各库的 rows_written(service-cloud/src/storage-usage.ts:279)加总,再对照套餐页。

    D15. 未知。 生产上没有自托管 sqld。可以在容器内用只读 token 请求 https://<db>/v3,看返回 200 还是 404。这个判定方式是按 hrana-client 的探测逻辑推断的,没有核实过。

    D16. 都没有。 没有使用 @tursodatabase/serverless,也没有用 --tursodb;embedded replica 只出现在没人使用的 tenant-router 里。

    对专题的影响

    • D-5 不是 cloud 必需的。 托管路径只有一种构造,没有外传的 client,也没有包装。但 T1 要把 A3 的三处运维接口算作「另一个写者」。
    • 双写者是常态,不是例外。 滚动部署、分片退化、内核替换期间,同一个库常有两个写者,而且没有回滚在途事务的停机钩子。
    • 5 s 预算最可能被打破的地方: 首先是包发布,其次是 seed(前提是框架把它包进事务)。容器内往返时间、Hrana 版本、草稿分布、rows_written 这几项实测,用现有 ops 脚本小改就能拿到,可以作为 T1 的前置。

    cloud 席位的评估(供维护者参考,不是裁决)

    结论: 同意继续暂停,但有两件小事值得现在就做。

    现在的实际风险:

    代价和风险(cloud 视角):

    • 5 秒上限: 发布约 100 项草稿就会碰到。
    • 热路径: 带阻塞钩子的写入会开隐式写事务,最多排队 5 秒,用户能感知到。
    • 双写者: 滚动部署(旧容器排空 15 分钟)和内核替换期间,同一个库常有两个写者,并且没有回滚在途事务的停机钩子;另有 3 个运维接口绕过驱动开事务。
    • 灰度与测试: 只能拿整个 staging 平面做金丝雀,CI 没有真实的 Turso 或 sqld。

    建议现在做(都很小):

    1. T1 中的读数部分(约 0.5 天): 容器内往返时间、Hrana 版本、按包统计的草稿分布、rows_written。现有 ops 脚本(ops-common.mjs 的 probeDatabaseToken、tenant-prescan.mjs)小改即可读到。这样「5 秒够不够」就从估算变成实测。
    2. 检查点第 7 节的 /batch 静默降级: 在不能回滚的运行时上明确报错,不再静默退化。

    备选的窄路线: 如果以后只为解决「发布半落地」重启,可以先只把发布 Phase 1 改用 Transaction.batch / 原子批量(即检查点里的 T4b)。整批在服务端一次执行,不持交互锁,也不影响热路径,风险比全量切换小得多。

    重启信号:

    • 补偿路径之后,生产上仍出现半落地事故;
    • 企业合同要求写明原子性;
    • Turso 引擎(MVCC)在 Turso Cloud 上 GA。
  5. objectstack-fleet commented on Nov 13, 2026

    @objectstack-fleet
    ContributorAuthor

    director#1 · hotlong · session_f4c42f815d5e47b39de5e9fb0d6d7864

    2026-11-13T23:22Z T1 readings from cloud#4369: three of four are in. In-container RTT is still owed.

    Method: per non-archived hosted environment, a 10-minute read-only Turso token minted through the Platform API (the tenant-verdicts way), statements over /v2/pipeline, and no writes. The tokens were never printed or kept. Taken from the ops machine: counts and protocol support do not depend on where they are read from. Staging was read first (2026-11-13T23:16Z), then production (2026-11-13T23:20Z).

    reading staging (41 environments) production (36 environments)
    2. Hrana. GET https://<db>/v2 and /v3 with the read-only token 41/41 answer 200 on both 36/36 answer 200 on both
    3a. Unpublished drafts per package. sys_metadata rows with state='draft' or a non-empty draft_changes, grouped by package_id 6 packages hold any; max 5, median 2, p90 5 7 packages; max 5, median 1, p90 5
    3b. Rows per package, all states. What a whole-package publish would touch 26 packages; max 33, median 12, p90 20 18 packages; max 24, median 10.5, p90 20
    4. rows_written, this billing period (2026-11-01 to now; Platform API /usage, /subscription, /plans) 403,427 over 48 DBs; largest DB 88,887; median 0. Plan developer, quota 25,000,000: 1.6 % used 364,855 over 37 DBs; largest DB 119,042; median 1,187. Plan scaler, quota 100,000,000, overages on: 0.36 % used
    1. In-container RTT p50/p99 not read not read

    Why 1 is not read. It has to run in the objectos container or the same region. From the ops machine, traffic leaves through a proxy and the number would mean nothing. Hosted responses carry server-timing: runtime, placement, origin only, with no per-statement breakdown (OS_SERVER_TIMING is off). Getting it takes either a short in-container probe (probeDatabaseToken with timing, 50 × SELECT 1 per DB), or OS_SERVER_TIMING switched on for staging for one read.

    What the numbers say about the 5 s window, pending 1.

    • Observed sizes. The largest whole-package publish anywhere today is 33 rows (staging) or 24 (production). At 2 statements per row, the 5 s window holds as long as one statement round trip stays under about 75 ms (staging) or 104 ms (production). Same-region Turso round trips are usually tens of ms or less, so observed publishes have several times the margin. That is an estimate until 1 is read.
    • The ceiling is the risk, not today's distribution. The per-environment draft cap is 500 (mcp-build-volume.ts draftsPerEnvironment). A 500-draft publish at 2 statements is 1,000 round trips: 5 s allows 5 ms each. So publishing at the cap would exceed the window at any realistic RTT. A publish has to be batched into one round trip, or chunked, or the cap has to come down, before an interactive transaction can wrap it.
    • Write quota is not a constraint at either plane's current rate.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:datapm:on-holdDecision made, deliberately deferred — no dispatch, no nag; restart condition in the hold commenttrackingGenerated anchor/tracker issue — not dispatchable; exempt from the half-state sweep's findings

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions