Most search applications using Apache Lucene assign a unique id, or primary key, to each indexed document. While Lucene itself does not require this (it could care less!), the application usually needs it to later replace, delete or retrieve that one document by its external id. Most servers built on top of Lucene, such as Elasticsearch and Solr, require a unique id and can auto-generate one if you do not provide it.

Sometimes your id values are already pre-defined, for example if an external database or content management system assigned one, or if you must use a URI, but if you are free to assign your own ids then what works best for Lucene?

One obvious choice is Java's UUID class, which generates version 4 universally unique identifiers, but it turns out this is the worst choice for performance: it is 4X slower than the fastest. To understand why requires some understanding of how Lucene finds terms.

BlockTree terms dictionary

The purpose of the terms dictionary is to store all unique terms seen during indexing, and map each term to its metadata (docFreqtotalTermFreq, etc.), as well as the postings (documents, offsets, postings and payloads). When a term is requested, the terms dictionary must locate it in the on-disk index and return its metadata.

The default codec uses the BlockTree terms dictionary, which stores all terms for each field in sorted binary order, and assigns the terms into blocks sharing a common prefix. Each block contains between 25 and 48 terms by default. It uses an in-memory prefix-trie index structure (an FST) to quickly map each prefix to the corresponding on-disk block, and on lookup it first checks the index based on the requested term's prefix, and then seeks to the appropriate on-disk block and scans to find the term.

In certain cases, when the terms in a segment have a predictable pattern, the terms index can know that the requested term cannot exist on-disk. This fast-match test can be a sizable performance gain especially when the index is cold (the pages are not cached by the the OS's IO cache) since it avoids a costly disk-seek. As Lucene is segment-based, a single id lookup must visit each segment until it finds a match, so quickly ruling out one or more segments can be a big win. It is also vital to keep your segment counts as low as possible!

Given this, fully random ids (like UUID V4) should perform worst, because they defeat the terms index fast-match test and require a disk seek for every segment. Ids with a predictable per-segment pattern, such as sequentially assigned values, or a timestamp, should perform best as they will maximize the gains from the terms index fast-match test.

Testing Performance

I created a simple performance tester to verify this; the full source code is here. The test first indexes 100 million ids into an index with 7/7/8 segment structure (7 big segments, 7 medium segments, 8 small segments), and then searches for a random subset of 2 million of the IDs, recording the best time of 5 runs. I used Java 1.7.0_55, on Ubuntu 14.04, with a 3.5 GHz Ivy Bridge Core i7 3770K.

Since Lucene's terms are now fully binary as of 4.0, the most compact way to store any value is in binary form where all 256 values of every byte are used. A 128-bit id value then requires 16 bytes.

I tested the following identifier sources:

For the UUIDs and Flake IDs I also tested binary encoding in addition to their standard (base 16 or 36) encoding. Note that I only tested lookup speed using one thread, but the results should scale linearly (on sufficiently concurrent hardware) as you add threads.

UUID K lookups/sec, 1 thread0150300450600Zero-pad sequentialUUID v1 [binary]NanotimeUUID v1SequentialFlake [binary]FlakeUUID v4 [binary]UUID v4

ID Source K lookups/sec, 1 thread
Zero-pad sequential 593.4
UUID v1 [binary] 509.6
Nanotime 461.8
UUID v1 430.3
Sequential 415.6
Flake [binary] 338.5
Flake 231.3
UUID v4 [binary] 157.8
UUID v4 149.4
 

Zero-padded sequential ids, encoded in binary are fastest, quite a bit faster than non-zero-padded sequential ids. UUID V4 (using Java's UUID.randomUUID()) is ~4X slower.

But for most applications, sequential ids are not practical. The 2nd fastest is UUID V1, encoded in binary. I was surprised this is so much faster than Flake IDs since Flake IDs use the same raw sources of information (time, node id, sequence) but shuffle the bits differently to preserve total ordering. I suspect the problem is the number of common leading digits that must be traversed in a Flake ID before you get to digits that differ across documents, since the high order bits of the 64-bit timestamp come first, whereas UUID V1 places the low order bits of the 64-bit timestamp first. Perhaps the terms index should optimize the case when all terms in one field share a common prefix.

I also separately tested varying the base from 10, 16, 36, 64, 256 and in general for the non-random ids, higher bases are faster. I was pleasantly surprised by this because I expected a base matching the BlockTree block size (25 to 48) would be best.

There are some important caveats to this test (patches welcome)! A real application would obviously be doing much more work than simply looking up ids, and the results may be different as hotspot must compile much more active code. The index is fully hot in my test (plenty of RAM to hold the entire index); for a cold index I would expect the results to be even more stark since avoiding a disk-seek becomes so much more important. In a real application, the ids using timestamps would be more spread apart in time; I could "simulate" this myself by faking the timestamps over a wider range. Perhaps this would close the gap between UUID V1 and Flake IDs? I used only one thread during indexing, but a real application with multiple indexing threads would spread out the ids across multiple segments at once.

I used Lucene's default TieredMergePolicy, but it is possible a smarter merge policy that favored merging segments whose ids were more "similar" might give better results. The test does not do any deletes/updates, which would require more work during lookup since a given id may be in more than one segment if it had been updated (just deleted in all but one of them).

Finally, I used using Lucene's default Codec, but we have nice postings formats optimized for primary-key lookups when you are willing to trade RAM for faster lookups, such as this Google summer-of-code project from last year and MemoryPostingsFormat. Likely these would provide sizable performance gains!

 
转自:http://blog.mikemccandless.com/2014/05/choosing-fast-unique-identifier-uuid.html

Choosing a fast unique identifier (UUID) for Lucene——有时间再看下的更多相关文章

  1. A Universally Unique IDentifier (UUID) URN Namespace

    w Network Working Group P. Leach Request for Comments: 4122 Microsoft Category: Standards Track M. M ...

  2. Atitit 深入了解UUID含义是通用唯一识别码 (Universally Unique Identifier),

    Atitit 深入了解UUID含义是通用唯一识别码 (Universally Unique Identifier), UUID1 作用1 组成1 全球唯一标识符(GUID)2 UUID 编辑 UUID ...

  3. java生成UUID通用唯一识别码 (Universally Unique Identifier)

    转自:http://blog.csdn.net/carefree31441/article/details/3998553 UUID含义是通用唯一识别码 (Universally Unique Ide ...

  4. (转)java生成UUID通用唯一识别码 (Universally Unique Identifier)

    (原文链接:http://blog.csdn.net/carefree31441/article/details/3998553)   UUID含义是通用唯一识别码 (Universally Uniq ...

  5. java生成UUID通用唯一识别码 (Universally Unique Identifier) 分类: B1_JAVA 2014-08-22 16:09 331人阅读 评论(0) 收藏

    转自:http://blog.csdn.net/carefree31441/article/details/3998553 UUID含义是通用唯一识别码 (Universally Unique Ide ...

  6. 全局唯一标识符(GUID,Globally Unique Identifier)

    全局唯一标识符(GUID,Globally Unique Identifier)是一种由算法生成的二进制长度为128位的数字标识符.GUID主要用于在拥有多个节点.多台计算机的网络或系统中.在理想情况 ...

  7. android unique identifier

    android get device mac address programmatically http://android-developers.blogspot.jp/2011/03/identi ...

  8. HTML5 + JS 网站追踪技术:帆布指纹识别 Canvas FingerPrinting Universally Unique Identifier,简称UUID

    1 1 1 HTML5 + JS  网站追踪技术:帆布指纹识别 Canvas FingerPrinting 1 一般情况下,网站或者广告联盟都会非常想要一种技术方式可以在网络上精确定位到每一个个体,这 ...

  9. 设备唯一标识方法(Unique Identifier):如何在Windows系统上获取设备的唯一标识 zz

    原文地址:http://www.vonwei.com/post/UniqueDeviceIDforWindows.html 唯一的标识一个设备是一个基本功能,可以拥有很多应用场景,比如软件授权(如何保 ...

随机推荐

  1. nginx的access_log与error_log

     参考文章:https://juejin.im/post/5aa09bb3f265da238f121b6c 本篇文章主要介绍一下 nginx 服务器两种日志查看:access_log.error_lo ...

  2. JSONObject和URL以及HttpURLConnection的使用

    1 将java对象类转成json格式 首先引入依赖jar文件 注意依赖文件的版本号,高版本可能没有对应的类 2 我的实体类中包含内部类注意内部类要public才能被序列化成json格式 import ...

  3. Java 之 InputStreamReader 类

    InputStream 类 1.概述 转换流 java.io.InputStreamReader ,是Reader的子类,是从字节流到字符流的桥梁.  该类读取字节,并使用指定的字符集将其解码为字符. ...

  4. UI5-技术篇-Implementing Expand Entity/Entity Set

    转载:https://blogs.sap.com/2014/07/18/implementing-expand-entityentity-set/ Requirement Considering a ...

  5. stm32 ADXL345传感器

    加速度灵敏度轴 沿敏感轴加速时相应输出电压增加 寄存器映射 寄存器定义 0x31-DATA_FORMAT SELF_TEST位:设置为1,自测力应用至传感器,造成输出数据转换.值为0时,禁用自测力 S ...

  6. 铰链joints

    Fixed Joint原理像阶层里的父子结构.关节会将对象锁在一个世界坐标或者锁在一个连接的刚体. 固定关节可以设定断裂力道(Break Farce)和断裂扭力(Break torque),破坏关节所 ...

  7. Spring Boot全局异常处理

    本文为大家分享了Spring Boot全局异常处理,供大家参考,具体内容如下 1.后台处理异常 a.引入thymeleaf依赖 <!-- thymeleaf模板插件 --> <dep ...

  8. python RSA加密、解密、签名

    python RSA加密.解密.签名 python中用于RSA加解密的库有好久个,本文主要讲解rsa.M2Crypto.Crypto这三个库对于RSA加密.解密.签名.验签的知识点. 知识基础 加密是 ...

  9. 1205 CSRF跨站请求与django中的auth模块使用

    目录 今日内容 昨日回顾 基于配置文件的编程思想 importlib模块 简单代码实现 跨站请求伪造csrf 1. 钓鱼网站 如何实现 模拟该现象的产生 2. 解决问题 解决 {% csrf_toke ...

  10. 《AlwaysRun!团队》第四次作业:项目需求调研与分析

     项目  内容  这个作业属于哪个课程 http://www.cnblogs.com/nwnu-daizh/  这个作业的要求在哪里 https://www.cnblogs.com/nwnu-daiz ...