Repository navigation
Description
Support hotspot key detection in Pegasus
Related PR #502
1 Background
At present, Pegasus lists the monitoring values for hotspot detection primitively, using manual methods for fault detection and troubleshooting, which is cumbersome for operation and maintenance. To make the data more visually presented, we have added a hotspot detection module #444 and #479, which scores relevant values of hotspot issues and feeds them back to falcon, reducing the burden of operation and maintenance. And in this Issue #495, I want to discuss the function of hotspot key detection.
This function is to find the possible hotspot key in the partition which hotspot detection algorithm recorded, and feed it back to the Ops, for timely troubleshooting.
At the same time, this work is also one of the solutions for hot data reading and writing problems.
2 Design
2.1 Timing diagram
2.2 How to start capturing data
According to #479, we can get a "hotspot_value" of each partition. We can set a threshold and if a partition's hotspot_value exceeded the threshold 3 times, the collector will send an RPC to the replica server.
2.2.1 First step
hotspot_calculator(in the collector) will check every partition's hotspot_value given by #479, if it is higher than THRESHOLD_OF_HOTSPOT_PARTITION_VALUE we set (normally it would be 4), it will be recorded in global_read_count/global_write_count. This function will be started every time hotspot_value updated, which is carried out once a minute.
2.2.2 Second step
hotspot_calculator(in the collector) will check global_read_count/global_write_count status. When global_read_count/global_write_count is higher than the THRESHOLD_OF_SEND_RPC_TO_DETECT_HOTKEY, which means these partitions are abnormal for threshold minutes, we will send an RPC to detect hotkey.
2.3 How to capture data traffic
There are two points we need to pay attention to Thread-safety and low resource occupation
To reduce the load, we introduce a two-level model to filter data.
2.3.1 performance pre-test
According to the performance test, #495 (comment). I found that shunt data flow before try_lock can significantly improve efficiency. Use random sampling can further reduce latency.
2.3.2 RPC
Ref #495 (comment)
2.3.3 hotkey_collector
hotkey_collector is used to capture and analyze data for one partition, it is running on the replica.hotkey_collector has four statuses:
- stop: hotkey_collector is stopped, all its data has been cleared, we can reuse it when needed.
- coarse_level: hotkey_collector is running, it will collect the hash value of data flow, without its specific content. If it finds the hot hash bucket successfully, its status will turn to fine_level. Otherwise, it will keep running.
- fine_level: hotkey_collector is running, it will sampling survey the specific content of data flow in the hottest hash bucket, including hash_key and sort_key. If it finds the hotkey successfully, its status will turn to finish. Otherwise, it will keep running.
- finish: hotkey_collector is waiting for hotkey_manager to store the result, after recording the result and send it back with the RPC, its status will turn to stop.
void capture_data(data)
{
if (collector_status == coarse)
coarse_capture_data(data);
if (collector_status == fine)
fine_capture_data(data);
}
void coarse_capture_data(data)
{
hash_table[hash(data)]++;
}
void fine_capture_data(data)
{
queue_number = rand(0-10); // Random range can be adjusted according to the actual situation
if (mutex[queue_number].try_lock()){
queue[queue_number].push(data); // When analyse data, we should merge these queue
}
}
void analyse_data()
{
if (collector_status == coarse)
if (analyse_coarse_data_successful)
collector_status = fine;
if (collector_status == fine)
if (analyse_fine_data_successful)
collector_status = finish;
if (collector_status == finish)
if (send_RPC_back_successful)
clear_data;
collector_status = stop;
if (time_out)
clear_data;
collector_status = stop;
}
capture_data() and analyse_data() have different frequencies, separate two functions contribute to improving efficiency.
capture_data() will be executed in https://github.com/XiaoMi/pegasus/blob/b7492caceae817cfdb50a1a71d42de9d16c4a234/src/server/pegasus_server_impl.cpp#L573 and https://github.com/XiaoMi/pegasus/blob/b7492caceae817cfdb50a1a71d42de9d16c4a234/src/server/pegasus_server_impl.cpp#L562
analyse_data() is a timing task.
2.4 How to stop capturing data traffic
It can be terminated by timed out or by manual(send a RPC)
2.5 How to notify the result of hotkey detection
By logging in derror(). We can search the specific error logs on the "hot" server or the collector.

Activity
neverchanje commentedon Mar 16, 2020
We can name this RPC to
hotkey_detect_rpc. You should define the structure of the request as well as response, and include them in your proposal. Because the API structures are highly related to your design.We can name it
stop_hotkey_detect_rpcneverchanje commentedon Mar 16, 2020
What is THRESHOLD_1 and THRESHOLD_2?
What is read_watch_list?
I recommend that you can describe the data structures first, the pseudocodes like functions or classes should not be our first concern. Of course, it will be fine to include them, you can give a highly-descriptive function name without implementation, rather than give something like "excpetion_read_check". I don't really know what it means.
foreverneverer commentedon Mar 17, 2020
proportional samplingbefore re-hashSmityz commentedon Mar 19, 2020
Update
2.2.1 How to start capturing data
benchmark
For the reason to further explore the implementation of the scheme, I did a benchmark to test the different ways to capture and analyse data. The two main ideas are optimizing
try_lockand usingatomic. And here is the result of the benchmark:Test code: https://github.com/Smityz/multithreading
Result analysis
According to the results we can know, we should use
atomicas much as possible, which has far more advance tolock. So, we can useatomicin coarse_level_capture.But it is difficult to use
atomicin fine_level_capture, since there is no atomic_map we can use. So I suggest using try_lock_split in fine_level_capture, and if we use randomize to limit data flow, it can also accelerate operation.By the way
rand()in <stdlib.h> is deprecated for it's performance.levy5307 commentedon Mar 23, 2020
Maybe you can use atomic<T> in fine_level_capture, and define a struct, which has a map member
acelyc111 commentedon Mar 23, 2020
When either coarse_level or fine_level run a period of time but no hot sub-partition or hot key detected, you should turn off
hotkey_collector, it's costly to run it all the time.Smityz commentedon Mar 24, 2020
Smityz commentedon Apr 24, 2020
A new idea of capture hotkey string(capture_fine_data)
In the old design, we use try_lock and random selection to reduce the probability of lockup. It's a good idea but still has a lock. In the new design, we will have an exciting lock-free method to capture the huge data flow.
Thread structure
How to allocate queue to the thread
By CAS we can handle this easily
How to ensure the thread-safety of queues
We can use the producer-consumer queue to ensure thread safety meanwhile keeping high performance.
https://github.com/cameron314/readerwriterqueue is recommended.
We can also use
boost::lockfree:queueorfolly::ProducerConsumerQueueto instead.feat: add perf counter for group check failure (#495)