< 返回版块

EAHITechnology 发表于 2023-03-23 14:55

Tags:rust, 日报

sqlx 真的很好,但是你肯定会受到编译时间的影响

“sqlx 真的很好,但是你肯定会受到编译时间的影响”。在我以前的工作中大量使用 sqlx 之后,这确实引起了我的共鸣。 即使使用 Ryzen 3700,"cargo check" 时间也从 5 秒增加到 10 秒再到 20 秒,虽然 cargo sqlx prepare 远程数据库是休息一下的好借口。 但是我们必须做点什么…… -- CosmicHorror


文章说的问题出现在 sqlx v0.5-v0.6 上, sqlx 的主要卖点之一是它可以针对实际数据库执行编译时查询检查。这意味着如果你有这样的代码:

use sqlx::{Result, SqliteConnection};

struct User {
    id: i64,
    name: String,
}

async fn get_user_by_id(db: &mut SqliteConnection, id: i64) -> Result<User> {
    sqlx::query_as!(
        User,
        "SELECT id, name FROM User WHERE id = ?",
        id,
    )
    .fetch_one(db)
    .await
}

并使用 DATABASE_URL env var 设置为您的数据库 URL 进行编译,然后它将在编译时连接到数据库以验证查询是否有效以及 Rust 类型是否与数据库的返回类型匹配。 如果您的查询无效,或者您的数据库类型与其对应的 Rust 不匹配,那么您将遇到编译时错误。

11c11
<         "SELECT id, name FROM User WHERE id = ?",
---
>         "SELECT id, name FROM User WHERE id = ? AND NOT deleted",
$ DATABASE_URL='sqlite:blog.db' cargo check
    Checking sqlx_blog v0.1.0 (/.../sqlx_blog)
error: error returned from database: (code: 1) no such column: deleted
  --> src/lib.rs:9:5
   |
9  | /     sqlx::query_as!(
10 | |         User,
11 | |         "SELECT id, name FROM User WHERE id = ? AND NOT deleted",
12 | |         id,
13 | |     )
   | |_____^
   |
   = note: this error originates in the macro ...

error: could not compile `sqlx_blog` due to previous error

当然,每次构建时都需要数据库连接可能会很麻烦(例如在 CI 中),因此 sqlx 还提供了进行离线构建的选项。 您只需将“offline”功能添加到 sqlx,现在您可以使用 sqlx-cli 中的 cargo-sqlx 准备一个 sqlx-data.json 文件来描述您的所有查询。

$ DATABASE_URL='sqlite:blog.db' cargo sqlx prepare
   Compiling sqlx_blog v0.1.0 (/.../sqlx_blog)
    Finished dev [unoptimized + debuginfo] target(s) in 0.10s
query data written to `sqlx-data.json` in the current directory; please ...

$ bat --plain sqlx-data.json
{
  "db": "SQLite",
  "{query_hash}": {
    "describe": {
      ... // A lot of information on the query
    },
    "query": "SELECT id, name FROM User WHERE id = ?"
  }
  ... // More entries for all other queries
}

现在,您的项目可以使用 sqlx-data.json 中的信息进行构建,而无需实时连接到数据库。


在编译时连接到数据库是人们在谈论 sqlx 时通常提到的那种 proc-macro (ab) 用法。它是把双刃剑。 在编译时启用离线模式时,67.5% 的时间被 expand_crate 占用了,似乎让 sqlx 成为了一个非常大的焦点,但是它在做什么占用了这么多时间? 然后作者的目光落在了那个约 500 KiB 的 sqlx-data.json 文件上,500 KiB 并没有那么大它可能真的处理得很差?JSON 打印得很漂亮。也许如果我们压缩它

$ hyperfine --warmup 1 --prepare 'touch src/lib.rs' 'cargo check'
Benchmark 1: cargo check
  Time (mean ± σ):      19.273 s ±  0.202 s    [User: 18.977 s, System: 0.542 s]
  Range (min … max):    18.839 s … 19.524 s    10 runs

$ mv sqlx-data.json sqlx-data.json.pretty

$ cat sqlx-data.json.pretty | jq -c > sqlx-data.json

$ hyperfine --warmup 1 --prepare 'touch src/lib.rs' 'cargo check'
Benchmark 1: cargo check
  Time (mean ± σ):      14.965 s ±  0.294 s    [User: 14.685 s, System: 0.531 s]
  Range (min … max):    14.449 s … 15.368 s    10 runs

-22% 的变化是巨大的,但现在留下了为什么 JSON 解析如此缓慢的问题。即使 sqlx 正在做一些非常糟糕的事情,比如为每个宏重新读取整个文件,那么仍然只有几百个查询。497 KiB * 203 查询结果为98.5 MiB,这对于优化的 JSON 解析器来说应该不算什么。cargo 使得改变我们构建依赖关系的方式变得足够容易。为什么不将其设置为对所有与 proc-macro 相关的位进行优化构建?那应该会加快很多事情。查阅文档 ,我们看到我们可以通过添加对所有 proc-macros 及其依赖项进行优化构建

[profile.dev.build-override]
opt-level = 3

我们再次执行

$ hyperfine --warmup 1 --prepare 'touch src/lib.rs' 'cargo check'
Benchmark 1: cargo check
  Time (mean ± σ):      7.021 s ±  0.077 s    [User: 6.696 s, System: 0.539 s]
  Range (min … max):    6.887 s …  7.176 s    10 runs

这样就又快了 12 秒,至此我们为这个问题打上一个创可贴。作者还在文章中对 sqlx 的代码层面对 json 解析进行了优化,并添加了缓存,使得在此基础上又快了 1s 。目前代码已经合并到了 sqlx 的更高版本中。PR地址

  • https://cosmichorror.dev/posts/speeding-up-sqlx-compile-times/

gptcommit -- 可以想 gpt3 提交代码的 git hook

用于使用 GPT-3 编写提交消息的 git prepare-commit-msg 挂钩。使用此工具,您可以轻松生成清晰、全面和描述性的提交消息,让您专注于编写代码。

  • https://github.com/zurawiki/gptcommit

From 日报小组 侯盛鑫 mock

社区学习交流平台订阅:

评论区

写评论

还没有评论

1 共 0 条评论, 1 页