Skip to content

Commit 9b86f9e

Browse files
authored
[improve] modify the negativeACK structure to reduce memory overhead (#497)
1 parent 639786f commit 9b86f9e

17 files changed

+190
-16
lines changed

.github/workflows/ci-pr-validation.yaml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ jobs:
167167
sudo apt-get install -y libcurl4-openssl-dev libssl-dev \
168168
protobuf-compiler libprotobuf-dev libboost-dev \
169169
libboost-dev libboost-program-options-dev \
170-
libzstd-dev libsnappy-dev libgmock-dev libgtest-dev
170+
libzstd-dev libsnappy-dev libgmock-dev libgtest-dev libroaring-dev
171171
- name: CMake
172172
run: cmake -B build -DBUILD_PERF_TOOLS=ON -DCMAKE_CXX_STANDARD=20
173173
- name: Build
@@ -188,16 +188,16 @@ jobs:
188188
matrix:
189189
include:
190190
- name: 'Windows x64'
191-
os: windows-2019
191+
os: windows-2022
192192
triplet: x64-windows-static
193193
suffix: 'windows-win64'
194-
generator: 'Visual Studio 16 2019'
194+
generator: 'Visual Studio 17 2022'
195195
arch: '-A x64'
196196
- name: 'Windows x86'
197-
os: windows-2019
197+
os: windows-2022
198198
triplet: x86-windows-static
199199
suffix: 'windows-win32'
200-
generator: 'Visual Studio 16 2019'
200+
generator: 'Visual Studio 17 2022'
201201
arch: '-A Win32'
202202

203203
steps:

.github/workflows/codeql-analysis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ jobs:
6060
sudo apt-get install -y libcurl4-openssl-dev libssl-dev \
6161
protobuf-compiler libprotobuf-dev libboost-dev \
6262
libboost-dev libboost-program-options-dev \
63-
libzstd-dev libsnappy-dev
63+
libzstd-dev libsnappy-dev libroaring-dev
6464
6565
- name: Build
6666
run: |

CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,13 +124,15 @@ if (INTEGRATE_VCPKG)
124124
find_package(protobuf CONFIG REQUIRED)
125125
find_package(zstd CONFIG REQUIRED)
126126
find_package(Snappy CONFIG REQUIRED)
127+
find_package(roaring CONFIG REQUIRED)
127128
set(COMMON_LIBS CURL::libcurl
128129
ZLIB::ZLIB
129130
OpenSSL::SSL
130131
OpenSSL::Crypto
131132
protobuf::libprotobuf
132133
$<IF:$<TARGET_EXISTS:zstd::libzstd_shared>,zstd::libzstd_shared,zstd::libzstd_static>
133134
Snappy::snappy
135+
roaring::roaring
134136
)
135137
if (USE_ASIO)
136138
find_package(asio CONFIG REQUIRED)

LegacyFindPackages.cmake

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,25 @@ if (NOT ZLIB_INCLUDE_DIRS OR NOT ZLIB_LIBRARIES)
117117
message(FATAL_ERROR "Could not find zlib")
118118
endif ()
119119

120+
find_package(roaring QUIET)
121+
if (NOT ROARING_FOUND)
122+
find_path(ROARING_INCLUDE_DIRS NAMES roaring/roaring.hh)
123+
find_library(ROARING_LIBRARIES NAMES roaring libroaring)
124+
endif ()
125+
message("ROARING_INCLUDE_DIRS: " ${ROARING_INCLUDE_DIRS})
126+
message("ROARING_LIBRARIES: " ${ROARING_LIBRARIES})
127+
if (NOT ROARING_INCLUDE_DIRS OR NOT ROARING_LIBRARIES)
128+
message(FATAL_ERROR "Could not find libroaring")
129+
endif ()
130+
file(READ "${ROARING_INCLUDE_DIRS}/roaring/roaring.hh" ROARING_HEADER_CONTENTS)
131+
string(REGEX MATCH "namespace roaring" ROARING_HAS_NAMESPACE "${ROARING_HEADER_CONTENTS}")
132+
if (ROARING_HAS_NAMESPACE)
133+
message(STATUS "Roaring64Map is in namespace roaring")
134+
else ()
135+
message(STATUS "Roaring64Map is in global namespace")
136+
add_definitions(-DROARING_NAMESPACE_GLOBAL)
137+
endif ()
138+
120139
if (LINK_STATIC AND NOT VCPKG_TRIPLET)
121140
find_library(LIB_ZSTD NAMES libzstd.a)
122141
message(STATUS "ZStd: ${LIB_ZSTD}")
@@ -129,6 +148,7 @@ if (LINK_STATIC AND NOT VCPKG_TRIPLET)
129148
elseif (LINK_STATIC AND VCPKG_TRIPLET)
130149
find_package(Protobuf REQUIRED)
131150
message(STATUS "Found protobuf static library: " ${Protobuf_LIBRARIES})
151+
find_package(roaring REQUIRED)
132152
if (MSVC AND (${CMAKE_BUILD_TYPE} STREQUAL Debug))
133153
find_library(ZLIB_LIBRARIES NAMES zlibd)
134154
else ()
@@ -231,6 +251,7 @@ include_directories(
231251
${Boost_INCLUDE_DIRS}
232252
${OPENSSL_INCLUDE_DIR}
233253
${ZLIB_INCLUDE_DIRS}
254+
${ROARING_INCLUDE_DIRS}
234255
${CURL_INCLUDE_DIRS}
235256
${Protobuf_INCLUDE_DIRS}
236257
${GTEST_INCLUDE_PATH}
@@ -246,6 +267,7 @@ set(COMMON_LIBS
246267
${CURL_LIBRARIES}
247268
${OPENSSL_LIBRARIES}
248269
${ZLIB_LIBRARIES}
270+
${ROARING_LIBRARIES}
249271
${ADDITIONAL_LIBRARIES}
250272
${CMAKE_DL_LIBS}
251273
)

dependencies.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,4 @@ zstd: 1.5.5
2626
snappy: 1.1.10
2727
openssl: 1.1.1w
2828
curl: 8.6.0
29+
roaring: 4.3.1

include/pulsar/ConsumerConfiguration.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,21 @@ class PULSAR_PUBLIC ConsumerConfiguration {
277277
*/
278278
long getNegativeAckRedeliveryDelayMs() const;
279279

280+
/**
281+
* Set the precision bit count for negative ack redelivery delay.
282+
* The lower bits of the redelivery time will be trimmed to reduce the memory occupation.
283+
* @param negativeAckPrecisionBitCnt
284+
* negative ack precision bit count
285+
*/
286+
void setNegativeAckPrecisionBitCnt(int negativeAckPrecisionBitCnt);
287+
288+
/**
289+
* Get the configured precision bit count for negative ack redelivery delay.
290+
*
291+
* @return redelivery time precision bit count
292+
*/
293+
int getNegativeAckPrecisionBitCnt() const;
294+
280295
/**
281296
* Set time window in milliseconds for grouping message ACK requests. An ACK request is not sent
282297
* to broker until the time window reaches its end, or the number of grouped messages reaches

lib/ConsumerConfiguration.cc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,16 @@ void ConsumerConfiguration::setAckGroupingTimeMs(long ackGroupingMillis) {
134134
impl_->ackGroupingTimeMs = ackGroupingMillis;
135135
}
136136

137+
int ConsumerConfiguration::getNegativeAckPrecisionBitCnt() const { return impl_->negativeAckPrecisionBitCnt; }
138+
139+
void ConsumerConfiguration::setNegativeAckPrecisionBitCnt(int negativeAckPrecisionBitCnt) {
140+
if (negativeAckPrecisionBitCnt < 0) {
141+
throw std::invalid_argument(
142+
"Consumer Config Exception: NegativeAckPrecisionBitCnt should be nonnegative number.");
143+
}
144+
impl_->negativeAckPrecisionBitCnt = negativeAckPrecisionBitCnt;
145+
}
146+
137147
long ConsumerConfiguration::getAckGroupingTimeMs() const { return impl_->ackGroupingTimeMs; }
138148

139149
void ConsumerConfiguration::setAckGroupingMaxSize(long maxGroupingSize) {

lib/ConsumerConfigurationImpl.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ struct ConsumerConfigurationImpl {
2626
long unAckedMessagesTimeoutMs{0};
2727
long tickDurationInMs{1000};
2828
long negativeAckRedeliveryDelayMs{60000};
29+
int negativeAckPrecisionBitCnt{8};
2930
long ackGroupingTimeMs{100};
3031
long ackGroupingMaxSize{1000};
3132
long brokerConsumerStatsCacheTimeInMs{30 * 1000L}; // 30 seconds

lib/NegativeAcksTracker.cc

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,19 @@
1919

2020
#include "NegativeAcksTracker.h"
2121

22+
#include <cstdint>
2223
#include <functional>
2324
#include <set>
25+
#include <utility>
2426

2527
#include "ClientImpl.h"
2628
#include "ConsumerImpl.h"
2729
#include "ExecutorService.h"
2830
#include "LogUtils.h"
2931
#include "MessageIdUtil.h"
32+
#include "pulsar/MessageBuilder.h"
33+
#include "pulsar/MessageId.h"
34+
#include "pulsar/MessageIdBuilder.h"
3035
DECLARE_LOG_OBJECT()
3136

3237
namespace pulsar {
@@ -41,6 +46,7 @@ NegativeAcksTracker::NegativeAcksTracker(const ClientImplPtr &client, ConsumerIm
4146
nackDelay_ =
4247
std::chrono::milliseconds(std::max(conf.getNegativeAckRedeliveryDelayMs(), MIN_NACK_DELAY_MILLIS));
4348
timerInterval_ = std::chrono::milliseconds((long)(nackDelay_.count() / 3));
49+
nackPrecisionBit_ = conf.getNegativeAckPrecisionBitCnt();
4450
LOG_DEBUG("Created negative ack tracker with delay: " << nackDelay_.count() << " ms - Timer interval: "
4551
<< timerInterval_.count());
4652
}
@@ -75,13 +81,22 @@ void NegativeAcksTracker::handleTimer(const ASIO_ERROR &ec) {
7581

7682
auto now = Clock::now();
7783

84+
// The map is sorted by time, so we can exit immediately when we traverse to a time that does not match
7885
for (auto it = nackedMessages_.begin(); it != nackedMessages_.end();) {
79-
if (it->second < now) {
80-
messagesToRedeliver.insert(it->first);
81-
it = nackedMessages_.erase(it);
82-
} else {
83-
++it;
86+
if (it->first > now) {
87+
// We are done with all the messages that need to be redelivered
88+
break;
8489
}
90+
91+
auto ledgerMap = it->second;
92+
for (auto ledgerIt = ledgerMap.begin(); ledgerIt != ledgerMap.end(); ++ledgerIt) {
93+
auto entrySet = ledgerIt->second;
94+
for (auto setIt = entrySet.begin(); setIt != entrySet.end(); ++setIt) {
95+
messagesToRedeliver.insert(
96+
MessageIdBuilder().ledgerId(ledgerIt->first).entryId(*setIt).build());
97+
}
98+
}
99+
it = nackedMessages_.erase(it);
85100
}
86101
lock.unlock();
87102

@@ -92,14 +107,27 @@ void NegativeAcksTracker::handleTimer(const ASIO_ERROR &ec) {
92107
scheduleTimer();
93108
}
94109

110+
std::chrono::steady_clock::time_point trimLowerBit(const std::chrono::steady_clock::time_point &tp,
111+
int bits) {
112+
// get origin timestamp in nanoseconds
113+
auto timestamp = std::chrono::duration_cast<std::chrono::nanoseconds>(tp.time_since_epoch()).count();
114+
115+
// trim lower bits
116+
auto trimmedTimestamp = timestamp & (~((1LL << bits) - 1));
117+
118+
return std::chrono::steady_clock::time_point(std::chrono::nanoseconds(trimmedTimestamp));
119+
}
120+
95121
void NegativeAcksTracker::add(const MessageId &m) {
96122
auto msgId = discardBatch(m);
97123
auto now = Clock::now();
98124

99125
{
100126
std::lock_guard<std::mutex> lock{mutex_};
127+
auto trimmedTimestamp = trimLowerBit(now + nackDelay_, nackPrecisionBit_);
128+
// If the timestamp is already in the map, we can just add the message to the existing entry
101129
// Erase batch id to group all nacks from same batch
102-
nackedMessages_[msgId] = now + nackDelay_;
130+
nackedMessages_[trimmedTimestamp][msgId.ledgerId()].add((uint64_t)msgId.entryId());
103131
}
104132

105133
scheduleTimer();

lib/NegativeAcksTracker.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
#include <map>
2828
#include <memory>
2929
#include <mutex>
30+
#include <roaring/roaring64map.hh>
31+
#include <unordered_map>
3032

3133
#include "AsioDefines.h"
3234
#include "AsioTimer.h"
@@ -39,6 +41,12 @@ class ClientImpl;
3941
using ClientImplPtr = std::shared_ptr<ClientImpl>;
4042
class ExecutorService;
4143
using ExecutorServicePtr = std::shared_ptr<ExecutorService>;
44+
using LedgerId = int64_t;
45+
#ifdef ROARING_NAMESPACE_GLOBAL
46+
using ConditionalRoaringMap = Roaring64Map;
47+
#else
48+
using ConditionalRoaringMap = roaring::Roaring64Map;
49+
#endif
4250

4351
class NegativeAcksTracker : public std::enable_shared_from_this<NegativeAcksTracker> {
4452
public:
@@ -64,8 +72,9 @@ class NegativeAcksTracker : public std::enable_shared_from_this<NegativeAcksTrac
6472

6573
std::chrono::milliseconds nackDelay_;
6674
std::chrono::milliseconds timerInterval_;
75+
int nackPrecisionBit_;
6776
typedef typename std::chrono::steady_clock Clock;
68-
std::map<MessageId, Clock::time_point> nackedMessages_;
77+
std::map<Clock::time_point, std::unordered_map<LedgerId, ConditionalRoaringMap>> nackedMessages_;
6978

7079
const DeadlineTimerPtr timer_;
7180
std::atomic_bool closed_{false};

0 commit comments

Comments
 (0)