From 58d4258db25a7fae5dabe0961f9dc3b6d2188d9c Mon Sep 17 00:00:00 2001 From: taylor-lagrange Date: Fri, 6 Feb 2026 14:28:21 +0800 Subject: [PATCH] Implement multi-level skiplist for indexlib --- aios/catalog/tools/TableSchemaConfig.cpp | 1 + .../indexlib/index/inverted_index/BUILD | 7 +- .../inverted_index/BufferedIndexDecoder.cpp | 25 +- .../indexlib/index/inverted_index/Constant.h | 2 + .../index/inverted_index/InDocStateKeeper.h | 1 - .../RangeSegmentPostingsIterator.cpp | 25 +- .../config/InvertedIndexConfig.cpp | 16 +- .../config/InvertedIndexConfig.h | 4 + .../config/InvertedIndexConfigSerializer.cpp | 7 +- .../index/inverted_index/format/BUILD | 13 +- .../inverted_index/format/DocListEncoder.cpp | 65 +- .../inverted_index/format/DocListEncoder.h | 6 +- .../format/DocListEncoderImproved.cpp | 64 +- .../format/DocListEncoderImproved.h | 6 +- .../format/DocListFormatOption.cpp | 8 +- .../format/DocListFormatOption.h | 10 +- .../format/InMemPositionListDecoder.cpp | 8 +- .../format/InMemPositionListDecoder.h | 2 +- .../format/PositionListEncoder.cpp | 48 +- .../format/PositionListEncoder.h | 8 +- .../format/PositionListFormatOption.cpp | 8 +- .../format/PositionListFormatOption.h | 16 +- .../format/PositionListSegmentDecoder.cpp | 26 +- .../format/PositionListSegmentDecoder.h | 4 +- .../format/PostingFormatOption.h | 4 + .../format/SkipListSegmentDecoder.h | 4 +- .../inverted_index/format/skiplist/BUILD | 46 ++ .../format/skiplist/BufferedSkipListWriter.h | 11 +- .../skiplist/InMemPairValueSkipListReader.h | 4 +- .../skiplist/InMemTriValueSkipListReader.h | 4 +- .../skiplist/MultiLevelSkipListReader.cpp | 27 + .../skiplist/MultiLevelSkipListReader.h | 599 +++++++++++++++ .../skiplist/MultiLevelSkipListWriter.cpp | 158 ++++ .../skiplist/MultiLevelSkipListWriter.h | 117 +++ .../skiplist/PairValueSkipListReader.cpp | 4 +- .../format/skiplist/PairValueSkipListReader.h | 18 +- .../format/skiplist/SkipListReader.h | 29 +- .../format/skiplist/SkipListWriter.h | 36 + .../skiplist/TriValueSkipListReader.cpp | 4 +- .../format/skiplist/TriValueSkipListReader.h | 19 +- .../index/inverted_index/format/test/BUILD | 3 +- .../test/InMemPositionListDecoderTest.cpp | 3 +- .../format/test/MultiLevelSkipListTest.cpp | 685 ++++++++++++++++++ .../format/test/PositionListEncoderTest.cpp | 4 +- .../format/test/PostingFormatOptionTest.cpp | 4 + .../legacy/config/configurator_define.cpp | 1 + .../legacy/config/configurator_define.h | 1 + .../english/Types-of-inverted-indexes-en.md | 2 +- .../sql/indexes/inverted.en-US.md | 7 +- docs/havenask_docs/sql/indexes/inverted.md | 4 +- 50 files changed, 2012 insertions(+), 166 deletions(-) create mode 100644 aios/storage/indexlib/index/inverted_index/format/skiplist/MultiLevelSkipListReader.cpp create mode 100644 aios/storage/indexlib/index/inverted_index/format/skiplist/MultiLevelSkipListReader.h create mode 100644 aios/storage/indexlib/index/inverted_index/format/skiplist/MultiLevelSkipListWriter.cpp create mode 100644 aios/storage/indexlib/index/inverted_index/format/skiplist/MultiLevelSkipListWriter.h create mode 100644 aios/storage/indexlib/index/inverted_index/format/skiplist/SkipListWriter.h create mode 100644 aios/storage/indexlib/index/inverted_index/format/test/MultiLevelSkipListTest.cpp diff --git a/aios/catalog/tools/TableSchemaConfig.cpp b/aios/catalog/tools/TableSchemaConfig.cpp index c3bfc1abbd..f477cd5c54 100644 --- a/aios/catalog/tools/TableSchemaConfig.cpp +++ b/aios/catalog/tools/TableSchemaConfig.cpp @@ -57,6 +57,7 @@ std::vector IndexConfig::intParams = {"term_payload_flag", "position_payload_flag", "term_frequency_flag", "term_frequency_bitmap", + "multi_level_skip_list", "format_version_id"}; std::vector IndexConfig::boolParams = {"has_primary_key_attribute"}; diff --git a/aios/storage/indexlib/index/inverted_index/BUILD b/aios/storage/indexlib/index/inverted_index/BUILD index f074e34346..d970bd6fed 100644 --- a/aios/storage/indexlib/index/inverted_index/BUILD +++ b/aios/storage/indexlib/index/inverted_index/BUILD @@ -427,7 +427,7 @@ strict_cc_library( '//aios/storage/indexlib/index/inverted_index/format:InMemPostingDecoder', '//aios/storage/indexlib/index/inverted_index/format:ShortListSegmentDecoder', '//aios/storage/indexlib/index/inverted_index/format:SkipListSegmentDecoder', - '//aios/storage/indexlib/index/inverted_index/format/skiplist:TriValueSkipListReader' + '//aios/storage/indexlib/index/inverted_index/format/skiplist:lib' ] ) strict_cc_library( @@ -435,7 +435,7 @@ strict_cc_library( deps=[ '//aios/storage/indexlib/index/inverted_index/format:InMemPositionListDecoder', '//aios/storage/indexlib/index/inverted_index/format:TermMeta', - '//aios/storage/indexlib/index/inverted_index/format/skiplist:PairValueSkipListReader' + '//aios/storage/indexlib/index/inverted_index/format/skiplist:lib' ] ) strict_cc_library( @@ -462,8 +462,7 @@ strict_cc_library( '//aios/storage/indexlib/index/inverted_index/format:ShortListSegmentDecoder', '//aios/storage/indexlib/index/inverted_index/format:SkipListSegmentDecoder', '//aios/storage/indexlib/index/inverted_index/format:TermMetaLoader', - '//aios/storage/indexlib/index/inverted_index/format/skiplist:PairValueSkipListReader', - '//aios/storage/indexlib/index/inverted_index/format/skiplist:TriValueSkipListReader', + '//aios/storage/indexlib/index/inverted_index/format/skiplist:lib', '//aios/storage/indexlib/util:simple_heap' ] ) diff --git a/aios/storage/indexlib/index/inverted_index/BufferedIndexDecoder.cpp b/aios/storage/indexlib/index/inverted_index/BufferedIndexDecoder.cpp index f3f2929f82..b36cbb7c18 100644 --- a/aios/storage/indexlib/index/inverted_index/BufferedIndexDecoder.cpp +++ b/aios/storage/indexlib/index/inverted_index/BufferedIndexDecoder.cpp @@ -24,6 +24,7 @@ #include "indexlib/index/inverted_index/format/TermMetaLoader.h" #include "indexlib/index/inverted_index/format/skiplist/PairValueSkipListReader.h" #include "indexlib/index/inverted_index/format/skiplist/TriValueSkipListReader.h" +#include "indexlib/index/inverted_index/format/skiplist/MultiLevelSkipListReader.h" namespace indexlib::index { AUTIL_LOG_SETUP(indexlib.index, BufferedIndexDecoder); @@ -257,12 +258,26 @@ BufferedSegmentIndexDecoder* BufferedIndexDecoder::CreateNormalSegmentDecoder(ui compressMode, enableShortListVbyteCompress); } - if (mCurSegPostingFormatOption.HasTfList()) { - return IE_POOL_COMPATIBLE_NEW_CLASS(_sessionPool, SkipListSegmentDecoder, _sessionPool, - &_docListReader, docListBeginPos, compressMode); + if (mCurSegPostingFormatOption.HasMultiLevelSkipList()) { + if (mCurSegPostingFormatOption.HasTfList()) { + return IE_POOL_COMPATIBLE_NEW_CLASS( + _sessionPool, SkipListSegmentDecoder, + _sessionPool, &_docListReader, docListBeginPos, compressMode); + } else { + return IE_POOL_COMPATIBLE_NEW_CLASS( + _sessionPool, SkipListSegmentDecoder, + _sessionPool, &_docListReader, docListBeginPos, compressMode); + } } else { - return IE_POOL_COMPATIBLE_NEW_CLASS(_sessionPool, SkipListSegmentDecoder, _sessionPool, - &_docListReader, docListBeginPos, compressMode); + if (mCurSegPostingFormatOption.HasTfList()) { + return IE_POOL_COMPATIBLE_NEW_CLASS( + _sessionPool, SkipListSegmentDecoder, _sessionPool, + &_docListReader, docListBeginPos, compressMode); + } else { + return IE_POOL_COMPATIBLE_NEW_CLASS( + _sessionPool, SkipListSegmentDecoder, _sessionPool, + &_docListReader, docListBeginPos, compressMode); + } } } } // namespace indexlib::index diff --git a/aios/storage/indexlib/index/inverted_index/Constant.h b/aios/storage/indexlib/index/inverted_index/Constant.h index 06de17b744..4ca641d10f 100644 --- a/aios/storage/indexlib/index/inverted_index/Constant.h +++ b/aios/storage/indexlib/index/inverted_index/Constant.h @@ -52,6 +52,8 @@ constexpr uint8_t MAX_DICT_INLINE_AVAILABLE_SIZE = 7; constexpr uint8_t COMPRESS_MODE_SIZE = 4; constexpr uint32_t MAX_UNCOMPRESSED_SKIP_LIST_SIZE = 10; constexpr uint8_t SKIP_LIST_BUFFER_SIZE = 32; +constexpr uint8_t SKIP_MULTIPLIER = 8; +constexpr uint8_t MAX_SKIP_LEVEL = 10; static constexpr const char* TRUNCATE_ATTRIBUTE_READER_CREATOR = "truncate_attribute_reader_creator"; static constexpr const char* TRUNCATE_META_FILE_READER_CREATOR = "truncate_meta_file_reader_creator"; diff --git a/aios/storage/indexlib/index/inverted_index/InDocStateKeeper.h b/aios/storage/indexlib/index/inverted_index/InDocStateKeeper.h index 805d05ba88..e0165692f3 100644 --- a/aios/storage/indexlib/index/inverted_index/InDocStateKeeper.h +++ b/aios/storage/indexlib/index/inverted_index/InDocStateKeeper.h @@ -21,7 +21,6 @@ #include "indexlib/index/inverted_index/format/NormalInDocState.h" #include "indexlib/index/inverted_index/format/PositionListSegmentDecoder.h" #include "indexlib/index/inverted_index/format/TermMeta.h" -#include "indexlib/index/inverted_index/format/skiplist/PairValueSkipListReader.h" namespace indexlib::index { diff --git a/aios/storage/indexlib/index/inverted_index/RangeSegmentPostingsIterator.cpp b/aios/storage/indexlib/index/inverted_index/RangeSegmentPostingsIterator.cpp index e5b9ae74ba..d2b4b8970a 100644 --- a/aios/storage/indexlib/index/inverted_index/RangeSegmentPostingsIterator.cpp +++ b/aios/storage/indexlib/index/inverted_index/RangeSegmentPostingsIterator.cpp @@ -23,6 +23,7 @@ #include "indexlib/index/inverted_index/format/TermMetaLoader.h" #include "indexlib/index/inverted_index/format/skiplist/PairValueSkipListReader.h" #include "indexlib/index/inverted_index/format/skiplist/TriValueSkipListReader.h" +#include "indexlib/index/inverted_index/format/skiplist/MultiLevelSkipListReader.h" namespace indexlib::index { AUTIL_LOG_SETUP(indexlib.index, RangeSegmentPostingsIterator); @@ -182,12 +183,26 @@ RangeSegmentPostingsIterator::CreateNormalSegmentDecoder(file_system::ByteSliceR compressMode, curSegPostingFormatOption.IsShortListVbyteCompress()); } - if (curSegPostingFormatOption.HasTfList()) { - return IE_POOL_COMPATIBLE_NEW_CLASS(_sessionPool, SkipListSegmentDecoder, _sessionPool, - docListReader, docListBeginPos, compressMode); + if (curSegPostingFormatOption.HasMultiLevelSkipList()) { + if (curSegPostingFormatOption.HasTfList()) { + return IE_POOL_COMPATIBLE_NEW_CLASS( + _sessionPool, SkipListSegmentDecoder, + _sessionPool, docListReader, docListBeginPos, compressMode); + } else { + return IE_POOL_COMPATIBLE_NEW_CLASS( + _sessionPool, SkipListSegmentDecoder, + _sessionPool, docListReader, docListBeginPos, compressMode); + } } else { - return IE_POOL_COMPATIBLE_NEW_CLASS(_sessionPool, SkipListSegmentDecoder, _sessionPool, - docListReader, docListBeginPos, compressMode); + if (curSegPostingFormatOption.HasTfList()) { + return IE_POOL_COMPATIBLE_NEW_CLASS( + _sessionPool, SkipListSegmentDecoder, _sessionPool, + docListReader, docListBeginPos, compressMode); + } else { + return IE_POOL_COMPATIBLE_NEW_CLASS( + _sessionPool, SkipListSegmentDecoder, _sessionPool, + docListReader, docListBeginPos, compressMode); + } } } } // namespace indexlib::index diff --git a/aios/storage/indexlib/index/inverted_index/config/InvertedIndexConfig.cpp b/aios/storage/indexlib/index/inverted_index/config/InvertedIndexConfig.cpp index b2d774b0dc..ec4907c25d 100644 --- a/aios/storage/indexlib/index/inverted_index/config/InvertedIndexConfig.cpp +++ b/aios/storage/indexlib/index/inverted_index/config/InvertedIndexConfig.cpp @@ -82,6 +82,7 @@ struct InvertedIndexConfig::Impl { bool isPatchCompressed = false; bool isVirtual = false; bool isShortListVbyteCompress = false; + bool isMultiLevelSkipList = false; bool hasTruncate = false; indexlib::config::PayloadConfig payloadConfig; @@ -115,6 +116,7 @@ struct InvertedIndexConfig::Impl { , isPatchCompressed(other.isPatchCompressed) , isVirtual(other.isVirtual) , isShortListVbyteCompress(other.isShortListVbyteCompress) + , isMultiLevelSkipList(other.isMultiLevelSkipList) , hasTruncate(other.hasTruncate) { } @@ -185,7 +187,9 @@ Status InvertedIndexConfig::CheckEqual(const InvertedIndexConfig& other) const CHECK_CONFIG_EQUAL(_impl->formatVersionId, other._impl->formatVersionId, "_impl->formatVersionId not equal"); CHECK_CONFIG_EQUAL(_impl->isShortListVbyteCompress, other._impl->isShortListVbyteCompress, "_impl->isShortListVbyteCompress not equal"); - + CHECK_CONFIG_EQUAL(_impl->isMultiLevelSkipList, + other._impl->isMultiLevelSkipList, + "_impl->isMultiLevelSkipList not equal"); for (size_t i = 0; i < _impl->shardingIndexConfigs.size(); i++) { auto status = _impl->shardingIndexConfigs[i]->CheckEqual(*other._impl->shardingIndexConfigs[i]); RETURN_IF_STATUS_ERROR(status, "sharding index config [%s] not equal", @@ -340,6 +344,16 @@ void InvertedIndexConfig::SetShortListVbyteCompress(bool isShortListVbyteCompres _impl->isShortListVbyteCompress = isShortListVbyteCompress; } +bool InvertedIndexConfig::IsMultiLevelSkipList() const +{ + return _impl->isMultiLevelSkipList; +} + +void InvertedIndexConfig::SetMultiLevelSkipList(bool isMultiLevelSkipList) +{ + _impl->isMultiLevelSkipList = isMultiLevelSkipList; +} + void InvertedIndexConfig::SetIsReferenceCompress(bool isReferenceCompress) { _impl->isReferenceCompress = isReferenceCompress; diff --git a/aios/storage/indexlib/index/inverted_index/config/InvertedIndexConfig.h b/aios/storage/indexlib/index/inverted_index/config/InvertedIndexConfig.h index 1bd2c6d3a6..44aee4eed7 100644 --- a/aios/storage/indexlib/index/inverted_index/config/InvertedIndexConfig.h +++ b/aios/storage/indexlib/index/inverted_index/config/InvertedIndexConfig.h @@ -178,6 +178,9 @@ class InvertedIndexConfig : public indexlibv2::config::IIndexConfig bool IsShortListVbyteCompress() const; void SetShortListVbyteCompress(bool isShortListVbyteCompress); + bool IsMultiLevelSkipList() const; + void SetMultiLevelSkipList(bool isMultiLevelSkipList); + void SetIsReferenceCompress(bool isReferenceCompress); bool IsReferenceCompress() const; @@ -272,6 +275,7 @@ class InvertedIndexConfig : public indexlibv2::config::IIndexConfig inline static const std::string DOC_PAYLOAD_FLAG = "doc_payload_flag"; inline static const std::string POSITION_PAYLOAD_FLAG = "position_payload_flag"; inline static const std::string POSITION_LIST_FLAG = "position_list_flag"; + inline static const std::string MULTI_LEVEL_SKIP_LIST = "multi_level_skip_list"; inline static const std::string TERM_FREQUENCY_FLAG = "term_frequency_flag"; inline static const std::string TERM_FREQUENCY_BITMAP = "term_frequency_bitmap"; inline static const std::string HIGH_FEQUENCY_DICTIONARY = "high_frequency_dictionary"; diff --git a/aios/storage/indexlib/index/inverted_index/config/InvertedIndexConfigSerializer.cpp b/aios/storage/indexlib/index/inverted_index/config/InvertedIndexConfigSerializer.cpp index 7d7e9506b2..6fbea5e732 100644 --- a/aios/storage/indexlib/index/inverted_index/config/InvertedIndexConfigSerializer.cpp +++ b/aios/storage/indexlib/index/inverted_index/config/InvertedIndexConfigSerializer.cpp @@ -164,7 +164,9 @@ void InvertedIndexConfigSerializer::Serialize(const indexlibv2::config::Inverted std::string indexCompressMode = indexlibv2::config::InvertedIndexConfig::INDEX_COMPRESS_MODE_REFERENCE; json->Jsonize(indexlibv2::config::InvertedIndexConfig::INDEX_COMPRESS_MODE, indexCompressMode); } - + if (indexConfig.IsMultiLevelSkipList()) { + json->Jsonize(indexlibv2::config::InvertedIndexConfig::MULTI_LEVEL_SKIP_LIST, enableValue); + } if (indexConfig.IsHashTypedDictionary()) { json->Jsonize(indexlibv2::config::InvertedIndexConfig::USE_HASH_DICTIONARY, indexConfig.IsHashTypedDictionary()); @@ -209,6 +211,9 @@ void InvertedIndexConfigSerializer::DeserializeCommonFields(const autil::legacy: indexlibv2::config::InvertedIndexConfig::INDEX_COMPRESS_MODE_PFOR_DELTA); indexConfig->SetIsReferenceCompress( (compressMode == indexlibv2::config::InvertedIndexConfig::INDEX_COMPRESS_MODE_REFERENCE) ? true : false); + int useMultiLevelSkipList = 0; + json.Jsonize(indexlibv2::config::InvertedIndexConfig::MULTI_LEVEL_SKIP_LIST, useMultiLevelSkipList, useMultiLevelSkipList); + indexConfig->SetMultiLevelSkipList(useMultiLevelSkipList == 1); bool isHashTypedDictionary = false; json.Jsonize(indexlibv2::config::InvertedIndexConfig::USE_HASH_DICTIONARY, isHashTypedDictionary, isHashTypedDictionary); diff --git a/aios/storage/indexlib/index/inverted_index/format/BUILD b/aios/storage/indexlib/index/inverted_index/format/BUILD index a363e88964..774fec2d87 100644 --- a/aios/storage/indexlib/index/inverted_index/format/BUILD +++ b/aios/storage/indexlib/index/inverted_index/format/BUILD @@ -104,7 +104,7 @@ strict_cc_library( '//aios/storage/indexlib/index/common/field_format/section_attribute:MultiSectionMeta', '//aios/storage/indexlib/index/common/numeric_compress:EncoderProvider', '//aios/storage/indexlib/index/inverted_index:InDocPositionState', - '//aios/storage/indexlib/index/inverted_index/format/skiplist:PairValueSkipListReader', + '//aios/storage/indexlib/index/inverted_index/format/skiplist:lib', '//aios/storage/indexlib/index/inverted_index/section_attribute:SectionAttributeReaderImpl', '//aios/storage/indexlib/util:object_pool' ] @@ -155,8 +155,7 @@ strict_cc_library( ':BufferedByteSlice', ':InMemPositionListDecoder', ':PositionListFormat', '//aios/autil:mem_pool_base', '//aios/storage/indexlib/file_system:byte_slice_rw', - '//aios/storage/indexlib/index/inverted_index/format/skiplist:BufferedSkipListWriter', - '//aios/storage/indexlib/index/inverted_index/format/skiplist:InMemPairValueSkipListReader' + '//aios/storage/indexlib/index/inverted_index/format/skiplist:lib' ] ) strict_cc_library( @@ -224,9 +223,7 @@ strict_cc_library( deps=[ ':BufferedByteSlice', ':DocListFormat', ':InMemDocListDecoder', ':PositionBitmapWriter', '//aios/autil:mem_pool_base', - '//aios/storage/indexlib/index/inverted_index/format/skiplist:BufferedSkipListWriter', - '//aios/storage/indexlib/index/inverted_index/format/skiplist:InMemPairValueSkipListReader', - '//aios/storage/indexlib/index/inverted_index/format/skiplist:InMemTriValueSkipListReader' + '//aios/storage/indexlib/index/inverted_index/format/skiplist:lib', ] ) strict_cc_library( @@ -234,9 +231,7 @@ strict_cc_library( deps=[ ':BufferedByteSlice', ':DocListFormat', ':InMemDocListDecoder', ':PositionBitmapWriter', '//aios/autil:mem_pool_base', - '//aios/storage/indexlib/index/inverted_index/format/skiplist:BufferedSkipListWriter', - '//aios/storage/indexlib/index/inverted_index/format/skiplist:InMemPairValueSkipListReader', - '//aios/storage/indexlib/index/inverted_index/format/skiplist:InMemTriValueSkipListReader' + '//aios/storage/indexlib/index/inverted_index/format/skiplist:lib' ] ) strict_cc_library( diff --git a/aios/storage/indexlib/index/inverted_index/format/DocListEncoder.cpp b/aios/storage/indexlib/index/inverted_index/format/DocListEncoder.cpp index 86656ddc39..9b834cc988 100644 --- a/aios/storage/indexlib/index/inverted_index/format/DocListEncoder.cpp +++ b/aios/storage/indexlib/index/inverted_index/format/DocListEncoder.cpp @@ -63,7 +63,7 @@ DocListEncoder::DocListEncoder(const DocListFormatOption& docListFormatOption, u DocListEncoder::~DocListEncoder() { if (_docSkipListWriter) { - _docSkipListWriter->~BufferedSkipListWriter(); + _docSkipListWriter->~SkipListWriter(); _docSkipListWriter = nullptr; } if (_ownDocListFormat) { @@ -199,16 +199,21 @@ void DocListEncoder::FlushDocListBuffer(uint8_t compressMode) void DocListEncoder::CreateDocSkipListWriter() { - void* buffer = _byteSlicePool->allocate(sizeof(BufferedSkipListWriter)); - autil::mem_pool::RecyclePool* bufferPool = - dynamic_cast(_docListBuffer.GetBufferPool()); - assert(bufferPool); - - BufferedSkipListWriter* docSkipListWriter = - new (buffer) BufferedSkipListWriter(_byteSlicePool, bufferPool, _compressMode); - docSkipListWriter->Init(_docListFormat->GetDocListSkipListFormat()); // skip list writer should be atomic created; - _docSkipListWriter = docSkipListWriter; + if (_docListFormatOption.HasMultiLevelSkipList()) { + void *buffer = _byteSlicePool->allocate(sizeof(MultiLevelSkipListWriter)); + MultiLevelSkipListWriter *docSkipListWriter = + new (buffer) MultiLevelSkipListWriter(_byteSlicePool); + _docSkipListWriter = docSkipListWriter; + } else { + void *buffer = _byteSlicePool->allocate(sizeof(BufferedSkipListWriter)); + autil::mem_pool::RecyclePool *bufferPool = dynamic_cast(_docListBuffer.GetBufferPool()); + assert(bufferPool); + BufferedSkipListWriter *docSkipListWriter = + new (buffer) BufferedSkipListWriter(_byteSlicePool, bufferPool, _compressMode); + docSkipListWriter->Init(_docListFormat->GetDocListSkipListFormat()); + _docSkipListWriter = docSkipListWriter; + } } void DocListEncoder::AddSkipListItem(uint32_t itemSize) @@ -234,17 +239,37 @@ InMemDocListDecoder* DocListEncoder::GetInMemDocListDecoder(autil::mem_pool::Poo if (_docSkipListWriter) { const DocListSkipListFormat* skipListFormat = _docListFormat->GetDocListSkipListFormat(); assert(skipListFormat); - - if (skipListFormat->HasTfList()) { - InMemTriValueSkipListReader* inMemSkipListReader = - IE_POOL_COMPATIBLE_NEW_CLASS(sessionPool, InMemTriValueSkipListReader, sessionPool); - inMemSkipListReader->Load(_docSkipListWriter); - skipListReader = inMemSkipListReader; + if (_docListFormatOption.HasMultiLevelSkipList()) { + MultiLevelSkipListWriter *skipwriter = + dynamic_cast(_docSkipListWriter); + assert(skipwriter != nullptr); + if (skipListFormat->HasTfList()) { + MultiLevelTriValueSkipListReader *inMemSkipListReader = + IE_POOL_COMPATIBLE_NEW_CLASS(sessionPool, MultiLevelTriValueSkipListReader); + skipwriter->SnapShot(inMemSkipListReader, sessionPool); + skipListReader = inMemSkipListReader; + } else { + MultiLevelPairValueSkipListReader *inMemSkipListReader = + IE_POOL_COMPATIBLE_NEW_CLASS(sessionPool, MultiLevelPairValueSkipListReader); + skipwriter->SnapShot(inMemSkipListReader, sessionPool); + skipListReader = inMemSkipListReader; + } } else { - InMemPairValueSkipListReader* inMemSkipListReader = IE_POOL_COMPATIBLE_NEW_CLASS( - sessionPool, InMemPairValueSkipListReader, sessionPool, _compressMode == REFERENCE_COMPRESS_MODE); - inMemSkipListReader->Load(_docSkipListWriter); - skipListReader = inMemSkipListReader; + BufferedSkipListWriter *bufferedWriter = + dynamic_cast(_docSkipListWriter); + assert(bufferedWriter != nullptr); + if (skipListFormat->HasTfList()) { + InMemTriValueSkipListReader *inMemSkipListReader = IE_POOL_COMPATIBLE_NEW_CLASS( + sessionPool, InMemTriValueSkipListReader, sessionPool); + inMemSkipListReader->Load(bufferedWriter); + skipListReader = inMemSkipListReader; + } else { + InMemPairValueSkipListReader *inMemSkipListReader = IE_POOL_COMPATIBLE_NEW_CLASS( + sessionPool, InMemPairValueSkipListReader, sessionPool, + _compressMode == REFERENCE_COMPRESS_MODE); + inMemSkipListReader->Load(bufferedWriter); + skipListReader = inMemSkipListReader; + } } } BufferedByteSlice* docListBuffer = diff --git a/aios/storage/indexlib/index/inverted_index/format/DocListEncoder.h b/aios/storage/indexlib/index/inverted_index/format/DocListEncoder.h index 1b54d38726..5f7b3a7976 100644 --- a/aios/storage/indexlib/index/inverted_index/format/DocListEncoder.h +++ b/aios/storage/indexlib/index/inverted_index/format/DocListEncoder.h @@ -27,6 +27,8 @@ #include "indexlib/index/inverted_index/format/InMemDocListDecoder.h" #include "indexlib/index/inverted_index/format/PositionBitmapWriter.h" #include "indexlib/index/inverted_index/format/skiplist/BufferedSkipListWriter.h" +#include "indexlib/index/inverted_index/format/skiplist/SkipListWriter.h" +#include "indexlib/index/inverted_index/format/skiplist/MultiLevelSkipListWriter.h" #include "indexlib/util/PoolUtil.h" #include "indexlib/util/SimplePool.h" @@ -80,7 +82,7 @@ class DocListEncoder private: const DocListFormat* TEST_GetDocListFormat() const { return _docListFormat; } BufferedByteSlice* TEST_GetDocListBuffer() { return &_docListBuffer; } - void TEST_SetDocSkipListWriter(BufferedSkipListWriter* skipListBuffer) { _docSkipListWriter = skipListBuffer; } + void TEST_SetDocSkipListWriter(SkipListWriter* skipListBuffer) { _docSkipListWriter = skipListBuffer; } void TEST_SetPositionBitmapWriter(PositionBitmapWriter* tfBitmapWriter) { @@ -104,7 +106,7 @@ class DocListEncoder index::CompressMode _compressMode; // 1byte // volatile for realtime PositionBitmapWriter* _tfBitmapWriter; // 8byte - BufferedSkipListWriter* volatile _docSkipListWriter; + SkipListWriter* volatile _docSkipListWriter; DocListFormat* _docListFormat; autil::mem_pool::Pool* _byteSlicePool; diff --git a/aios/storage/indexlib/index/inverted_index/format/DocListEncoderImproved.cpp b/aios/storage/indexlib/index/inverted_index/format/DocListEncoderImproved.cpp index cfb0d82044..b394364bf4 100644 --- a/aios/storage/indexlib/index/inverted_index/format/DocListEncoderImproved.cpp +++ b/aios/storage/indexlib/index/inverted_index/format/DocListEncoderImproved.cpp @@ -62,7 +62,7 @@ DocListEncoderImproved::DocListEncoderImproved(const DocListFormatOption& docLis DocListEncoderImproved::~DocListEncoderImproved() { if (_docSkipListWriter) { - _docSkipListWriter->~BufferedSkipListWriter(); + _docSkipListWriter->~SkipListWriter(); _docSkipListWriter = nullptr; } if (_ownDocListFormat) { @@ -198,16 +198,21 @@ void DocListEncoderImproved::FlushDocListBuffer(uint8_t compressMode) void DocListEncoderImproved::CreateDocSkipListWriter() { - void* buffer = _byteSlicePool->allocate(sizeof(BufferedSkipListWriter)); - autil::mem_pool::RecyclePool* bufferPool = - dynamic_cast(_docListBuffer.GetBufferPool()); - assert(bufferPool); - - BufferedSkipListWriter* docSkipListWriter = - new (buffer) BufferedSkipListWriter(_byteSlicePool, bufferPool, _compressMode); - docSkipListWriter->Init(_docListFormat->GetDocListSkipListFormat()); // skip list writer should be atomic created; - _docSkipListWriter = docSkipListWriter; + if (_docListFormatOption.HasMultiLevelSkipList()) { + void *buffer = _byteSlicePool->allocate(sizeof(MultiLevelSkipListWriter)); + MultiLevelSkipListWriter *docSkipListWriter = + new (buffer) MultiLevelSkipListWriter(_byteSlicePool); + _docSkipListWriter = docSkipListWriter; + } else { + void *buffer = _byteSlicePool->allocate(sizeof(BufferedSkipListWriter)); + autil::mem_pool::RecyclePool *bufferPool = dynamic_cast(_docListBuffer.GetBufferPool()); + assert(bufferPool); + BufferedSkipListWriter *docSkipListWriter = + new (buffer) BufferedSkipListWriter(_byteSlicePool, bufferPool, _compressMode); + docSkipListWriter->Init(_docListFormat->GetDocListSkipListFormat()); + _docSkipListWriter = docSkipListWriter; + } } void DocListEncoderImproved::AddSkipListItem(uint32_t itemSize) @@ -234,16 +239,37 @@ InMemDocListDecoder* DocListEncoderImproved::GetInMemDocListDecoder(autil::mem_p const DocListSkipListFormat* skipListFormat = _docListFormat->GetDocListSkipListFormat(); assert(skipListFormat); - if (skipListFormat->HasTfList()) { - InMemTriValueSkipListReader* inMemSkipListReader = - IE_POOL_COMPATIBLE_NEW_CLASS(sessionPool, InMemTriValueSkipListReader, sessionPool); - inMemSkipListReader->Load(_docSkipListWriter); - skipListReader = inMemSkipListReader; + if (_docListFormatOption.HasMultiLevelSkipList()) { + MultiLevelSkipListWriter *skipwriter = + dynamic_cast(_docSkipListWriter); + assert(skipwriter != nullptr); + if (skipListFormat->HasTfList()) { + MultiLevelTriValueSkipListReader *inMemSkipListReader = + IE_POOL_COMPATIBLE_NEW_CLASS(sessionPool, MultiLevelTriValueSkipListReader); + skipwriter->SnapShot(inMemSkipListReader, sessionPool); + skipListReader = inMemSkipListReader; + } else { + MultiLevelPairValueSkipListReader *inMemSkipListReader = + IE_POOL_COMPATIBLE_NEW_CLASS(sessionPool, MultiLevelPairValueSkipListReader); + skipwriter->SnapShot(inMemSkipListReader, sessionPool); + skipListReader = inMemSkipListReader; + } } else { - InMemPairValueSkipListReader* inMemSkipListReader = IE_POOL_COMPATIBLE_NEW_CLASS( - sessionPool, InMemPairValueSkipListReader, sessionPool, _compressMode == REFERENCE_COMPRESS_MODE); - inMemSkipListReader->Load(_docSkipListWriter); - skipListReader = inMemSkipListReader; + BufferedSkipListWriter *bufferedWriter = + dynamic_cast(_docSkipListWriter); + assert(bufferedWriter != nullptr); + if (skipListFormat->HasTfList()) { + InMemTriValueSkipListReader *inMemSkipListReader = IE_POOL_COMPATIBLE_NEW_CLASS( + sessionPool, InMemTriValueSkipListReader, sessionPool); + inMemSkipListReader->Load(bufferedWriter); + skipListReader = inMemSkipListReader; + } else { + InMemPairValueSkipListReader *inMemSkipListReader = IE_POOL_COMPATIBLE_NEW_CLASS( + sessionPool, InMemPairValueSkipListReader, sessionPool, + _compressMode == REFERENCE_COMPRESS_MODE); + inMemSkipListReader->Load(bufferedWriter); + skipListReader = inMemSkipListReader; + } } } BufferedByteSlice* docListBuffer = diff --git a/aios/storage/indexlib/index/inverted_index/format/DocListEncoderImproved.h b/aios/storage/indexlib/index/inverted_index/format/DocListEncoderImproved.h index e4af0c2748..3403fec008 100644 --- a/aios/storage/indexlib/index/inverted_index/format/DocListEncoderImproved.h +++ b/aios/storage/indexlib/index/inverted_index/format/DocListEncoderImproved.h @@ -27,6 +27,8 @@ #include "indexlib/index/inverted_index/format/InMemDocListDecoder.h" #include "indexlib/index/inverted_index/format/PositionBitmapWriter.h" #include "indexlib/index/inverted_index/format/skiplist/BufferedSkipListWriter.h" +#include "indexlib/index/inverted_index/format/skiplist/SkipListWriter.h" +#include "indexlib/index/inverted_index/format/skiplist/MultiLevelSkipListWriter.h" #include "indexlib/util/PoolUtil.h" #include "indexlib/util/SimplePool.h" @@ -78,7 +80,7 @@ class DocListEncoderImproved const DocListFormat* TEST_GetDocListFormat() const { return _docListFormat; } BufferedByteSlice* TEST_GetDocListBuffer() { return &_docListBuffer; } - void TEST_SetDocSkipListWriter(BufferedSkipListWriter* skipListBuffer) { _docSkipListWriter = skipListBuffer; } + void TEST_SetDocSkipListWriter(SkipListWriter* skipListBuffer) { _docSkipListWriter = skipListBuffer; } void TEST_SetPositionBitmapWriter(PositionBitmapWriter* tfBitmapWriter) { @@ -102,7 +104,7 @@ class DocListEncoderImproved CompressMode _compressMode; // 1byte // volatile for realtime PositionBitmapWriter* _tfBitmapWriter; // 8byte - BufferedSkipListWriter* volatile _docSkipListWriter; + SkipListWriter* volatile _docSkipListWriter; DocListFormat* _docListFormat; autil::mem_pool::Pool* _byteSlicePool; diff --git a/aios/storage/indexlib/index/inverted_index/format/DocListFormatOption.cpp b/aios/storage/indexlib/index/inverted_index/format/DocListFormatOption.cpp index 385e5acc99..c4c8310752 100644 --- a/aios/storage/indexlib/index/inverted_index/format/DocListFormatOption.cpp +++ b/aios/storage/indexlib/index/inverted_index/format/DocListFormatOption.cpp @@ -23,7 +23,7 @@ bool DocListFormatOption::operator==(const DocListFormatOption& right) const { return _hasTf == right._hasTf && _hasTfList == right._hasTfList && _hasTfBitmap == right._hasTfBitmap && _hasDocPayload == right._hasDocPayload && _hasFieldMap == right._hasFieldMap && - _shortListVbyteCompress == right._shortListVbyteCompress; + _shortListVbyteCompress == right._shortListVbyteCompress && _hasMultiLevelSkipList == right._hasMultiLevelSkipList; } void JsonizableDocListFormatOption::Jsonize(autil::legacy::Jsonizable::JsonWrapper& json) @@ -33,6 +33,7 @@ void JsonizableDocListFormatOption::Jsonize(autil::legacy::Jsonizable::JsonWrapp bool hasTfBitmap; bool hasDocPayload; bool hasFieldMap; + bool hasMultiLevelSkipList = false; bool shortListVbyteCompress = false; if (json.GetMode() == FROM_JSON) { @@ -41,6 +42,8 @@ void JsonizableDocListFormatOption::Jsonize(autil::legacy::Jsonizable::JsonWrapp json.Jsonize("has_term_frequency_bitmap", hasTfBitmap); json.Jsonize("has_doc_payload", hasDocPayload); json.Jsonize("has_field_map", hasFieldMap); + // Compatible with previous formats + json.Jsonize("has_multi_level_skip_list", hasMultiLevelSkipList, hasMultiLevelSkipList); json.Jsonize("is_shortlist_vbyte_compress", shortListVbyteCompress, shortListVbyteCompress); _docListFormatOption._hasTf = hasTf ? 1 : 0; @@ -48,6 +51,7 @@ void JsonizableDocListFormatOption::Jsonize(autil::legacy::Jsonizable::JsonWrapp _docListFormatOption._hasTfBitmap = hasTfBitmap ? 1 : 0; _docListFormatOption._hasDocPayload = hasDocPayload ? 1 : 0; _docListFormatOption._hasFieldMap = hasFieldMap ? 1 : 0; + _docListFormatOption._hasMultiLevelSkipList = hasMultiLevelSkipList ? 1 : 0; _docListFormatOption.SetShortListVbyteCompress(shortListVbyteCompress); } else { hasTf = _docListFormatOption._hasTf == 1; @@ -55,6 +59,7 @@ void JsonizableDocListFormatOption::Jsonize(autil::legacy::Jsonizable::JsonWrapp hasTfBitmap = _docListFormatOption._hasTfBitmap == 1; hasDocPayload = _docListFormatOption._hasDocPayload == 1; hasFieldMap = _docListFormatOption._hasFieldMap == 1; + hasMultiLevelSkipList = _docListFormatOption._hasMultiLevelSkipList == 1; shortListVbyteCompress = _docListFormatOption.IsShortListVbyteCompress(); json.Jsonize("has_term_frequency", hasTf); @@ -62,6 +67,7 @@ void JsonizableDocListFormatOption::Jsonize(autil::legacy::Jsonizable::JsonWrapp json.Jsonize("has_term_frequency_bitmap", hasTfBitmap); json.Jsonize("has_doc_payload", hasDocPayload); json.Jsonize("has_field_map", hasFieldMap); + json.Jsonize("has_multi_level_skip_list", hasMultiLevelSkipList); json.Jsonize("is_shortlist_vbyte_compress", shortListVbyteCompress); } } diff --git a/aios/storage/indexlib/index/inverted_index/format/DocListFormatOption.h b/aios/storage/indexlib/index/inverted_index/format/DocListFormatOption.h index 3e25acb510..c38afb33c2 100644 --- a/aios/storage/indexlib/index/inverted_index/format/DocListFormatOption.h +++ b/aios/storage/indexlib/index/inverted_index/format/DocListFormatOption.h @@ -49,6 +49,7 @@ class DocListFormatOption _hasTfBitmap = 0; } _shortListVbyteCompress = 0; + _hasMultiLevelSkipList = 0; _unused = 0; } @@ -60,6 +61,12 @@ class DocListFormatOption bool operator==(const DocListFormatOption& right) const; bool IsShortListVbyteCompress() const { return _shortListVbyteCompress == 1; } void SetShortListVbyteCompress(bool flag) { _shortListVbyteCompress = flag ? 1 : 0; } + bool HasMultiLevelSkipList() const { + return _hasMultiLevelSkipList == 1; + } + void SetMultiLevelSkipList(bool hasMultiLevelSkipList) { + _hasMultiLevelSkipList = hasMultiLevelSkipList; + } private: uint8_t _hasTf : 1; @@ -68,7 +75,8 @@ class DocListFormatOption uint8_t _hasDocPayload : 1; uint8_t _hasFieldMap : 1; uint8_t _shortListVbyteCompress : 1; - uint8_t _unused : 2; + uint8_t _hasMultiLevelSkipList : 1; + uint8_t _unused : 1; friend class DocListEncoderTest; friend class DocListMemoryBufferTest; diff --git a/aios/storage/indexlib/index/inverted_index/format/InMemPositionListDecoder.cpp b/aios/storage/indexlib/index/inverted_index/format/InMemPositionListDecoder.cpp index 6044044eb7..733165484c 100644 --- a/aios/storage/indexlib/index/inverted_index/format/InMemPositionListDecoder.cpp +++ b/aios/storage/indexlib/index/inverted_index/format/InMemPositionListDecoder.cpp @@ -30,7 +30,7 @@ InMemPositionListDecoder::InMemPositionListDecoder(const PositionListFormatOptio InMemPositionListDecoder::~InMemPositionListDecoder() { IE_POOL_COMPATIBLE_DELETE_CLASS(_sessionPool, _posListBuffer); } -void InMemPositionListDecoder::Init(ttf_t totalTF, PairValueSkipListReader* skipListReader, +void InMemPositionListDecoder::Init(ttf_t totalTF, SkipListReader* skipListReader, BufferedByteSlice* posListBuffer) { assert(posListBuffer); @@ -63,11 +63,9 @@ bool InMemPositionListDecoder::SkipTo(ttf_t currentTTF, NormalInDocState* state) } uint32_t offset, len; - auto [status, ret] = _posSkipListReader->SkipTo(currentTTF + 1, _decodedPosCount, offset, len); + auto [status, ret] = _posSkipListReader->SkipTo(currentTTF + 1, _decodedPosCount, _preRecordTTF, offset, len); THROW_IF_STATUS_ERROR(status); - if (ret) { - _preRecordTTF = _posSkipListReader->GetPrevKey(); - } else { + if (!ret) { offset = _posSkipListReader->GetLastValueInBuffer(); _preRecordTTF = _posSkipListReader->GetCurrentKey(); _decodedPosCount = _totalTf; diff --git a/aios/storage/indexlib/index/inverted_index/format/InMemPositionListDecoder.h b/aios/storage/indexlib/index/inverted_index/format/InMemPositionListDecoder.h index 7bb2109652..98cc178211 100644 --- a/aios/storage/indexlib/index/inverted_index/format/InMemPositionListDecoder.h +++ b/aios/storage/indexlib/index/inverted_index/format/InMemPositionListDecoder.h @@ -31,7 +31,7 @@ class InMemPositionListDecoder : public PositionListSegmentDecoder InMemPositionListDecoder(const PositionListFormatOption& option, autil::mem_pool::Pool* sessionPool); ~InMemPositionListDecoder(); - void Init(ttf_t totalTF, PairValueSkipListReader* skipListReader, BufferedByteSlice* posListBuffer); + void Init(ttf_t totalTF, SkipListReader* skipListReader, BufferedByteSlice* posListBuffer); bool SkipTo(ttf_t currentTTF, NormalInDocState* state) override; diff --git a/aios/storage/indexlib/index/inverted_index/format/PositionListEncoder.cpp b/aios/storage/indexlib/index/inverted_index/format/PositionListEncoder.cpp index 362b48a751..1df9710906 100644 --- a/aios/storage/indexlib/index/inverted_index/format/PositionListEncoder.cpp +++ b/aios/storage/indexlib/index/inverted_index/format/PositionListEncoder.cpp @@ -85,17 +85,23 @@ uint32_t PositionListEncoder::GetDumpLength() const void PositionListEncoder::CreatePosSkipListWriter() { - assert(_posSkipListWriter == nullptr); - autil::mem_pool::RecyclePool* bufferPool = - dynamic_cast(_posListBuffer.GetBufferPool()); - assert(bufferPool); - - void* buffer = _byteSlicePool->allocate(sizeof(BufferedSkipListWriter)); - BufferedSkipListWriter* posSkipListWriter = new (buffer) BufferedSkipListWriter(_byteSlicePool, bufferPool); - posSkipListWriter->Init(_posListFormat->GetPositionSkipListFormat()); - + assert(_posSkipListWriter == NULL); // skip list writer should be atomic created; - _posSkipListWriter = posSkipListWriter; + if (_posListFormatOption.HasMultiLevelSkipList()) { + void *buffer = _byteSlicePool->allocate(sizeof(MultiLevelSkipListWriter)); + MultiLevelSkipListWriter *posSkipListWriter = + new (buffer) MultiLevelSkipListWriter(_byteSlicePool); + _posSkipListWriter = posSkipListWriter; + } else { + autil::mem_pool::RecyclePool *bufferPool = dynamic_cast(_posListBuffer.GetBufferPool()); + assert(bufferPool); + + void *buffer = _byteSlicePool->allocate(sizeof(BufferedSkipListWriter)); + BufferedSkipListWriter *posSkipListWriter = + new (buffer) BufferedSkipListWriter(_byteSlicePool, bufferPool); + posSkipListWriter->Init(_posListFormat->GetPositionSkipListFormat()); + _posSkipListWriter = posSkipListWriter; + } } void PositionListEncoder::AddPosSkipListItem(uint32_t totalPosCount, uint32_t compressedPosSize, bool needFlush) @@ -131,13 +137,27 @@ InMemPositionListDecoder* PositionListEncoder::GetInMemPositionListDecoder(autil ttf_t ttf = _totalPosCount; // TODO: delete buffer in pool - InMemPairValueSkipListReader* inMemSkipListReader = nullptr; + SkipListReader *inMemSkipListReader = nullptr; if (_posSkipListWriter) { // not support tf bitmap in realtime segment assert(!_posListFormatOption.HasTfBitmap()); - inMemSkipListReader = - IE_POOL_COMPATIBLE_NEW_CLASS(sessionPool, InMemPairValueSkipListReader, sessionPool, false); - inMemSkipListReader->Load(_posSkipListWriter); + if (_posListFormatOption.HasMultiLevelSkipList()) { + MultiLevelSkipListWriter *skipwriter = + dynamic_cast(_posSkipListWriter); + assert(skipwriter != nullptr); + MultiLevelPairValueSkipListReader *skipReader = + IE_POOL_COMPATIBLE_NEW_CLASS(sessionPool, MultiLevelPairValueSkipListReader); + skipwriter->SnapShot(skipReader, sessionPool); + inMemSkipListReader = skipReader; + } else { + BufferedSkipListWriter *bufferedWriter = + dynamic_cast(_posSkipListWriter); + assert(bufferedWriter != nullptr); + InMemPairValueSkipListReader *skipReader = IE_POOL_COMPATIBLE_NEW_CLASS( + sessionPool, InMemPairValueSkipListReader, sessionPool, false); + skipReader->Load(bufferedWriter); + inMemSkipListReader = skipReader; + } } BufferedByteSlice* posListBuffer = IE_POOL_COMPATIBLE_NEW_CLASS(sessionPool, BufferedByteSlice, sessionPool, sessionPool); diff --git a/aios/storage/indexlib/index/inverted_index/format/PositionListEncoder.h b/aios/storage/indexlib/index/inverted_index/format/PositionListEncoder.h index 451ecf1912..d48c665f6b 100644 --- a/aios/storage/indexlib/index/inverted_index/format/PositionListEncoder.h +++ b/aios/storage/indexlib/index/inverted_index/format/PositionListEncoder.h @@ -26,6 +26,8 @@ #include "indexlib/index/inverted_index/format/PositionListFormat.h" #include "indexlib/index/inverted_index/format/PositionListFormatOption.h" #include "indexlib/index/inverted_index/format/skiplist/BufferedSkipListWriter.h" +#include "indexlib/index/inverted_index/format/skiplist/SkipListWriter.h" +#include "indexlib/index/inverted_index/format/skiplist/MultiLevelSkipListWriter.h" namespace indexlib::index { @@ -56,7 +58,7 @@ class PositionListEncoder ~PositionListEncoder() { if (_posSkipListWriter) { - _posSkipListWriter->~BufferedSkipListWriter(); + _posSkipListWriter->~SkipListWriter(); _posSkipListWriter = nullptr; } if (_isOwnFormat) { @@ -76,7 +78,7 @@ class PositionListEncoder const util::ByteSliceList* GetPositionList() const { return _posListBuffer.GetByteSliceList(); } // only for ut - void SetDocSkipListWriter(BufferedSkipListWriter* writer) { _posSkipListWriter = writer; } + void SetDocSkipListWriter(SkipListWriter* writer) { _posSkipListWriter = writer; } const PositionListFormat* GetPositionListFormat() const { return _posListFormat; } @@ -91,7 +93,7 @@ class PositionListEncoder uint32_t _totalPosCount; // 4byte PositionListFormatOption _posListFormatOption; // 1byte bool _isOwnFormat; // 1byte - BufferedSkipListWriter* _posSkipListWriter; + SkipListWriter* _posSkipListWriter; const PositionListFormat* _posListFormat; autil::mem_pool::Pool* _byteSlicePool; diff --git a/aios/storage/indexlib/index/inverted_index/format/PositionListFormatOption.cpp b/aios/storage/indexlib/index/inverted_index/format/PositionListFormatOption.cpp index c8590567c1..9c34b7f6b0 100644 --- a/aios/storage/indexlib/index/inverted_index/format/PositionListFormatOption.cpp +++ b/aios/storage/indexlib/index/inverted_index/format/PositionListFormatOption.cpp @@ -22,7 +22,7 @@ AUTIL_LOG_SETUP(indexlib.index, PositionListFormatOption); bool PositionListFormatOption::operator==(const PositionListFormatOption& right) const { return _hasPositionList == right._hasPositionList && _hasPositionPayload == right._hasPositionPayload && - _hasTfBitmap == right._hasTfBitmap; + _hasTfBitmap == right._hasTfBitmap && _hasMultiLevelSkipList == right._hasMultiLevelSkipList; } void JsonizablePositionListFormatOption::Jsonize(autil::legacy::Jsonizable::JsonWrapper& json) @@ -30,23 +30,29 @@ void JsonizablePositionListFormatOption::Jsonize(autil::legacy::Jsonizable::Json bool hasPositionList; bool hasPositionPayload; bool hasTfBitmap; + bool hasMultiLevelSkipList = false; if (json.GetMode() == FROM_JSON) { json.Jsonize("has_position_list", hasPositionList); json.Jsonize("has_position_payload", hasPositionPayload); json.Jsonize("has_term_frequency_bitmap", hasTfBitmap); + // Compatible with previous formats + json.Jsonize("has_multi_level_skip_list", hasMultiLevelSkipList, hasMultiLevelSkipList); _positionListFormatOption._hasPositionList = hasPositionList ? 1 : 0; _positionListFormatOption._hasPositionPayload = hasPositionPayload ? 1 : 0; _positionListFormatOption._hasTfBitmap = hasTfBitmap ? 1 : 0; + _positionListFormatOption._hasMultiLevelSkipList = hasMultiLevelSkipList ? 1 : 0; } else { hasPositionList = _positionListFormatOption._hasPositionList == 1; hasPositionPayload = _positionListFormatOption._hasPositionPayload == 1; hasTfBitmap = _positionListFormatOption._hasTfBitmap == 1; + hasMultiLevelSkipList = _positionListFormatOption._hasMultiLevelSkipList == 1; json.Jsonize("has_position_list", hasPositionList); json.Jsonize("has_position_payload", hasPositionPayload); json.Jsonize("has_term_frequency_bitmap", hasTfBitmap); + json.Jsonize("has_multi_level_skip_list", hasMultiLevelSkipList); } } diff --git a/aios/storage/indexlib/index/inverted_index/format/PositionListFormatOption.h b/aios/storage/indexlib/index/inverted_index/format/PositionListFormatOption.h index 909e618bde..0e20ffdf08 100644 --- a/aios/storage/indexlib/index/inverted_index/format/PositionListFormatOption.h +++ b/aios/storage/indexlib/index/inverted_index/format/PositionListFormatOption.h @@ -47,19 +47,27 @@ class PositionListFormatOption } else { _hasTfBitmap = 0; } + _hasMultiLevelSkipList = 0; _unused = 0; } bool HasPositionList() const { return _hasPositionList == 1; } bool HasPositionPayload() const { return _hasPositionPayload == 1; } bool HasTfBitmap() const { return _hasTfBitmap == 1; } + bool HasMultiLevelSkipList() const { + return _hasMultiLevelSkipList == 1; + } + void SetMultiLevelSkipList(bool hasMultiLevelSkipList) { + _hasMultiLevelSkipList = hasMultiLevelSkipList; + } bool operator==(const PositionListFormatOption& right) const; private: - uint8_t _hasPositionList : 1; - uint8_t _hasPositionPayload : 1; - uint8_t _hasTfBitmap : 1; - uint8_t _unused : 5; + uint8_t _hasPositionList : 1; + uint8_t _hasPositionPayload : 1; + uint8_t _hasTfBitmap : 1; + uint8_t _hasMultiLevelSkipList : 1; + uint8_t _unused : 4; friend class PositionListEncoderTest; friend class JsonizablePositionListFormatOption; diff --git a/aios/storage/indexlib/index/inverted_index/format/PositionListSegmentDecoder.cpp b/aios/storage/indexlib/index/inverted_index/format/PositionListSegmentDecoder.cpp index b1b3a7279d..7066374a09 100644 --- a/aios/storage/indexlib/index/inverted_index/format/PositionListSegmentDecoder.cpp +++ b/aios/storage/indexlib/index/inverted_index/format/PositionListSegmentDecoder.cpp @@ -104,8 +104,14 @@ void PositionListSegmentDecoder::InitPositionSkipList(const ByteSliceList* posLi _decodedPosCount = totalTF; state->SetRecordOffset(posSkipListEnd); } else { - _posSkipListReader = IE_POOL_COMPATIBLE_NEW_CLASS(_sessionPool, PairValueSkipListReader); - _posSkipListReader->Load(posList, posSkipListStart, posSkipListEnd, (totalTF - 1) / MAX_POS_PER_RECORD + 1); + if (_option.HasMultiLevelSkipList()) { + _posSkipListReader = + IE_POOL_COMPATIBLE_NEW_CLASS(_sessionPool, MultiLevelPairValueSkipListReader); + } else { + _posSkipListReader = + IE_POOL_COMPATIBLE_NEW_CLASS(_sessionPool, PairValueSkipListReader); + } + _posSkipListReader->Load(_sessionPool, posList, posSkipListStart, posSkipListEnd, (totalTF - 1) / MAX_POS_PER_RECORD + 1); _decodedPosCount = 0; } } @@ -118,8 +124,14 @@ void PositionListSegmentDecoder::InitPositionSkipList(ByteSlice* posList, tf_t t _decodedPosCount = totalTF; state->SetRecordOffset(posSkipListEnd); } else { - _posSkipListReader = IE_POOL_COMPATIBLE_NEW_CLASS(_sessionPool, PairValueSkipListReader); - _posSkipListReader->Load(posList, posSkipListStart, posSkipListEnd, (totalTF - 1) / MAX_POS_PER_RECORD + 1); + if (_option.HasMultiLevelSkipList()) { + _posSkipListReader = + IE_POOL_COMPATIBLE_NEW_CLASS(_sessionPool, MultiLevelPairValueSkipListReader); + } else { + _posSkipListReader = + IE_POOL_COMPATIBLE_NEW_CLASS(_sessionPool, PairValueSkipListReader); + } + _posSkipListReader->Load(_sessionPool, posList, posSkipListStart, posSkipListEnd, (totalTF - 1) / MAX_POS_PER_RECORD + 1); _decodedPosCount = 0; } } @@ -208,17 +220,17 @@ bool PositionListSegmentDecoder::SkipTo(ttf_t currentTTF, NormalInDocState* stat return true; } - uint32_t offset, len; + uint32_t prevKey, offset, len; if (_posSkipListReader == nullptr) { return false; } - auto [status, ret] = _posSkipListReader->SkipTo(currentTTF + 1, _decodedPosCount, offset, len); + auto [status, ret] = _posSkipListReader->SkipTo(currentTTF + 1, _decodedPosCount, prevKey, offset, len); THROW_IF_STATUS_ERROR(status); if (!ret) { return false; } state->SetRecordOffset(offset + _posListBegin); - _preRecordTTF = _posSkipListReader->GetPrevKey(); + _preRecordTTF = prevKey; state->SetOffsetInRecord(currentTTF - _preRecordTTF); return true; } diff --git a/aios/storage/indexlib/index/inverted_index/format/PositionListSegmentDecoder.h b/aios/storage/indexlib/index/inverted_index/format/PositionListSegmentDecoder.h index 3ad9c4b721..ad3531a0e7 100644 --- a/aios/storage/indexlib/index/inverted_index/format/PositionListSegmentDecoder.h +++ b/aios/storage/indexlib/index/inverted_index/format/PositionListSegmentDecoder.h @@ -23,6 +23,8 @@ #include "indexlib/index/inverted_index/format/NormalInDocState.h" #include "indexlib/index/inverted_index/format/PositionBitmapReader.h" #include "indexlib/index/inverted_index/format/PositionListFormatOption.h" +#include "indexlib/index/inverted_index/format/skiplist/SkipListReader.h" +#include "indexlib/index/inverted_index/format/skiplist/MultiLevelSkipListReader.h" #include "indexlib/index/inverted_index/format/skiplist/PairValueSkipListReader.h" #include "indexlib/util/byte_slice_list/ByteSliceList.h" @@ -74,7 +76,7 @@ class PositionListSegmentDecoder uint32_t& recordOffset, int32_t& offsetInRecord); protected: - PairValueSkipListReader* _posSkipListReader; + SkipListReader* _posSkipListReader; autil::mem_pool::Pool* _sessionPool; const Int32Encoder* _posEncoder; const Int8Encoder* _posPayloadEncoder; diff --git a/aios/storage/indexlib/index/inverted_index/format/PostingFormatOption.h b/aios/storage/indexlib/index/inverted_index/format/PostingFormatOption.h index e210317dbc..23bcc99447 100644 --- a/aios/storage/indexlib/index/inverted_index/format/PostingFormatOption.h +++ b/aios/storage/indexlib/index/inverted_index/format/PostingFormatOption.h @@ -43,6 +43,9 @@ class PostingFormatOption { InitOptionFlag(indexConfigPtr->GetOptionFlag()); + _docListFormatOption.SetMultiLevelSkipList(indexConfigPtr->IsMultiLevelSkipList()); + _posListFormatOption.SetMultiLevelSkipList(indexConfigPtr->IsMultiLevelSkipList()); + _dictInlineItemCount = CalculateDictInlineItemCount(); _docListCompressMode = indexConfigPtr->IsReferenceCompress() ? indexlib::index::REFERENCE_COMPRESS_MODE : indexlib::index::PFOR_DELTA_COMPRESS_MODE; @@ -58,6 +61,7 @@ class PostingFormatOption bool HasPositionPayload() const { return _posListFormatOption.HasPositionPayload(); } bool HasTermFrequency() const { return _docListFormatOption.HasTermFrequency(); } bool HasTermPayload() const { return _hasTermPayload; } + bool HasMultiLevelSkipList() const { return _docListFormatOption.HasMultiLevelSkipList(); } bool IsShortListVbyteCompress() const { return _docListFormatOption.IsShortListVbyteCompress(); } format_versionid_t GetFormatVersion() const { return _formatVersion; } void SetFormatVersion(format_versionid_t id) { _formatVersion = id; } diff --git a/aios/storage/indexlib/index/inverted_index/format/SkipListSegmentDecoder.h b/aios/storage/indexlib/index/inverted_index/format/SkipListSegmentDecoder.h index 0520f4ed32..e72947d24e 100644 --- a/aios/storage/indexlib/index/inverted_index/format/SkipListSegmentDecoder.h +++ b/aios/storage/indexlib/index/inverted_index/format/SkipListSegmentDecoder.h @@ -90,7 +90,7 @@ void SkipListSegmentDecoder::InitSkipList(uint32_t start, uint32_t { IE_POOL_COMPATIBLE_DELETE_CLASS(_sessionPool, _skipListReader); _skipListReader = IE_POOL_COMPATIBLE_NEW_CLASS(_sessionPool, SkipListType, compressMode == REFERENCE_COMPRESS_MODE); - _skipListReader->Load(postingList, start, end, (df - 1) / MAX_DOC_PER_RECORD + 1); + _skipListReader->Load(_sessionPool, postingList, start, end, (df - 1) / MAX_DOC_PER_RECORD + 1); } template @@ -99,7 +99,7 @@ void SkipListSegmentDecoder::InitSkipList(uint32_t start, uint32_t { IE_POOL_COMPATIBLE_DELETE_CLASS(_sessionPool, _skipListReader); _skipListReader = IE_POOL_COMPATIBLE_NEW_CLASS(_sessionPool, SkipListType, compressMode == REFERENCE_COMPRESS_MODE); - _skipListReader->Load(postingList, start, end, (df - 1) / MAX_DOC_PER_RECORD + 1); + _skipListReader->Load(_sessionPool, postingList, start, end, (df - 1) / MAX_DOC_PER_RECORD + 1); } template diff --git a/aios/storage/indexlib/index/inverted_index/format/skiplist/BUILD b/aios/storage/indexlib/index/inverted_index/format/skiplist/BUILD index ef013d29e2..3a2e6e3c9a 100644 --- a/aios/storage/indexlib/index/inverted_index/format/skiplist/BUILD +++ b/aios/storage/indexlib/index/inverted_index/format/skiplist/BUILD @@ -1,5 +1,10 @@ load('//aios/storage:defs.bzl', 'strict_cc_library') package(default_visibility=['//aios/storage/indexlib:__subpackages__']) +strict_cc_library( + name='SkipListWriter', + srcs=[], + deps=['//aios/storage/indexlib/file_system:byte_slice_rw'] +) strict_cc_library( name='SkipListReader', deps=['//aios/storage/indexlib/file_system:byte_slice_rw'] @@ -38,8 +43,49 @@ strict_cc_library( strict_cc_library( name='BufferedSkipListWriter', deps=[ + ':SkipListWriter', '//aios/storage/indexlib/file_system:byte_slice_rw', '//aios/storage/indexlib/index/inverted_index/format:BufferedByteSlice', '//aios/storage/indexlib/index/inverted_index/format:ShortListOptimizeUtil' ] ) +strict_cc_library( + name='MultiLevelSkipListReader', + deps=[ + ':SkipListReader', + '//aios/storage/indexlib/file_system:byte_slice_rw', + '//aios/storage/indexlib/index/inverted_index/format:BufferedByteSlice', + '//aios/storage/indexlib/index/inverted_index/format:ShortListOptimizeUtil' + ] +) + +strict_cc_library( + name='MultiLevelSkipListWriter', + deps=[ + ':SkipListReader', + ':SkipListWriter', + ':MultiLevelSkipListReader', + "@com_google_absl//absl/container:inlined_vector", + '//aios/storage/indexlib/file_system:byte_slice_rw', + '//aios/storage/indexlib/index/inverted_index/format:BufferedByteSlice', + '//aios/storage/indexlib/index/inverted_index/format:ShortListOptimizeUtil', + '//aios/storage/indexlib/index/common/numeric_compress:VbyteCompressor' + ] +) + +strict_cc_library( + name='lib', + srcs=[], + hdrs = [], + deps=[ + ':SkipListReader', + ':SkipListWriter', + ':MultiLevelSkipListReader', + ':MultiLevelSkipListWriter', + ':BufferedSkipListWriter', + ':InMemPairValueSkipListReader', + ':InMemTriValueSkipListReader', + ':TriValueSkipListReader', + ':PairValueSkipListReader', + ] +) diff --git a/aios/storage/indexlib/index/inverted_index/format/skiplist/BufferedSkipListWriter.h b/aios/storage/indexlib/index/inverted_index/format/skiplist/BufferedSkipListWriter.h index ac847e27e5..5677ff6cc8 100644 --- a/aios/storage/indexlib/index/inverted_index/format/skiplist/BufferedSkipListWriter.h +++ b/aios/storage/indexlib/index/inverted_index/format/skiplist/BufferedSkipListWriter.h @@ -17,23 +17,24 @@ #include +#include "indexlib/index/inverted_index/format/skiplist/SkipListWriter.h" #include "indexlib/index/inverted_index/format/BufferedByteSlice.h" #include "indexlib/index/inverted_index/format/ShortListOptimizeUtil.h" namespace indexlib::index { -class BufferedSkipListWriter : public BufferedByteSlice +class BufferedSkipListWriter : public BufferedByteSlice, public SkipListWriter { public: BufferedSkipListWriter(autil::mem_pool::Pool* byteSlicePool, autil::mem_pool::RecyclePool* bufferPool, index::CompressMode compressMode = index::PFOR_DELTA_COMPRESS_MODE); virtual ~BufferedSkipListWriter() = default; - void AddItem(uint32_t deltaValue1); - void AddItem(uint32_t key, uint32_t value1); - void AddItem(uint32_t key, uint32_t value1, uint32_t value2); + void AddItem(uint32_t deltaValue1) override; + void AddItem(uint32_t key, uint32_t value1) override; + void AddItem(uint32_t key, uint32_t value1, uint32_t value2) override; - size_t FinishFlush(); + size_t FinishFlush() override; void Dump(const std::shared_ptr& file) override; size_t EstimateDumpSize() const override; diff --git a/aios/storage/indexlib/index/inverted_index/format/skiplist/InMemPairValueSkipListReader.h b/aios/storage/indexlib/index/inverted_index/format/skiplist/InMemPairValueSkipListReader.h index a0306a2695..bed100c5a4 100644 --- a/aios/storage/indexlib/index/inverted_index/format/skiplist/InMemPairValueSkipListReader.h +++ b/aios/storage/indexlib/index/inverted_index/format/skiplist/InMemPairValueSkipListReader.h @@ -32,13 +32,13 @@ class InMemPairValueSkipListReader : public PairValueSkipListReader InMemPairValueSkipListReader(const InMemPairValueSkipListReader& other) = delete; // InMemPairValueSkipListReader should not call this method - void Load(const util::ByteSliceList* byteSliceList, uint32_t start, uint32_t end, + void Load(autil::mem_pool::Pool *pool, const util::ByteSliceList* byteSliceList, uint32_t start, uint32_t end, const uint32_t& itemCount) override { assert(false); } - void Load(util::ByteSlice* byteSlice, uint32_t start, uint32_t end, const uint32_t& itemCount) override + void Load(autil::mem_pool::Pool *pool, util::ByteSlice* byteSlice, uint32_t start, uint32_t end, const uint32_t& itemCount) override { assert(false); } diff --git a/aios/storage/indexlib/index/inverted_index/format/skiplist/InMemTriValueSkipListReader.h b/aios/storage/indexlib/index/inverted_index/format/skiplist/InMemTriValueSkipListReader.h index d537027ac9..1e60682954 100644 --- a/aios/storage/indexlib/index/inverted_index/format/skiplist/InMemTriValueSkipListReader.h +++ b/aios/storage/indexlib/index/inverted_index/format/skiplist/InMemTriValueSkipListReader.h @@ -28,13 +28,13 @@ class InMemTriValueSkipListReader : public TriValueSkipListReader explicit InMemTriValueSkipListReader(autil::mem_pool::Pool* sessionPool = nullptr); ~InMemTriValueSkipListReader(); - void Load(const util::ByteSliceList* byteSliceList, uint32_t start, uint32_t end, + void Load(autil::mem_pool::Pool *pool, const util::ByteSliceList* byteSliceList, uint32_t start, uint32_t end, const uint32_t& itemCount) override { assert(false); } - void Load(util::ByteSlice* byteSlice, uint32_t start, uint32_t end, const uint32_t& itemCount) override + void Load(autil::mem_pool::Pool *pool, util::ByteSlice* byteSlice, uint32_t start, uint32_t end, const uint32_t& itemCount) override { assert(false); } diff --git a/aios/storage/indexlib/index/inverted_index/format/skiplist/MultiLevelSkipListReader.cpp b/aios/storage/indexlib/index/inverted_index/format/skiplist/MultiLevelSkipListReader.cpp new file mode 100644 index 0000000000..4523ddbb05 --- /dev/null +++ b/aios/storage/indexlib/index/inverted_index/format/skiplist/MultiLevelSkipListReader.cpp @@ -0,0 +1,27 @@ +/* + * Copyright 2014-present Alibaba Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "indexlib/index/inverted_index/format/skiplist/MultiLevelSkipListReader.h" + +namespace indexlib::index { + +template <> +alog::Logger* MultiLevelPairValueSkipListReader::_logger = + alog::Logger::getLogger("indexlib.index.MultiLevelPairValueSkipListReader"); +template <> +alog::Logger* MultiLevelTriValueSkipListReader::_logger = + alog::Logger::getLogger("indexlib.index.MultiLevelTriValueSkipListReader"); + +} // namespace indexlib::index diff --git a/aios/storage/indexlib/index/inverted_index/format/skiplist/MultiLevelSkipListReader.h b/aios/storage/indexlib/index/inverted_index/format/skiplist/MultiLevelSkipListReader.h new file mode 100644 index 0000000000..3ee59568b6 --- /dev/null +++ b/aios/storage/indexlib/index/inverted_index/format/skiplist/MultiLevelSkipListReader.h @@ -0,0 +1,599 @@ +/* + * Copyright 2014-present Alibaba Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include + +#include "indexlib/file_system/ByteSliceReader.h" +#include "indexlib/index/inverted_index/Constant.h" +#include "indexlib/index/inverted_index/format/skiplist/SkipListReader.h" + +struct LengthInfo { + uint8_t lengths[4]; // Each controlByte corresponds to the length of 4 fields. +}; +constexpr LengthInfo MakeLengthInfo(uint8_t controlByte) { + LengthInfo info{}; + for (uint8_t i = 0; i < 4; ++i) { + info.lengths[i] = static_cast(((controlByte >> (i * 2)) & 0x03) + 1); + } + return info; +} +constexpr std::array MakeLengthTable() { + std::array table{}; + for (int c = 0; c < 256; ++c) { + table[c] = MakeLengthInfo(static_cast(c)); + } + return table; +} +constexpr std::array MakeTotalLenTable() { + std::array table{}; + for (int count = 2; count <= 4; ++count) { + int idx = count - 2; // 2 -> 0, 3 -> 1, 4 -> 2 + for (int c = 0; c < 256; ++c) { + uint8_t controlByte = static_cast(c); + uint8_t sum = 0; + for (int i = 0; i < count; ++i) { + uint8_t len = static_cast(((controlByte >> (i * 2)) & 0x03) + 1); + sum = static_cast(sum + len); + } + table[idx * 256 + c] = sum; + } + } + return table; +} + +// Encoding 2 to 4 numbers can share the same decoding table, but the total length of different +// encodings is different. +constexpr auto LENGTH_TABLE = MakeLengthTable(); +constexpr auto TOTAL_LEN_TABLE = MakeTotalLenTable(); + +namespace indexlib::index { + +using namespace indexlib::file_system; + +struct MultiLevelSkipListLevelMeta { + uint32_t levelBegin; + uint32_t levelEnd; + uint32_t numSkipped; + uint32_t skipDoc; + uint32_t offset; + uint32_t childPointer; + uint32_t ttf; +}; + +class GroupVarintCompressor { +public: + static constexpr uint32_t BUFFER_SIZE = 20; + /** + * @brief Compress 2~4 uint32 values into the specified buffer + * @param values Input uint32 array + * @param count Number of values [2,4] + * @param output Output buffer pointer + * @return Total number of bytes after compression + */ + static size_t Compress(const uint32_t *values, uint8_t count, uint8_t *output) { + assert(2 <= count && count <= 4); + + uint8_t *currentOutput = output; + + // Calculate the encoded length (in bytes) of each value and the control byte + uint8_t lengths[4]; + uint8_t controlByte = 0; + for (uint8_t i = 0; i < count; i++) { + // 0 also occupies 1 byte + lengths[i] = + values[i] == 0 ? 1 : static_cast((32 - __builtin_clz(values[i]) + 7) / 8); + controlByte |= ((lengths[i] - 1) & 0x03) << (i * 2); + } + + // Write control byte + *currentOutput++ = controlByte; + + // Write the low bytes of each value according to its length + for (uint8_t i = 0; i < count; i++) { + *(uint32_t *)(currentOutput) = values[i]; + currentOutput += lengths[i]; + } + + return static_cast(currentOutput - output); + } + + /** + * @brief Decompress leaf-level data into MultiLevelSkipListLevelMeta + * @tparam EnableTTF Whether the TTF field is included + */ + template + static FSResult DecompressLeaf(ByteSliceReader *reader, + MultiLevelSkipListLevelMeta &meta) { + uint8_t dataBuffer[BUFFER_SIZE]; + const uint8_t *data = nullptr; + const LengthInfo *info = nullptr; + uint8_t totalLen = 0; + + constexpr uint8_t COUNT = EnableTTF ? 3 : 2; + + auto ret = decodeCommon(reader, COUNT, info, dataBuffer, data, totalLen); + RETURN_IF_FS_ERROR(ret.Code(), "Read DecompressLeaf failed"); + + meta.skipDoc += Extract(data, info->lengths[0]); + if constexpr (EnableTTF) { + meta.ttf += Extract(data, info->lengths[1]); + meta.offset += Extract(data, info->lengths[2]); + } else { + meta.offset += Extract(data, info->lengths[1]); + } + return FSEC_OK; + } + + /** + * @brief Decompress non-leaf-level data into MultiLevelSkipListLevelMeta + * @tparam EnableTTF Whether the TTF field is included + * @param levelBegin The start position of the next level, used for: + * childPointer = offset + levelBegin + */ + template + static FSResult DecompressNonLeaf(ByteSliceReader *reader, + MultiLevelSkipListLevelMeta &meta, + uint32_t levelBegin) { + uint8_t dataBuffer[BUFFER_SIZE]; + const uint8_t *data = nullptr; + const LengthInfo *info = nullptr; + uint8_t totalLen = 0; + + constexpr uint8_t COUNT = EnableTTF ? 4 : 3; + + auto ret = decodeCommon(reader, COUNT, info, dataBuffer, data, totalLen); + RETURN_IF_FS_ERROR(ret.Code(), "Read DecompressNonLeaf failed"); + + meta.skipDoc += Extract(data, info->lengths[0]); + if constexpr (EnableTTF) { + meta.ttf += Extract(data, info->lengths[1]); + meta.offset += Extract(data, info->lengths[2]); + meta.childPointer = Extract(data, info->lengths[3]) + levelBegin; + } else { + meta.offset += Extract(data, info->lengths[1]); + meta.childPointer = Extract(data, info->lengths[2]) + levelBegin; + } + return FSEC_OK; + } + + /** + * @brief Only decompress the childPointer field of a non-leaf level + * @tparam EnableTTF Whether the TTF field is included + */ + template + static FSResult DecompressChildPointer(ByteSliceReader *reader, + MultiLevelSkipListLevelMeta &meta, + uint32_t levelBegin) { + uint8_t dataBuffer[BUFFER_SIZE]; + const uint8_t *data = nullptr; + const LengthInfo *info = nullptr; + uint8_t totalLen = 0; + + constexpr uint8_t COUNT = EnableTTF ? 4 : 3; + + auto ret = decodeCommon(reader, COUNT, info, dataBuffer, data, totalLen); + RETURN_IF_FS_ERROR(ret.Code(), "Read DecompressChildPointer failed"); + + const uint8_t lastIdx = static_cast(COUNT - 1); + // Jump to the last field: totalLen - length of the last field + data += totalLen - info->lengths[lastIdx]; + + meta.childPointer = Extract(data, info->lengths[lastIdx]) + levelBegin; + return FSEC_OK; + } + + /** + * @brief Common decoding interface for unit tests, decodes outlen values into output + */ + static void Decode(ByteSliceReader *reader, uint32_t outlen, uint32_t *output) { + assert(2 <= outlen && outlen <= 4); + + uint8_t dataBuffer[BUFFER_SIZE]; + const uint8_t *data = nullptr; + const LengthInfo *info = nullptr; + uint8_t totalLen = 0; + uint8_t count = static_cast(outlen); + + decodeCommon(reader, count, info, dataBuffer, data, totalLen).GetOrThrow(); + + for (uint32_t i = 0; i < outlen; ++i) { + output[i] = Extract(data, info->lengths[i]); + } + } + +private: + /** + * @brief Common decode function: read control byte & read data with corresponding length + * + * @param reader ByteSliceReader instance + * @param count Number of values to decode this time (2/3/4) + * @param lengthInfo Output: field length info LENGTH_TABLE[controlByte] + * @param dataBuffer Local buffer used for ReadMayCopy + * @param data Output: actual data start pointer (may point to internal buffer) + * @param totalLen Output: total number of bytes read this time + */ + static inline FSResult decodeCommon(ByteSliceReader *reader, uint8_t count, + const LengthInfo *&lengthInfo, uint8_t *dataBuffer, + const uint8_t *&data, uint8_t &totalLen) { + assert(2 <= count && count <= 4); + + auto ret = reader->ReadByte(); + RETURN_IF_FS_ERROR(ret.Code(), "Read Gvint controlByte failed"); + uint8_t controlByte = ret.Value(); + + // Unified length table: lengths[4] + lengthInfo = &LENGTH_TABLE[controlByte]; + + // totalLen comes from TOTAL_LEN_TABLE[(count-2) * 256 + controlByte] + const uint8_t countIndex = static_cast(count - 2); + totalLen = TOTAL_LEN_TABLE[countIndex * 256 + controlByte]; + + void *bufferPtr = dataBuffer; + auto ret2 = reader->ReadMayCopy(bufferPtr, totalLen); + RETURN_IF_FS_ERROR(ret2.Code(), "Read Gvint content failed"); + if (unlikely(ret2.Value() != totalLen)) { + RETURN_IF_FS_ERROR(FSEC_ERROR, "Read Gvint content length mismatch"); + } + data = static_cast(bufferPtr); + return FSEC_OK; + } + + /** + * @brief Decode a uint32 from buffer using len bytes and advance the data pointer + */ + static inline uint32_t Extract(const uint8_t *&data, uint8_t len) { + uint32_t v; + switch (len) { + case 1: + v = data[0]; + break; + case 2: + v = *(const uint16_t *)(data); + break; + case 3: + v = *(const uint16_t *)(data) | (uint32_t(data[2]) << 16); + break; + case 4: + default: + v = *(const uint32_t *)(data); + break; + } + data += len; + return v; + } + +private: + static inline alog::Logger *_logger = + alog::Logger::getLogger("indexlib.index.GroupVarintCompressor"); +}; + +template +class MultiLevelSkipListReader : public SkipListReader { +private: + using SkipListReader::Load; + +public: + using keytype_t = uint32_t; + MultiLevelSkipListReader(bool isReferenceCompress = false); + virtual ~MultiLevelSkipListReader(); + +public: + void Load(autil::mem_pool::Pool *pool, const util::ByteSliceList *byteSliceList, uint32_t start, + uint32_t end, const uint32_t &itemCount) override; + + void Load(autil::mem_pool::Pool *pool, util::ByteSlice *byteSlice, uint32_t start, uint32_t end, + const uint32_t &itemCount) override; + std::pair SkipTo(uint32_t queryKey, uint32_t &key, uint32_t &prevKey, + uint32_t &value, uint32_t &delta) override; + uint32_t GetPrevKey() const override { + return _lastDoc; + } + // After the pos list completes its skip list iterations, GetCurrentKey() will be called, which + // needs to return _lastDoc. + uint32_t GetCurrentKey() const override { + if (unlikely(_readers == nullptr)) { + return 0; + } + uint32_t doc = _metas[0].skipDoc; + return doc == END_DOC ? _lastDoc : doc; + } + uint32_t GetCurrentTTF() const override { + if (unlikely(_readers == nullptr)) { + return 0; + } + return _metas[0].ttf; + } + uint32_t GetPrevTTF() const override { + return _lastTTF; + } + // This function is used when the skip list has been traversed to find the last element in the + // list; it should return _lastDoc. + uint32_t GetLastKeyInBuffer() const override { + return GetPrevKey(); + } + uint32_t GetLastValueInBuffer() const override { + if (unlikely(_readers == nullptr)) { + return 0; + } + return _metas[0].offset; + } + +private: + void allocateData(autil::mem_pool::Pool *pool); + void LoadFirstItem(); + inline FSResult SeekChild(uint8_t level); + inline FSResult LoadNextSkip(uint8_t level); + template + void LoadInternal(autil::mem_pool::Pool *pool, Content content, uint32_t start, uint32_t end, + uint32_t itemCount); + const static uint32_t END_DOC = std::numeric_limits::max(); + // This value does not change with the seek of the skip list + uint8_t _skipLevel; + file_system::ByteSliceReader *_readers; + struct MultiLevelSkipListLevelMeta *_metas; + uint8_t _numberOfSkipLevel; + uint32_t _lastDoc; + uint32_t _lastTTF; + uint32_t _lastOffset; + uint32_t _lastChildPointer; + + static constexpr uint64_t SkipInterval[MAX_SKIP_LEVEL] = { + 1ULL, // SKIP_MULTIPLIER^0 = 1 + SKIP_MULTIPLIER, // SKIP_MULTIPLIER^1 + SKIP_MULTIPLIER *SKIP_MULTIPLIER, // SKIP_MULTIPLIER^2 + SKIP_MULTIPLIER *SKIP_MULTIPLIER *SKIP_MULTIPLIER, // SKIP_MULTIPLIER^3 + SKIP_MULTIPLIER *SKIP_MULTIPLIER *SKIP_MULTIPLIER *SKIP_MULTIPLIER, // SKIP_MULTIPLIER^4 + SKIP_MULTIPLIER *SKIP_MULTIPLIER *SKIP_MULTIPLIER *SKIP_MULTIPLIER + *SKIP_MULTIPLIER, // SKIP_MULTIPLIER^5 + SKIP_MULTIPLIER *SKIP_MULTIPLIER *SKIP_MULTIPLIER *SKIP_MULTIPLIER *SKIP_MULTIPLIER + *SKIP_MULTIPLIER, // SKIP_MULTIPLIER^6 + SKIP_MULTIPLIER *SKIP_MULTIPLIER *SKIP_MULTIPLIER *SKIP_MULTIPLIER *SKIP_MULTIPLIER + *SKIP_MULTIPLIER *SKIP_MULTIPLIER, // SKIP_MULTIPLIER^7 + SKIP_MULTIPLIER *SKIP_MULTIPLIER *SKIP_MULTIPLIER *SKIP_MULTIPLIER *SKIP_MULTIPLIER + *SKIP_MULTIPLIER *SKIP_MULTIPLIER *SKIP_MULTIPLIER, // SKIP_MULTIPLIER^8 + SKIP_MULTIPLIER *SKIP_MULTIPLIER *SKIP_MULTIPLIER *SKIP_MULTIPLIER *SKIP_MULTIPLIER + *SKIP_MULTIPLIER *SKIP_MULTIPLIER *SKIP_MULTIPLIER *SKIP_MULTIPLIER // SKIP_MULTIPLIER^9 + }; + + friend class MultiLevelSkipListWriter; + +private: + inline AUTIL_LOG_DECLARE(); +}; + +template +MultiLevelSkipListReader::MultiLevelSkipListReader(bool isReferenceCompress) + : _skipLevel(0), + _readers(nullptr), + _metas(nullptr), + _numberOfSkipLevel(0), + _lastDoc(0), + _lastTTF(0), + _lastOffset(0), + _lastChildPointer(0) {} + +template +MultiLevelSkipListReader::~MultiLevelSkipListReader() {} + +template +void MultiLevelSkipListReader::LoadFirstItem() { + _numberOfSkipLevel = _skipLevel; + _lastDoc = 0; + if constexpr (EnableTTF) { + _lastTTF = 0; + } + _lastOffset = 0; + _lastChildPointer = 0; + _skippedItemCount = 0; + // Load the first element of the skip list. If the skip list exists, at least one node must + // exist. + GroupVarintCompressor::DecompressLeaf(&_readers[0], _metas[0]).GetOrThrow(); + _metas[0].numSkipped = 1; +} + +template +void MultiLevelSkipListReader::allocateData(autil::mem_pool::Pool *pool) { + size_t readerSize = sizeof(file_system::ByteSliceReader) * _skipLevel; + size_t metaSize = sizeof(MultiLevelSkipListLevelMeta) * _skipLevel; + + // Allocate aligned memory for each of the two types. + char *readerData = + static_cast(pool->allocateAlign(readerSize, alignof(file_system::ByteSliceReader))); + char *metaData = + static_cast(pool->allocateAlign(metaSize, alignof(MultiLevelSkipListLevelMeta))); + + // Initialize the ByteSliceMemoryReader array + _readers = reinterpret_cast(readerData); + for (uint8_t i = 0; i < _skipLevel; ++i) { + new (_readers + i) file_system::ByteSliceReader(); + } + + // Initialize the MultiLevelSkipListLevelMeta array + memset(metaData, 0, metaSize); + _metas = reinterpret_cast(metaData); +} + +template +void MultiLevelSkipListReader::Load(autil::mem_pool::Pool *pool, + const util::ByteSliceList *byteSliceList, + uint32_t start, uint32_t end, + const uint32_t &itemCount) { + SkipListReader::Load(byteSliceList, start, end); + LoadInternal(pool, byteSliceList, start, end, itemCount); +} + +template +void MultiLevelSkipListReader::Load(autil::mem_pool::Pool *pool, + util::ByteSlice *byteSlice, uint32_t start, + uint32_t end, const uint32_t &itemCount) { + SkipListReader::Load(byteSlice, start, end); + LoadInternal(pool, byteSlice, start, end, itemCount); +} + +template +template +void MultiLevelSkipListReader::LoadInternal(autil::mem_pool::Pool *pool, Content content, + uint32_t start, uint32_t end, + uint32_t itemCount) { + _skipLevel = 1 + std::log(itemCount) / std::log(SKIP_MULTIPLIER); + _skipLevel = std::min(_skipLevel, MAX_SKIP_LEVEL); + assert(_skipLevel != 0); + allocateData(pool); + + // No encoding of level 0 length + if (_skipLevel == 1) { + _metas[0].levelBegin = start; + _metas[0].levelEnd = end; + if constexpr (std::is_const_v>) { + using NonConstType = std::remove_const_t> *; + _readers[0].Open(const_cast(content)).GetOrThrow(); + } else { + _readers[0].Open(content).GetOrThrow(); + } + _readers[0].Seek(start).GetOrThrow(); + } else { + uint32_t levelLen[MAX_SKIP_LEVEL]; + uint32_t totalLenExceptLevel0 = 0; + levelLen[0] = 0; + for (size_t i = 1; i < _skipLevel; i++) { + levelLen[i] = _byteSliceReader.ReadVUInt32().GetOrThrow(); + totalLenExceptLevel0 += levelLen[i]; + } + start = _byteSliceReader.Tell(); + // Calculate the length of level 0 based on the value of end. + levelLen[0] = end - start - totalLenExceptLevel0; + uint32_t baseOffset = start; + for (size_t i = 0; i < _skipLevel; ++i) { + _metas[i].levelBegin = baseOffset; + baseOffset += levelLen[i]; + _metas[i].levelEnd = baseOffset; + if constexpr (std::is_const_v>) { + using NonConstType = std::remove_const_t> *; + _readers[i].Open(const_cast(content)).GetOrThrow(); + } else { + _readers[i].Open(content).GetOrThrow(); + } + _readers[i].Seek(_metas[i].levelBegin).GetOrThrow(); + } + } + LoadFirstItem(); +} + +template +std::pair MultiLevelSkipListReader::SkipTo( + uint32_t queryKey, uint32_t &key, uint32_t &prevKey, uint32_t &value, uint32_t &delta) { + // _skipLevel: + // 1. Offline index skiplist is always not empty + // 2. Real-time index skiplist may be empty (because the real-time index skiplist snapshot may + // have been forked when the skiplist was first created; in this case, simply check if the + // skiplist is empty and return false). + // _numberOfSkipLevel: + // 1. If the skiplist is exhausted at this point, return false. + if (unlikely(_skipLevel == 0 || _numberOfSkipLevel == 0)) { + return std::make_pair(Status::OK(), false); + } + int32_t level = 0; + + // If there is no skip node, the process ends directly. + if (queryKey <= _metas[0].skipDoc) { + goto end; + } else { + // Pre-probe a level 0 node. If the node is found, return immediately without traversing + // higher levels of the skip list. + // If traversing a multi-level skip list and consecutive level 0 nodes are found, this will + // be optimized to traversing a single-level skip list. + auto ret = LoadNextSkip(0); + RETURN2_IF_STATUS_ERROR(ret.Status(), false, + "MultiLevelSkipListReader prefetch level0 node failed"); + if (queryKey <= _metas[0].skipDoc) { + goto end; + } + } + + while (level < _numberOfSkipLevel - 1 && queryKey > _metas[level + 1].skipDoc) { + level++; + } + while (level >= 0) { + if (queryKey > _metas[level].skipDoc) { + auto ret = LoadNextSkip(level); + RETURN2_IF_STATUS_ERROR(ret.Status(), false, + "MultiLevelSkipListReader LoadNextSkip failed"); + } else { + if (level > 0 && _lastChildPointer > _readers[level - 1].Tell()) { + auto ret = SeekChild(level - 1); + RETURN2_IF_STATUS_ERROR(ret.Status(), false, + "MultiLevelSkipListReader SeekChild failed"); + } + level--; + } + } +end: + key = GetCurrentKey(); + prevKey = GetPrevKey(); + value = _lastOffset; + delta = _metas[0].offset - _lastOffset; + // The definition of _skippedItemCount is the number up to prev. + _skippedItemCount = _metas[0].numSkipped - 1; + return std::make_pair(Status::OK(), _metas[0].skipDoc != END_DOC); +} + +template +FSResult MultiLevelSkipListReader::LoadNextSkip(uint8_t level) { + _lastDoc = _metas[level].skipDoc; + if constexpr (EnableTTF) { + _lastTTF = _metas[level].ttf; + } + _lastOffset = _metas[level].offset; + _lastChildPointer = _metas[level].childPointer; + _metas[level].numSkipped += SkipInterval[level]; + // Check if the skip list at this level has been traversed. + // If skipping to data exceeding the skip list's range, the skip list is exhausted; check + // BYTE_SLICE_EOF. Return false directly. + if (_readers[level].Tell() == _metas[level].levelEnd || + _readers[level].Tell() == ByteSliceReader::BYTE_SLICE_EOF) { + // Use END_DOC, not INVALID_DOCID, as -1 cannot represent document boundaries. + _metas[level].skipDoc = END_DOC; + if (_numberOfSkipLevel > level) _numberOfSkipLevel = level; + return FSEC_OK; + } + // Read skip list data + return level != 0 + ? GroupVarintCompressor::DecompressNonLeaf( + &_readers[level], _metas[level], _metas[level - 1].levelBegin) + : GroupVarintCompressor::DecompressLeaf(&_readers[level], _metas[level]); +} + +template +FSResult MultiLevelSkipListReader::SeekChild(uint8_t level) { + auto ret = _readers[level].Seek(_lastChildPointer); + RETURN_IF_FS_ERROR(ret.Code(), "Seek Skiplist ChildPointer failed"); + _metas[level].numSkipped = _metas[level + 1].numSkipped - SkipInterval[level + 1]; + _metas[level].skipDoc = _lastDoc; + if constexpr (EnableTTF) { + _metas[level].ttf = _lastTTF; + } + _metas[level].offset = _lastOffset; + if (level > 0) { + return GroupVarintCompressor::DecompressChildPointer( + &_readers[level], _metas[level], _metas[level - 1].levelBegin); + } + return FSEC_OK; +} + +typedef MultiLevelSkipListReader MultiLevelPairValueSkipListReader; +typedef MultiLevelSkipListReader MultiLevelTriValueSkipListReader; +} // namespace indexlib::index diff --git a/aios/storage/indexlib/index/inverted_index/format/skiplist/MultiLevelSkipListWriter.cpp b/aios/storage/indexlib/index/inverted_index/format/skiplist/MultiLevelSkipListWriter.cpp new file mode 100644 index 0000000000..88fa240192 --- /dev/null +++ b/aios/storage/indexlib/index/inverted_index/format/skiplist/MultiLevelSkipListWriter.cpp @@ -0,0 +1,158 @@ +/* + * Copyright 2014-present Alibaba Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "indexlib/index/inverted_index/format/skiplist/MultiLevelSkipListWriter.h" + +#include +#include +#include +#include +#include + +#include "indexlib/index/common/numeric_compress/VbyteCompressor.h" + +namespace indexlib::index { + +AUTIL_LOG_SETUP(indexlib.index, MultiLevelSkipListWriter); + +MultiLevelSkipListWriter::MultiLevelSkipListWriter(autil::mem_pool::Pool* byteSlicePool) + : _byteSlicePool(byteSlicePool) {} + +MultiLevelSkipListWriter::~MultiLevelSkipListWriter() { + for (size_t i = 0; i < _skipBuffers.size(); ++i) { + _skipBuffers[i]->~ByteSliceWriter(); + uint8_t* mem = reinterpret_cast(_skipBuffers[i]); + _byteSlicePool->deallocate(mem, sizeof(file_system::ByteSliceWriter)); + } +} + +void MultiLevelSkipListWriter::AddSkipLevel() { + void* mem = _byteSlicePool->allocate(sizeof(file_system::ByteSliceWriter)); + file_system::ByteSliceWriter* writer = new (mem) file_system::ByteSliceWriter(_byteSlicePool); + _skipBuffers.emplace_back(writer); + _lastKeys.push_back(0); + _lastTTFs.push_back(0); + _lastOffsets.push_back(0); + _levelEnds[_skipLevel.load(std::memory_order_relaxed)] = 0; + _skipLevel.fetch_add(1, std::memory_order_release); +} + +void MultiLevelSkipListWriter::AddItemInternal(uint32_t key, uint32_t offset, + std::optional ttf) { + _accumulateOffset += offset; + uint32_t skipCount = ++_skipCount; + uint8_t numLevels = 1; + if (skipCount % SKIP_MULTIPLIER == 0) { + numLevels++; + skipCount /= SKIP_MULTIPLIER; + while ((skipCount % SKIP_MULTIPLIER) == 0 && numLevels < MAX_SKIP_LEVEL) { + numLevels++; + skipCount /= SKIP_MULTIPLIER; + } + } + + // The current skip list has insufficient levels; a new skip list needs to be created. Each call + // will create at most one new skip list level. + if (numLevels > _skipLevel.load(std::memory_order_relaxed)) { + AddSkipLevel(); + } + + // write level 0 data + // Level 0 does not contain a childPointer. + uint32_t childPointer = 0; + { + uint8_t output[GroupVarintCompressor::BUFFER_SIZE]; + size_t len; + if (ttf.has_value()) { + uint32_t ttf_value = ttf.value(); + uint32_t input[3] = {key - _lastKeys[0], ttf_value - _lastTTFs[0], + _accumulateOffset - _lastOffsets[0]}; + // Level 0 does not contain a childPointer. + len = GroupVarintCompressor::Compress(input, 3 , output); + _lastTTFs[0] = ttf_value; + } else { + uint32_t input[2] = {key - _lastKeys[0], _accumulateOffset - _lastOffsets[0]}; + len = GroupVarintCompressor::Compress(input, 2, output); + } + _skipBuffers[0]->Write(output, len); + _lastKeys[0] = key; + _lastOffsets[0] = _accumulateOffset; + childPointer = _skipBuffers[0]->GetSize(); + } + + // write higher level data + for (size_t level = 1; level < numLevels; level++) { + uint32_t newChildPointer = _skipBuffers[level]->GetSize(); + + uint8_t output[GroupVarintCompressor::BUFFER_SIZE]; + size_t len; + if (ttf.has_value()) { + uint32_t ttf_value = ttf.value(); + uint32_t input[4] = {key - _lastKeys[level], ttf_value - _lastTTFs[level], + _accumulateOffset - _lastOffsets[level], childPointer}; + len = GroupVarintCompressor::Compress(input, 4, output); + _lastTTFs[level] = ttf_value; + } else { + uint32_t input[3] = {key - _lastKeys[level], _accumulateOffset - _lastOffsets[level], + childPointer}; + len = GroupVarintCompressor::Compress(input, 3, output); + } + _skipBuffers[level]->Write(output, len); + _lastKeys[level] = key; + _lastOffsets[level] = _accumulateOffset; + + childPointer = newChildPointer; + } + // Once all modifications to the skiplist are complete, update the view from bottom to top, + // making the changes to the skiplist visible externally. + for (size_t level = 0; level < numLevels; level++) { + _levelEnds[level].store(_skipBuffers[level]->GetSize(), std::memory_order_release); + } +} + +void MultiLevelSkipListWriter::AddItem(uint32_t key, uint32_t ttf, uint32_t offset) { + AddItemInternal(key, offset, std::make_optional(ttf)); +} + +void MultiLevelSkipListWriter::AddItem(uint32_t key, uint32_t offset) { + AddItemInternal(key, offset); +} + +void MultiLevelSkipListWriter::Dump(const file_system::FileWriterPtr& file) { + if (_skipBuffers.size() == 0) { + return; + } else { + for (size_t i = 1; i < _skipBuffers.size(); ++i) { + file->WriteVUInt32(_skipBuffers[i]->GetSize()).GetOrThrow(); + } + for (size_t i = 0; i < _skipBuffers.size(); ++i) { + _skipBuffers[i]->Dump(file).GetOrThrow(); + } + } +} + +size_t MultiLevelSkipListWriter::EstimateDumpSize() const { + uint32_t skipListSize = 0; + for (size_t i = 0; i < _skipBuffers.size(); ++i) { + // The length of level0 is not encoded; it can be directly derived from the total length. + if (i != 0) { + skipListSize += VByteCompressor::GetVInt32Length(_skipBuffers[i]->GetSize()); + } + skipListSize += _skipBuffers[i]->GetSize(); + } + return skipListSize; +} + +} // namespace indexlib::index diff --git a/aios/storage/indexlib/index/inverted_index/format/skiplist/MultiLevelSkipListWriter.h b/aios/storage/indexlib/index/inverted_index/format/skiplist/MultiLevelSkipListWriter.h new file mode 100644 index 0000000000..d9f6de576c --- /dev/null +++ b/aios/storage/indexlib/index/inverted_index/format/skiplist/MultiLevelSkipListWriter.h @@ -0,0 +1,117 @@ +/* + * Copyright 2014-present Alibaba Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include +#include +#include +#include +#include + +#include "absl/container/inlined_vector.h" +#include "indexlib/index/inverted_index/format/BufferedByteSlice.h" +#include "indexlib/index/inverted_index/format/ShortListOptimizeUtil.h" +#include "indexlib/index/inverted_index/format/skiplist/MultiLevelSkipListReader.h" +#include "indexlib/index/inverted_index/format/skiplist/SkipListWriter.h" + +namespace indexlib::index { + +class MultiLevelSkipListWriter : public SkipListWriter { +public: + MultiLevelSkipListWriter(autil::mem_pool::Pool *byteSlicePool); + virtual ~MultiLevelSkipListWriter(); + +public: + void AddItem(uint32_t offset) override {}; + void AddItem(uint32_t key, uint32_t offset) override; + void AddItem(uint32_t key, uint32_t ttf, uint32_t offset) override; + size_t FinishFlush() override { + return 0; + }; + size_t EstimateDumpSize() const override; + void Dump(const std::shared_ptr &file) override; + template + void SnapShot(MultiLevelSkipListReader *reader, autil::mem_pool::Pool *pool) const; + +private: + void AddSkipLevel(); + void AddItemInternal(uint32_t key, uint32_t offset, std::optional ttf = std::nullopt); + +private: + autil::mem_pool::Pool *_byteSlicePool; + + constexpr static uint32_t WRITER_INLINE_NUM = 3; + absl::InlinedVector _lastKeys; + absl::InlinedVector _lastTTFs; + absl::InlinedVector _lastOffsets; + absl::InlinedVector _skipBuffers; + + std::atomic _levelEnds[MAX_SKIP_LEVEL]; + // Record the maximum skip list level. The skip list will not exceed 256, so uint8_t is + // sufficient for storage. + std::atomic _skipLevel = 0; + // The number of elements indexed in the skip list, i.e., the total number of skip lists in + // level 0. + uint32_t _skipCount = 0; + // Record the current offset for writing to the skip list. + uint32_t _accumulateOffset = 0; + +private: + friend class MultiLevelSkipListWriterTest; + AUTIL_LOG_DECLARE(); +}; + +template +void MultiLevelSkipListWriter::SnapShot(MultiLevelSkipListReader *reader, + autil::mem_pool::Pool *pool) const { + // Create a snapshot + uint8_t currentLevel = _skipLevel.load(std::memory_order_acquire); + + // The current skiplist is empty. + if (currentLevel == 0) { + // Used to exit immediately after SkipTo check. + reader->_skipLevel = 0; + return; + } + + // Ensure that each skip list entry at the high level can index its corresponding low level + // entry. + // 1. Write: Update from bottom to top, ensuring that the corresponding low level entry has + // already been written when each high level entry is written. + // 2. Read: Read from top to bottom, ensuring that the corresponding low level entry has already + // been written when each high level entry is read. + uint32_t levelEnds[MAX_SKIP_LEVEL]; + for (int32_t level = currentLevel - 1; level >= 0; level--) { + levelEnds[level] = _levelEnds[level].load(std::memory_order_acquire); + } + + // Check again, level 0 might be null. + if (unlikely(levelEnds[0] == 0)) { + reader->_skipLevel = 0; + return; + } + + // Start initializing the skip list + reader->_skipLevel = currentLevel; + reader->allocateData(pool); + for (size_t i = 0; i < reader->_skipLevel; i++) { + reader->_metas[i].levelEnd = levelEnds[i]; + reader->_readers[i].Open(_skipBuffers[i]->GetByteSliceList()).GetOrThrow(); + } + reader->LoadFirstItem(); +} + +} // namespace indexlib::index diff --git a/aios/storage/indexlib/index/inverted_index/format/skiplist/PairValueSkipListReader.cpp b/aios/storage/indexlib/index/inverted_index/format/skiplist/PairValueSkipListReader.cpp index 50b64f0667..dda034cf7c 100644 --- a/aios/storage/indexlib/index/inverted_index/format/skiplist/PairValueSkipListReader.cpp +++ b/aios/storage/indexlib/index/inverted_index/format/skiplist/PairValueSkipListReader.cpp @@ -54,14 +54,14 @@ PairValueSkipListReader::PairValueSkipListReader(const PairValueSkipListReader& PairValueSkipListReader::~PairValueSkipListReader() {} -void PairValueSkipListReader::Load(const util::ByteSliceList* byteSliceList, uint32_t start, uint32_t end, +void PairValueSkipListReader::Load(autil::mem_pool::Pool *pool, const util::ByteSliceList* byteSliceList, uint32_t start, uint32_t end, const uint32_t& itemCount) { SkipListReader::Load(byteSliceList, start, end); InnerLoad(start, end, itemCount); } -void PairValueSkipListReader::Load(util::ByteSlice* byteSlice, uint32_t start, uint32_t end, const uint32_t& itemCount) +void PairValueSkipListReader::Load(autil::mem_pool::Pool *pool, util::ByteSlice* byteSlice, uint32_t start, uint32_t end, const uint32_t& itemCount) { SkipListReader::Load(byteSlice, start, end); InnerLoad(start, end, itemCount); diff --git a/aios/storage/indexlib/index/inverted_index/format/skiplist/PairValueSkipListReader.h b/aios/storage/indexlib/index/inverted_index/format/skiplist/PairValueSkipListReader.h index eef0d68bbd..2afc4a448d 100644 --- a/aios/storage/indexlib/index/inverted_index/format/skiplist/PairValueSkipListReader.h +++ b/aios/storage/indexlib/index/inverted_index/format/skiplist/PairValueSkipListReader.h @@ -33,18 +33,16 @@ class PairValueSkipListReader : public SkipListReader virtual ~PairValueSkipListReader(); public: - virtual void Load(const util::ByteSliceList* byteSliceList, uint32_t start, uint32_t end, - const uint32_t& itemCount); + void Load(autil::mem_pool::Pool *pool, const util::ByteSliceList* byteSliceList, uint32_t start, uint32_t end, + const uint32_t& itemCount) override; - virtual void Load(util::ByteSlice* byteSlice, uint32_t start, uint32_t end, const uint32_t& itemCount); + void Load(autil::mem_pool::Pool *pool, util::ByteSlice* byteSlice, uint32_t start, uint32_t end, const uint32_t& itemCount) override; std::pair SkipTo(uint32_t queryKey, uint32_t& key, uint32_t& prevKey, uint32_t& value, uint32_t& delta) override; - inline std::pair SkipTo(uint32_t queryKey, uint32_t& key, uint32_t& value, uint32_t& delta); + uint32_t GetPrevKey() const override { return _prevKey; } - uint32_t GetPrevKey() const { return _prevKey; } - - uint32_t GetCurrentKey() const { return _currentKey; } + uint32_t GetCurrentKey() const override { return _currentKey; } virtual std::pair LoadBuffer(); @@ -78,10 +76,4 @@ class PairValueSkipListReader : public SkipListReader AUTIL_LOG_DECLARE(); }; -/////////////////////////////////////////////// -inline std::pair PairValueSkipListReader::SkipTo(uint32_t queryKey, uint32_t& key, uint32_t& value, - uint32_t& delta) -{ - return SkipTo(queryKey, key, _prevKey, value, delta); -} } // namespace indexlib::index diff --git a/aios/storage/indexlib/index/inverted_index/format/skiplist/SkipListReader.h b/aios/storage/indexlib/index/inverted_index/format/skiplist/SkipListReader.h index ae9853911f..58e7b87cbb 100644 --- a/aios/storage/indexlib/index/inverted_index/format/skiplist/SkipListReader.h +++ b/aios/storage/indexlib/index/inverted_index/format/skiplist/SkipListReader.h @@ -28,15 +28,20 @@ class SkipListReader virtual ~SkipListReader(); public: - virtual void Load(const util::ByteSliceList* byteSliceList, uint32_t start, uint32_t end); + virtual void Load(autil::mem_pool::Pool *pool, + const util::ByteSliceList *byteSliceList, uint32_t start, + uint32_t end, const uint32_t &itemCount) = 0; - virtual void Load(util::ByteSlice* byteSlice, uint32_t start, uint32_t end); + virtual void Load(autil::mem_pool::Pool *pool, util::ByteSlice *byteSlice, + uint32_t start, uint32_t end, + const uint32_t &itemCount) = 0; - virtual std::pair SkipTo(uint32_t queryKey, uint32_t& key, uint32_t& prevKey, uint32_t& prevValue, - uint32_t& valueDelta) - { - assert(false); - return {}; + virtual std::pair SkipTo(uint32_t queryKey, uint32_t &key, + uint32_t &prevKey, + uint32_t &prevValue, + uint32_t &valueDelta) { + assert(false); + return {}; } uint32_t GetStart() const { return _start; } @@ -52,7 +57,15 @@ class SkipListReader virtual uint32_t GetLastValueInBuffer() const { return 0; } virtual uint32_t GetLastKeyInBuffer() const { return 0; } -protected: + virtual uint32_t GetPrevKey() const { return 0; } + + virtual uint32_t GetCurrentKey() const { return 0; } + + protected: + virtual void Load(const util::ByteSliceList *byteSliceList, uint32_t start, + uint32_t end); + + virtual void Load(util::ByteSlice *byteSlice, uint32_t start, uint32_t end); uint32_t _start; uint32_t _end; file_system::ByteSliceReader _byteSliceReader; diff --git a/aios/storage/indexlib/index/inverted_index/format/skiplist/SkipListWriter.h b/aios/storage/indexlib/index/inverted_index/format/skiplist/SkipListWriter.h new file mode 100644 index 0000000000..5e594a7597 --- /dev/null +++ b/aios/storage/indexlib/index/inverted_index/format/skiplist/SkipListWriter.h @@ -0,0 +1,36 @@ +/* + * Copyright 2014-present Alibaba Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include "indexlib/file_system/file/FileWriter.h" + +namespace indexlib::index { + +class SkipListWriter { +public: + virtual void AddItem(uint32_t offset) = 0; + virtual void AddItem(uint32_t key, uint32_t offset) = 0; + virtual void AddItem(uint32_t key, uint32_t ttf, uint32_t offset) = 0; + virtual size_t FinishFlush() = 0; + virtual size_t EstimateDumpSize() const = 0; + virtual void Dump(const std::shared_ptr& file) = 0; + virtual ~SkipListWriter() = default; + +protected: + SkipListWriter() = default; +}; + +} // namespace indexlib::index diff --git a/aios/storage/indexlib/index/inverted_index/format/skiplist/TriValueSkipListReader.cpp b/aios/storage/indexlib/index/inverted_index/format/skiplist/TriValueSkipListReader.cpp index c42ca1c542..1efd454bbc 100644 --- a/aios/storage/indexlib/index/inverted_index/format/skiplist/TriValueSkipListReader.cpp +++ b/aios/storage/indexlib/index/inverted_index/format/skiplist/TriValueSkipListReader.cpp @@ -51,14 +51,14 @@ TriValueSkipListReader::TriValueSkipListReader(const TriValueSkipListReader& oth TriValueSkipListReader::~TriValueSkipListReader() {} -void TriValueSkipListReader::Load(const util::ByteSliceList* byteSliceList, uint32_t start, uint32_t end, +void TriValueSkipListReader::Load(autil::mem_pool::Pool *pool, const util::ByteSliceList* byteSliceList, uint32_t start, uint32_t end, const uint32_t& itemCount) { SkipListReader::Load(byteSliceList, start, end); InnerLoad(start, end, itemCount); } -void TriValueSkipListReader::Load(util::ByteSlice* byteSlice, uint32_t start, uint32_t end, const uint32_t& itemCount) +void TriValueSkipListReader::Load(autil::mem_pool::Pool *pool, util::ByteSlice* byteSlice, uint32_t start, uint32_t end, const uint32_t& itemCount) { SkipListReader::Load(byteSlice, start, end); InnerLoad(start, end, itemCount); diff --git a/aios/storage/indexlib/index/inverted_index/format/skiplist/TriValueSkipListReader.h b/aios/storage/indexlib/index/inverted_index/format/skiplist/TriValueSkipListReader.h index d75ee51c2c..24315bc302 100644 --- a/aios/storage/indexlib/index/inverted_index/format/skiplist/TriValueSkipListReader.h +++ b/aios/storage/indexlib/index/inverted_index/format/skiplist/TriValueSkipListReader.h @@ -33,19 +33,16 @@ class TriValueSkipListReader : public SkipListReader ~TriValueSkipListReader(); public: - virtual void Load(const util::ByteSliceList* byteSliceList, uint32_t start, uint32_t end, - const uint32_t& itemCount); + void Load(autil::mem_pool::Pool *pool, const util::ByteSliceList* byteSliceList, uint32_t start, uint32_t end, + const uint32_t& itemCount) override; - virtual void Load(util::ByteSlice* byteSlice, uint32_t start, uint32_t end, const uint32_t& itemCount); + void Load(autil::mem_pool::Pool *pool, util::ByteSlice* byteSlice, uint32_t start, uint32_t end, const uint32_t& itemCount) override; std::pair SkipTo(uint32_t queryDocId, uint32_t& docId, uint32_t& prevDocId, uint32_t& offset, uint32_t& delta) override; + uint32_t GetPrevKey() const override { return _prevDocId; } - inline std::pair SkipTo(uint32_t queryDocId, uint32_t& docId, uint32_t& offset, uint32_t& delta); - - uint32_t GetPrevDocId() const { return _prevDocId; } - - uint32_t GetCurrentDocId() const { return _currentDocId; } + uint32_t GetCurrentKey() const override { return _currentDocId; } uint32_t GetCurrentTTF() const override { return _currentTTF; } @@ -74,10 +71,4 @@ class TriValueSkipListReader : public SkipListReader AUTIL_LOG_DECLARE(); }; -////////////////////////////////////////////// -inline std::pair TriValueSkipListReader::SkipTo(uint32_t queryDocId, uint32_t& docId, uint32_t& offset, - uint32_t& delta) -{ - return SkipTo(queryDocId, docId, _prevDocId, offset, delta); -} } // namespace indexlib::index diff --git a/aios/storage/indexlib/index/inverted_index/format/test/BUILD b/aios/storage/indexlib/index/inverted_index/format/test/BUILD index 5336724caf..a9d9fccc7a 100644 --- a/aios/storage/indexlib/index/inverted_index/format/test/BUILD +++ b/aios/storage/indexlib/index/inverted_index/format/test/BUILD @@ -18,7 +18,7 @@ strict_cc_fast_test( 'PositionListSegmentDecoderTest.cpp', 'PostingDecoderImplTest.cpp', 'PostingFormatOptionTest.cpp', 'PostingFormatTest.cpp', 'ShortListOptimizeUtilTest.cpp', 'TermMetaLoaderTest.cpp', - 'TermMetaTest.cpp' + 'TermMetaTest.cpp', 'MultiLevelSkipListTest.cpp' ], copts=(['-fno-access-control'] + if_clang(['-std=c++20'])), data=glob(['testdata/**']), @@ -65,6 +65,7 @@ strict_cc_fast_test( '//aios/storage/indexlib/index/inverted_index/format:TermMeta', '//aios/storage/indexlib/index/inverted_index/format:TermMetaDumper', '//aios/storage/indexlib/index/inverted_index/format:TermMetaLoader', + '//aios/storage/indexlib/index/inverted_index/format/skiplist:lib', '//aios/storage/indexlib/table/normal_table:table', '//aios/unittest_framework' ] diff --git a/aios/storage/indexlib/index/inverted_index/format/test/InMemPositionListDecoderTest.cpp b/aios/storage/indexlib/index/inverted_index/format/test/InMemPositionListDecoderTest.cpp index d94d67fa88..293c80ba4b 100644 --- a/aios/storage/indexlib/index/inverted_index/format/test/InMemPositionListDecoderTest.cpp +++ b/aios/storage/indexlib/index/inverted_index/format/test/InMemPositionListDecoderTest.cpp @@ -259,10 +259,9 @@ TEST_F(InMemPositionListDecoderTest, testSkipTo) InMemPositionListDecoder decoder(option, &_byteSlicePool); // skipListReader call skipTo : return true MockSkipListReader* skipListReader = IE_POOL_COMPATIBLE_NEW_CLASS((&_byteSlicePool), MockSkipListReader); - skipListReader->SetPrevKey(10); EXPECT_CALL(*skipListReader, SkipTo(_, _, _, _, _)) - .WillOnce(DoAll(SetArgReferee<1>(30), SetArgReferee<3>(123), Return(std::make_pair(Status::OK(), true)))); + .WillOnce(DoAll(SetArgReferee<1>(30), SetArgReferee<2>(10), SetArgReferee<3>(123), Return(std::make_pair(Status::OK(), true)))); decoder.Init(100, skipListReader, posListBuffer); ASSERT_TRUE(decoder.SkipTo(20, &state)); diff --git a/aios/storage/indexlib/index/inverted_index/format/test/MultiLevelSkipListTest.cpp b/aios/storage/indexlib/index/inverted_index/format/test/MultiLevelSkipListTest.cpp new file mode 100644 index 0000000000..1429a0856e --- /dev/null +++ b/aios/storage/indexlib/index/inverted_index/format/test/MultiLevelSkipListTest.cpp @@ -0,0 +1,685 @@ +#include + +#include "autil/mem_pool/Pool.h" +#include "autil/mem_pool/RecyclePool.h" +#include "autil/mem_pool/SimpleAllocator.h" +#include "indexlib/file_system/file/BufferedFileReader.h" +#include "indexlib/file_system/file/BufferedFileWriter.h" +#include "indexlib/file_system/fslib/FslibWrapper.h" +#include "indexlib/index/inverted_index/format/BufferedByteSliceReader.h" +#include "indexlib/index/inverted_index/format/DocListFormatOption.h" +#include "indexlib/index/inverted_index/format/DocListSkipListFormat.h" +#include "indexlib/index/inverted_index/format/skiplist/BufferedSkipListWriter.h" +#include "indexlib/index/inverted_index/format/skiplist/MultiLevelSkipListReader.h" +#include "indexlib/index/inverted_index/format/skiplist/MultiLevelSkipListWriter.h" +#include "indexlib/index/inverted_index/format/skiplist/PairValueSkipListReader.h" +#include "indexlib/index/inverted_index/format/skiplist/TriValueSkipListReader.h" +#include "indexlib/util/PathUtil.h" +#include "unittest/unittest.h" + +namespace indexlib::index { + +class MultiLevelSkipListTest : public TESTBASE { +public: + MultiLevelSkipListTest() : _byteSlicePool(1024), _bufferPool(1024) { + DocListFormatOption option(OPTION_FLAG_ALL); + _docListSkipListFormat.reset(new DocListSkipListFormat( + option, indexlibv2::config::InvertedIndexConfig::DEFAULT_FORMAT_VERSION)); + } + ~MultiLevelSkipListTest() {}; + +public: + void setUp() override { + _rootDir = util::PathUtil::JoinPath(GET_TEMP_DATA_PATH(), "multi_level_skip_list_test"); + file_system::FslibWrapper::MkDirE(_rootDir); + } + void tearDown() override {} + +private: + std::shared_ptr _docListSkipListFormat; + autil::mem_pool::Pool _byteSlicePool; + autil::mem_pool::RecyclePool _bufferPool; + std::string _rootDir; +}; + +void WriteSkipListBuffer(std::shared_ptr skipListBuffer, size_t df) { + uint32_t c = 1; + docid_t docid = MAX_DOC_PER_RECORD - 1; + while (docid < df) { + skipListBuffer->AddItem(docid, c); + if (docid == df - 1) { + break; + } + docid += MAX_DOC_PER_RECORD; + c++; + } + if (docid != df - 1) { + skipListBuffer->AddItem(df - 1, c); + } +} + +TEST_F(MultiLevelSkipListTest, testSimpleProcess) { + std::shared_ptr skipListBuffer( + new MultiLevelSkipListWriter(&_byteSlicePool)); + skipListBuffer->AddItem(1, 2, 3); + // 4 bytes: + // Content: control byte + key + value1 + value2 + // Level 0 has no childpointer + ASSERT_EQ((size_t)4, skipListBuffer->EstimateDumpSize()); +} + +TEST_F(MultiLevelSkipListTest, testSingleLayer) { + std::shared_ptr skipListBuffer( + new MultiLevelSkipListWriter(&_byteSlicePool)); + + // Construct a single-level skip list + size_t df = MAX_DOC_PER_RECORD * (SKIP_MULTIPLIER - 1); + WriteSkipListBuffer(skipListBuffer, df); + + ASSERT_EQ(1, skipListBuffer->_skipLevel); + + file_system::BufferedFileWriterPtr fileWriter(new file_system::BufferedFileWriter()); + std::string filePath = _rootDir + "/dump_file_single_layer"; + ASSERT_EQ(FSEC_OK, fileWriter->Open(filePath, filePath)); + size_t dumpSize = skipListBuffer->EstimateDumpSize(); + skipListBuffer->Dump(fileWriter); + ASSERT_EQ(FSEC_OK, fileWriter->Close()); + + file_system::BufferedFileReaderPtr fileReader(new file_system::BufferedFileReader()); + ASSERT_EQ(FSEC_OK, fileReader->Open(filePath)); + ASSERT_EQ(dumpSize, fileReader->GetLength()); + indexlib::util::ByteSlice* slice = indexlib::util::ByteSlice::CreateObject(dumpSize); + ASSERT_EQ(FSEC_OK, fileReader->Read(slice->data, dumpSize, 0, ReadOption()).Code()); + indexlib::util::ByteSliceList sliceList = indexlib::util::ByteSliceList(slice); + + std::shared_ptr skipListReader( + new MultiLevelPairValueSkipListReader()); + skipListReader->Load(&_byteSlicePool, &sliceList, 0, sliceList.GetTotalSize(), + (df - 1) / MAX_POS_PER_RECORD + 1); + ASSERT_EQ(1, skipListReader->_numberOfSkipLevel); + + // Inspecting the data at level 0, the SkipReader::Load function prefetches the first node. + docid_t docId = skipListReader->_metas[0].skipDoc; + uint32_t addr = skipListReader->_metas[0].offset; + for (uint32_t i = 1; i < SKIP_MULTIPLIER - 1; ++i) { + uint32_t data[2]; + GroupVarintCompressor::Decode(&skipListReader->_readers[0], 2, data); + docId += data[0]; + addr += data[1]; + ASSERT_EQ(docId, (MAX_DOC_PER_RECORD - 1) + i * MAX_DOC_PER_RECORD); + ASSERT_EQ(data[1], i + 1); + } + ASSERT_EQ(skipListReader->_metas[0].levelEnd, skipListReader->_readers[0].Tell()); +} + +// Write data using MultiLevelSkipListWriter, then use MultiLevelPairValueSkipListReader validate +// the data at each level +TEST_F(MultiLevelSkipListTest, testDump) { + std::shared_ptr skipListBuffer( + new MultiLevelSkipListWriter(&_byteSlicePool)); + + // Construct a 3-level skip list + size_t df = MAX_DOC_PER_RECORD * SKIP_MULTIPLIER * SKIP_MULTIPLIER; + WriteSkipListBuffer(skipListBuffer, df); + + ASSERT_EQ(3, skipListBuffer->_skipLevel); + + file_system::BufferedFileWriterPtr fileWriter(new file_system::BufferedFileWriter()); + std::string filePath = _rootDir + "/dump_file"; + ASSERT_EQ(FSEC_OK, fileWriter->Open(filePath, filePath)); + size_t dumpSize = skipListBuffer->EstimateDumpSize(); + skipListBuffer->Dump(fileWriter); + ASSERT_EQ(FSEC_OK, fileWriter->Close()); + + file_system::BufferedFileReaderPtr fileReader(new file_system::BufferedFileReader()); + ASSERT_EQ(FSEC_OK, fileReader->Open(filePath)); + ASSERT_EQ(dumpSize, fileReader->GetLength()); + indexlib::util::ByteSlice* slice = indexlib::util::ByteSlice::CreateObject(dumpSize); + ASSERT_EQ(FSEC_OK, fileReader->Read(slice->data, dumpSize, 0, ReadOption()).Code()); + indexlib::util::ByteSliceList sliceList = indexlib::util::ByteSliceList(slice); + + std::shared_ptr skipListReader( + new MultiLevelPairValueSkipListReader()); + skipListReader->Load(&_byteSlicePool, &sliceList, 0, sliceList.GetTotalSize(), + (df - 1) / MAX_POS_PER_RECORD + 1); + ASSERT_EQ(3, skipListReader->_numberOfSkipLevel); + + docid_t docId = skipListReader->_metas[0].skipDoc; + uint32_t addr = skipListReader->_metas[0].offset; + std::vector level2; + std::vector offset2; + std::vector child2; + for (uint32_t i = 1; i < SKIP_MULTIPLIER * SKIP_MULTIPLIER; ++i) { + uint32_t data[2]; + GroupVarintCompressor::Decode(&skipListReader->_readers[0], 2, data); + docId += data[0]; + addr += data[1]; + ASSERT_EQ(docId, (MAX_DOC_PER_RECORD - 1) + i * MAX_DOC_PER_RECORD); + ASSERT_EQ(data[1], i + 1); + if ((i + 1) % SKIP_MULTIPLIER == 0) { + level2.push_back(docId); + offset2.push_back(addr); + child2.push_back(skipListReader->_readers[0].Tell() - + skipListReader->_metas[0].levelBegin); + } + } + ASSERT_EQ(skipListReader->_metas[0].levelEnd, skipListReader->_readers[0].Tell()); + + // Check the level 1 data + docId = 0; + addr = 0; + docid_t level3; + uint32_t offset3; + uint32_t child3; + for (uint32_t i = 0; i < SKIP_MULTIPLIER; ++i) { + if (i == SKIP_MULTIPLIER - 1) { + child3 = skipListReader->_readers[1].Tell() - skipListReader->_metas[1].levelBegin; + } + uint32_t data[3]; + GroupVarintCompressor::Decode(&skipListReader->_readers[1], 3, data); + docId += data[0]; + addr += data[1]; + if (i == SKIP_MULTIPLIER - 1) { + level3 = docId; + offset3 = addr; + } + ASSERT_EQ(level2[i], docId); + ASSERT_EQ(offset2[i], addr); + ASSERT_EQ(child2[i], data[2]); + } + ASSERT_EQ(skipListReader->_metas[1].levelEnd, skipListReader->_readers[1].Tell()); + + // Check the level 2 data + uint32_t data[3]; + GroupVarintCompressor::Decode(&skipListReader->_readers[2], 3, data); + ASSERT_EQ(data[0], level3); + ASSERT_EQ(data[1], offset3); + ASSERT_EQ(data[2], child3); + ASSERT_EQ(skipListReader->_metas[2].levelEnd, skipListReader->_readers[2].Tell()); +} + +TEST_F(MultiLevelSkipListTest, testSkipTo) { + std::shared_ptr skipListBuffer( + new MultiLevelSkipListWriter(&_byteSlicePool)); + + // Construct a 3-level skip list + size_t df = MAX_DOC_PER_RECORD * SKIP_MULTIPLIER * SKIP_MULTIPLIER * (SKIP_MULTIPLIER - 1); + // A flush will be triggered when the document is written to the MAX_DOC_PER_RECORD - 1st + // document. + for (docid_t docid = MAX_DOC_PER_RECORD - 1; docid < df; docid += MAX_DOC_PER_RECORD) { + skipListBuffer->AddItem(docid, 100); + } + + ASSERT_EQ(3, skipListBuffer->_skipLevel); + + file_system::BufferedFileWriterPtr fileWriter(new file_system::BufferedFileWriter()); + std::string filePath = _rootDir + "/skipto_file"; + ASSERT_EQ(FSEC_OK, fileWriter->Open(filePath, filePath)); + size_t dumpSize = skipListBuffer->EstimateDumpSize(); + skipListBuffer->Dump(fileWriter); + ASSERT_EQ(FSEC_OK, fileWriter->Close()); + + file_system::BufferedFileReaderPtr fileReader(new file_system::BufferedFileReader()); + ASSERT_EQ(FSEC_OK, fileReader->Open(filePath)); + ASSERT_EQ(dumpSize, fileReader->GetLength()); + indexlib::util::ByteSlice* slice = indexlib::util::ByteSlice::CreateObject(dumpSize); + ASSERT_EQ(FSEC_OK, fileReader->Read(slice->data, dumpSize, 0, ReadOption()).Code()); + indexlib::util::ByteSliceList sliceList = indexlib::util::ByteSliceList(slice); + + std::shared_ptr skipListReader( + new MultiLevelPairValueSkipListReader()); + skipListReader->Load(&_byteSlicePool, &sliceList, 0, sliceList.GetTotalSize(), + (df - 1) / MAX_POS_PER_RECORD + 1); + + uint32_t key = 0; + uint32_t prevKey = 0; + uint32_t value = 0; + uint32_t delta = 0; + // case 1 + ASSERT_EQ(skipListReader->SkipTo(0, key, prevKey, value, delta).second, true); + ASSERT_EQ(key, 127); + ASSERT_EQ(prevKey, 0); + ASSERT_EQ(value, 0); + ASSERT_EQ(delta, 100); + ASSERT_EQ(skipListReader->GetSkippedItemCount(), (prevKey + 1) / MAX_DOC_PER_RECORD); + // case 2 + ASSERT_EQ(skipListReader->SkipTo(MAX_DOC_PER_RECORD + 1, key, prevKey, value, delta).second, + true); + ASSERT_EQ(key, MAX_DOC_PER_RECORD * 2 - 1); + ASSERT_EQ(prevKey, MAX_DOC_PER_RECORD - 1); + ASSERT_EQ(value, 100); + ASSERT_EQ(delta, 100); + ASSERT_EQ(skipListReader->GetSkippedItemCount(), (prevKey + 1) / MAX_DOC_PER_RECORD); + // case 3 + ASSERT_EQ(skipListReader + ->SkipTo(MAX_DOC_PER_RECORD * (SKIP_MULTIPLIER * SKIP_MULTIPLIER) - 20, key, + prevKey, value, delta) + .second, + true); + ASSERT_EQ(key, MAX_DOC_PER_RECORD * (SKIP_MULTIPLIER * SKIP_MULTIPLIER) - 1); + ASSERT_EQ(prevKey, MAX_DOC_PER_RECORD * (SKIP_MULTIPLIER * SKIP_MULTIPLIER - 1) - 1); + ASSERT_EQ(value, (SKIP_MULTIPLIER * SKIP_MULTIPLIER - 1) * 100); + ASSERT_EQ(delta, 100); + ASSERT_EQ(skipListReader->GetSkippedItemCount(), (prevKey + 1) / MAX_DOC_PER_RECORD); + // case 4 + ASSERT_EQ(skipListReader->SkipTo(df, key, prevKey, value, delta).second, false); +} + +TEST_F(MultiLevelSkipListTest, testRealtimeSkipTo) { + std::shared_ptr skipListBuffer( + new MultiLevelSkipListWriter(&_byteSlicePool)); + size_t df = MAX_DOC_PER_RECORD * SKIP_MULTIPLIER * SKIP_MULTIPLIER * (SKIP_MULTIPLIER - 1); + uint32_t key = 0; + uint32_t prevKey = 0; + uint32_t value = 0; + uint32_t delta = 0; + + // case 1 + std::shared_ptr skipListReader1( + new MultiLevelPairValueSkipListReader()); + skipListBuffer->SnapShot(skipListReader1.get(), &_byteSlicePool); + // case 2 + skipListBuffer->AddItem(MAX_DOC_PER_RECORD - 1, 100); + std::shared_ptr skipListReader2( + new MultiLevelPairValueSkipListReader()); + skipListBuffer->SnapShot(skipListReader2.get(), &_byteSlicePool); + // case 3 + std::shared_ptr skipListReader3( + new MultiLevelPairValueSkipListReader()); + for (docid_t docid = 2 * MAX_DOC_PER_RECORD - 1; docid < df; docid += MAX_DOC_PER_RECORD) { + skipListBuffer->AddItem(docid, 100); + } + skipListBuffer->SnapShot(skipListReader3.get(), &_byteSlicePool); + + // assert + ASSERT_EQ(skipListReader1->SkipTo(0, key, prevKey, value, delta).second, false); + ASSERT_EQ(skipListReader2->SkipTo(0, key, prevKey, value, delta).second, true); + ASSERT_EQ(key, 127); + ASSERT_EQ(prevKey, 0); + ASSERT_EQ(value, 0); + ASSERT_EQ(delta, 100); + ASSERT_EQ(skipListReader2->GetSkippedItemCount(), (prevKey + 1) / MAX_DOC_PER_RECORD); + ASSERT_EQ(skipListReader2->SkipTo(MAX_DOC_PER_RECORD + 1, key, prevKey, value, delta).second, + false); + ASSERT_EQ(skipListReader3->SkipTo(0, key, prevKey, value, delta).second, true); + ASSERT_EQ(key, 127); + ASSERT_EQ(prevKey, 0); + ASSERT_EQ(value, 0); + ASSERT_EQ(delta, 100); + ASSERT_EQ(skipListReader3->GetSkippedItemCount(), (prevKey + 1) / MAX_DOC_PER_RECORD); + ASSERT_EQ(skipListReader3->SkipTo(MAX_DOC_PER_RECORD + 1, key, prevKey, value, delta).second, + true); + ASSERT_EQ(key, MAX_DOC_PER_RECORD * 2 - 1); + ASSERT_EQ(prevKey, MAX_DOC_PER_RECORD - 1); + ASSERT_EQ(value, 100); + ASSERT_EQ(delta, 100); + ASSERT_EQ(skipListReader3->GetSkippedItemCount(), (prevKey + 1) / MAX_DOC_PER_RECORD); + ASSERT_EQ(skipListReader3 + ->SkipTo(MAX_DOC_PER_RECORD * (SKIP_MULTIPLIER * SKIP_MULTIPLIER) - 20, key, + prevKey, value, delta) + .second, + true); + ASSERT_EQ(key, MAX_DOC_PER_RECORD * (SKIP_MULTIPLIER * SKIP_MULTIPLIER) - 1); + ASSERT_EQ(prevKey, MAX_DOC_PER_RECORD * (SKIP_MULTIPLIER * SKIP_MULTIPLIER - 1) - 1); + ASSERT_EQ(value, (SKIP_MULTIPLIER * SKIP_MULTIPLIER - 1) * 100); + ASSERT_EQ(delta, 100); + ASSERT_EQ(skipListReader3->GetSkippedItemCount(), (prevKey + 1) / MAX_DOC_PER_RECORD); + ASSERT_EQ(skipListReader3->SkipTo(df, key, prevKey, value, delta).second, false); +} + +TEST_F(MultiLevelSkipListTest, testDumpWithTTF) { + std::shared_ptr skipListBuffer( + new MultiLevelSkipListWriter(&_byteSlicePool)); + + size_t df = MAX_DOC_PER_RECORD * SKIP_MULTIPLIER * SKIP_MULTIPLIER; + uint32_t c = 1; + for (docid_t docid = MAX_DOC_PER_RECORD - 1; docid < df; docid += MAX_DOC_PER_RECORD) { + // Assuming the term appears twice in each document, then ttf = 2 * (docid + 1) + skipListBuffer->AddItem(docid, 2 * (docid + 1), c); + c++; + } + + ASSERT_EQ(3, skipListBuffer->_skipLevel); + + file_system::BufferedFileWriterPtr fileWriter(new file_system::BufferedFileWriter()); + std::string filePath = _rootDir + "/dump_file_with_ttf"; + ASSERT_EQ(FSEC_OK, fileWriter->Open(filePath, filePath)); + size_t dumpSize = skipListBuffer->EstimateDumpSize(); + skipListBuffer->Dump(fileWriter); + ASSERT_EQ(FSEC_OK, fileWriter->Close()); + + file_system::BufferedFileReaderPtr fileReader(new file_system::BufferedFileReader()); + ASSERT_EQ(FSEC_OK, fileReader->Open(filePath)); + ASSERT_EQ(dumpSize, fileReader->GetLength()); + indexlib::util::ByteSlice* slice = indexlib::util::ByteSlice::CreateObject(dumpSize); + ASSERT_EQ(FSEC_OK, fileReader->Read(slice->data, dumpSize, 0, ReadOption()).Code()); + indexlib::util::ByteSliceList sliceList = indexlib::util::ByteSliceList(slice); + + std::shared_ptr skipListReader( + new MultiLevelTriValueSkipListReader()); + skipListReader->Load(&_byteSlicePool, &sliceList, 0, sliceList.GetTotalSize(), + (df - 1) / MAX_POS_PER_RECORD + 1); + ASSERT_EQ(3, skipListReader->_numberOfSkipLevel); + + docid_t docId = skipListReader->_metas[0].skipDoc; + uint32_t ttf = skipListReader->_metas[0].ttf; + uint32_t addr = skipListReader->_metas[0].offset; + std::vector level2; + std::vector ttf2; + std::vector offset2; + std::vector child2; + for (uint32_t i = 1; i < SKIP_MULTIPLIER * SKIP_MULTIPLIER; ++i) { + uint32_t data[3]; + GroupVarintCompressor::Decode(&skipListReader->_readers[0], 3, data); + docId += data[0]; + ttf += data[1]; + addr += data[2]; + ASSERT_EQ(docId, (MAX_DOC_PER_RECORD - 1) + i * MAX_DOC_PER_RECORD); + ASSERT_EQ(data[1], 2 * MAX_DOC_PER_RECORD); + ASSERT_EQ(data[2], i + 1); + if ((i + 1) % SKIP_MULTIPLIER == 0) { + level2.push_back(docId); + ttf2.push_back(ttf); + offset2.push_back(addr); + child2.push_back(skipListReader->_readers[0].Tell() - + skipListReader->_metas[0].levelBegin); + } + } + ASSERT_EQ(skipListReader->_metas[0].levelEnd, skipListReader->_readers[0].Tell()); + + docId = 0; + ttf = 0; + addr = 0; + docid_t level3; + uint32_t ttf3; + uint32_t offset3; + uint32_t child3; + for (uint32_t i = 0; i < SKIP_MULTIPLIER; ++i) { + if (i == SKIP_MULTIPLIER - 1) { + child3 = skipListReader->_readers[1].Tell() - skipListReader->_metas[1].levelBegin; + } + uint32_t data[4]; + GroupVarintCompressor::Decode(&skipListReader->_readers[1], 4, data); + docId += data[0]; + ttf += data[1]; + addr += data[2]; + if (i == SKIP_MULTIPLIER - 1) { + level3 = docId; + ttf3 = ttf; + offset3 = addr; + } + ASSERT_EQ(level2[i], docId); + ASSERT_EQ(ttf2[i], ttf); + ASSERT_EQ(offset2[i], addr); + ASSERT_EQ(child2[i], data[3]); + } + ASSERT_EQ(skipListReader->_metas[1].levelEnd, skipListReader->_readers[1].Tell()); + + uint32_t data[4]; + GroupVarintCompressor::Decode(&skipListReader->_readers[2], 4, data); + ASSERT_EQ(data[0], level3); + ASSERT_EQ(data[1], ttf3); + ASSERT_EQ(data[2], offset3); + ASSERT_EQ(data[3], child3); + ASSERT_EQ(skipListReader->_metas[2].levelEnd, skipListReader->_readers[2].Tell()); +} + +TEST_F(MultiLevelSkipListTest, testSkipToWithTTF) { + std::shared_ptr skipListBuffer( + new MultiLevelSkipListWriter(&_byteSlicePool)); + + size_t df = MAX_DOC_PER_RECORD * SKIP_MULTIPLIER * SKIP_MULTIPLIER * (SKIP_MULTIPLIER - 1); + for (docid_t docid = MAX_DOC_PER_RECORD - 1; docid < df; docid += MAX_DOC_PER_RECORD) { + skipListBuffer->AddItem(docid, 2 * (docid + 1), 100); + } + + ASSERT_EQ(3, skipListBuffer->_skipLevel); + + file_system::BufferedFileWriterPtr fileWriter(new file_system::BufferedFileWriter()); + std::string filePath = _rootDir + "/skipto_file"; + ASSERT_EQ(FSEC_OK, fileWriter->Open(filePath, filePath)); + size_t dumpSize = skipListBuffer->EstimateDumpSize(); + skipListBuffer->Dump(fileWriter); + ASSERT_EQ(FSEC_OK, fileWriter->Close()); + + file_system::BufferedFileReaderPtr fileReader(new file_system::BufferedFileReader()); + ASSERT_EQ(FSEC_OK, fileReader->Open(filePath)); + ASSERT_EQ(dumpSize, fileReader->GetLength()); + indexlib::util::ByteSlice* slice = indexlib::util::ByteSlice::CreateObject(dumpSize); + ASSERT_EQ(FSEC_OK, fileReader->Read(slice->data, dumpSize, 0, ReadOption()).Code()); + indexlib::util::ByteSliceList sliceList = indexlib::util::ByteSliceList(slice); + + std::shared_ptr skipListReader( + new MultiLevelTriValueSkipListReader()); + skipListReader->Load(&_byteSlicePool, &sliceList, 0, sliceList.GetTotalSize(), + (df - 1) / MAX_POS_PER_RECORD + 1); + + uint32_t key = 0; + uint32_t prevKey = 0; + uint32_t value = 0; + uint32_t delta = 0; + // case 1 + ASSERT_EQ(skipListReader->SkipTo(0, key, prevKey, value, delta).second, true); + ASSERT_EQ(key, 127); + ASSERT_EQ(prevKey, 0); + ASSERT_EQ(value, 0); + ASSERT_EQ(delta, 100); + ASSERT_EQ(skipListReader->GetSkippedItemCount(), (prevKey + 1) / MAX_DOC_PER_RECORD); + ASSERT_EQ(skipListReader->GetCurrentTTF(), 256); + ASSERT_EQ(skipListReader->GetPrevTTF(), 0); + // case 2 + ASSERT_EQ(skipListReader->SkipTo(MAX_DOC_PER_RECORD + 1, key, prevKey, value, delta).second, + true); + ASSERT_EQ(key, MAX_DOC_PER_RECORD * 2 - 1); + ASSERT_EQ(prevKey, MAX_DOC_PER_RECORD - 1); + ASSERT_EQ(value, 100); + ASSERT_EQ(delta, 100); + ASSERT_EQ(skipListReader->GetSkippedItemCount(), (prevKey + 1) / MAX_DOC_PER_RECORD); + ASSERT_EQ(skipListReader->GetCurrentTTF(), (key + 1) * 2); + ASSERT_EQ(skipListReader->GetPrevTTF(), (key + 1) * 2 - MAX_DOC_PER_RECORD * 2); + // case 3 + ASSERT_EQ(skipListReader + ->SkipTo(MAX_DOC_PER_RECORD * (SKIP_MULTIPLIER * SKIP_MULTIPLIER) - 20, key, + prevKey, value, delta) + .second, + true); + ASSERT_EQ(key, MAX_DOC_PER_RECORD * (SKIP_MULTIPLIER * SKIP_MULTIPLIER) - 1); + ASSERT_EQ(prevKey, MAX_DOC_PER_RECORD * (SKIP_MULTIPLIER * SKIP_MULTIPLIER - 1) - 1); + ASSERT_EQ(value, (SKIP_MULTIPLIER * SKIP_MULTIPLIER - 1) * 100); + ASSERT_EQ(delta, 100); + ASSERT_EQ(skipListReader->GetSkippedItemCount(), (prevKey + 1) / MAX_DOC_PER_RECORD); + ASSERT_EQ(skipListReader->GetCurrentTTF(), (key + 1) * 2); + ASSERT_EQ(skipListReader->GetPrevTTF(), (key + 1) * 2 - MAX_DOC_PER_RECORD * 2); + // case 4 + ASSERT_EQ(skipListReader->SkipTo(df, key, prevKey, value, delta).second, false); +} + +TEST_F(MultiLevelSkipListTest, testRealtimeSkipToWithTTF) { + std::shared_ptr skipListBuffer( + new MultiLevelSkipListWriter(&_byteSlicePool)); + size_t df = MAX_DOC_PER_RECORD * SKIP_MULTIPLIER * SKIP_MULTIPLIER * (SKIP_MULTIPLIER - 1); + uint32_t key = 0; + uint32_t prevKey = 0; + uint32_t value = 0; + uint32_t delta = 0; + + // case 1 + std::shared_ptr skipListReader1( + new MultiLevelTriValueSkipListReader()); + skipListBuffer->SnapShot(skipListReader1.get(), &_byteSlicePool); + // case 2 + skipListBuffer->AddItem(MAX_DOC_PER_RECORD - 1, 256, 100); + std::shared_ptr skipListReader2( + new MultiLevelTriValueSkipListReader()); + skipListBuffer->SnapShot(skipListReader2.get(), &_byteSlicePool); + // case 3 + std::shared_ptr skipListReader3( + new MultiLevelTriValueSkipListReader()); + for (docid_t docid = 2 * MAX_DOC_PER_RECORD - 1; docid < df; docid += MAX_DOC_PER_RECORD) { + skipListBuffer->AddItem(docid, 2 * (docid + 1), 100); + } + skipListBuffer->SnapShot(skipListReader3.get(), &_byteSlicePool); + + // assert + ASSERT_EQ(skipListReader1->SkipTo(0, key, prevKey, value, delta).second, false); + ASSERT_EQ(skipListReader1->_readers, nullptr); + ASSERT_EQ(skipListReader1->GetPrevKey(), 0); + ASSERT_EQ(skipListReader1->GetCurrentKey(), 0); + ASSERT_EQ(skipListReader1->GetCurrentTTF(), 0); + ASSERT_EQ(skipListReader1->GetPrevTTF(), 0); + ASSERT_EQ(skipListReader1->GetLastKeyInBuffer(), 0); + ASSERT_EQ(skipListReader1->GetLastValueInBuffer(), 0); + ASSERT_EQ(skipListReader2->SkipTo(0, key, prevKey, value, delta).second, true); + ASSERT_EQ(key, 127); + ASSERT_EQ(prevKey, 0); + ASSERT_EQ(value, 0); + ASSERT_EQ(delta, 100); + ASSERT_EQ(skipListReader2->GetSkippedItemCount(), (prevKey + 1) / MAX_DOC_PER_RECORD); + ASSERT_EQ(skipListReader2->GetCurrentTTF(), 256); + ASSERT_EQ(skipListReader2->GetPrevTTF(), 0); + ASSERT_EQ(skipListReader2->SkipTo(128, key, prevKey, value, delta).second, false); + ASSERT_EQ(skipListReader3->SkipTo(0, key, prevKey, value, delta).second, true); + ASSERT_EQ(key, 127); + ASSERT_EQ(prevKey, 0); + ASSERT_EQ(value, 0); + ASSERT_EQ(delta, 100); + ASSERT_EQ(skipListReader3->GetSkippedItemCount(), (prevKey + 1) / MAX_DOC_PER_RECORD); + ASSERT_EQ(skipListReader3->GetCurrentTTF(), 256); + ASSERT_EQ(skipListReader3->GetPrevTTF(), 0); + ASSERT_EQ(skipListReader3->SkipTo(129, key, prevKey, value, delta).second, true); + ASSERT_EQ(key, 255); + ASSERT_EQ(prevKey, 127); + ASSERT_EQ(value, 100); + ASSERT_EQ(delta, 100); + ASSERT_EQ(skipListReader3->GetSkippedItemCount(), (prevKey + 1) / MAX_DOC_PER_RECORD); + ASSERT_EQ(skipListReader3->GetCurrentTTF(), 512); + ASSERT_EQ(skipListReader3->GetPrevTTF(), 256); + ASSERT_EQ(skipListReader3 + ->SkipTo(MAX_DOC_PER_RECORD * (SKIP_MULTIPLIER * SKIP_MULTIPLIER) - 20, key, + prevKey, value, delta) + .second, + true); + ASSERT_EQ(key, MAX_DOC_PER_RECORD * (SKIP_MULTIPLIER * SKIP_MULTIPLIER) - 1); + ASSERT_EQ(prevKey, MAX_DOC_PER_RECORD * (SKIP_MULTIPLIER * SKIP_MULTIPLIER - 1) - 1); + ASSERT_EQ(value, (SKIP_MULTIPLIER * SKIP_MULTIPLIER - 1) * 100); + ASSERT_EQ(delta, 100); + ASSERT_EQ(skipListReader3->GetSkippedItemCount(), (prevKey + 1) / MAX_DOC_PER_RECORD); + ASSERT_EQ(skipListReader3->GetCurrentTTF(), (key + 1) * 2); + ASSERT_EQ(skipListReader3->GetPrevTTF(), (key + 1) * 2 - MAX_DOC_PER_RECORD * 2); + ASSERT_EQ(skipListReader3->SkipTo(df, key, prevKey, value, delta).second, false); +} + +TEST_F(MultiLevelSkipListTest, testDiffWithOneLevelSkipList) { + std::shared_ptr skipListBuffer( + new MultiLevelSkipListWriter(&_byteSlicePool)); + std::shared_ptr skipListBuffer1( + new BufferedSkipListWriter(&_byteSlicePool, &_bufferPool)); + skipListBuffer1->Init(_docListSkipListFormat.get()); + + size_t df = MAX_DOC_PER_RECORD * SKIP_MULTIPLIER * SKIP_MULTIPLIER * SKIP_MULTIPLIER * + (SKIP_MULTIPLIER / 2); + size_t itemCount = 0; + for (docid_t docid = MAX_DOC_PER_RECORD - 1; docid < df; docid += MAX_DOC_PER_RECORD) { + skipListBuffer->AddItem(docid, 2 * (docid + 1), 100); + skipListBuffer1->AddItem(docid, 2 * (docid + 1), 100); + itemCount++; + } + skipListBuffer1->FinishFlush(); + + file_system::BufferedFileWriterPtr fileWriter(new file_system::BufferedFileWriter()); + std::string filePath = _rootDir + "/multilevel"; + ASSERT_EQ(FSEC_OK, fileWriter->Open(filePath, filePath)); + skipListBuffer->Dump(fileWriter); + ASSERT_EQ(FSEC_OK, fileWriter->Close()); + + file_system::BufferedFileWriterPtr fileWriter1(new file_system::BufferedFileWriter()); + std::string filePath1 = _rootDir + "/onelevel"; + ASSERT_EQ(FSEC_OK, fileWriter1->Open(filePath1, filePath1)); + skipListBuffer1->Dump(fileWriter1); + ASSERT_EQ(FSEC_OK, fileWriter1->Close()); + + file_system::BufferedFileReaderPtr fileReader(new file_system::BufferedFileReader()); + ASSERT_EQ(FSEC_OK, fileReader->Open(filePath)); + indexlib::util::ByteSlice* slice = + indexlib::util::ByteSlice::CreateObject(fileReader->GetLength()); + ASSERT_EQ(FSEC_OK, + fileReader->Read(slice->data, fileReader->GetLength(), 0, ReadOption()).Code()); + indexlib::util::ByteSliceList sliceList = indexlib::util::ByteSliceList(slice); + + std::shared_ptr skipListReader( + new MultiLevelTriValueSkipListReader()); + skipListReader->Load(&_byteSlicePool, &sliceList, 0, sliceList.GetTotalSize(), + (df - 1) / MAX_POS_PER_RECORD + 1); + + file_system::BufferedFileReaderPtr fileReader1(new file_system::BufferedFileReader()); + ASSERT_EQ(FSEC_OK, fileReader1->Open(filePath1)); + indexlib::util::ByteSlice* slice1 = + indexlib::util::ByteSlice::CreateObject(fileReader1->GetLength()); + ASSERT_EQ(FSEC_OK, + fileReader1->Read(slice1->data, fileReader1->GetLength(), 0, ReadOption()).Code()); + indexlib::util::ByteSliceList sliceList1 = indexlib::util::ByteSliceList(slice1); + + TriValueSkipListReader* skipListReader1 = new TriValueSkipListReader(); + skipListReader1->Load(&_byteSlicePool, &sliceList1, 0, sliceList1.GetTotalSize(), itemCount); + + auto asseet_func = [&](uint32_t target) { + uint32_t key = 0; + uint32_t prevKey = 0; + uint32_t value = 0; + uint32_t delta = 0; + uint32_t key1 = 0; + uint32_t prevKey1 = 0; + uint32_t value1 = 0; + uint32_t delta1 = 0; + bool success = skipListReader->SkipTo(target, key, prevKey, value, delta).second; + bool success1 = skipListReader1->SkipTo(target, key1, prevKey1, value1, delta1).second; + ASSERT_EQ(success, success1); + if (success) { + ASSERT_EQ(key, key1); + ASSERT_EQ(prevKey, prevKey1); + ASSERT_EQ(value, value1); + ASSERT_EQ(delta, delta1); + ASSERT_EQ(skipListReader->GetSkippedItemCount(), + skipListReader1->GetSkippedItemCount()); + ASSERT_EQ(skipListReader->GetCurrentTTF(), skipListReader1->GetCurrentTTF()); + ASSERT_EQ(skipListReader->GetPrevTTF(), skipListReader1->GetPrevTTF()); + } + }; + + asseet_func(0); + asseet_func(129); + asseet_func(4000); + asseet_func(57340); + asseet_func(df); +} + +TEST_F(MultiLevelSkipListTest, testRealtimeSkipToEnd) { + std::shared_ptr skipListBuffer( + new MultiLevelSkipListWriter(&_byteSlicePool)); + + size_t df = MAX_DOC_PER_RECORD * SKIP_MULTIPLIER; + for (docid_t docid = MAX_DOC_PER_RECORD - 1; docid < df; docid += MAX_DOC_PER_RECORD) { + skipListBuffer->AddItem(docid, 100); + } + ASSERT_EQ(2, skipListBuffer->_skipLevel); + + std::shared_ptr skipListReader( + new MultiLevelPairValueSkipListReader()); + skipListBuffer->SnapShot(skipListReader.get(), &_byteSlicePool); + + uint32_t key = 0; + uint32_t prevKey = 0; + uint32_t value = 0; + uint32_t delta = 0; + ASSERT_EQ(skipListReader->SkipTo(df + 3, key, prevKey, value, delta).second, false); + ASSERT_EQ(skipListReader->GetCurrentKey(), df - 1); + ASSERT_EQ(skipListReader->GetLastKeyInBuffer(), df - 1); + ASSERT_EQ(skipListReader->GetLastValueInBuffer(), SKIP_MULTIPLIER * 100); + ASSERT_EQ(skipListReader->_skippedItemCount, SKIP_MULTIPLIER); + ASSERT_EQ(key, df - 1); + ASSERT_EQ(prevKey, df - 1); + ASSERT_EQ(value, SKIP_MULTIPLIER * 100); + ASSERT_EQ(delta, 0); +} + +} // namespace indexlib::index diff --git a/aios/storage/indexlib/index/inverted_index/format/test/PositionListEncoderTest.cpp b/aios/storage/indexlib/index/inverted_index/format/test/PositionListEncoderTest.cpp index a971915765..0933eb9b38 100644 --- a/aios/storage/indexlib/index/inverted_index/format/test/PositionListEncoderTest.cpp +++ b/aios/storage/indexlib/index/inverted_index/format/test/PositionListEncoderTest.cpp @@ -67,7 +67,7 @@ class PositionListEncoderTest : public TESTBASE ASSERT_EQ((nullptr == positionEncoder._posSkipListWriter), skipListWriterIsNull); if (!skipListWriterIsNull) { - BufferedSkipListWriter* skipListWriter = positionEncoder._posSkipListWriter; + BufferedSkipListWriter* skipListWriter = dynamic_cast(positionEncoder._posSkipListWriter); ASSERT_TRUE(skipListWriter != nullptr); } } @@ -147,7 +147,7 @@ TEST_F(PositionListEncoderTest, testAddPositionWithSkipList) for (uint32_t i = MAX_POS_PER_RECORD; i < 2 * MAX_POS_PER_RECORD; i++) { positionEncoder.AddPosition(2 * i, i); } - BufferedSkipListWriter* skipListWriter = positionEncoder._posSkipListWriter; + BufferedSkipListWriter* skipListWriter = dynamic_cast(positionEncoder._posSkipListWriter); BufferedByteSliceReader reader; reader.Open(skipListWriter); diff --git a/aios/storage/indexlib/index/inverted_index/format/test/PostingFormatOptionTest.cpp b/aios/storage/indexlib/index/inverted_index/format/test/PostingFormatOptionTest.cpp index ccd9d6b9df..3e60757171 100644 --- a/aios/storage/indexlib/index/inverted_index/format/test/PostingFormatOptionTest.cpp +++ b/aios/storage/indexlib/index/inverted_index/format/test/PostingFormatOptionTest.cpp @@ -84,6 +84,8 @@ TEST_F(PostingFormatOptionTest, testJsonizeCompatibleWithCompressMode) " false," " \"has_term_frequency_bitmap\":" " false," + " \"has_multi_level_skip_list\":" + " false," " \"has_term_frequency_list\":" " false" " }," @@ -97,6 +99,8 @@ TEST_F(PostingFormatOptionTest, testJsonizeCompatibleWithCompressMode) " false," " \"has_position_payload\":" " false," + " \"has_multi_level_skip_list\":" + " false," " \"has_term_frequency_bitmap\":" " false" " }" diff --git a/aios/storage/indexlib/legacy/config/configurator_define.cpp b/aios/storage/indexlib/legacy/config/configurator_define.cpp index f6f8dbfb24..65d15898c0 100644 --- a/aios/storage/indexlib/legacy/config/configurator_define.cpp +++ b/aios/storage/indexlib/legacy/config/configurator_define.cpp @@ -124,6 +124,7 @@ const std::string PRIMARY_KEY_STORAGE_TYPE = "pk_storage_type"; const std::string INDEX_UPDATABLE = "index_updatable"; const std::string PATCH_COMPRESSED = "patch_compressed"; const std::string SORT_FIELD = "sort_field"; +const std::string MULTI_LEVEL_SKIP_LIST = "multi_level_skip_list"; // customize index const std::string CUSTOM_DATA_DIR_NAME = "custom"; diff --git a/aios/storage/indexlib/legacy/config/configurator_define.h b/aios/storage/indexlib/legacy/config/configurator_define.h index 63c7345e7b..138ca6b38f 100644 --- a/aios/storage/indexlib/legacy/config/configurator_define.h +++ b/aios/storage/indexlib/legacy/config/configurator_define.h @@ -130,6 +130,7 @@ extern const std::string PRIMARY_KEY_STORAGE_TYPE; extern const std::string INDEX_UPDATABLE; extern const std::string PATCH_COMPRESSED; extern const std::string SORT_FIELD; +extern const std::string MULTI_LEVEL_SKIP_LIST; // customize index extern const std::string CUSTOM_DATA_DIR_NAME; diff --git a/docs/havenask_docs/_resources/english/Types-of-inverted-indexes-en.md b/docs/havenask_docs/_resources/english/Types-of-inverted-indexes-en.md index 37893a523c..f7a53d7941 100644 --- a/docs/havenask_docs/_resources/english/Types-of-inverted-indexes-en.md +++ b/docs/havenask_docs/_resources/english/Types-of-inverted-indexes-en.md @@ -192,7 +192,7 @@ You can use truncation and bitmaps and tfbitmaps of high-frequency words to impr ``` -The following parameters have the same meanings in the configurations of TEXT and PACK indexes: index_name, index_type, term_payload_flag, doc_payload_flag, position_payload_flag, position_list_flag, term_frequency_flag, and file_compress. The exception is that index_type must be set to TEXT and the index_fields parameter supports only one field in the configuration of the TEXT index. +The following parameters have the same meanings in the configurations of TEXT and PACK indexes: index_name, index_type, term_payload_flag, doc_payload_flag, position_payload_flag, position_list_flag,multi_level_skip_list,term_frequency_flag, and file_compress. The exception is that index_type must be set to TEXT and the index_fields parameter supports only one field in the configuration of the TEXT index. #### Additional considerations diff --git a/docs/havenask_docs/sql/indexes/inverted.en-US.md b/docs/havenask_docs/sql/indexes/inverted.en-US.md index 0444604c80..b3da7ee0e8 100644 --- a/docs/havenask_docs/sql/indexes/inverted.en-US.md +++ b/docs/havenask_docs/sql/indexes/inverted.en-US.md @@ -183,6 +183,7 @@ A PACK index is a multi-field index that is created on fields of the TEXT type. "position_payload_flag": "1", "term_frequency_flag": "1", "term_frequency_bitmap": "1", + "multi_level_skip_list": "0", "high_frequency_dictionary": "bitmap1", "high_frequency_adaptive_dictionary": "df", "high_frequency_term_posting_type": "both", @@ -207,6 +208,7 @@ A PACK index is a multi-field index that is created on fields of the TEXT type. - high_frequency_term_posting_type: the type of the bitmap index. If you set the parameter for creating a bitmap index or an adaptive bitmap index, you can set this parameter to both or bitmap to configure the type of the bitmap index. If you set this parameter to both, a bitmap index and an inverted index are created. If you set this parameter to bitmap, only a bitmap index is created. The default value is bitmap. - index_fields: the fields on which you want to create an index. These fields must be of the TEXT type and use the same analyzer. - boost: the weight of the field in an index. You can specify the name of the field on which you want to create the index and the boost value. +- multi_level_skip_list: Whether to build a multi-level skip list for this index. The default is not to build. Currently, ha3 uses a single-level skip list index for the inverted index data by default. For most scenarios, a single-level skip list is sufficient. However, in some scenarios, such as when you know the docid of a document and want to query the hit status of this document in certain inverted chains, using a multi-level skip list can significantly improve query performance. - index_analyzer: the analyzer that is used during the query. If you specify an analyzer, the analyzer is used to convert text to terms during the query. In this case, the analyzer can be inconsistent with the analyzers that are used in the fields. If you do not specify this parameter, the analyzers used in the fields are used. In this case, the analyzers that are used in the fields must be consistent. Take note that the analyzer can be only added to an index whose field type is TEXT. - compress_type: You can configure one of ZSTD,SNAPPY,LZ4,LZ4HC, and ZLIB. - format_version_id: specifies the version ID of the inverted index. The default value is 0, which indicates the inverted format of the AIOS benchmark that is migrated from indexlib. The optional value is 1. The default value is 0. The default value is 1. The default value is 1. The default value is 1. The default value is 1. The default value is 1. The default value is 1. The default value is 1. The default value is 1. The default value is 1. @@ -238,7 +240,8 @@ A TEXT index is a single-field index that is created on fields of the TEXT type. "doc_payload_flag": "1", "position_payload_flag": "1", "position_list_flag": "1", - "term_frequency_flag": "1" + "term_frequency_flag": "1", + "multi_level_skip_list": "0" }, "index_fields": [ { @@ -249,7 +252,7 @@ A TEXT index is a single-field index that is created on fields of the TEXT type. } } ``` -In the text index configuration, index_name,index_type,term_payload_flag,doc_payload_flag,position_payload_flag,position_list_flag,term_frequency_flag, and compress_type have the same meaning, except that index_type must be set to TEXT and index_fields supports only one field. +In the text index configuration, index_name,index_type,term_payload_flag,doc_payload_flag,position_payload_flag,position_list_flag,multi_level_skip_list,term_frequency_flag, and compress_type have the same meaning, except that index_type must be set to TEXT and index_fields supports only one field. #### Limits - The index_name parameter cannot be set to summary. diff --git a/docs/havenask_docs/sql/indexes/inverted.md b/docs/havenask_docs/sql/indexes/inverted.md index 685b366f99..19b04ed061 100644 --- a/docs/havenask_docs/sql/indexes/inverted.md +++ b/docs/havenask_docs/sql/indexes/inverted.md @@ -186,6 +186,7 @@ PACK索引是多字段索引。对TEXT类型的字段建立索引。与TEXT索 "high_frequency_dictionary": "bitmap1", "high_frequency_adaptive_dictionary": "df", "high_frequency_term_posting_type": "both", + "multi_level_skip_list": "0", "index_analyzer": "simple_analyzer", "format_version_id": "1" }, @@ -207,6 +208,7 @@ PACK索引是多字段索引。对TEXT类型的字段建立索引。与TEXT索 - high_frequency_term_posting_type:如果配置了bitmap索引或者自适应bitmap索引,则可以配置bitmap索引的类型(both/bitmap)。如果配置成both,表示既有bitmap,也有倒排索引。否则只有bitmap信息,默认值是bitmap。 - index_fields:表示需要进入该index的field,field必须为TEXT类型,而且需要使用相同的分析器。 - boost:配置建立索引的field的名字以及该field在这个索引中的权重(boost的值)。 +- multi_level_skip_list:是否为该索引构建多级跳表,默认不构建。目前 ha3 默认使用单层跳表索引倒排链数据,针对大部分场景单级跳表已经够用,但在某些场景下,比如已知一个 doc 的 docid,想去查询这个文档在某些倒排链中的命中情况,使用多级跳表可以显著提升查询性能。 - index_analyzer:配置查询过程中的分词器。如果配置了该分词器,查询过程中就采用该分词器进行分词,此时该分词器可以和field中的分词器不一致。如果不配置该项,则采用field中的分词器,此时要求field中分词器保持一致。注意该分词器只能在field类型为text的索引中添加。 - compress_type:可配置ZSTD,SNAPPY,LZ4,LZ4HC,ZLIB中的一种 - format_version_id:指定倒排索引的版本id,默认为0(代表indexlib迁移aios基准版本的倒排格式),可选设置为1(支持一系列倒排存储格式优化,包括:短链vByte压缩、newPForDelta压缩算法优化、连续docid区间dictInline存储) @@ -249,7 +251,7 @@ TEXT索引是单字段索引。用于对TEXT类型的字段建立索引。采用 } } ``` -TEXT索引配置中的index_name,index_type,term_payload_flag,doc_payload_flag,position_payload_flag,position_list_flag,term_frequency_flag, compress_type的含义相同,只是index_type必须为TEXT,并且index_fields只支持一个字段。 +TEXT索引配置中的index_name,index_type,term_payload_flag,doc_payload_flag,position_payload_flag,position_list_flag,multi_level_skip_list, term_frequency_flag, compress_type的含义相同,只是index_type必须为TEXT,并且index_fields只支持一个字段。 #### 注意事项 - index_name 不允许命名为"summary"。