Choosing a fast unique identifier (UUID) for Lucene——有时间再看下
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.
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 (docFreq, totalTermFreq, 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:
- Sequential IDs (0, 1, 2, ...), binary encoded.
- Zero-padded sequential IDs (00000000, 00000001, ...), binary encoded.
- Nanotime, binary encoded. But remember that nanotime is tricky.
- UUID V1, derived from a timestamp, nodeID and sequence counter, using this implementation.
- UUID V4, randomly generated using Java's
UUID.randomUUID(). - Flake IDs, using this implementation.
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.
| 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!
Choosing a fast unique identifier (UUID) for Lucene——有时间再看下的更多相关文章
- A Universally Unique IDentifier (UUID) URN Namespace
w Network Working Group P. Leach Request for Comments: 4122 Microsoft Category: Standards Track M. M ...
- Atitit 深入了解UUID含义是通用唯一识别码 (Universally Unique Identifier),
Atitit 深入了解UUID含义是通用唯一识别码 (Universally Unique Identifier), UUID1 作用1 组成1 全球唯一标识符(GUID)2 UUID 编辑 UUID ...
- java生成UUID通用唯一识别码 (Universally Unique Identifier)
转自:http://blog.csdn.net/carefree31441/article/details/3998553 UUID含义是通用唯一识别码 (Universally Unique Ide ...
- (转)java生成UUID通用唯一识别码 (Universally Unique Identifier)
(原文链接:http://blog.csdn.net/carefree31441/article/details/3998553) UUID含义是通用唯一识别码 (Universally Uniq ...
- 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 ...
- 全局唯一标识符(GUID,Globally Unique Identifier)
全局唯一标识符(GUID,Globally Unique Identifier)是一种由算法生成的二进制长度为128位的数字标识符.GUID主要用于在拥有多个节点.多台计算机的网络或系统中.在理想情况 ...
- android unique identifier
android get device mac address programmatically http://android-developers.blogspot.jp/2011/03/identi ...
- HTML5 + JS 网站追踪技术:帆布指纹识别 Canvas FingerPrinting Universally Unique Identifier,简称UUID
1 1 1 HTML5 + JS 网站追踪技术:帆布指纹识别 Canvas FingerPrinting 1 一般情况下,网站或者广告联盟都会非常想要一种技术方式可以在网络上精确定位到每一个个体,这 ...
- 设备唯一标识方法(Unique Identifier):如何在Windows系统上获取设备的唯一标识 zz
原文地址:http://www.vonwei.com/post/UniqueDeviceIDforWindows.html 唯一的标识一个设备是一个基本功能,可以拥有很多应用场景,比如软件授权(如何保 ...
随机推荐
- 你听过稀疏数组(sparseArray)吗?
稀疏数组(sparseArray) 基本介绍 当一个数组中大部分元素为0,或者为同一个值的数组时,可以使用稀疏数组来保存该数组. 稀疏数组的处理方法是: 1.记录数组一共有几行几列,有多少个不同的值 ...
- CSS ,flex: 1的用处
flex: 1:的妙用 首先 flex 是 flex-grow.flex-shrink.flex-basis的缩写. 当 flex 取值为一个非负数字,则该数字为 flex-grow 值,flex- ...
- requests模块的基本用法
requests 什么是requests模块 python中封装好的一个基于网络请求的模块 作用 用来模拟浏览器发送请求 环境安装 pip install requests 编码流程 指定 url 发 ...
- laravel withCount 统计关联数量
roleModel定义关联 hasmany public function users(){ return $this->hasMany('App\Models\Users', 'role_i ...
- linux7 上安装mongodb4.2.1操作步骤
MongoDB是一个通用的.基于文档的分布式数据库,它是为现代应用程序开发人员和云时代而构建的.没有数据库能让你更有效率. 1.下载需要的软件包https://www.mongodb.com/down ...
- nginx日志配置笔记:if条件
1.特定条件写日志: 参照: https://stackoverflow.com/questions/19011719/how-to-write-only-logs-with-200-status h ...
- docker 安装elk
https://www.cnblogs.com/fbtop/p/11005469.html
- pypython解构
*******重点 解构:把线性结构的元素解开,并按顺序的赋给其他变量 左边接纳的要与右边解开的个数一致. lst = [3,5] first,second = lst print(first,sec ...
- Error: EACCES: permission denied when trying to install ESLint using npm
https://stackoverflow.com/questions/35954725/error-eacces-permission-denied-when-trying-to-install-e ...
- P1330 封锁阳光大学[搜索+染色]
题目来源:洛谷 题目描述 曹是一只爱刷街的老曹,暑假期间,他每天都欢快地在阳光大学的校园里刷街.河蟹看到欢快的曹,感到不爽.河蟹决定封锁阳光大学,不让曹刷街. 阳光大学的校园是一张由N个点构成的无向图 ...