[{"data":1,"prerenderedAt":2409},["ShallowReactive",2],{"/blog/how-we-diagnosed-and-resolved-redis-latency-spikes":3,"navigation-en-us":1622,"banner-en-us":2050,"footer-en-us":2060,"blog-post-authors-en-us-Matt Smiley":2304,"blog-related-posts-en-us-how-we-diagnosed-and-resolved-redis-latency-spikes":2319,"blog-promotions-en-us":2345,"next-steps-en-us":2399},{"id":4,"title":5,"authors":6,"body":8,"category":1600,"date":1601,"description":1602,"extension":1603,"externalUrl":1604,"faq":1604,"featured":1605,"heroImage":1606,"meta":1607,"navigation":1608,"path":1609,"seo":1610,"slug":1614,"stem":1615,"tags":1616,"template":1620,"updatedDate":1604,"__hash__":1621},"blogPosts/en-us/blog/how-we-diagnosed-and-resolved-redis-latency-spikes.md","How we diagnosed and resolved Redis latency spikes with BPF and other tools",[7],"Matt Smiley",{"type":9,"value":10,"toc":1581},"minimark",[11,15,18,21,24,29,32,42,49,56,70,95,98,102,114,117,129,134,140,145,151,154,158,161,164,200,207,213,220,226,229,235,253,259,268,275,294,300,303,325,657,666,669,688,692,701,720,729,751,754,758,764,767,770,787,790,793,801,811,814,817,828,841,844,861,889,897,913,916,942,945,958,961,981,985,994,999,1005,1008,1022,1025,1047,1050,1064,1069,1072,1079,1083,1091,1099,1105,1108,1129,1138,1142,1145,1148,1171,1174,1230,1236,1239,1243,1246,1263,1277,1305,1308,1311,1328,1332,1335,1341,1344,1350,1356,1359,1362,1368,1372,1375,1378,1382,1385,1428,1432,1435,1438,1504,1507,1577],[12,13,14],"p",{},"If you enjoy performance engineering and peeling back abstraction layers to ask underlying subsystems to explain themselves, this article’s for you. The context is a chronic Redis latency problem, and you are about to tour a practical example of using BPF and profiling tools in concert with standard metrics to reveal unintuitive behaviors of a complex system.",[12,16,17],{},"Beyond the tools and techniques, we also use an iterative hypothesis-testing approach to compose a behavior model of the system dynamics. This model tells us what factors influence the problem's severity and triggering conditions.",[12,19,20],{},"Ultimately, we find the root cause, and its remedy is delightfully boring and effective. We uncover a three-phase cycle involving two distinct saturation points and a simple fix to break that cycle. Along the way, we inspect aspects of the system’s behavior using stack sampling profiles, heat maps and flamegraphs, experimental tuning, source and binary analysis, instruction-level BPF instrumentation, and targeted latency injection under specific entry and exit conditions.",[12,22,23],{},"If you are short on time, the takeaways are summarized at the end. But the journey is the fun part, so let's dig in!",[25,26,28],"h2",{"id":27},"introducing-the-problem-chronic-latency","Introducing the problem: Chronic latency",[12,30,31],{},"GitLab makes extensive use of Redis, and, on GitLab.com SaaS, we use separate Redis clusters for certain functions. This tale concerns a Redis instance acting exclusively as a least recently used (LRU) cache.",[12,33,34,35,41],{},"This cache had a chronic latency problem that started occurring intermittently over two years ago and in recent months had become significantly worse: Every few minutes, it suffered from bursts of very high latency and corresponding throughput drop, eating into its Service Level Objective (SLO). These latency spikes impacted user-facing response times and ",[36,37,40],"a",{"href":38,"rel":39},"https://gitlab.com/gitlab-org/gitlab/-/issues/360578#note_966597336",[],"burned error budgets"," for dependent features, and this is what we aimed to solve.",[12,43,44,48],{},[45,46,47],"strong",{},"Graph:"," Spikes in the rate of extremely slow (1 second) Redis requests, each corresponding to an eviction burst",[12,50,51],{},[52,53],"img",{"alt":54,"src":55},"Graph showing spikes in the slow request rate every few minutes","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782397736/blog/Content%20Images/2022-11-28-diagnosing-redis-latency-spikes-with-bpf-and-friends/00_redis_slow_request_rate_spikes_during_each_eviction_burst.png",[12,57,58,59,64,65,69],{},"In prior work, we had already completed several mitigating optimizations. These sufficed for a while, but organic growth had resurfaced this as an important ",[36,60,63],{"href":61,"rel":62},"https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/1601#why-is-it-important-to-get-to-the-root-of-the-latency-spikes",[],"long-term scaling problem",". We had also already ruled out externally triggered causes, such as request floods, connection rate spikes, host-level resource contention, etc. These latency spikes were consistently associated with memory usage reaching the eviction threshold (",[66,67,68],"code",{},"maxmemory","), not by changes in client traffic patterns or other processes competing with Redis for CPU time, memory bandwidth, or network I/O.",[12,71,72,73,78,79,84,85,88,89,94],{},"We ",[36,74,77],{"href":75,"rel":76},"https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/1567",[],"initially thought"," that Redis 6.2’s new ",[36,80,83],{"href":81,"rel":82},"https://github.com/redis/redis/pull/7653",[],"eviction throttling mechanism"," might alleviate our eviction burst overhead. It did not. That mechanism solves a different problem: It prevents a stall condition where a single call to ",[66,86,87],{},"performEvictions"," could run arbitrarily long. In contrast, during this analysis we ",[36,90,93],{"href":91,"rel":92},"https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/1601#note_977816216",[],"discovered"," that our problem (both before and after upgrading Redis) was related to numerous calls collectively reducing Redis throughput, rather than a few extremely slow calls causing a complete stall.",[12,96,97],{},"To discover our bottleneck and its potential solutions, we needed to investigate Redis’s behavior during our workload’s eviction bursts.",[25,99,101],{"id":100},"a-little-background-on-redis-evictions","A little background on Redis evictions",[12,103,104,105,113],{},"At the time, our cache was oversubscribed, trying to hold more cache keys than the ",[36,106,109,110,112],{"href":107,"rel":108},"https://redis.io/docs/reference/eviction/",[],"configured ",[66,111,68],{}," threshold"," could hold, so evictions from the LRU cache were expected. But the dense concentration of that eviction overhead was surprising and troubling.",[12,115,116],{},"Redis is essentially single-threaded. With a few exceptions, the “main” thread does almost all tasks serially, including handling client requests and evictions, among other things. Spending more time on X means there is less remaining time to do Y, so think about queuing behavior as the story unfolds.",[12,118,119,120,122,123,125,126,128],{},"Whenever Redis reaches its ",[66,121,68],{}," threshold, it frees memory by evicting some keys, aiming to do just enough evictions to get back under ",[66,124,68],{},". However, contrary to expectation, the metrics for memory usage and eviction rate (shown below) indicated that instead of a continuous steady eviction rate, there were abrupt burst events that freed much more memory than expected. After each eviction burst, no evictions occurred until memory usage climbed back up to the ",[66,127,68],{}," threshold again.",[12,130,131,133],{},[45,132,47],{}," Redis memory usage drops by 300-500 MB during each eviction burst:",[12,135,136],{},[52,137],{"alt":138,"src":139},"Memory usage repeatedly rises gradually to 64 GB and then abruptly drops","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782397736/blog/Content%20Images/2022-11-28-diagnosing-redis-latency-spikes-with-bpf-and-friends/01_redis_memory_usage_dips_during_eviction_bursts.png",[12,141,142,144],{},[45,143,47],{}," Key eviction spikes match the timing and size of the memory usage dips shown above",[12,146,147],{},[52,148],{"alt":149,"src":150},"Eviction counter shows a large spike each time the previous graph showed a large memory usage drop","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782397737/blog/Content%20Images/2022-11-28-diagnosing-redis-latency-spikes-with-bpf-and-friends/02_redis_eviction_bursts.png",[12,152,153],{},"This apparent excess of evictions became the central mystery. Initially, we thought answering that question might reveal a way to smooth the eviction rate, spreading out the overhead and avoiding the latency spikes. Instead, we discovered that these bursts are an interaction effect that we need to avoid, but more on that later.",[25,155,157],{"id":156},"eviction-bursts-cause-cpu-saturation","Eviction bursts cause CPU saturation",[12,159,160],{},"As shown above, we had found that these latency spikes correlated perfectly with large spikes in the cache’s eviction rate, but we did not yet understand why the evictions were concentrated into bursts that last a few seconds and occur every few minutes.",[12,162,163],{},"As a first step, we wanted to verify a causal relationship between eviction bursts and latency spikes.",[12,165,166,167,174,175,182,183,190,191,196,197,199],{},"To test this, we used ",[36,168,171],{"href":169,"rel":170},"https://www.brendangregg.com/perf.html",[],[66,172,173],{},"perf"," to run a CPU sampling profile on the Redis main thread. Then we applied a filter to split that profile, isolating the samples where it was calling the ",[36,176,179,181],{"href":177,"rel":178},"https://github.com/redis/redis/blob/6.2.6/src/evict.c#L512",[],[66,180,87],{}," function",". Using ",[36,184,187],{"href":185,"rel":186},"https://github.com/Netflix/flamescope",[],[66,188,189],{},"flamescope",", we can visualize the profile’s CPU usage as a ",[36,192,195],{"href":193,"rel":194},"https://www.brendangregg.com/HeatMaps/subsecondoffset.html",[],"subsecond offset heat map",", where each second on the X axis is folded into a column of 20 msec buckets along the Y axis. This visualization style highlights sub-second activity patterns. Comparing these two heat maps confirmed that during an eviction burst, ",[66,198,87],{}," is starving all other main thread code paths for CPU time.",[12,201,202,204,205],{},[45,203,47],{}," Redis main thread CPU time, excluding calls to ",[66,206,87],{},[12,208,209],{},[52,210],{"alt":211,"src":212},"Heat map shows one large gap and two small gaps in an otherwise uniform pattern of 70 percent to 80 percent CPU usage","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782397737/blog/Content%20Images/2022-11-28-diagnosing-redis-latency-spikes-with-bpf-and-friends/03_heat_map_of_redis_main_thread_during_eviction_burst__excluding_performEvictions.png",[12,214,215,217,218],{},[45,216,47],{}," Remainder of the same profile, showing only the calls to ",[66,219,87],{},[12,221,222],{},[52,223],{"alt":224,"src":225},"This heat map shows the gaps in the previous heap map were CPU time spent performing evictions","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782397737/blog/Content%20Images/2022-11-28-diagnosing-redis-latency-spikes-with-bpf-and-friends/04_heat_map_of_redis_main_thread_during_eviction_burst__only_performEvictions.png",[12,227,228],{},"These results confirm that eviction bursts are causing CPU starvation on the main thread, which acts as a throughput bottleneck and increases Redis’s response time latency.  These CPU utilization bursts typically lasted a few seconds, so they were too short-lived to trigger alerts but were still user impacting.",[12,230,231,232,234],{},"For context, the following flamegraph shows where ",[66,233,87],{}," spends its CPU time. There are a few interesting things here, but the most important takeaways are:",[236,237,238,246],"ul",{},[239,240,241,242,245],"li",{},"It gets called synchronously by ",[66,243,244],{},"processCommand"," (which handles all client requests).",[239,247,248,249,252],{},"It handles many of its own deletes. Despite its name, the ",[66,250,251],{},"dbAsyncDelete"," function only delegates deletes to a helper thread under certain conditions which turn out to be rare for this workload.",[12,254,255],{},[52,256],{"alt":257,"src":258},"Flamegraph of calls to function performEvictions, as described above","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782397750/blog/Content%20Images/2022-11-28-diagnosing-redis-latency-spikes-with-bpf-and-friends/05_flamegraph_of_redis_main_thread_during_eviction_burst__only_performEvictions.png",[12,260,261,262,267],{},"For more details on this analysis, see the ",[36,263,266],{"href":264,"rel":265},"https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/1601#note_854745083",[],"walkthrough and methodology",".",[25,269,271,272,274],{"id":270},"how-fast-are-individual-calls-to-performevictions","How fast are individual calls to ",[66,273,87],{},"?",[12,276,277,278,280,281,283,284,286,287,289,290,293],{},"Each incoming request to Redis is handled by a call to ",[66,279,244],{},", and it always concludes by calling the ",[66,282,87],{}," function. That call to ",[66,285,87],{}," is frequently a no-op, returning immediately after checking that the ",[66,288,68],{}," threshold has not been breached. But when the threshold is exceeded, it will continue evicting keys until it either reaches its ",[66,291,292],{},"mem_tofree"," goal or exceeds its configured time limit per call.",[12,295,296,297,299],{},"The CPU heat maps shown earlier proved that ",[66,298,87],{}," calls were collectively consuming a large majority of CPU time for up to several seconds.",[12,301,302],{},"To complement that, we also measured the wall clock time of individual calls.",[12,304,305,306,309,310,315,316,318,319,324],{},"Using the ",[66,307,308],{},"funclatency"," CLI tool (part of the ",[36,311,314],{"href":312,"rel":313},"https://github.com/iovisor/bcc",[],"BCC suite of BPF tools","), we measured call duration by instrumenting entry and exit from the ",[66,317,87],{}," function and aggregated those measurements into a ",[36,320,323],{"href":321,"rel":322},"https://en.wikipedia.org/wiki/Histogram",[],"histogram"," at 1-second intervals. When no evictions were occurring, the calls were consistently low latency (4-7 usecs/call). This is the no-op case described above (including 2.5 usecs/call of instrumentation overhead). But during an eviction burst, the results shift to a bimodal distribution, including a combination of the fast no-op calls along with much slower calls that are actively performing evictions:",[326,327,332],"pre",{"className":328,"code":329,"language":330,"meta":331,"style":331},"language-shell shiki shiki-themes github-light","$ sudo funclatency-bpfcc --microseconds --timestamp --interval 1 --duration 600 --pid $( pgrep -o redis-server ) '/opt/gitlab/embedded/bin/redis-server:performEvictions'\n...\n23:54:03\n     usecs               : count     distribution\n         0 -> 1          : 0        |                                        |\n         2 -> 3          : 576      |************                            |\n         4 -> 7          : 1896     |****************************************|\n         8 -> 15         : 392      |********                                |\n        16 -> 31         : 84       |*                                       |\n        32 -> 63         : 62       |*                                       |\n        64 -> 127        : 94       |*                                       |\n       128 -> 255        : 182      |***                                     |\n       256 -> 511        : 826      |*****************                       |\n       512 -> 1023       : 750      |***************                         |\n\n","shell","",[66,333,334,391,397,403,418,445,469,490,515,539,561,584,608,632],{"__ignoreMap":331},[335,336,339,343,347,350,354,357,360,363,366,369,372,376,379,382,385,388],"span",{"class":337,"line":338},"line",1,[335,340,342],{"class":341},"s7eDp","$",[335,344,346],{"class":345},"sYBdl"," sudo",[335,348,349],{"class":345}," funclatency-bpfcc",[335,351,353],{"class":352},"sYu0t"," --microseconds",[335,355,356],{"class":352}," --timestamp",[335,358,359],{"class":352}," --interval",[335,361,362],{"class":352}," 1",[335,364,365],{"class":352}," --duration",[335,367,368],{"class":352}," 600",[335,370,371],{"class":352}," --pid",[335,373,375],{"class":374},"sgsFI"," $( ",[335,377,378],{"class":341},"pgrep",[335,380,381],{"class":352}," -o",[335,383,384],{"class":345}," redis-server",[335,386,387],{"class":374}," ) ",[335,389,390],{"class":345},"'/opt/gitlab/embedded/bin/redis-server:performEvictions'\n",[335,392,394],{"class":337,"line":393},2,[335,395,396],{"class":352},"...\n",[335,398,400],{"class":337,"line":399},3,[335,401,402],{"class":341},"23:54:03\n",[335,404,406,409,412,415],{"class":337,"line":405},4,[335,407,408],{"class":341},"     usecs",[335,410,411],{"class":345},"               :",[335,413,414],{"class":345}," count",[335,416,417],{"class":345},"     distribution\n",[335,419,421,424,427,431,433,436,439,442],{"class":337,"line":420},5,[335,422,423],{"class":341},"         0",[335,425,426],{"class":374}," -",[335,428,430],{"class":429},"sD7c4",">",[335,432,362],{"class":352},[335,434,435],{"class":345},"          :",[335,437,438],{"class":352}," 0",[335,440,441],{"class":429},"        |",[335,443,444],{"class":429},"                                        |\n",[335,446,448,451,453,455,458,460,463,466],{"class":337,"line":447},6,[335,449,450],{"class":341},"         2",[335,452,426],{"class":374},[335,454,430],{"class":429},[335,456,457],{"class":352}," 3",[335,459,435],{"class":345},[335,461,462],{"class":352}," 576",[335,464,465],{"class":429},"      |************",[335,467,468],{"class":429},"                            |\n",[335,470,472,475,477,479,482,484,487],{"class":337,"line":471},7,[335,473,474],{"class":341},"         4",[335,476,426],{"class":374},[335,478,430],{"class":429},[335,480,481],{"class":352}," 7",[335,483,435],{"class":345},[335,485,486],{"class":352}," 1896",[335,488,489],{"class":429},"     |****************************************|\n",[335,491,493,496,498,500,503,506,509,512],{"class":337,"line":492},8,[335,494,495],{"class":341},"         8",[335,497,426],{"class":374},[335,499,430],{"class":429},[335,501,502],{"class":352}," 15",[335,504,505],{"class":345},"         :",[335,507,508],{"class":352}," 392",[335,510,511],{"class":429},"      |********",[335,513,514],{"class":429},"                                |\n",[335,516,518,521,523,525,528,530,533,536],{"class":337,"line":517},9,[335,519,520],{"class":341},"        16",[335,522,426],{"class":374},[335,524,430],{"class":429},[335,526,527],{"class":352}," 31",[335,529,505],{"class":345},[335,531,532],{"class":352}," 84",[335,534,535],{"class":429},"       |*",[335,537,538],{"class":429},"                                       |\n",[335,540,542,545,547,549,552,554,557,559],{"class":337,"line":541},10,[335,543,544],{"class":341},"        32",[335,546,426],{"class":374},[335,548,430],{"class":429},[335,550,551],{"class":352}," 63",[335,553,505],{"class":345},[335,555,556],{"class":352}," 62",[335,558,535],{"class":429},[335,560,538],{"class":429},[335,562,564,567,569,571,574,577,580,582],{"class":337,"line":563},11,[335,565,566],{"class":341},"        64",[335,568,426],{"class":374},[335,570,430],{"class":429},[335,572,573],{"class":352}," 127",[335,575,576],{"class":345},"        :",[335,578,579],{"class":352}," 94",[335,581,535],{"class":429},[335,583,538],{"class":429},[335,585,587,590,592,594,597,599,602,605],{"class":337,"line":586},12,[335,588,589],{"class":341},"       128",[335,591,426],{"class":374},[335,593,430],{"class":429},[335,595,596],{"class":352}," 255",[335,598,576],{"class":345},[335,600,601],{"class":352}," 182",[335,603,604],{"class":429},"      |***",[335,606,607],{"class":429},"                                     |\n",[335,609,611,614,616,618,621,623,626,629],{"class":337,"line":610},13,[335,612,613],{"class":341},"       256",[335,615,426],{"class":374},[335,617,430],{"class":429},[335,619,620],{"class":352}," 511",[335,622,576],{"class":345},[335,624,625],{"class":352}," 826",[335,627,628],{"class":429},"      |*****************",[335,630,631],{"class":429},"                       |\n",[335,633,635,638,640,642,645,648,651,654],{"class":337,"line":634},14,[335,636,637],{"class":341},"       512",[335,639,426],{"class":374},[335,641,430],{"class":429},[335,643,644],{"class":352}," 1023",[335,646,647],{"class":345},"       :",[335,649,650],{"class":352}," 750",[335,652,653],{"class":429},"      |***************",[335,655,656],{"class":429},"                         |\n",[12,658,659,660,662,663,665],{},"This measurement also directly confirmed and quantified the throughput drop in Redis requests handled per second: The call rate to ",[66,661,87],{}," (and hence to ",[66,664,244],{},") dropped to 20% of its norm from before the evictions began, from 25K to 5K calls per second.",[12,667,668],{},"This has a huge impact on clients: New requests are arriving at 5x the rate they are being completed. And crucially, we will see soon that this asymmetry is what drives the eviction burst.",[12,670,261,671,676,677,682,683,267],{},[36,672,675],{"href":673,"rel":674},"https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/1601#note_857869826",[],"safety check"," for instrumentation overhead and the ",[36,678,681],{"href":679,"rel":680},"https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/1601#note_857907521",[],"results walkthrough",". And for more general reference, the BPF instrumentation overhead estimate is based on these ",[36,684,687],{"href":685,"rel":686},"https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/1383",[],"benchmark results",[25,689,691],{"id":690},"experiment-can-tuning-mitigate-eviction-driven-cpu-saturation","Experiment: Can tuning mitigate eviction-driven CPU saturation?",[12,693,694,695,700],{},"The analyses so far had shown that evictions were severely starving the Redis main thread for CPU time. There were still important unanswered questions (which we will return to shortly), but this was already enough info to ",[36,696,699],{"href":697,"rel":698},"https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/1601#note_859236777",[],"suggest some experiments"," to test potential mitigations:",[236,702,703,706],{},[239,704,705],{},"Can we spread out the eviction overhead so it takes longer to reach its goal but consumes a smaller percentage of the main thread’s time?",[239,707,708,709,714,715,719],{},"Are evictions freeing more memory than expected due to scheduling a lot of keys to be asynchronously deleted by the ",[36,710,713],{"href":711,"rel":712},"https://github.com/redis/redis/blob/6.2.6/redis.conf#L1079",[],"lazyfree mechanism","? Lazyfree is an optional feature that lets the Redis main thread ",[36,716,718],{"href":697,"rel":717},[],"delegate to an async helper thread"," the expensive task of deleting keys that have more than 64 elements. These async evictions do not count immediately towards the eviction loop’s memory goal, so if many keys qualify for lazyfree, this could potentially drive many extra iterations of the eviction loop.",[12,721,722,723,728],{},"The ",[36,724,727],{"href":725,"rel":726},"https://gitlab.com/gitlab-com/gl-infra/production/-/issues/7172#note_971197943",[],"answers"," to both turned out to be no:",[236,730,731,741],{},[239,732,733,734,737,738,740],{},"Reducing ",[66,735,736],{},"maxmemory-eviction-tenacity"," to its minimum setting still did not make ",[66,739,87],{}," cheap enough to avoid accumulating a request backlog. It did increase response rate, but arrival rate still far exceeded it, so this was not an effective mitigation.",[239,742,743,744,747,748,750],{},"Disabling ",[66,745,746],{},"lazyfree-lazy-eviction"," did not prevent the eviction burst from dropping memory usage far below ",[66,749,68],{},". Those lazyfrees represent a small percentage of reclaimed memory. This rules out one of the potential explanations for the mystery of excessive memory being freed.",[12,752,753],{},"Having ruled out two potential mitigations and one candidate hypothesis, at this point we return to the pivotal question: Why are several hundred extra megabytes of memory being freed by the end of each eviction burst?",[25,755,757],{"id":756},"why-do-evictions-occur-in-bursts-and-free-too-much-memory","Why do evictions occur in bursts and free too much memory?",[12,759,760,761,763],{},"Each round of eviction aims to free just barely enough memory to get back under the ",[66,762,68],{}," threshold.",[12,765,766],{},"With a steady rate of demand for new memory allocations, the eviction rate should be similarly steady. The rate of arriving cache writes does appear to be steady. So why are evictions happening in dense bursts, rather than smoothly? And why do they reduce memory usage on a scale of hundreds of megabytes rather than hundreds of bytes?",[12,768,769],{},"Some potential explanations to explore:",[236,771,772,775,778,784],{},[239,773,774],{},"Do evictions only end when a large key gets evicted, spontaneously freeing enough memory to skip evictions for a while? No, the memory usage drop is far bigger than the largest keys in the dataset.",[239,776,777],{},"Do deferred lazyfree evictions cause the eviction loop to overshoot its goal, freeing more memory than intended? No, the above experiment disproved this hypothesis.",[239,779,780,781,783],{},"Is something causing the eviction loop to sometimes calculate an unexpectedly large value for its ",[66,782,292],{}," goal? We explore this next. The answer is no, but checking it led to a new insight.",[239,785,786],{},"Is a feedback loop causing evictions to become somehow self-amplifying? If so, what conditions lead to entering and leaving this state? This turned out to be correct.",[12,788,789],{},"These were all plausible and testable hypotheses, and each would point towards a different solution to the eviction-driven latency problem.",[12,791,792],{},"The first two hypotheses we have already eliminated.",[12,794,795,796,798,799,267],{},"To test the next two, we built custom BPF instrumentation to peek at the calculation of ",[66,797,292],{}," at the start of each call to ",[66,800,87],{},[25,802,804,805,807,808],{"id":803},"observing-the-mem_tofree-calculation-with-bpftrace","Observing the ",[66,806,292],{}," calculation with ",[66,809,810],{},"bpftrace",[12,812,813],{},"This part of the investigation was a personal favorite and led to a critical realization about the nature of the problem.",[12,815,816],{},"As noted above, our two remaining hypotheses were:",[236,818,819,825],{},[239,820,821,822,824],{},"an unexpectedly large ",[66,823,292],{}," goal",[239,826,827],{},"a self-amplifying feedback loop",[12,829,830,831,837,838,840],{},"To differentiate between them, we used ",[36,832,835],{"href":833,"rel":834},"https://github.com/iovisor/bpftrace",[],[66,836,810],{}," to instrument the calculation of ",[66,839,292],{},", looking at its input variables and results.",[12,842,843],{},"This set of measurements directly tests the following:",[236,845,846,855],{},[239,847,848,849,851,852,854],{},"Does each call to ",[66,850,87],{}," aim to free a small amount of memory -- perhaps roughly the size of an average cache entry? If ",[66,853,292],{}," ever approaches hundreds of megabytes, that would confirm the first hypothesis and reveal what part of the calculation was causing that large value. Otherwise, it rules out the first hypothesis and makes the feedback loop hypothesis more likely.",[239,856,857,858,860],{},"Does the replication buffer size significantly influence ",[66,859,292],{}," as a feedback mechanism? Each eviction adds to this buffer, just like normal writes do. If this buffer grows large (possibly partly due to evictions) and then abruptly shrinks (due to the peer consuming it), that would cause a spontaneous large drop in memory usage, ending evictions and instantly reducing memory usage. This is one potential way for evictions to drive a feedback loop.",[12,862,863,864,866,867,872,873,880,881,888],{},"To peek at the values of the ",[66,865,292],{}," calculation (",[36,868,871],{"href":869,"rel":870},"https://gitlab.com/gitlab-com/gl-infra/scalability/uploads/cab2cd03231f8dd4819f77b44d768cb9/redis_snoop.getMaxmemoryState.sha_25a228b839a93a1395907a03f83e1eee448b0f14.production_thresholds.bt",[],"script","), we needed to isolate the ",[36,874,877,878],{"href":875,"rel":876},"https://github.com/redis/redis/blob/6.2.6/src/evict.c#L523",[],"correct call from ",[66,879,87],{}," to the ",[36,882,885],{"href":883,"rel":884},"https://github.com/redis/redis/blob/6.2.6/src/evict.c#L374-L407",[],[66,886,887],{},"getMaxmemoryState"," function and reverse engineer its assembly to find the right instruction and register to instrument for each of the source code level variables that we wanted to capture. From that data we generate histograms for each of the following variables:",[326,890,895],{"className":891,"code":893,"language":894,"meta":331},[892],"language-text","mem_reported = zmalloc_used_memory()        // All used memory tracked by jemalloc\noverhead = freeMemoryGetNotCountedMemory()  // Replication output buffers + AOF buffer\nmem_used = mem_reported - overhead          // Non-exempt used memory\nmem_tofree = mem_used - maxmemory           // Eviction goal\n","text",[66,896,893],{"__ignoreMap":331},[12,898,899,903,904,908,909,912],{},[900,901,902],"em",{},"Caveat:"," Our ",[36,905,907],{"href":869,"rel":906},[],"custom BPF instrumentation"," is specific to this particular build of the ",[66,910,911],{},"redis-server"," binary, since it attaches to virtual addresses that are likely to change the next time Redis is compiled. But the approach is able to be generalized. Treat this as a concrete example of using BPF to inspect source code variables in the middle of a function call without having to rebuild the binary. Because we are peeking at the function’s intermediate state and because the compiler inlined this function call, we needed to do binary analysis to find the correct instrumentation points. In general, peeking at a function’s arguments or return value is easier and more portable, but in this case it would not suffice.",[12,914,915],{},"The results:",[236,917,918,933,936],{},[239,919,920,921,923,924,926,927,929,930,932],{},"Ruled out the first hypothesis: Each call to ",[66,922,87],{}," had a small target value (",[66,925,292],{}," \u003C 2 MB). This means each call to ",[66,928,87],{}," did a small amount of work. Redis’s mysterious rapid drop in memory usage cannot have been caused by an abnormally large ",[66,931,292],{}," target evicting a big batch of keys all at once. Instead, there must be many calls collectively driving down memory usage.",[239,934,935],{},"The replication output buffers remained consistently small, ruling out one of the potential feedback loop mechanisms.",[239,937,938,939,941],{},"Surprisingly, ",[66,940,292],{}," was usually 16 KB to 64 KB, which is larger than a typical cache entry. This size discrepancy hints that cache keys may not be the main source of the memory pressure perpetuating the eviction burst once it begins.",[12,943,944],{},"All of the above results were consistent with the feedback loop hypothesis.",[12,946,947,948,950,951,954,955,267],{},"In addition to answering the initial questions, we got a bonus outcome: Concurrently measuring both ",[66,949,292],{}," and ",[66,952,953],{},"mem_used"," revealed a crucial new fact – ",[900,956,957],{},"the memory reclaim is a completely distinct phase from the eviction burst",[12,959,960],{},"Reframing the pathology as exhibiting separate phases for evictions versus memory reclaim led to a series of realizations, described in the next section. From that emerged a coherent hypothesis explaining all the observed properties of the pathology.",[12,962,963,964,969,970,975,976,267],{},"For more details on this analysis, see ",[36,965,968],{"href":966,"rel":967},"https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/1601#note_982498636",[],"methodology notes",", ",[36,971,974],{"href":972,"rel":973},"https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/1601#note_982499538",[],"build notes"," supporting the disassembly of the Redis binary, and ",[36,977,980],{"href":978,"rel":979},"https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/1601#note_977994182",[],"initial interpretations",[25,982,984],{"id":983},"three-phase-cycle","Three-phase cycle",[12,986,987,988,993],{},"With the above results indicating a distinct separation between the evictions and the memory reclaim, we can now concisely characterize ",[36,989,992],{"href":990,"rel":991},"https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/1601#note_982623949",[],"three phases"," in the cycle of eviction-driven latency spikes.",[12,995,996,998],{},[45,997,47],{}," Diagram (not to scale) comparing memory and CPU usage to request and response rates during each of the three phases",[12,1000,1001],{},[52,1002],{"alt":1003,"src":1004},"Diagram summarizes the text that follows, showing CPU and memory saturate in Phase 2 until request rate drops to match response rate, after which they recover","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782397750/blog/Content%20Images/2022-11-28-diagnosing-redis-latency-spikes-with-bpf-and-friends/06_3_phase_cycle_of_eviction_bursts.png",[12,1006,1007],{},"Phase 1: Not saturated (7-15 minutes)",[236,1009,1010,1016],{},[239,1011,1012,1013,1015],{},"Memory usage is below ",[66,1014,68],{},". No evictions occur during this phase.",[239,1017,1018,1019,1021],{},"Memory usage grows organically until reaching ",[66,1020,68],{},", which starts the next phase.",[12,1023,1024],{},"Phase 2: Saturated memory and CPU (6-8 seconds)",[236,1026,1027,1033,1036],{},[239,1028,1029,1030,1032],{},"When memory usage reaches ",[66,1031,68],{},", evictions begin.",[239,1034,1035],{},"Evictions occur only during this phase, and they occur intermittently and frequently.",[239,1037,1038,1039,1041,1042,1044,1045,267],{},"Demand for memory frequently exceeds free capacity, repeatedly pushing memory usage above ",[66,1040,68],{},". Throughout this phase, memory usage oscillates close to the ",[66,1043,68],{}," threshold, evicting a small amount of memory at a time, just enough to get back under ",[66,1046,68],{},[12,1048,1049],{},"Phase 3: Rapid memory reclaim (30-60 seconds)",[236,1051,1052,1055,1058,1061],{},[239,1053,1054],{},"No evictions occur during this phase.",[239,1056,1057],{},"During this phase, something that had been holding a lot of memory starts quickly and steadily releasing it.",[239,1059,1060],{},"Without the overhead of running evictions, CPU time is again spent mostly on handling requests (starting with the backlog that accumulated during Phase 2).",[239,1062,1063],{},"Memory usage drops rapidly and steadily. By the time this phase ends, hundreds of megabytes have been freed. Afterwards, the cycle restarts with Phase 1.",[12,1065,1066,1067,763],{},"At the transition between Phase 2 and Phase 3, evictions abruptly ended because memory usage stays below the ",[66,1068,68],{},[12,1070,1071],{},"Reaching that transition point where memory pressure becomes negative signals that whatever was driving the memory demand in Phase 2 has started releasing memory faster than it is consuming it, shrinking the footprint it had accumulated during the previous phase.",[12,1073,1074,1075,1078],{},"What is this ",[45,1076,1077],{},"mystery memory consumer"," that bloats its demand during Phase 2 and frees it during Phase 3?",[25,1080,1082],{"id":1081},"the-mystery-revealed","The mystery revealed",[12,1084,1085,1090],{},[36,1086,1089],{"href":1087,"rel":1088},"https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/1601#note_982651298",[],"Modeling the phase transitions"," gave us some useful constraints that a viable hypothesis must satisfy. The mystery memory consumer must:",[236,1092,1093,1096],{},[239,1094,1095],{},"quickly bloat its footprint to hundreds of megabytes on a timescale of less than 10 seconds (the duration of Phase 2), under conditions triggered by the start of an eviction burst",[239,1097,1098],{},"quickly release its accumulated excess on a timescale of just tens of seconds (the duration of Phase 3), under the conditions immediately following an eviction burst",[12,1100,1101,1104],{},[45,1102,1103],{},"The answer:"," The client input/output buffers meet those constraints to be the mystery memory consumer.",[12,1106,1107],{},"Here is how that hypothesis plays out:",[236,1109,1110,1113,1120,1123,1126],{},[239,1111,1112],{},"During Phase 1 (healthy state), the Redis main thread’s CPU usage is already fairly high. At the start of Phase 2, when evictions begin, the eviction overhead saturates the main thread’s CPU capacity, quickly dropping response rate below the incoming request rate.",[239,1114,1115,1116,1119],{},"This throughput mismatch between arrivals versus responses ",[45,1117,1118],{},"is itself the amplifier"," that takes over driving the eviction burst. As the size of that rate gap increases, the proportion of time spent doing evictions also increases.",[239,1121,1122],{},"Accumulating a backlog of requests requires memory, and that backlog continues to grow until enough clients are stalled that the arrival rate drops to match the response rate. As clients stall, the arrival rate falls, and with it the memory pressure, eviction rate, and CPU overhead begin to reduce.",[239,1124,1125],{},"At the equilibrium point when arrival rate falls to match response rate, memory demand is satisfied and evictions stop (ending Phase 2). Without the eviction overhead, more CPU time is available to process the backlog, so response rate increases above request arrival rate. This recovery phase steadily consumes the request backlog, incrementally freeing memory as it goes (Phase 3).",[239,1127,1128],{},"Once the backlog is resolved, the arrival and response rates match again. CPU usage is back to its Phase 1 norm, and memory usage has temporarily dropped in proportion to the max size of Phase 2’s request backlog.",[12,1130,1131,1132,1137],{},"We confirmed this hypothesis via a ",[36,1133,1136],{"href":1134,"rel":1135},"https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/1601#note_987049036",[],"latency injection experiment"," showing that queuing alone explains the pathology. This outcome supports the conclusion that the extra memory demand originates from response rate falling below request arrival rate.",[25,1139,1141],{"id":1140},"remedies-how-to-avoid-entering-the-eviction-burst-cycle","Remedies: How to avoid entering the eviction burst cycle",[12,1143,1144],{},"Now that we understand the dynamics of the pathology, we can draw confident conclusions about viable solutions.",[12,1146,1147],{},"Redis evictions are only self-amplifying when all of the following conditions are present:",[236,1149,1150,1159,1165],{},[239,1151,1152,1155,1156,1158],{},[45,1153,1154],{},"Memory saturation:"," Memory usage reaches the ",[66,1157,68],{}," limit, causing evictions to start.",[239,1160,1161,1164],{},[45,1162,1163],{},"CPU saturation:"," The baseline CPU usage by the Redis main thread’s normal workload is close enough to a whole core that the eviction overhead pushes it to saturation. This reduces the response rate below request arrival rate, inducing self-amplification via increased memory demand for request buffering.",[239,1166,1167,1170],{},[45,1168,1169],{},"Many active clients:"," The saturation only lasts as long as request arrival rate exceeds response rate. Stalled clients no longer contribute to that arrival rate, so the saturation lasts longer and has a greater impact if Redis has many active clients still sending requests.",[12,1172,1173],{},"Viable remedies include:",[236,1175,1176,1208],{},[239,1177,1178,1179,1181,1182],{},"Avoid memory saturation by any combination of the following to make peak memory usage less than the ",[66,1180,68],{}," limit:\n",[236,1183,1184,1187,1202,1205],{},[239,1185,1186],{},"Reduce cache time to live (TTL)",[239,1188,1189,1190,1192,1193,1201],{},"Increase ",[66,1191,68],{}," (and host memory if needed, but watch out for ",[36,1194,1197,1200],{"href":1195,"rel":1196},"https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/1889",[],[66,1198,1199],{},"numa_balancing"," CPU overhead"," on hosts with multiple NUMA nodes)",[239,1203,1204],{},"Adjust client behavior to avoid writing unnecessary cache entries",[239,1206,1207],{},"Split the cache among multiple instances (sharding or functional partitioning, helps avoid both memory and CPU saturation)",[239,1209,1210,1211],{},"Avoid CPU saturation by any combination of the following to make peak CPU usage for the workload plus eviction overhead be less than 1 CPU core:\n",[236,1212,1213,1216,1219,1222,1224,1227],{},[239,1214,1215],{},"Use the fastest processor available for single-threaded instructions per second",[239,1217,1218],{},"Isolate the redis-server process (particularly its main thread) from any other competing CPU-intensive processes (dedicated host, taskset, cpuset)",[239,1220,1221],{},"Adjust client behavior to avoid unnecessary cache lookups or writes",[239,1223,1207],{},[239,1225,1226],{},"Offload work from the Redis main thread (io-threads, lazyfree)",[239,1228,1229],{},"Reduce eviction tenacity (only gives a minor benefit in our experiments)",[12,1231,1232,1233,1235],{},"More exotic potential remedies could include a new Redis feature. One idea is to exempt ephemeral allocations like client buffers from counting towards the ",[66,1234,68],{}," limit, instead applying that limit only to key storage. Alternatively, we could limit evictions to only consume at most a configurable percentage of the main thread’s time, so that most of its time is still spent on request throughput rather than eviction overhead.",[12,1237,1238],{},"Unfortunately, either of those features would trade one failure mode for another, reducing the risk of eviction-driven CPU saturation while increasing the risk of unbounded memory growth at the process level, which could potentially saturate the host or cgroup and lead to an OOM, or out of memory, kill. That trade-off may not be worthwhile, and in any case it is not currently an option.",[25,1240,1242],{"id":1241},"our-solution","Our solution",[12,1244,1245],{},"We had already exhausted the low-hanging fruit for CPU efficiency, so we focused our attention on avoiding memory saturation.",[12,1247,1248,1249,1254,1255,1262],{},"To improve the cache’s memory efficiency, we ",[36,1250,1253],{"href":1251,"rel":1252},"https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/1601#note_990891708",[],"evaluated"," which types of cache keys were using the most space and how much ",[36,1256,1259],{"href":1257,"rel":1258},"https://redis.io/commands/object-idletime/",[],[66,1260,1261],{},"IDLETIME"," they had accrued since last access. This memory usage profile identified some rarely used cache entries (which waste space), helped inform the TTL, or time to live, tuning by first focusing on keys with a high idle time, and highlighted some useful potential cutpoints for functionally partitioning the cache.",[12,1264,72,1265,1270,1271,1276],{},[36,1266,1269],{"href":1267,"rel":1268},"https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/1601#note_1014582669",[],"decided"," to concurrently pursue several cache efficiency improvements and opened an ",[36,1272,1275],{"href":1273,"rel":1274},"https://gitlab.com/groups/gitlab-com/gl-infra/-/epics/764",[],"epic"," for it. The goal was to avoid chronic memory saturation, and the main action items were:",[236,1278,1279,1288,1297],{},[239,1280,1281,1282,1287],{},"Iteratively reduce the cache’s ",[36,1283,1286],{"href":1284,"rel":1285},"https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/1854",[],"default TTL"," from 2 weeks to 8 hours (helped a lot!)",[239,1289,1290,1291,1296],{},"Switch to ",[36,1292,1295],{"href":1293,"rel":1294},"https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/1601#note_1026821730",[],"client-side caching"," for certain cache keys (efficiently avoids spending shared cache space on non-shared cache entries)",[239,1298,1299,1304],{},[36,1300,1303],{"href":1301,"rel":1302},"https://gitlab.com/groups/gitlab-com/gl-infra/-/epics/762",[],"Partition"," a set of cache keys to a separate Redis instance",[12,1306,1307],{},"The TTL reduction was the simplest solution and turned out to be a big win. One of our main concerns with TTL reduction was that the additional cache misses could potentially increase workload on other parts of the infrastructure. Some cache misses are more expensive than others, and our metrics are not granular enough to quantify the cost of cache misses per type of cache entry. This concern is why we applied the TTL adjustment incrementally and monitored for SLO violations. Fortunately, our inference was correct: Reducing TTL did not significantly reduce the cache hit rate, and the additional cache misses did not cause noticeable impact to downstream subsystems.",[12,1309,1310],{},"The TTL reduction turned out to be sufficient to drop memory usage consistently a little below its saturation point.",[12,1312,1313,1314,1316,1317,1322,1323,267],{},"Increasing ",[66,1315,68],{}," had initially not been feasible because the original peak memory demand (prior to the efficiency improvements) was expected to be larger than the max size of the VMs we use for Redis. However, once we dropped memory demand below saturation, then we could confidently ",[36,1318,1321],{"href":1319,"rel":1320},"https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/1868",[],"provision headroom"," for future growth and re-enable ",[36,1324,1327],{"href":1325,"rel":1326},"https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/1883",[],"saturation alerting",[25,1329,1331],{"id":1330},"results","Results",[12,1333,1334],{},"The following graph shows Redis memory usage transitioning out of its chronically saturated state, with annotations describing the milestones when latency spikes ended and when saturation margin became wide enough to be considered safe:",[12,1336,1337],{},[52,1338],{"alt":1339,"src":1340},"Redis memory usage stops showing a flat top saturation","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782397750/blog/Content%20Images/2022-11-28-diagnosing-redis-latency-spikes-with-bpf-and-friends/07_epic_results__memory_saturation_avoided_by_TTL_reductions.png",[12,1342,1343],{},"Zooming into the days when we rolled out the TTL adjustments, we can see the harmful eviction-driven latency spikes vanish as we drop memory usage below its saturation point, exactly as predicted:",[12,1345,1346],{},[52,1347],{"alt":1348,"src":1349},"Redis memory usage starts as a flat line and then falls below that saturation line","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782397749/blog/Content%20Images/2022-11-28-diagnosing-redis-latency-spikes-with-bpf-and-friends/08_results__redis_memory_usage_stops_saturating.png",[12,1351,1352],{},[52,1353],{"alt":1354,"src":1355},"Redis response time spikes stop occurring at the exact point when memory stops being saturated","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782397750/blog/Content%20Images/2022-11-28-diagnosing-redis-latency-spikes-with-bpf-and-friends/09_results__redis_latency_spikes_end.png",[12,1357,1358],{},"These eviction-driven latency spikes had been the biggest cause of slowess in Redis cache.",[12,1360,1361],{},"Solving this source of slowness significantly improved the user experience. This 1-year lookback shows only the long-tail portion of the improvement, not even the full benefit.  Each weekday had roughly 2 million Redis requests slower than 1 second, until our fix in mid-August:",[12,1363,1364],{},[52,1365],{"alt":1366,"src":1367},"Graph of the daily count of Redis cache requests slower than 1 second, showing roughly 2 million slow requests per day on weekdays until mid-August, when the TTL adjustments were applied","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782397750/blog/Content%20Images/2022-11-28-diagnosing-redis-latency-spikes-with-bpf-and-friends/10_results__1_year_retrospective_of_slow_redis_requests_per_day.png",[25,1369,1371],{"id":1370},"conclusions","Conclusions",[12,1373,1374],{},"We solved a long-standing latency problem that had been worsening as the workload grew, and we learned a lot along the way. This article focuses mostly on the Redis discoveries, since those are general behaviors that some of you may encounter in your travels. We also developed some novel tools and analytical methods and uncovered several useful environment-specific facts about our workload, infrastructure, and observability, leading to several additional improvements and proposals not mentioned above.",[12,1376,1377],{},"Overall, we made several efficiency improvements and broke the cycle that was driving the pathology. Memory demand now stays well below the saturation point, eliminating the latency spikes that were burning error budgets for the development teams and causing intermittent slowness for users. All stakeholders are happy, and we came away with deeper domain knowledge and sharper skills!",[25,1379,1381],{"id":1380},"key-insights-summary","Key insights summary",[12,1383,1384],{},"The following notes summarize what we learned about Redis eviction behavior (current as of version 6.2):",[236,1386,1387,1396,1402,1405,1408,1425],{},[239,1388,1389,1390,1392,1393,1395],{},"The same memory budget (",[66,1391,68],{},") is shared by key storage and client connection buffers. A spike in demand for client connection buffers counts towards the ",[66,1394,68],{}," limit, in the same way that a spike in key inserts or key size would.",[239,1397,1398,1399,1401],{},"Redis performs evictions in the foreground on its main thread. All time spent in ",[66,1400,87],{}," is time not spent handling client requests. Consequently, during an eviction burst, Redis has a lower throughput ceiling.",[239,1403,1404],{},"If eviction overhead saturates the main thread’s CPU, then response rate falls below request arrival rate. Redis accumulates a request backlog (which consumes memory), and clients experience this as slowness.",[239,1406,1407],{},"The memory used for pending requests requires more evictions, driving the eviction burst until enough clients are stalled that arrival rate falls back below response rate. At that equilibrium point, evictions stop, eviction overhead vanishes, Redis rapidly handles its request backlog, and that backlog’s memory gets freed.",[239,1409,1410,1411],{},"Triggering this cycle requires all of the following:\n",[236,1412,1413,1419,1422],{},[239,1414,1415,1416,1418],{},"Redis is configured with a ",[66,1417,68],{}," limit, and its memory demand exceeds that size. This memory saturation causes evictions to begin.",[239,1420,1421],{},"Redis main thread’s CPU utilization is high enough under its normal workload that having to also perform evictions drives it to CPU saturation. This reduces response rate below request rate, causing a growing request backlog and high latency.",[239,1423,1424],{},"Many active clients are connected. The duration of the eviction burst and the size of memory spent on client connection buffers increases proportionally to the number of active clients.",[239,1426,1427],{},"Prevent this cycle by avoiding either memory or CPU saturation. In our case, avoiding memory saturation was easier (mainly by reducing cache TTL).",[25,1429,1431],{"id":1430},"further-reading","Further reading",[12,1433,1434],{},"The following lists summarize the analytical tools and methods cited in this article. These tools are all highly versatile and any of them can provide a massive level-up when working on performance engineering problems.",[12,1436,1437],{},"Tools:",[236,1439,1440,1452,1462,1472],{},[239,1441,1442,1445,1446,1448,1449,1451],{},[36,1443,173],{"href":169,"rel":1444},[]," - A Linux performance analysis multitool. In this article, we used ",[66,1447,173],{}," as a sampling profiler, capturing periodic stack traces of the ",[66,1450,911],{}," process's main thread when it is actively running on a CPU.",[239,1453,1454,1458,1459,1461],{},[36,1455,1457],{"href":185,"rel":1456},[],"Flamescope"," - A visualization tool for rendering a ",[66,1460,173],{}," profile (and other formats) into an interactive subsecond heat map. This tool invites the user to explore the timeline for microbursts of activity or inactivity and render flamegraphs of those interesting timespans to explore what code paths were active.",[239,1463,1464,1468,1469,1471],{},[36,1465,1467],{"href":312,"rel":1466},[],"BCC"," - BCC is a framework for building BPF tools, and it ships with many useful tools out of the box. In this article, we used ",[66,1470,308],{}," to measure the call durations of a specific Redis function and render the results as a histogram.",[239,1473,1474,1477,1478,1481,1482,1487,1488,1495,1496,1498,1499,1503],{},[36,1475,810],{"href":833,"rel":1476},[]," - Another BPF framework, ideal for answering ad-hoc questions about your system's behavior. It uses an ",[66,1479,1480],{},"awk","-like syntax and is ",[36,1483,1486],{"href":1484,"rel":1485},"https://github.com/iovisor/bpftrace#readme",[],"quick to learn",". In this article, we wrote a ",[36,1489,1491,1492,1494],{"href":869,"rel":1490},[],"custom ",[66,1493,810],{}," script"," for observing the variables used in computing how much memory to free during each round of evictions. This script's instrumentation points are specific to our particular build of ",[66,1497,911],{},", but the ",[36,1500,1502],{"href":966,"rel":1501},[],"approach is able to be generalized"," and illustrates how versatile this tool can be.",[12,1505,1506],{},"Usage examples:",[236,1508,1509,1521,1532,1551,1571],{},[239,1510,1511,1515,1516,950,1518,1520],{},[36,1512,1514],{"href":264,"rel":1513},[],"Example"," - Walkthrough of using ",[66,1517,173],{},[66,1519,189],{}," to capture, filter, and visualize the stack sampling CPU profiles of the Redis main thread.",[239,1522,1523,1526,1527,1529,1530,267],{},[36,1524,1514],{"href":673,"rel":1525},[]," - Walkthrough (including safety check) of using ",[66,1528,308],{}," to measure the durations of the frequent calls to function ",[66,1531,87],{},[239,1533,1534,1537,1538,950,1540,1542,1543,969,1545,969,1547,1550],{},[36,1535,1514],{"href":725,"rel":1536},[]," - Experiment for adjusting Redis settings ",[66,1539,746],{},[66,1541,736],{}," and observing the results using ",[66,1544,173],{},[66,1546,308],{},[66,1548,1549],{},"funcslower",", and the Redis metrics for eviction count and memory usage.",[239,1552,1553,1556,1557,1559,1560,1562,1563,1565,1566,1570],{},[36,1554,1514],{"href":966,"rel":1555},[]," - This is a working example (script included) of using ",[66,1558,810],{}," to observe the values of a function's variables. In this case we inspected the ",[66,1561,292],{}," calculation at the start of ",[66,1564,87],{},". Also, these ",[36,1567,1569],{"href":972,"rel":1568},[],"companion notes"," discuss some build-specific considerations.",[239,1572,1573,1576],{},[36,1574,1514],{"href":1134,"rel":1575},[]," - Describes the latency injection experiment (the first of the three ideas). This experiment confirmed that memory demand increases at the predicted rate when we slow response rate to below request arrival rate, in the same way evictions do. This result confirmed the request queuing itself is the source of the memory pressure that amplifies the eviction burst once it begins.",[1578,1579,1580],"style",{},"html pre.shiki code .s7eDp, html code.shiki .s7eDp{--shiki-default:#6F42C1}html pre.shiki code .sYBdl, html code.shiki .sYBdl{--shiki-default:#032F62}html pre.shiki code .sYu0t, html code.shiki .sYu0t{--shiki-default:#005CC5}html pre.shiki code .sgsFI, html code.shiki .sgsFI{--shiki-default:#24292E}html pre.shiki code .sD7c4, html code.shiki .sD7c4{--shiki-default:#D73A49}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":331,"searchDepth":393,"depth":393,"links":1582},[1583,1584,1585,1586,1588,1589,1590,1592,1593,1594,1595,1596,1597,1598,1599],{"id":27,"depth":393,"text":28},{"id":100,"depth":393,"text":101},{"id":156,"depth":393,"text":157},{"id":270,"depth":393,"text":1587},"How fast are individual calls to performEvictions?",{"id":690,"depth":393,"text":691},{"id":756,"depth":393,"text":757},{"id":803,"depth":393,"text":1591},"Observing the mem_tofree calculation with bpftrace",{"id":983,"depth":393,"text":984},{"id":1081,"depth":393,"text":1082},{"id":1140,"depth":393,"text":1141},{"id":1241,"depth":393,"text":1242},{"id":1330,"depth":393,"text":1331},{"id":1370,"depth":393,"text":1371},{"id":1380,"depth":393,"text":1381},{"id":1430,"depth":393,"text":1431},"engineering","2022-11-28","How we uncovered a three-phase cycle involving two distinct saturation points and a simple fix to break that cycle.","md",null,false,"https://res.cloudinary.com/about-gitlab-com/image/upload/v1749667913/Blog/Hero%20Images/clocks.jpg",{},true,"/en-us/blog/how-we-diagnosed-and-resolved-redis-latency-spikes",{"title":5,"description":1602,"ogTitle":5,"ogDescription":1602,"noIndex":1605,"ogImage":1606,"ogUrl":1611,"ogSiteName":1612,"ogType":1613,"canonicalUrls":1611},"https://about.gitlab.com/blog/how-we-diagnosed-and-resolved-redis-latency-spikes","https://about.gitlab.com","article","how-we-diagnosed-and-resolved-redis-latency-spikes","en-us/blog/how-we-diagnosed-and-resolved-redis-latency-spikes",[1617,1618,1619],"performance","tutorial","DevOps","BlogPost","1m1vcFCXNdItpDFOkkhVp54wmk4XM36lIYOCJdXouTY",{"logo":1623,"freeTrial":1628,"sales":1633,"login":1638,"items":1643,"search":1970,"minimal":2001,"duo":2020,"switchNav":2029,"pricingDeployment":2040},{"config":1624},{"href":1625,"dataGaName":1626,"dataGaLocation":1627},"/","gitlab logo","header",{"text":1629,"config":1630},"Get free trial",{"href":1631,"dataGaName":1632,"dataGaLocation":1627},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com&glm_content=default-saas-trial/","free trial",{"text":1634,"config":1635},"Request a demo",{"href":1636,"dataGaName":1637,"dataGaLocation":1627},"/sales/?contact-topic=request-demo","sales",{"text":1639,"config":1640},"Sign in",{"href":1641,"dataGaName":1642,"dataGaLocation":1627},"https://gitlab.com/users/sign_in/","sign in",[1644,1673,1773,1778,1892,1948],{"text":1645,"config":1646,"menu":1648},"Platform",{"dataNavLevelOne":1647},"platform",{"type":1649,"columns":1650},"cards",[1651,1657,1665],{"title":1645,"description":1652,"link":1653},"The intelligent orchestration platform for DevSecOps",{"text":1654,"config":1655},"Explore our Platform",{"href":1656,"dataGaName":1647,"dataGaLocation":1627},"/platform/",{"title":1658,"description":1659,"link":1660},"GitLab Duo Agent Platform","Agentic AI for the entire software lifecycle",{"text":1661,"config":1662},"Meet GitLab Duo",{"href":1663,"dataGaName":1664,"dataGaLocation":1627},"/gitlab-duo-agent-platform/","gitlab duo agent platform",{"title":1666,"description":1667,"link":1668},"Why GitLab","See the top reasons enterprises choose GitLab",{"text":1669,"config":1670},"Learn more",{"href":1671,"dataGaName":1672,"dataGaLocation":1627},"/why-gitlab/","why gitlab",{"text":1674,"left":1608,"config":1675,"menu":1677},"Product",{"dataNavLevelOne":1676},"solutions",{"type":1678,"link":1679,"columns":1683,"feature":1752},"lists",{"text":1680,"config":1681},"View all Solutions",{"href":1682,"dataGaName":1676,"dataGaLocation":1627},"/solutions/",[1684,1708,1731],{"title":1685,"description":1686,"link":1687,"items":1692},"Automation","CI/CD and automation to accelerate deployment",{"config":1688},{"icon":1689,"href":1690,"dataGaName":1691,"dataGaLocation":1627},"AutomatedCodeAlt","/solutions/delivery-automation/","automated software delivery",[1693,1697,1700,1704],{"text":1694,"config":1695},"CI/CD",{"href":1696,"dataGaLocation":1627,"dataGaName":1694},"/solutions/continuous-integration/",{"text":1658,"config":1698},{"href":1663,"dataGaLocation":1627,"dataGaName":1699},"gitlab duo agent platform - product menu",{"text":1701,"config":1702},"Source Code Management",{"href":1703,"dataGaLocation":1627,"dataGaName":1701},"/solutions/source-code-management/",{"text":1705,"config":1706},"Automated Software Delivery",{"href":1690,"dataGaLocation":1627,"dataGaName":1707},"Automated software delivery",{"title":1709,"description":1710,"link":1711,"items":1716},"Security","Deliver code faster without compromising security",{"config":1712},{"href":1713,"dataGaName":1714,"dataGaLocation":1627,"icon":1715},"/solutions/application-security-testing/","security and compliance","ShieldCheckLight",[1717,1721,1726],{"text":1718,"config":1719},"Application Security Testing",{"href":1713,"dataGaName":1720,"dataGaLocation":1627},"Application security testing",{"text":1722,"config":1723},"Software Supply Chain Security",{"href":1724,"dataGaLocation":1627,"dataGaName":1725},"/solutions/supply-chain/","Software supply chain security",{"text":1727,"config":1728},"Software Compliance",{"href":1729,"dataGaName":1730,"dataGaLocation":1627},"/solutions/software-compliance/","software compliance",{"title":1732,"link":1733,"items":1738},"Measurement",{"config":1734},{"icon":1735,"href":1736,"dataGaName":1737,"dataGaLocation":1627},"DigitalTransformation","/solutions/visibility-measurement/","visibility and measurement",[1739,1743,1747],{"text":1740,"config":1741},"Visibility & Measurement",{"href":1736,"dataGaLocation":1627,"dataGaName":1742},"Visibility and Measurement",{"text":1744,"config":1745},"Value Stream Management",{"href":1746,"dataGaLocation":1627,"dataGaName":1744},"/solutions/value-stream-management/",{"text":1748,"config":1749},"Analytics & Insights",{"href":1750,"dataGaLocation":1627,"dataGaName":1751},"/solutions/analytics-and-insights/","Analytics and insights",{"title":1753,"type":1678,"items":1754},"GitLab for",[1755,1761,1767],{"text":1756,"config":1757},"Enterprise",{"icon":1758,"href":1759,"dataGaLocation":1627,"dataGaName":1760},"Building","/enterprise/","enterprise",{"text":1762,"config":1763},"Small Business",{"icon":1764,"href":1765,"dataGaLocation":1627,"dataGaName":1766},"Work","/small-business/","small business",{"text":1768,"config":1769},"Public Sector",{"icon":1770,"href":1771,"dataGaLocation":1627,"dataGaName":1772},"Organization","/solutions/public-sector/","public sector",{"text":1774,"config":1775},"Pricing",{"href":1776,"dataGaName":1777,"dataGaLocation":1627,"dataNavLevelOne":1777},"/pricing/","pricing",{"text":1779,"config":1780,"menu":1782},"Resources",{"dataNavLevelOne":1781},"resources",{"type":1678,"link":1783,"columns":1787,"feature":1881},{"text":1784,"config":1785},"View all resources",{"href":1786,"dataGaName":1781,"dataGaLocation":1627},"/resources/",[1788,1821,1848],{"title":1789,"items":1790},"Getting started",[1791,1796,1801,1806,1811,1816],{"text":1792,"config":1793},"Install",{"href":1794,"dataGaName":1795,"dataGaLocation":1627},"/install/","install",{"text":1797,"config":1798},"Quick start guides",{"href":1799,"dataGaName":1800,"dataGaLocation":1627},"/get-started/","quick setup checklists",{"text":1802,"config":1803},"Learn",{"href":1804,"dataGaLocation":1627,"dataGaName":1805},"https://university.gitlab.com/","learn",{"text":1807,"config":1808},"Product documentation",{"href":1809,"dataGaName":1810,"dataGaLocation":1627},"https://docs.gitlab.com/","product documentation",{"text":1812,"config":1813},"Best practice videos",{"href":1814,"dataGaName":1815,"dataGaLocation":1627},"/getting-started-videos/","best practice videos",{"text":1817,"config":1818},"Integrations",{"href":1819,"dataGaName":1820,"dataGaLocation":1627},"/integrations/","integrations",{"title":1822,"items":1823},"Discover",[1824,1829,1834,1839,1843],{"text":1825,"config":1826},"Customer success stories",{"href":1827,"dataGaName":1828,"dataGaLocation":1627},"/customers/","customer success stories",{"text":1830,"config":1831},"Blog",{"href":1832,"dataGaName":1833,"dataGaLocation":1627},"/blog/","blog",{"text":1835,"config":1836},"Demo Hub",{"href":1837,"dataGaName":1838,"dataGaLocation":1627},"/demo-hub/","demo hub",{"text":1840,"config":1841},"The Source",{"href":1842,"dataGaName":1833,"dataGaLocation":1627},"/the-source/",{"text":1844,"config":1845},"Remote",{"href":1846,"dataGaName":1847,"dataGaLocation":1627},"https://handbook.gitlab.com/handbook/company/culture/all-remote/","remote",{"title":1849,"items":1850},"Connect",[1851,1856,1861,1866,1871,1876],{"text":1852,"config":1853},"GitLab Services",{"href":1854,"dataGaName":1855,"dataGaLocation":1627},"/services/","services",{"text":1857,"config":1858},"Contribute",{"href":1859,"dataGaName":1860,"dataGaLocation":1627},"https://contributors.gitlab.com","contribute",{"text":1862,"config":1863},"Community",{"href":1864,"dataGaName":1865,"dataGaLocation":1627},"/community/","community",{"text":1867,"config":1868},"Forum",{"href":1869,"dataGaName":1870,"dataGaLocation":1627},"https://forum.gitlab.com/","forum",{"text":1872,"config":1873},"Events",{"href":1874,"dataGaName":1875,"dataGaLocation":1627},"/events/","events",{"text":1877,"config":1878},"Partners",{"href":1879,"dataGaName":1880,"dataGaLocation":1627},"/partners/","partners",{"config":1882,"title":1885,"text":1886,"link":1887},{"background":1883,"textColor":1884},"url('https://res.cloudinary.com/about-gitlab-com/image/upload/v1777322348/qpq8yrgn8knii57omj0c.png')","#000","What’s new in GitLab","Stay updated with our latest features and improvements.",{"text":1888,"config":1889},"Read the latest",{"href":1890,"dataGaName":1891,"dataGaLocation":1627},"/whats-new/","whats new",{"text":1893,"config":1894,"menu":1896},"Company",{"dataNavLevelOne":1895},"company",{"type":1678,"columns":1897},[1898],{"items":1899},[1900,1905,1911,1913,1918,1923,1928,1933,1938,1943],{"text":1901,"config":1902},"About",{"href":1903,"dataGaName":1904,"dataGaLocation":1627},"/company/","about",{"text":1906,"config":1907,"footerGa":1910},"Jobs",{"href":1908,"dataGaName":1909,"dataGaLocation":1627},"/jobs/","jobs",{"dataGaName":1909},{"text":1872,"config":1912},{"href":1874,"dataGaName":1875,"dataGaLocation":1627},{"text":1914,"config":1915},"Leadership",{"href":1916,"dataGaName":1917,"dataGaLocation":1627},"/company/team/e-group/","leadership",{"text":1919,"config":1920},"Handbook",{"href":1921,"dataGaName":1922,"dataGaLocation":1627},"https://handbook.gitlab.com/","handbook",{"text":1924,"config":1925},"Investor relations",{"href":1926,"dataGaName":1927,"dataGaLocation":1627},"https://ir.gitlab.com/overview/default.aspx","investor relations",{"text":1929,"config":1930},"Trust Center",{"href":1931,"dataGaName":1932,"dataGaLocation":1627},"/security/","trust center",{"text":1934,"config":1935},"AI Transparency Center",{"href":1936,"dataGaName":1937,"dataGaLocation":1627},"/ai-transparency-center/","ai transparency center",{"text":1939,"config":1940},"Newsletter",{"href":1941,"dataGaName":1942,"dataGaLocation":1627},"/company/contact/#contact-forms","newsletter",{"text":1944,"config":1945},"Press",{"href":1946,"dataGaName":1947,"dataGaLocation":1627},"/press/","press",{"text":1949,"config":1950,"menu":1951},"Contact us",{"dataNavLevelOne":1895},{"type":1678,"columns":1952},[1953],{"items":1954},[1955,1960,1965],{"text":1956,"config":1957},"Talk to sales",{"href":1958,"dataGaName":1959,"dataGaLocation":1627},"/sales/","talk to sales",{"text":1961,"config":1962},"Support portal",{"href":1963,"dataGaName":1964,"dataGaLocation":1627},"https://support.gitlab.com/hc/en-us","support portal",{"text":1966,"config":1967},"Customer portal",{"href":1968,"dataGaName":1969,"dataGaLocation":1627},"https://customers.gitlab.com/customers/sign_in/","customer portal",{"close":1971,"login":1972,"suggestions":1979},"Close",{"text":1973,"link":1974},"To search repositories and projects, login to",{"text":1975,"config":1976},"gitlab.com",{"href":1641,"dataGaName":1977,"dataGaLocation":1978},"search login","search",{"text":1980,"default":1981},"Suggestions",[1982,1984,1988,1990,1994,1998],{"text":1658,"config":1983},{"href":1663,"dataGaName":1658,"dataGaLocation":1978},{"text":1985,"config":1986},"Code Suggestions (AI)",{"href":1987,"dataGaName":1985,"dataGaLocation":1978},"/solutions/code-suggestions/",{"text":1694,"config":1989},{"href":1696,"dataGaName":1694,"dataGaLocation":1978},{"text":1991,"config":1992},"GitLab on AWS",{"href":1993,"dataGaName":1991,"dataGaLocation":1978},"/partners/technology-partners/aws/",{"text":1995,"config":1996},"GitLab on Google Cloud",{"href":1997,"dataGaName":1995,"dataGaLocation":1978},"/partners/technology-partners/google-cloud-platform/",{"text":1999,"config":2000},"Why GitLab?",{"href":1671,"dataGaName":1999,"dataGaLocation":1978},{"freeTrial":2002,"mobileIcon":2007,"desktopIcon":2012,"secondaryButton":2015},{"text":2003,"config":2004},"Start free trial",{"href":2005,"dataGaName":1632,"dataGaLocation":2006},"https://gitlab.com/-/trials/new/","nav",{"altText":2008,"config":2009},"Gitlab Icon",{"src":2010,"dataGaName":2011,"dataGaLocation":2006},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203874/jypbw1jx72aexsoohd7x.svg","gitlab icon",{"altText":2008,"config":2013},{"src":2014,"dataGaName":2011,"dataGaLocation":2006},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203875/gs4c8p8opsgvflgkswz9.svg",{"text":2016,"config":2017},"Get Started",{"href":2018,"dataGaName":2019,"dataGaLocation":2006},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com/get-started/","get started",{"freeTrial":2021,"mobileIcon":2025,"desktopIcon":2027},{"text":2022,"config":2023},"Learn more about GitLab Duo",{"href":1663,"dataGaName":2024,"dataGaLocation":2006},"gitlab duo",{"altText":2008,"config":2026},{"src":2010,"dataGaName":2011,"dataGaLocation":2006},{"altText":2008,"config":2028},{"src":2014,"dataGaName":2011,"dataGaLocation":2006},{"button":2030,"mobileIcon":2035,"desktopIcon":2037},{"text":2031,"config":2032},"/switch",{"href":2033,"dataGaName":2034,"dataGaLocation":2006},"#contact","switch",{"altText":2008,"config":2036},{"src":2010,"dataGaName":2011,"dataGaLocation":2006},{"altText":2008,"config":2038},{"src":2039,"dataGaName":2011,"dataGaLocation":2006},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1773335277/ohhpiuoxoldryzrnhfrh.png",{"freeTrial":2041,"mobileIcon":2046,"desktopIcon":2048},{"text":2042,"config":2043},"Back to pricing",{"href":1776,"dataGaName":2044,"dataGaLocation":2006,"icon":2045},"back to pricing","GoBack",{"altText":2008,"config":2047},{"src":2010,"dataGaName":2011,"dataGaLocation":2006},{"altText":2008,"config":2049},{"src":2014,"dataGaName":2011,"dataGaLocation":2006},{"title":2051,"titleMobile":2052,"button":2053,"config":2058},"Duo Agent Platform delivers 400% ROI, per new Forrester Consulting study.","400% ROI: Forrester TEI for GitLab Duo",{"text":1669,"config":2054},{"href":2055,"dataGaName":2056,"dataGaLocation":2057},"https://about.gitlab.com/blog/gitlab-duo-agent-platform-delivers-400-percent-roi/","forrester-tei-dap-banner","global-banner",{"layout":2059,"disabled":1605},"release",{"data":2061},{"text":2062,"source":2063,"edit":2069,"contribute":2074,"config":2079,"items":2084,"minimal":2293},"Git is a trademark of Software Freedom Conservancy and our use of 'GitLab' is under license",{"text":2064,"config":2065},"View page source",{"href":2066,"dataGaName":2067,"dataGaLocation":2068},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/","page source","footer",{"text":2070,"config":2071},"Edit this page",{"href":2072,"dataGaName":2073,"dataGaLocation":2068},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/content/","web ide",{"text":2075,"config":2076},"Please contribute",{"href":2077,"dataGaName":2078,"dataGaLocation":2068},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/CONTRIBUTING.md/","please contribute",{"twitter":2080,"facebook":2081,"youtube":2082,"linkedin":2083},"https://twitter.com/gitlab","https://www.facebook.com/gitlab","https://www.youtube.com/channel/UCnMGQ8QHMAnVIsI3xJrihhg","https://www.linkedin.com/company/gitlab-com",[2085,2132,2185,2229,2261],{"title":1774,"links":2086,"subMenu":2101},[2087,2091,2096],{"text":2088,"config":2089},"View plans",{"href":1776,"dataGaName":2090,"dataGaLocation":2068},"view plans",{"text":2092,"config":2093},"Why Premium?",{"href":2094,"dataGaName":2095,"dataGaLocation":2068},"/pricing/premium/","why premium",{"text":2097,"config":2098},"Why Ultimate?",{"href":2099,"dataGaName":2100,"dataGaLocation":2068},"/pricing/ultimate/","why ultimate",[2102],{"title":2103,"links":2104},"Contact Us",[2105,2108,2110,2112,2117,2122,2127],{"text":2106,"config":2107},"Contact sales",{"href":1958,"dataGaName":1637,"dataGaLocation":2068},{"text":1961,"config":2109},{"href":1963,"dataGaName":1964,"dataGaLocation":2068},{"text":1966,"config":2111},{"href":1968,"dataGaName":1969,"dataGaLocation":2068},{"text":2113,"config":2114},"Status",{"href":2115,"dataGaName":2116,"dataGaLocation":2068},"https://status.gitlab.com/","status",{"text":2118,"config":2119},"Terms of use",{"href":2120,"dataGaName":2121,"dataGaLocation":2068},"/terms/","terms of use",{"text":2123,"config":2124},"Privacy statement",{"href":2125,"dataGaName":2126,"dataGaLocation":2068},"/privacy/","privacy statement",{"text":2128,"config":2129},"Cookie preferences",{"dataGaName":2130,"dataGaLocation":2068,"id":2131,"isOneTrustButton":1608},"cookie preferences","ot-sdk-btn",{"title":1674,"links":2133,"subMenu":2142},[2134,2138],{"text":2135,"config":2136},"DevSecOps platform",{"href":1656,"dataGaName":2137,"dataGaLocation":2068},"devsecops platform",{"text":2139,"config":2140},"AI-Assisted Development",{"href":1663,"dataGaName":2141,"dataGaLocation":2068},"ai-assisted development",[2143],{"title":2144,"links":2145},"Topics",[2146,2151,2156,2160,2165,2170,2175,2180],{"text":2147,"config":2148},"CICD",{"href":2149,"dataGaName":2150,"dataGaLocation":2068},"/topics/ci-cd/","cicd",{"text":2152,"config":2153},"GitOps",{"href":2154,"dataGaName":2155,"dataGaLocation":2068},"/topics/gitops/","gitops",{"text":1619,"config":2157},{"href":2158,"dataGaName":2159,"dataGaLocation":2068},"/topics/devops/","devops",{"text":2161,"config":2162},"Version Control",{"href":2163,"dataGaName":2164,"dataGaLocation":2068},"/topics/version-control/","version control",{"text":2166,"config":2167},"DevSecOps",{"href":2168,"dataGaName":2169,"dataGaLocation":2068},"/topics/devsecops/","devsecops",{"text":2171,"config":2172},"Cloud Native",{"href":2173,"dataGaName":2174,"dataGaLocation":2068},"/topics/cloud-native/","cloud native",{"text":2176,"config":2177},"AI for Coding",{"href":2178,"dataGaName":2179,"dataGaLocation":2068},"/topics/devops/ai-for-coding/","ai for coding",{"text":2181,"config":2182},"Agentic AI",{"href":2183,"dataGaName":2184,"dataGaLocation":2068},"/topics/agentic-ai/","agentic ai",{"title":2186,"links":2187},"Solutions",[2188,2190,2192,2197,2201,2204,2208,2211,2213,2216,2219,2224],{"text":1718,"config":2189},{"href":1713,"dataGaName":1718,"dataGaLocation":2068},{"text":1707,"config":2191},{"href":1690,"dataGaName":1691,"dataGaLocation":2068},{"text":2193,"config":2194},"Agile development",{"href":2195,"dataGaName":2196,"dataGaLocation":2068},"/solutions/agile-delivery/","agile delivery",{"text":2198,"config":2199},"SCM",{"href":1703,"dataGaName":2200,"dataGaLocation":2068},"source code management",{"text":2147,"config":2202},{"href":1696,"dataGaName":2203,"dataGaLocation":2068},"continuous integration & delivery",{"text":2205,"config":2206},"Value stream management",{"href":1746,"dataGaName":2207,"dataGaLocation":2068},"value stream management",{"text":2152,"config":2209},{"href":2210,"dataGaName":2155,"dataGaLocation":2068},"/solutions/gitops/",{"text":1756,"config":2212},{"href":1759,"dataGaName":1760,"dataGaLocation":2068},{"text":2214,"config":2215},"Small business",{"href":1765,"dataGaName":1766,"dataGaLocation":2068},{"text":2217,"config":2218},"Public sector",{"href":1771,"dataGaName":1772,"dataGaLocation":2068},{"text":2220,"config":2221},"Education",{"href":2222,"dataGaName":2223,"dataGaLocation":2068},"/solutions/education/","education",{"text":2225,"config":2226},"Financial services",{"href":2227,"dataGaName":2228,"dataGaLocation":2068},"/solutions/finance/","financial services",{"title":1779,"links":2230},[2231,2233,2235,2237,2240,2242,2245,2247,2249,2251,2253,2255,2257,2259],{"text":1792,"config":2232},{"href":1794,"dataGaName":1795,"dataGaLocation":2068},{"text":1797,"config":2234},{"href":1799,"dataGaName":1800,"dataGaLocation":2068},{"text":1802,"config":2236},{"href":1804,"dataGaName":1805,"dataGaLocation":2068},{"text":1807,"config":2238},{"href":1809,"dataGaName":2239,"dataGaLocation":2068},"docs",{"text":1830,"config":2241},{"href":1832,"dataGaName":1833,"dataGaLocation":2068},{"text":2243,"config":2244},"What's new",{"href":1890,"dataGaName":1891,"dataGaLocation":2068},{"text":1825,"config":2246},{"href":1827,"dataGaName":1828,"dataGaLocation":2068},{"text":1844,"config":2248},{"href":1846,"dataGaName":1847,"dataGaLocation":2068},{"text":1852,"config":2250},{"href":1854,"dataGaName":1855,"dataGaLocation":2068},{"text":1857,"config":2252},{"href":1859,"dataGaName":1860,"dataGaLocation":2068},{"text":1862,"config":2254},{"href":1864,"dataGaName":1865,"dataGaLocation":2068},{"text":1867,"config":2256},{"href":1869,"dataGaName":1870,"dataGaLocation":2068},{"text":1872,"config":2258},{"href":1874,"dataGaName":1875,"dataGaLocation":2068},{"text":1877,"config":2260},{"href":1879,"dataGaName":1880,"dataGaLocation":2068},{"title":1893,"links":2262},[2263,2265,2267,2269,2271,2273,2277,2282,2284,2286,2288],{"text":1901,"config":2264},{"href":1903,"dataGaName":1895,"dataGaLocation":2068},{"text":1906,"config":2266},{"href":1908,"dataGaName":1909,"dataGaLocation":2068},{"text":1914,"config":2268},{"href":1916,"dataGaName":1917,"dataGaLocation":2068},{"text":1919,"config":2270},{"href":1921,"dataGaName":1922,"dataGaLocation":2068},{"text":1924,"config":2272},{"href":1926,"dataGaName":1927,"dataGaLocation":2068},{"text":2274,"config":2275},"Sustainability",{"href":2276,"dataGaName":2274,"dataGaLocation":2068},"/sustainability/",{"text":2278,"config":2279},"Diversity, inclusion and belonging (DIB)",{"href":2280,"dataGaName":2281,"dataGaLocation":2068},"/diversity-inclusion-belonging/","Diversity, inclusion and belonging",{"text":1929,"config":2283},{"href":1931,"dataGaName":1932,"dataGaLocation":2068},{"text":1939,"config":2285},{"href":1941,"dataGaName":1942,"dataGaLocation":2068},{"text":1944,"config":2287},{"href":1946,"dataGaName":1947,"dataGaLocation":2068},{"text":2289,"config":2290},"Modern Slavery Transparency Statement",{"href":2291,"dataGaName":2292,"dataGaLocation":2068},"https://handbook.gitlab.com/handbook/legal/modern-slavery-act-transparency-statement/","modern slavery transparency statement",{"items":2294},[2295,2298,2301],{"text":2296,"config":2297},"Terms",{"href":2120,"dataGaName":2121,"dataGaLocation":2068},{"text":2299,"config":2300},"Cookies",{"dataGaName":2130,"dataGaLocation":2068,"id":2131,"isOneTrustButton":1608},{"text":2302,"config":2303},"Privacy",{"href":2125,"dataGaName":2126,"dataGaLocation":2068},[2305],{"id":2306,"title":7,"body":1604,"config":2307,"content":2309,"description":1604,"extension":2313,"meta":2314,"navigation":1608,"path":2315,"seo":2316,"stem":2317,"__hash__":2318},"blogAuthors/en-us/blog/authors/matt-smiley.yml",{"template":2308},"BlogAuthor",{"name":7,"config":2310},{"headshot":2311,"ctfId":2312},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1749682529/Blog/Author%20Headshots/msmiley-headshot.jpg","msmiley","yml",{},"/en-us/blog/authors/matt-smiley",{},"en-us/blog/authors/matt-smiley","GaLE5OWIxcwOcDqqFiyWy1UVdAIZeYkr1nYOINaNM8E",[2320,2329,2337],{"title":2321,"description":2322,"heroImage":2323,"category":1600,"date":2324,"authors":2325,"slug":2328,"externalUrl":1604},"Confidential AI for GitLab Self-Hosted","Give developers AI coding agents in GitLab Duo without source code leaving a hardware-encrypted boundary — no GPUs needed.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1773866173/vte9qh8rriznvyclhkes.png","2026-08-06",[2326,2327],"Mathias Ewald","Martin Paloncy, Edgeless Systems","confidential-ai-for-gitlab-self-hosted",{"title":2330,"description":2331,"heroImage":2332,"category":1600,"date":2333,"authors":2334,"slug":2336,"externalUrl":1604},"Green DevOps: Why carbon measurement belongs in your CI/CD pipeline","CI/CD pipelines have a hidden carbon cost. Here's why measuring it matters, and how you can get started with Eco CI and Carmen in GitLab.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1765809212/noh0mdfn9o94ry9ykura.png","2026-07-09",[2335],"Lysanne Pinto","green-devops-carbon-measurement-cicd-pipeline",{"title":2338,"description":2339,"heroImage":2340,"category":1600,"date":2341,"authors":2342,"slug":2344,"externalUrl":1604},"How to build CI/CD observability at scale","This practical guide to GitLab pipeline analytics helps self-managed users gain operational insights using Prometheus and Grafana.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1774465167/n5hlvrsrheadeccyr1oz.png","2026-04-28",[2343],"Paul Meresanu","how-to-build-ci-cd-observability-at-scale",{"promotions":2346},[2347,2361,2373,2385],{"id":2348,"categories":2349,"header":2351,"text":2352,"button":2353,"image":2358},"ai-modernization",[2350],"ai","Is AI achieving its promise at scale?","Quiz will take 5 minutes or less",{"text":2354,"config":2355},"Get your AI maturity score",{"href":2356,"dataGaName":2357,"dataGaLocation":1833},"/assessments/ai-modernization-assessment/","modernization assessment",{"config":2359},{"src":2360},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138786/qix0m7kwnd8x2fh1zq49.png",{"id":2362,"categories":2363,"header":2365,"text":2352,"button":2366,"image":2370},"devops-modernization",[2364,2169],"product","Are you just managing tools or shipping innovation?",{"text":2367,"config":2368},"Get your DevOps maturity score",{"href":2369,"dataGaName":2357,"dataGaLocation":1833},"/assessments/devops-modernization-assessment/",{"config":2371},{"src":2372},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138785/eg818fmakweyuznttgid.png",{"id":2374,"categories":2375,"header":2377,"text":2352,"button":2378,"image":2382},"security-modernization",[2376],"security","Are you trading speed for security?",{"text":2379,"config":2380},"Get your security maturity score",{"href":2381,"dataGaName":2357,"dataGaLocation":1833},"/assessments/security-modernization-assessment/",{"config":2383},{"src":2384},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138786/p4pbqd9nnjejg5ds6mdk.png",{"id":2386,"paths":2387,"header":2390,"text":2391,"button":2392,"image":2397},"github-azure-migration",[2388,2389],"migration-from-azure-devops-to-gitlab","integrating-azure-devops-scm-and-gitlab","Is your team ready for GitHub's Azure move?","GitHub is already rebuilding around Azure. Find out what it means for you.",{"text":2393,"config":2394},"See how GitLab compares to GitHub",{"href":2395,"dataGaName":2396,"dataGaLocation":1833},"/compare/gitlab-vs-github/github-azure-migration/","github azure migration",{"config":2398},{"src":2372},{"header":2400,"blurb":2401,"button":2402,"secondaryButton":2407},"Start building faster today","See what your team can do with the intelligent orchestration platform for DevSecOps.\n",{"text":2403,"config":2404},"Get your free trial",{"href":2405,"dataGaName":1632,"dataGaLocation":2406},"https://gitlab.com/-/trial_registrations/new?glm_content=default-saas-trial&glm_source=about.gitlab.com/","feature",{"text":2106,"config":2408},{"href":1958,"dataGaName":1637,"dataGaLocation":2406},1786803762623]