跳到内容

Lance 性能指南

本指南提供了优化 Lance 应用程序性能的技巧和窍门。

追踪事件

Lance 使用追踪来记录事件。如果您正在运行 pylance,那么这些事件将作为日志消息发出。对于 Rust 连接,您可以使用 tracing crate 来捕获这些事件。

文件审计

当重要文件被创建或删除时,会发出文件审计事件。

事件 参数 描述
lance::file_audit 模式 I/O 操作的模式(创建、删除、未验证删除)
lance::file_audit 类型 受影响的文件类型(清单、数据文件、索引文件、删除文件)

I/O 事件

当执行重要的 I/O 操作时,特别是与索引相关的操作时,会发出 I/O 事件。当索引从内存缓存加载时,不会发出这些事件。正确的缓存利用对于性能很重要,这些事件旨在帮助您调试缓存使用情况。

事件 参数 描述
lance::io_events 类型 I/O 操作的类型(打开标量索引、打开向量索引、加载向量部分、加载标量部分)

执行事件

当执行计划运行时,会发出执行事件。这些事件对于调试查询性能很有用。

事件 参数 描述
lance::execution 类型 执行事件的类型(目前只有 plan_run 一种类型)
lance::execution output_rows 计划输出中的行数
lance::execution iops 计划执行的 I/O 操作次数
lance::execution bytes_read 计划读取的字节数
lance::execution indices_loaded 计划加载的索引数
lance::execution parts_loaded 计划加载的索引分区数
lance::execution index_comparisons 在各种索引中执行的比较次数

线程模型

Lance 旨在实现线程安全和高性能。除非另有明确说明,否则可以并发调用 Lance API。用户可以创建多个表并在线程之间共享表。操作可以在同一表上并行运行,但某些操作可能导致冲突。有关详细信息,请参阅冲突解决

大多数 Lance 操作将使用多个线程并行执行工作。Lance 中有两个线程池:I/O 线程池和计算线程池。I/O 线程池用于从磁盘读取和写入数据。计算线程池用于对数据执行计算。用户可以配置每个池中的线程数。

I/O 线程池用于从磁盘读取和写入数据。I/O 线程池中的线程数由操作使用的对象存储决定。本地对象存储默认使用 8 个线程。云对象存储默认使用 64 个线程。这是一个相当保守的默认值,您可能需要 128 或 256 个线程才能在某些云提供商上饱和网络带宽。可以使用 LANCE_IO_THREADS 环境变量来覆盖 I/O 线程数。如果您增加此变量,您可能还需要增加 io_buffer_size

计算线程池用于对数据执行计算。计算线程池中的线程数由机器上的核心数决定。可以通过设置 LANCE_CPU_THREADS 环境变量来覆盖计算线程池中的线程数。这通常在同一台机器上运行多个 Lance 进程时完成(例如,在使用 Ray 等工具时)。请记住,解码数据是计算密集型操作,即使工作负载看起来是 I/O 密集型(例如扫描表),它仍然可能需要相当多的计算线程才能实现峰值性能。

内存要求

Lance 旨在实现内存效率。操作应该从磁盘流式传输数据,而不需要将整个数据集加载到内存中。但是,Lance 中有几个组件可能会占用大量内存。

索引缓存

Lance 使用索引缓存来加速查询。这会将向量和标量索引缓存在内存中。在创建 LanceDataset 时,可以使用 index_cache_size 参数配置此缓存的最大大小。此缓存是一个 LRU 缓存,按“条目数”进行大小调整。每个条目的大小以及所需的条目数取决于所讨论的索引。例如,IVF/PQ 向量索引包含 1 个头部条目和每个分区 1 个条目。每个条目的大小由向量数和 PQ 参数(例如子向量数)决定。您可以通过检查 dataset.session().size_bytes() 的结果来查看此缓存的大小。

索引缓存不跨表共享。为了获得最佳性能,您应该创建一个表并在整个应用程序中共享它。

扫描数据

搜索(例如向量搜索、全文搜索)不会使用大量内存来保存数据,因为它们通常不会返回大量数据。但是,扫描数据可能会占用大量内存。扫描是流式操作,但我们需要足够的内存来保存正在扫描的数据。所需的内存量主要由 io_buffer_sizebatch_size 变量决定。

每个 I/O 线程应该有足够的内存来缓冲一个完整的数据页。目前,页面通常在 8 到 32 MB 之间。这意味着,根据经验法则,每个 I/O 线程通常应该有大约 32MB 的内存。默认的 io_buffer_size 是 2GB,足以缓冲 64 个数据页。如果您增加 I/O 线程数,您也应该增加 io_buffer_size

扫描还将并行在 CPU 线程上解码数据(并运行任何过滤或计算)。任何时候解码的数据量由 batch_size 和行的大小决定。每个 CPU 线程将需要足够的内存来容纳一个批次。一旦批次交付给您的应用程序,Lance 就不再跟踪它们,因此如果内存是一个问题,那么您还应该注意不要在自己的应用程序中积累内存(例如,通过运行 to_table 或以其他方式在内存中收集所有批次)。

默认的 batch_size 是 8192 行。当您主要处理标量数据时,您希望将批次保持在 1MB 左右,因此计算线程所需的内存量相当小。但是,当处理大数据时,您可能需要调低 batch_size 以控制内存使用。例如,当处理 1024 维向量嵌入(例如 32 位浮点数)时,8192 行将是 32MB 数据。如果您将其分布在 16 个 CPU 线程上,那么每次扫描将需要 512MB 的计算内存。您可能会发现每批处理 1024 行更合适。

总而言之,扫描可能使用高达 (2 * io_buffer_size) + (batch_size * num_compute_threads) 字节的内存。请记住,io_buffer_size 是一个软限制(例如,我们目前不能一次读取少于一页的数据),因此如果您看到内存使用量略微超过此限制,不一定是一个错误。

上述限制是指每次扫描的限制。整个进程还会应用一个额外的 IOPS 限制。此限制由 LANCE_PROCESS_IO_THREADS_LIMIT 环境变量指定。默认值为 128,对于大多数工作负载来说已经足够了。如果您正在处理高吞吐量工作负载,可以增加此限制。您甚至可以通过将其设置为零来完全禁用此限制。请注意,这通常会导致对象存储出现过多的重试和超时问题。