感恩字节

如果用一个词来总结我在字节的日子,我想我会用成长。用这个词也很自然吧,毕竟初入职场后最重要的十年待在字节,并且还是一家成长这么快的公司,经常我还会反思自己的成长速度远远没有跟上公司的需要。对于字节我只有感恩,他不仅给了我超预期的工资回报,更重要的是给我有挑战的项目帮助我成长和证明自己,也给了我很多可以联系一辈子的朋友。 我要感谢我在字节的 leader 们,谢谢你们对我的指导和信任,特别是早期的 leader 容忍了我很多不成熟的做法;对一起合作过的同学,过往如果有沟通方式或者做事方式不对的地方,这里说一声抱歉;对于我之前指导过或者帮助过的同学,你们都有一个美好的未来,而且其中有不少已经做到了比我更重要的岗位。 字节依旧是一家伟大的公司,依旧会有很好的发展,祝福在字节奋斗的小伙伴们!

2025/8/4
articleCard.readMore

duckdb-rs will be the offical DuckDB rust client

Background DuckDB is an in-process SQL OLAP database management system implemented in C++. When it was first open-sourced, it was positioned as a columnar database comparable to SQLite, providing the same ease of use. With just a header file and a cpp file, it could be easily embeded in any program, even offering a SQLite-compatible interface, which caught the attention of many people source. I started paying attention to DuckDB a long time ago and began writing the first line of duckdb-rs code on June 7, 2021. About a month later, I wrote a blog post introducing the process of building this library, marking the completion of the initial version. Over the past two years, I have released approximately 19 versions, get more than 200 stars in GitHub.

2023/7/26
articleCard.readMore

duckdb-rs 即将成为 DuckDB 官方 rust 客户端

背景 DuckDB 是一个 C++ 编写的单机版嵌入式分析型数据库。它刚开源的时候是对标 SQLite 的列存数据库,并提供与 SQLite 一样的易用性,编译成一个头文件和一个 cpp 文件就可以在程序中使用,甚至提供与 SQLite 兼容的接口,因此受到了很多人的关注。 我很久之前就开始关注 DuckDB,并在 2021-06-07 开始写第一行 duckdb-rs 的代码,在 一个多月后写了一篇博客介绍了构建这个库的过程,算是实现了第一个版本。到今天差不多2年的时间,前后发布了19个版本,收获了 200 多个star。 最近一年其实还有很多需求和想法去做优化,但是发现自己并没有那么多时间,收到的 issue 也越来越多。经过沟通,我会把这个库转给 DuckDB 官方来维护,相信 duckdb-rs 一定会发展得越来越好。同时也非常感谢 Mark 和 Hannes 愿意接手这个仓库并把它作为官方的 rust 客户端。 这篇博客总结下我维护的这段时间主要做的事,以及我认为可以改善的点,算是对过去的总结和对未来的憧憬。 关键决策 这个库是 duckdb 的 rust 客户端,所以关注这个库的群体首先是认可 duckdb 的用户,其次因为他们是 rust 技术栈。下面我列举一些我认为是让这个库“成功”的一些关键点。 初始版本基于 rusqlite 开发。因为我也是一个 rust 初学者,之前只拿 rust 做过一个项目,这是第二次使用 rust。基于 rustqlite 这样一个成熟的仓库做改造,能让我很快得到一个可用的版本,快速建立信心;另外整个程序的组织,API 的设计都已经经过了验证,不容易走弯路;整体的代码质量也能有基本保障。 基于 arrow 格式来交换数据。arrow 现在基本上算是列存储的数据交换标准,在很多开源项目中都有使用,duckdb 对 arrow 的支持也比较完善。虽然 duckdb 有自己的原生 C 接口,但是基于 arrow 格式来做数据交换,能让 rust 和 c-api 调用相对稳定,不会因为 duckdb 迭代导致 C 接口的变更,我们也需要一直变更,一定程度上减轻了维护的工作量,也减少了接口变更对用户的影响。 完善的 CI 流程,我认为所有的开源项目都应该要做到这一点。因为继承自 rusqlite,这个库从一开始就有 CI 流程,能保证合并到 master 的代码是没问题的,并且 CI 里面还有关于内存泄漏的检测,避免了 ffi 带了的可能不安全的问题。发布过程也是自动化的,只要打个 tag 就自动发布到 crate。CI 的机制保障了任何感兴趣的人都可以提交 MR 并得到检验,也保证自己如果长时间不维护了不至于都不知道从哪里开始改。 几个 MR 下面我挑选几个我认为比较关键的,并且不是我贡献的 MR:

2023/7/26
articleCard.readMore

基于 apache-arrow 的 duckdb rust 客户端

背景 duckdb 是一个 C++ 编写的单机版嵌入式分析型数据库。它刚开源的时候是对标 SQLite 的列存数据库,并提供与 SQLite 一样的易用性,编译成一个头文件和一个 cpp 文件就可以在程序中使用,甚至提供与 SQLite 兼容的接口,因此受到了很多人的关注。 本文介绍笔者近期开发的 duckdb-rs 库,让大家可以很方便地在 rust 代码库中使用 duckdb 的功能。 libduckdb-sys 了解过 rust 的同学可能知道,rust 提供了 ffi 的方式与其他语言互通。因为 duckdb 本身是 C++ 编写的,想要在 rust 里面使用 duckdb,就需要考虑 ffi 的问题。而基于 ffi 对其他语言程序封装的基础库,一般会被命名为 libxxx-sys,这也就是 libduckdb-sys 的由来。 为了方便大家使用,duckdb 提供了 C++ 原生接口,C 接口,以及与 SQLite3 兼容的 C 接口。我在做 libduckdb-sys 的时候对这三种接口都尝试过,相关的讨论可以参见 Rust Support,我这里介绍一下当时的情况。 基于 SQLite3 接口 最开始我使用的是 SQLite3 的接口,原因主要有三个: 我对 SQLite 比较熟悉,想必用起来会比较方便; 觉得 SQLite 的接口被广泛使用,接口比较稳定,以后不至于大改; 也许是最重要的一点,市面上已经有 SQLite 的 rust 封装rusqlite,基于 SQLite 的接口应该能最大程度复用 rusqlite 的代码。 尝试之后确实发现很快能把程序跑起来,基本的功能也能使用。但是随着进一步的深入以及对 duckdb 更多的了解,发现了一些弊端:

2021/7/27
articleCard.readMore

Simple: SQLite3 结巴分词插件

一年前开发 simple 分词器,实现了微信在两篇文章中描述的,基于 SQLite 支持中文和拼音的搜索方案。具体背景参见这篇文章。项目发布后受到了一些朋友的关注,后续也发布了一些改进,提升了项目易用性。 最近重新体验微信客户端搜索功能,发现对于中文的搜索已经不是基于单字命中,而是更精准的基于词组。比如搜索“法国”,之前如果句子中有“法”和“国”两个字时也会命中,所以如果一句话里包含“国法”就会被命中,但是这跟“法国”没有任何关系。 本文描述对 simple 分词器添加的基于词组命中的实现,从而实现更好的查找效果。另外本文也会基于之前在 issue 中大家提到的问题,提供一个怎么使用 SQLite FTS 表的建议。 背景 先简单回顾一下之前的实现,因为结巴分词只跟中文有关,所以本文会略去拼音的部分。 搜索主要分为两部分,建立索引和命中索引。为了实现中文的搜索,我们先把句子按照单字拆分,按照单字建立索引;然后对于用户的输入,也同样按照单字拆分,这样 query 就能命中索引了。为了支持词组搜索,再按照单字拆分就很难满足需求了,所以可以考虑的方案是要么改索引,要么改 query。如果改索引的话会有一些问题,比如如果用户就输入了一个字比如“国”,但是我们建索引的时候把“法国”放到了一起,那“国”字就命中不了了,所以最好是保持单字索引不变,通过改写 query 来达到检索词组的效果。 实现 simple 分词器之前提供了一个 simple_query() 函数来帮助用户生成 query,我们也可以加一个新的函数来实现词组的功能。经过简单的调研,我们发现 cppjieba 用 C++ 实现了结巴分词的功能,很适用与我们的需求。 所以我实现了一个新的函数叫做 jieba_query() ,它的使用方式跟 simple_query() 一样,内部实现时,我们会先使用 cppjieba 对输入进行分词,再根据分词的结果构建 SQLite3 能理解的 query ,从而实现了词组匹配的功能。具体的逻辑可以参考 这里 。对于不需要结巴分词功能的用户,可以在编译的时候使用 -DSIMPLE_WITH_JIEBA=OFF 关闭结巴分词的功能,这样能减少编译文件的大小,方便客户端对文件大小敏感的场景使用。 使用 本文想着重介绍一下 SQLite3 FTS5 功能使用的问题,这些问题都是有朋友在项目的 issue 中提到过的,都是非常好的问题,但是也说明有不少人对怎么使用 FTS 表不太清楚,希望本文能解决一些疑惑。 首先第一点,FTS5 表虽然是一个虚拟表,提供了全文搜索的功能,但是它整体还是跳不出 SQL 的范畴,所以其实很多用法和其他 SQL 表是一样的,当然它也跳不出 SQL 的限制。比如有一个 issue 问如果表中有多列的时候,能不能检索全表,但是只返回命中的那些列?答案是不行的,因为按照 SQL 的语法规则,SELECT 语句后面必须显示说明你想要 SELECT 哪些列,所以结果列是必须用户指定的,如果我们像知道哪些列命中了,只能通过其他一些手段,感兴趣的朋友可以看这个 issue36。 另外 simple 分词器提供了不少额外的功能,比如 simple_query() 和 simple_highlight() 等辅助函数,但是它并不影响我们使用原有 FTS5 的功能,比如如果想按照相关度排序,FTS5 自带的 order by rank 功能还是可以继续可以使用,也就是说 FTS5 页面 提供的所有功能都是可以和 simple 分词器一起使用的。

2021/2/21
articleCard.readMore

xeus-clickhouse: Jupyter 的 ClickHouse 内核

在科学计算领域,Jupyter 是一个使用非常广泛的集成开发环境,它支持多种主流的编程语言比如 Python, C++, R 或者 Julia。同时,数据科学最重要的还是数据,而 SQL 是操作数据最直观的语言。前段时间看到一篇文章,有人给 sqlite 做了一个 jupyter 的内核,感觉很有意思。所以我尝试给 ClickHouse 做了一个 jupyter 的内核,目前已经有了一个可以试用的版本,下面做一个简单介绍。 现状 新内核允许用户用 ClickHouse SQL 的语法直接操作远程 CH 数据库,通过一些扩展操作比如 %CONNECT 支持与 ch cli 一样的连接参数,后续也有计划使用 jupyter magics 支持更多的数据可视化操作。 项目参考了 jupyter sqlite 内核的实现方式,是基于 xeus 框架来实现的。xeus 是一个 c++ 的 lib 库,它对 jupyter 的内核做了很好的封装,我们只需要专注于内核相关的功能就可以了。目前对于 ch 的操作基于 clickhouse-cpp 来实现,它是 ch 的 cpp 客户端。 目前实现处于早期阶段,但是基础功能已经可用。它支持了几乎 CH 所有 SQL 语法,具体例子可以参考 clickhouse.ipynb。xeus-clickhouse 在 jupyter notebook 和 jupyter lab 中以 HTML 表格的形式展示数据;在 jupyter console 中,我们使用 tabulate 库只做纯文本的表格。

2020/6/28
articleCard.readMore

用 od 查看 ClickHouse 的索引文件

背景 学习 ClickHouse (后面简称 CH) 的时候,会对 CH 到底怎么组织磁盘上的 MergeTree 文件有很多疑惑。关于 MergeTree 的介绍可以参考[1],但是如果想具体看下磁盘上的文件,没有现成的工具。本文参考 [2] 介绍通过 od 查看磁盘文件的方法,感兴趣的话可以自己试一下,会对 MergeTree 有更深的理解。 本文以 官方Tutorial 中的 hits_v1 表为例来说明。下面主要描述怎么看 primary.idx 文件和 [column].mrk 文件。在 MergeTree 数据结构中,primary.idx 可认为是一级索引,mrk 文件是用作定位具体文件偏移量的,他们的行数是相同且一一对应。 查看 primary.idx primary.idx 里面的文件是把主键的索引写入到磁盘文件中,hits_v1 的主键为 order by 语句中的字段,即 ORDER BY (CounterID, EventDate, intHash32(UserID)),CounterID 类型是 uint32,存储为4字节;EventDate 类型是 Date,存储是 2字节整型;intHash32 是4字节整型。CH 的文件内容非常紧凑,每个字段是紧挨着写入的,没有其他类似空格符等浪费。所以 primary.idx 的存储格式是 4+2+4,然后每隔 8192 行写一行索引。查看内容的方法为: # sql 选择第一行索引的内容 Select CounterID,toRelativeDayNum(EventDate),intHash32(UserID) from tutorial.hits_v1 limit 0,1; # od 查看 3 个字段 od -An -i -j 0 -N 4 primary.idx od -An -i -j 4 -N 2 primary.idx od -An -i -j 6 -N 4 primary.idx # 类似的,sql 选择第二行索引的内容 Select CounterID,toRelativeDayNum(EventDate),intHash32(UserID) from tutorial.hits_v1 limit 8192,1; # od 查看 3 个字段 od -An -i -j 10 -N 4 primary.idx od -An -i -j 14 -N 2 primary.idx od -An -i -j 16 -N 4 primary.idx 关于 od 的选项介绍如下:

2020/3/18
articleCard.readMore

Spacemacs Intro

Intro video: https://www.ixigua.com/i6803300850765660676/ What’s spacemacs? A emacs configuration framework Support both emacs and vim editing styles Great programming tool Project management: SPC+p projectile SPC+p+p SPC+p+f SPC+p+t Search: SPC+s ripgrep SPC+* SPC+/ SPC+s+s Navigation: lsp Code: gd, SPC+j+i Buffer: SPC+b Window: SPC+w, ALT+num SPC+a Editing: vim evil Version Control: magit Shell: SPC+' eshell Help: SPC+?, SPC+h

2020/3/12
articleCard.readMore

Simple: 一个支持中文和拼音搜索的 sqlite fts5插件

之前的工作关系,需要在手机上支持中文和拼音搜索。由于手机上存储数据一般都是用 sqlite,所以是基于 sqlite3 fts5 来实现。这段时间再次入门 c++,所以想用 c++ 实现一下,一来用于练手,二来当时做的时候发现网络上这方面开源的实现不多,也造福下其他人。 背景 搜索现在几乎是每个 APP 必备的功能,用户已经习惯了搜索框搜一下,避免到处去找。搜索也是帮助用户查找旧信息,发现新功能的一个重要手段。平常我们用微信的时候经常会搜索联系人和聊天记录,发现微信这一块做的还是非常好的。关于微信的全文搜索,可以看看这两篇文章:微信全文搜索优化之路 和 微信移动端的全文检索多音字问题解决方案 。 第一篇文章主要是问题和原理的概述,第二篇文章是核心分词器的实现。我写的这个项目主要是实现了 simple 分词器,并提供一些辅助函数帮助使用。 Simple 分词器 搜索的核心是建倒排索引,建索引的核心是分词器。 跟名字一下,Simple 分词器的规则非常简单: 空白符跳过 连续的数字作为整体是一个索引 连续的英文字母作为整体并转换成小写索引 中文字单独建索引,并且把中文字转成拼音后也建搜索,这样就能同时支持中文和拼音检索。另外把拼音首字母也建索引,这样搜索 zjl 就能命中 “周杰伦”。 其他字符统一单独建索引,这样搜索 😊 也能搜到 上面的 5 条都比较好理解,关于中文为什么这么做(而不是连续的中文一起建索引),是由于客户端搜索的需求决定的。具体可以参考上面微信的两篇文章。 有了上面的规则,代码写起来就很简单了,核心逻辑 30 行就解决了。这块代码运行效率也比较高,一遍扫描 O(n) 的复杂度就完成了分词操作。 query 拆分 索引建好之后,query 需要根据分词规则来写才能查询到数据。比如根据上面的逻辑: 如果查数字,我们要把搜索词当作前缀来用,比如用户搜索 123, query 就需要换成 123*,这样如果索引里面有 12345 也能被搜索出来 对于英文,除了要当作前缀,还需要把搜索词转成小写,比如用护搜索 Hello,query 就需要换成 hello*, 这样如果索引里面有 HelloWorld 也能被命中 对于中文和其他字符,都要拆成单个的才能命中索引 最后对于拼音(其实我们没办法区分英文和拼音,统一当作拼音处理就行),需要把拼音按照规则拆分,因为我们的拼音索引是单字建立的。这样如果用户搜索 “zhangliangy”,拼音就可以被拆成 ‘zhang AND liang AND y*’,从而命中"张靓颖"。具体规则微信的文章中也有详述。 可以看到 query 词重构的逻辑也比较多,在之前的项目中没有好的办法,所以是自己在应用层代码里面组装好了 query 再给 sqlite 去搜的,这样其实不太方便。在这个项目中,我实现了一个 simple_query 的字符串函数,输入一个 string,它会给转换成组装好的搜索词,用法跟使用 sqlite 内置函数一样,这样就方便很多了,下面是一个例子:

2020/3/8
articleCard.readMore

About

Hi there github: https://github.com/wangfenjin

2020/3/1
articleCard.readMore

Showcase

Header 2 Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam nec interdum metus. Aenean rutrum ligula sodales ex auctor, sed tempus dui mollis. Curabitur ipsum dui, aliquet nec commodo at, tristique eget ante. Donec quis dolor nec nunc mollis interdum vel in purus. Sed vitae leo scelerisque, sollicitudin elit sed, congue ante. In augue nisl, vestibulum commodo est a, tristique porttitor est. Proin laoreet iaculis ornare. Nullam ut neque quam.

2018/7/18
articleCard.readMore