Show HN: Efficiently searching compressed text files
github.com
github.com
It works because the Burrows-Wheeler transform which is used in compression is closely related to the suffix array, which is a data structure used as a substring index. By adding a small amount of additional metadata to the compressed Burrows-Wheeler transformed string you can search for any substring. Its practical performance is slower than a suffix array, but the memory usage can be 100x lower.
It is nice that you were able to make it work with ordinary gzip files. In bioinformatics, an approach to this problem has been to create a gzip-backwards-compatible (i.e., can be read by zcat but not written to by gzip) format called BGZF [1] which acts like ordinary gzip except each compressed block is the same size, which eases indexing and partial decompression.
I would be interested to know how you solved the problem of finding the right block and offset when each compressed block can be an arbitrary size as it is in ordinary gzip format.
[1] http://blastedbio.blogspot.com/2011/11/bgzf-blocked-bigger-b...
The trick is to store the gzip internal state as a checkpoint every few hundred MB. Then to decode data at a particular offset, you find the further internal state that's before your offset; reinitialize the compression internal state with that, and then scan on until you get to the offset you need.
The zindex file that's generated contains both these checkpoints and the index that lets one scan there.
There's an open issue I'd like to resolve to add support for indexing a particular field: would support for arguments like those the "cut" UNIX utility allow for your use case?
Well, my exact use case, which I imagine I'm not alone in, is indexing a tab-delimited file with a single-line header containing the column names. So indexing by column index would be adequate, but it would present two very minor problems: 1) I'd have to figure out a way to exclude the header row from output (or alternately, to always output it), and 2) it would be slightly more convenient to refer to the columns to be indexed by name rather than position, as some of these have thousands of columns.
Can you have multiple indexes on the same GZIP file? And if so, does it create multiple .zindex files or re-use the same one?
I don't know if SQLite supports custom data retrieval backends like Postgres, but what would be REALLY interesting is to write one such that the indexing information is in SQLite, and you can use SQL to query row data directly from the GZIP index (I know this is beyond the scope of your project, I'm just thinking out loud now).
The code and file format support multiple indices in the same zindex file but I haven't got a decent way to pass command line arguments to define them yet!
Watch this space: also feel free to ping me on email for more comments!
We adapted his code at One Laptop Per Child to preload Wikipedia snapshots on every laptop:
http://blog.printf.net/articles/2008/06/02/wikipedia-on-xo/
http://blog.printf.net/articles/2010/05/09/peru-olpc-and-wik...
Restarting at every N-bytes boundary – for example, every 128K – is another option, though that requires a little more fancy thought to padding.
Another way to skip the need for an external index using any of these techniques, but still know the raw-data line/byte-offset when jumping to a deep position, is to encode those tallies (either chunk-by-chunk or from-start) as GZIP header "extra field" extensions. (Here's an example extension that just records per-chunk compressed- and uncompressed- byte-lengths: http://archive-access.sourceforge.net/warc/warc_file_format-...)
[1] https://www.usenix.org/conference/nsdi15/technical-sessions/...
For my use case I wanted to put stuff together without writing too much novel code; zindex uses a SQLite file to store indices and checkpoints. I'm hoping to add json support to zq/zindex, which means a general text search isn't that useful.
Additionally the original log files I run against in my use case are generated upstream of me and are many many multi-gigabyte gzipped log files. I didn't want to add too much extra storage for my logs: the "key" I index on is a tiny part of my log, so the storage space for the index is considerably less than the original text (even accounting for the gzip checkpoints).
My use case is MAC addresses. So, if I search for 'dead.beef.cafe', this would be perfect if it could know that I wanted to see entries indexed by 'DE:AD:BE:EF:CA:FE" also.
Is that possible? This tool is so amazing. If it did this, it would be able to hugely improve my daily workflow.
edit: Actually, it is going to be able to hugely improve my workflow anyway, because I often want to see data about IP version 4 addresses, too, and it handles those fine. But I'd still like to be able to use it for MACs.
I've raised an issue to capture this use case; feel free to chime in there! :) https://github.com/mattgodbolt/zindex/issues/12
I haven't tried yet, but this seems like a great tool.
I run the indexer in parallel on multiple log files: in general there's no way to parallelize the indexing of a single file as decompressing a gzipped file requires all the bytes up to that point. Unless you already have a gzip checkpoint around somewhere, you need to sequentially scan: this is part of what zindex does to allow quick random access.
Thanks for the feedback :)
news.ycombinator.com/item?id=7079427
I also must thank Edward Kmett for introducing me to these.
However, you could construct the compressed version of your search string and search for it (or in the case of LZW, generate it as you continue to search and decompress only small portions as needed).
Either way, you still have to go through the file. at least once.
EDIT: Actually, if your search string falls inside a compression block, it may be more difficult without uncompressing (and then discarding).
I guess for most of us it's better to just search plain text files, if possible?
Edit: well best case is 8:1 if you put a lot of time into it. Gzip with default settings is more like 4:1 which is still significant.
Other compression formats can give better ratios, if that's an option for your project. bzip2, xz, 7z.
Btw. welcome to Silicon Valley.
Given the discontinued status of Strigi and CLucene, it would be great if one would maintain the Jstream C++ library. Or take its ideas and implement it in zindex.