Lance性能指南

本指南提供优化Lance应用性能的技巧与诀窍。

追踪事件

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

文件审计

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

事件

参数

描述

lance::file_audit

mode

I/O操作的模式(创建、删除、删除未验证)

lance::file_audit

type

受影响的文件类型(清单文件、数据文件、索引文件、删除文件)

输入/输出事件

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

事件

参数

描述

lance::io_events

type

I/O操作的类型(open_scalar_index, open_vector_index, load_vector_part, load_scalar_part)

执行事件

执行事件在执行计划运行时被触发。这些事件对于调试查询性能非常有用。

事件

参数

描述

lance::execution

type

执行事件的类型(目前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操作会使用多线程并行执行任务。LanceDB中有两个线程池:IO线程池和计算线程池。IO线程池用于从磁盘读写数据,计算线程池用于对数据进行计算处理。每个池中的线程数可由用户配置。

IO线程池用于从磁盘读写数据。IO线程池中的线程数量由操作所关联的对象存储决定。本地对象存储默认使用8个线程,云对象存储默认使用64个线程。这是一个相当保守的默认值,在某些云服务提供商上,可能需要128或256个线程才能充分利用网络带宽。可以通过LANCE_IO_THREADS环境变量来覆盖IO线程数。如果增加此变量,可能还需要同时增加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到32MB之间。这意味着,作为经验法则,通常每个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是一个软性限制(例如目前我们无法一次读取少于一个页面) 因此如果您看到内存使用量略微超过此限制,不一定是个错误。