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_size
和 batch_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,对于大多数工作负载来说已经足够了。如果您正在处理高吞吐量工作负载,可以增加此限制。您甚至可以通过将其设置为零来完全禁用此限制。请注意,这通常会导致对象存储出现过多的重试和超时问题。