[{"data":1,"prerenderedAt":1346},["ShallowReactive",2],{"/blog/scaling-our-use-of-sidekiq":3,"navigation-en-us":558,"banner-en-us":986,"footer-en-us":996,"blog-post-authors-en-us-Rachel Nienaber":1241,"blog-related-posts-en-us-scaling-our-use-of-sidekiq":1256,"blog-promotions-en-us":1282,"next-steps-en-us":1336},{"id":4,"title":5,"authors":6,"body":8,"category":536,"date":537,"description":538,"extension":539,"externalUrl":540,"faq":540,"featured":541,"heroImage":542,"meta":543,"navigation":544,"path":545,"seo":546,"slug":550,"stem":551,"tags":552,"template":556,"updatedDate":540,"__hash__":557},"blogPosts/en-us/blog/scaling-our-use-of-sidekiq.md","How we scaled async workload processing at GitLab.com using Sidekiq",[7],"Rachel Nienaber",{"type":9,"value":10,"toc":525},"minimark",[11,16,27,30,60,63,90,93,108,111,123,130,133,138,141,144,172,175,182,185,189,194,203,206,255,258,284,287,291,306,309,312,315,318,358,366,370,387,390,393,399,402,406,419,425,428,449,453,456,459,468,476,479,488,493,499,505,511],[12,13,15],"h2",{"id":14},"sidekiq-at-gitlab","Sidekiq at GitLab",[17,18,19,20,26],"p",{},"GitLab is a Ruby-on-Rails application that processes a lot of data. Much of this processing can be done asynchronously,\nand one of the solutions we use to accomplish this is ",[21,22,25],"a",{"href":23,"rel":24},"https://github.com/mperham/sidekiq/wiki",[],"Sidekiq"," which is a background-processing\nframework for Ruby. It handles jobs that are better processed asynchronously outside the web request/response cycle.",[17,28,29],{},"There are a few terms that that we'll use in this post:",[31,32,33,42,48,54],"ul",{},[34,35,36,37,41],"li",{},"A ",[38,39,40],"strong",{},"worker class"," is a class defined in our application to process a task in Sidekiq.",[34,43,36,44,47],{},[38,45,46],{},"job"," is an instance of a worker class, so each job represents a single task.",[34,49,36,50,53],{},[38,51,52],{},"queue"," is a collection of jobs (potentially for different worker classes) that are waiting to be processed.",[34,55,36,56,59],{},[38,57,58],{},"worker thread"," is a thread processing jobs in particular queues. Each Sidekiq process can have multiple worker threads.",[17,61,62],{},"Then there are two terms specific to GitLab.com:",[31,64,65,84],{},[34,66,36,67,70,71,75,76,79,80,83],{},[38,68,69],{},"Sidekiq role"," is a configuration for a particular group of queues. For instance, we might have a ",[72,73,74],"code",{},"push_actions"," role that is for processing the ",[72,77,78],{},"post_receive"," and ",[72,81,82],{},"process_commit"," queues.",[34,85,36,86,89],{},[38,87,88],{},"Sidekiq node"," is an instance of the GitLab application for a Sidekiq role. A Sidekiq node can have multiple Sidekiq processes.",[17,91,92],{},"Back in 2013, in version 6.3 of GitLab, every Sidekiq worker class had its own queue. We weren't strict in monitoring the creation of\nnew worker classes. There was no strategic plan for assigning queues to where they would execute.",[17,94,95,96,99,100,103,104,107],{},"In 2016, we tried to introduce order again, and rearranged the queues to be based on features. We followed this with a change in\n2017 to have a dedicated queue for each worker class again, and we were able to monitor queues more accurately and impose specific\nthrottles and limits to each. It was easy to quickly make decisions about the queues as they were running because of how\nthe work was distributed. The queues were grouped, and the names of these groups were ",[72,97,98],{},"realtime",", ",[72,101,102],{},"asap",", and ",[72,105,106],{},"besteffort"," for example.",[17,109,110],{},"At the time, we knew that this was not the approach recommended by the author of Sidekiq, Mike Perham, but we felt that we knew what\nthe trade-offs were. In fact, Mike wrote:",[112,113,114],"blockquote",{},[17,115,116,117,122],{},"“I don't recommend having more than a handful of queues. Lots of queues makes for a more complex\nsystem ",[21,118,121],{"href":119,"rel":120},"https://github.com/antirez/redis/issues/1785",[],"and Sidekiq Pro cannot reliably"," handle multiple queues without\npolling. M Sidekiq Pro processes polling N queues means O(M*N) operations per second slamming Redis.”",[17,124,125,126],{},"From ",[21,127,128],{"href":128,"rel":129},"https://github.com/mperham/sidekiq/wiki/Advanced-Options#queues",[],[17,131,132],{},"This served us well for nearly two years before this approach no longer matched our scaling needs.",[134,135,137],"h3",{"id":136},"pressure-from-availability-issues","Pressure from availability issues",[17,139,140],{},"In mid-2019 GitLab.com experienced several different major incidents related to the way we\nprocess background queues.",[17,142,143],{},"Examples of these incidents:",[31,145,146,154,163],{},[34,147,148,153],{},[21,149,152],{"href":150,"rel":151},"https://gitlab.com/gitlab-com/gl-infra/infrastructure/issues/7479",[],"Gitaly n+1 calls caused bad latency and resulted in the Sidekiq queues growing",".\nThis was due to the way we processed tags in Gitaly.",[34,155,156,157,162],{},"A user generated many notes on a single commit which ",[21,158,161],{"href":159,"rel":160},"https://gitlab.com/gitlab-com/gl-infra/production/issues/1028",[],"slowed down the new_note Sidekiq queue","\nand led to a delay of sending out notifications.",[34,164,165,166,171],{},"CI jobs took very long to complete because ",[21,167,170],{"href":168,"rel":169},"https://gitlab.com/gitlab-com/gl-infra/infrastructure/-/issues/7402",[],"jobs in the pipeline_processing:pipeline_process Sidekiq queue piled up",".\n2 pipelines caused a high amount of Sidekiq jobs, Sidekiq pipeline nodes were maxing out their CPU, pipeline_processing\njobs were causing many SQL calls and the pgbouncer pool for Sidekiq was becoming saturated.",[17,173,174],{},"All of these were showing that we needed to take action.",[17,176,177],{},[178,179],"img",{"alt":180,"src":181},"Sidekiq throughput per job","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782399027/blog/Content%20Images/sidekiq_throughput_per_job.png",[17,183,184],{},"This image shows how many jobs we process per second over a 24 hour period. This shows the variety of jobs and\ngives an idea of the scale of jobs in relation to each other.",[134,186,188],{"id":187},"improvements","Improvements",[190,191,193],"h4",{"id":192},"changing-the-relationship-between-jobs-and-sidekiq-roles","Changing the relationship between jobs and Sidekiq roles",[17,195,196,197,202],{},"In ",[21,198,201],{"href":199,"rel":200},"https://gitlab.com/gitlab-com/gl-infra/infrastructure/issues/7219",[],"infrastructure#7219 (closed)"," we significantly\naltered our approach for how jobs were related to Sidekiq roles.",[17,204,205],{},"We started from a position where:",[207,208,209,230,241],"ol",{},[34,210,211,212],{},"We had a many-to-many relationship between Sidekiq jobs and Sidekiq roles.\n",[207,213,214,220],{},[34,215,216,217,219],{},"For example, most pipeline jobs ran on the ",[72,218,106],{}," nodes, but some ran on the pipeline nodes.",[34,221,222,223,99,225,79,227,229],{},"Some jobs ran on up to three types of node: eg ",[72,224,98],{},[72,226,102],{},[72,228,106],{}," priorities.",[34,231,232,233],{},"Worker threads were reserved for single queues.\n",[207,234,235],{},[34,236,237,238,240],{},"For example, one eighth of the ",[72,239,98],{}," queue might be reserved for new_note jobs. In the event of a glut of\nnew_note jobs, most of the fleet would sit idle while one worker thread would be saturated. Worse, adding more nodes would\nonly increase processing power by 1/8th of a node, not the full compute capacity of the new node.",[34,242,243,244],{},"Urgent and non-urgent jobs would be in the same queue.\n",[207,245,246,252],{},[34,247,248,249,251],{},"For example, some jobs in the ",[72,250,98],{}," queue would take up to 10 minutes to process.",[34,253,254],{},"This is a bit like allowing overloaded trolleys in the 10 items-or-less lane.",[17,256,257],{},"Once the issue was completed, we now had:",[207,259,260,268,276],{},[34,261,262,263],{},"A one-to-one relationship between Sidekiq jobs and Sidekiq roles\n",[207,264,265],{},[34,266,267],{},"Each job will execute on exactly one Sidekiq role",[34,269,270,271],{},"All worker threads will run all jobs, and each Sidekiq node will have the same number of worker threads\n",[207,272,273],{},[34,274,275],{},"When a glut of jobs comes in, 100% of compute on a node can be dedicated to executing the jobs",[34,277,278,279],{},"Slow jobs and fast jobs are kept apart\n",[207,280,281],{},[34,282,283],{},"The 10 items or less lane is now being enforced.",[17,285,286],{},"While this was a significant improvement, it introduced some technical debt. We fixed everything for a moment in time,\nknowing that as soon as the application changed this would be out of date, and as time went on, would only get more out\nof date until we were back in the same position. To try and mitigate this in future, we started to look at classifying\nthe workloads and using queue selectors.",[190,288,290],{"id":289},"queue-selectors-deployed-in-sidekiq-cluster","Queue Selectors Deployed in Sidekiq Cluster",[17,292,293,294,299,300,305],{},"In the\n",[21,295,298],{"href":296,"rel":297},"https://gitlab.com/groups/gitlab-com/gl-infra/-/epics/96",[],"Background Processing Improvements Epic",", we looked at ways\nthat we could simplify the structure so that background processing could be in a position to scale to 100x the traffic\nat the time. We also needed the processing to be unsurprising. Operators (and developers) should understand where a job\nwill run, why it is queueing up and how to reduce queues. We decided to move to using ",[21,301,304],{"href":302,"rel":303},"https://docs.gitlab.com/administration/sidekiq/extra_sidekiq_processes/",[],"queue selectors","\nto help us to keep the queue definitions correct. (This approach is still experimental).",[17,307,308],{},"In addition, the infrastructure team should not reactively (and manually) route Sidekiq jobs to priority fleets, as\nwas the situation previously. Developers should have the ability to specify the requirements of their workloads and\nhave these automatically processed on a queue designed to support that type of work.",[17,310,311],{},"Sidekiq processes can be configured to select specific queues for processing. Instead of making this selection by name,\nwe wanted to make the selection on how the workload for that queue was classified.",[17,313,314],{},"We came up with an approach for classifying background jobs by their workload and building a sustainable way of grouping\nsimilar workloads together.",[17,316,317],{},"When a new job is created, developers need to do this to classify the workload. This is done through",[31,319,320,342,350],{},[34,321,322,323,328,329,99,332,79,335,338,339,341],{},"Specifying the ",[21,324,327],{"href":325,"rel":326},"https://docs.gitlab.com/development/sidekiq/",[],"urgency of the job",". The options\nare ",[72,330,331],{},"high",[72,333,334],{},"low",[72,336,337],{},"none",". If the delay of a job would have user impact, then the job is ",[72,340,331],{}," urgency.",[34,343,344,345,349],{},"Noting if the ",[21,346,348],{"href":325,"rel":347},[],"job has external dependencies","\nthat could impact their availability. (For example, if they communicate with user-specified Kubernetes clusters).",[34,351,352,353,357],{},"Adding an ",[21,354,356],{"href":325,"rel":355},[],"annotation declaring if the worker class will be cpu-bound or memory-bound",". Knowing\nthis allows us to make decisions around how much thread concurrency a Ruby process can tolerate, or targeting memory-bound\njobs to low-concurrency, high-memory nodes.",[17,359,360,361,365],{},"There is additional guidance available to ",[21,362,364],{"href":325,"rel":363},[],"determine if the worker class should be marked as cpu-bound",".",[190,367,369],{"id":368},"slas-are-based-on-these-attributes","SLAs are based on these attributes",[207,371,372,375,378,381,384],{},[34,373,374],{},"High urgency jobs should not queue for more than 10 seconds.",[34,376,377],{},"High urgency jobs should not take more than 10 seconds to execute (this SLA is the responsibility of the owning team to ensure that high throughput is maintained).",[34,379,380],{},"Low urgency jobs should not queue for more than 1 minute.",[34,382,383],{},"Jobs without urgency have no queue SLA.",[34,385,386],{},"Non-high urgency jobs should not take more than 5 minutes to execute.",[17,388,389],{},"In each case, the queuing SLAs are the responsibility of the infrastructure team, as they need to ensure that the fleet is\ncorrectly provisioned to meet the SLA.",[17,391,392],{},"The execution latency SLAs are the responsibility of the development team owning the worker class, as they need to ensure that the\nworker class is sufficiently performant to ensure throughput.",[17,394,395],{},[178,396],{"alt":397,"src":398},"Sidekiq certain queues spike","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782399027/blog/Content%20Images/sidekiq_authorized_projects.png",[17,400,401],{},"This image shows the challenges we faced by having jobs of different urgency running on the same queue.\nThe purple lines show spikes from one particular worker, where many jobs were added to the queue,\ncausing delays to other jobs which were often of equal or higher importance.",[134,403,405],{"id":404},"challenge-during-rollout-brpop","Challenge during rollout - BRPOP",[17,407,408,409,412,413,418],{},"As the number of background queues in the GitLab application grows, this approach continues to burden our Sidekiq Redis\nservers. On GitLab.com, our ",[72,410,411],{},"catchall"," Sidekiq nodes monitor about 200 queues, and the Redis ",[21,414,417],{"href":415,"rel":416},"https://redis.io/commands/brpop",[],"BRPOP","\ncommands used to monitor the queues consume a significant amount of time (by Redis latency standards).",[17,420,421,422,424],{},"The number of clients listening made this problem worse. For ",[72,423,106],{}," we had 7 nodes, each running 8 processes,\nwith 15 threads watching those queues - meaning 840 clients.",[17,426,427],{},"The command causing the problem was BRPOP. The time taken to perform this command also relates\nto the number of listeners on those keys. The addition of multiple keys increases contention in the system which causes\nlots of connections to block. And when the key list is longer the problem gets worse. The keylist represents the number of\nqueues, the more queues we have, the more keys we are listening to. We saw this problem on the nodes that process the most queues.",[17,429,430,431,436,437,442,443,448],{},"We raised an issue in the Redis issue tracker about the ",[21,432,435],{"href":433,"rel":434},"https://github.com/antirez/redis/issues/7071",[],"performance we observed when many clients performed BRPOP on the\nsame key",". It was fantastic when ",[21,438,441],{"href":439,"rel":440},"https://github.com/antirez",[],"Salvatore","\nresponded within the hour and the patch was available the same day!  This fix was made in Redis 6 and backported to Redis 5.\n",[21,444,447],{"href":445,"rel":446},"https://gitlab.com/gitlab-org/omnibus-gitlab/-/merge_requests/4126",[],"Omnibus has also been upgraded to use this fix",", and it will\nbe available in the major release 13.0.",[134,450,452],{"id":451},"current-state-as-of-june-2020","Current State (as of June 2020)",[17,454,455],{},"Migrating to these new selectors has been completed as of late April 2020.",[17,457,458],{},"We reduced our Sidekiq fleet from 49 nodes with 314 CPUs, to 26 nodes with 158 CPUs. This has also reduced our cost.\nThe average utilization is more evenly spread across the new fleets.",[17,460,461,462,467],{},"Also, we have ",[21,463,466],{"href":464,"rel":465},"https://gitlab.com/groups/gitlab-com/gl-infra/-/epics/181",[],"moved Sidekiq-cluster to Core",". Previously, running\nSidekiq in clustered mode (i.e. spawning more than one process) was\ntechnically only available as part of GitLab EE distributions, and for self-managed environments only in the Starter+ tiers.\nBecause of that, when booting Sidekiq up in a development env with the GDK, the least common denominator was assumed,\nwhich was to run Sidekiq in a single-process setup. That can be a problem, because it means there is a divergence between\nthe environment developers work on, and what will actually run in production (i.e. gitlab.com and higher-tier self-managed environments).",[17,469,196,470,475],{},[21,471,474],{"href":472,"rel":473},"https://docs.gitlab.com/releases/",[],"release 13.0"," Sidekiq Cluster is used by default.",[17,477,478],{},"We’re also better placed to migrate to Kubernetes.  The selector approach is a lot more compatible with making good\ndecisions about things like CPU allocations + limits for Kubernetes workloads, and this will make the job of our delivery\nteam easier, leading to further cost reductions from auto-scaling deployed resources to match actual load.",[17,480,481,482,487],{},"Our next piece of work with Sidekiq will be to ",[21,483,486],{"href":484,"rel":485},"https://gitlab.com/groups/gitlab-com/gl-infra/-/epics/194",[],"reduce the number of queues that we need to watch","\nand we will post a follow-up to this blog post when the work is completed.",[17,489,490],{},[38,491,492],{},"Read more about infrastructure issues:",[17,494,495],{},[21,496,498],{"href":497},"/blog/directed-acyclic-graph/","Faster pipelines with DAG",[17,500,501],{},[21,502,504],{"href":503},"/blog/best-practices-for-kubernetes-runners/","Keep Kubernetes runners moving",[17,506,507],{},[21,508,510],{"href":509},"/blog/parent-child-pipelines/","Understand parent-child pipelines",[17,512,513,514,519,520],{},"Cover image by ",[21,515,518],{"href":516,"rel":517},"https://unsplash.com/@z734923105",[],"Jerry Zhang"," on ",[21,521,524],{"href":522,"rel":523},"https://www.unsplash.com",[],"Unsplash",{"title":526,"searchDepth":527,"depth":527,"links":528},"",2,[529],{"id":14,"depth":527,"text":15,"children":530},[531,533,534,535],{"id":136,"depth":532,"text":137},3,{"id":187,"depth":532,"text":188},{"id":404,"depth":532,"text":405},{"id":451,"depth":532,"text":452},"engineering","2020-06-24","Sidekiq was a great tool for async processing until it couldn't keep up. Here's how we made it scale.","md",null,false,"https://res.cloudinary.com/about-gitlab-com/image/upload/v1749667068/Blog/Hero%20Images/sidekiqmountain.jpg",{},true,"/en-us/blog/scaling-our-use-of-sidekiq",{"title":5,"description":538,"ogTitle":5,"ogDescription":538,"noIndex":541,"ogImage":542,"ogUrl":547,"ogSiteName":548,"ogType":549,"canonicalUrls":547},"https://about.gitlab.com/blog/scaling-our-use-of-sidekiq","https://about.gitlab.com","article","scaling-our-use-of-sidekiq","en-us/blog/scaling-our-use-of-sidekiq",[553,554,555],"inside GitLab","performance","workflow","BlogPost","owsrDSfI4MKht_A8UBgxDOlA8bmSIa_LU7FMg3u29sE",{"logo":559,"freeTrial":564,"sales":569,"login":574,"items":579,"search":906,"minimal":937,"duo":956,"switchNav":965,"pricingDeployment":976},{"config":560},{"href":561,"dataGaName":562,"dataGaLocation":563},"/","gitlab logo","header",{"text":565,"config":566},"Get free trial",{"href":567,"dataGaName":568,"dataGaLocation":563},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com&glm_content=default-saas-trial/","free trial",{"text":570,"config":571},"Request a demo",{"href":572,"dataGaName":573,"dataGaLocation":563},"/sales/?contact-topic=request-demo","sales",{"text":575,"config":576},"Sign in",{"href":577,"dataGaName":578,"dataGaLocation":563},"https://gitlab.com/users/sign_in/","sign in",[580,609,709,714,828,884],{"text":581,"config":582,"menu":584},"Platform",{"dataNavLevelOne":583},"platform",{"type":585,"columns":586},"cards",[587,593,601],{"title":581,"description":588,"link":589},"The intelligent orchestration platform for DevSecOps",{"text":590,"config":591},"Explore our Platform",{"href":592,"dataGaName":583,"dataGaLocation":563},"/platform/",{"title":594,"description":595,"link":596},"GitLab Duo Agent Platform","Agentic AI for the entire software lifecycle",{"text":597,"config":598},"Meet GitLab Duo",{"href":599,"dataGaName":600,"dataGaLocation":563},"/gitlab-duo-agent-platform/","gitlab duo agent platform",{"title":602,"description":603,"link":604},"Why GitLab","See the top reasons enterprises choose GitLab",{"text":605,"config":606},"Learn more",{"href":607,"dataGaName":608,"dataGaLocation":563},"/why-gitlab/","why gitlab",{"text":610,"left":544,"config":611,"menu":613},"Product",{"dataNavLevelOne":612},"solutions",{"type":614,"link":615,"columns":619,"feature":688},"lists",{"text":616,"config":617},"View all Solutions",{"href":618,"dataGaName":612,"dataGaLocation":563},"/solutions/",[620,644,667],{"title":621,"description":622,"link":623,"items":628},"Automation","CI/CD and automation to accelerate deployment",{"config":624},{"icon":625,"href":626,"dataGaName":627,"dataGaLocation":563},"AutomatedCodeAlt","/solutions/delivery-automation/","automated software delivery",[629,633,636,640],{"text":630,"config":631},"CI/CD",{"href":632,"dataGaLocation":563,"dataGaName":630},"/solutions/continuous-integration/",{"text":594,"config":634},{"href":599,"dataGaLocation":563,"dataGaName":635},"gitlab duo agent platform - product menu",{"text":637,"config":638},"Source Code Management",{"href":639,"dataGaLocation":563,"dataGaName":637},"/solutions/source-code-management/",{"text":641,"config":642},"Automated Software Delivery",{"href":626,"dataGaLocation":563,"dataGaName":643},"Automated software delivery",{"title":645,"description":646,"link":647,"items":652},"Security","Deliver code faster without compromising security",{"config":648},{"href":649,"dataGaName":650,"dataGaLocation":563,"icon":651},"/solutions/application-security-testing/","security and compliance","ShieldCheckLight",[653,657,662],{"text":654,"config":655},"Application Security Testing",{"href":649,"dataGaName":656,"dataGaLocation":563},"Application security testing",{"text":658,"config":659},"Software Supply Chain Security",{"href":660,"dataGaLocation":563,"dataGaName":661},"/solutions/supply-chain/","Software supply chain security",{"text":663,"config":664},"Software Compliance",{"href":665,"dataGaName":666,"dataGaLocation":563},"/solutions/software-compliance/","software compliance",{"title":668,"link":669,"items":674},"Measurement",{"config":670},{"icon":671,"href":672,"dataGaName":673,"dataGaLocation":563},"DigitalTransformation","/solutions/visibility-measurement/","visibility and measurement",[675,679,683],{"text":676,"config":677},"Visibility & Measurement",{"href":672,"dataGaLocation":563,"dataGaName":678},"Visibility and Measurement",{"text":680,"config":681},"Value Stream Management",{"href":682,"dataGaLocation":563,"dataGaName":680},"/solutions/value-stream-management/",{"text":684,"config":685},"Analytics & Insights",{"href":686,"dataGaLocation":563,"dataGaName":687},"/solutions/analytics-and-insights/","Analytics and insights",{"title":689,"type":614,"items":690},"GitLab for",[691,697,703],{"text":692,"config":693},"Enterprise",{"icon":694,"href":695,"dataGaLocation":563,"dataGaName":696},"Building","/enterprise/","enterprise",{"text":698,"config":699},"Small Business",{"icon":700,"href":701,"dataGaLocation":563,"dataGaName":702},"Work","/small-business/","small business",{"text":704,"config":705},"Public Sector",{"icon":706,"href":707,"dataGaLocation":563,"dataGaName":708},"Organization","/solutions/public-sector/","public sector",{"text":710,"config":711},"Pricing",{"href":712,"dataGaName":713,"dataGaLocation":563,"dataNavLevelOne":713},"/pricing/","pricing",{"text":715,"config":716,"menu":718},"Resources",{"dataNavLevelOne":717},"resources",{"type":614,"link":719,"columns":723,"feature":817},{"text":720,"config":721},"View all resources",{"href":722,"dataGaName":717,"dataGaLocation":563},"/resources/",[724,757,784],{"title":725,"items":726},"Getting started",[727,732,737,742,747,752],{"text":728,"config":729},"Install",{"href":730,"dataGaName":731,"dataGaLocation":563},"/install/","install",{"text":733,"config":734},"Quick start guides",{"href":735,"dataGaName":736,"dataGaLocation":563},"/get-started/","quick setup checklists",{"text":738,"config":739},"Learn",{"href":740,"dataGaLocation":563,"dataGaName":741},"https://university.gitlab.com/","learn",{"text":743,"config":744},"Product documentation",{"href":745,"dataGaName":746,"dataGaLocation":563},"https://docs.gitlab.com/","product documentation",{"text":748,"config":749},"Best practice videos",{"href":750,"dataGaName":751,"dataGaLocation":563},"/getting-started-videos/","best practice videos",{"text":753,"config":754},"Integrations",{"href":755,"dataGaName":756,"dataGaLocation":563},"/integrations/","integrations",{"title":758,"items":759},"Discover",[760,765,770,775,779],{"text":761,"config":762},"Customer success stories",{"href":763,"dataGaName":764,"dataGaLocation":563},"/customers/","customer success stories",{"text":766,"config":767},"Blog",{"href":768,"dataGaName":769,"dataGaLocation":563},"/blog/","blog",{"text":771,"config":772},"Demo Hub",{"href":773,"dataGaName":774,"dataGaLocation":563},"/demo-hub/","demo hub",{"text":776,"config":777},"The Source",{"href":778,"dataGaName":769,"dataGaLocation":563},"/the-source/",{"text":780,"config":781},"Remote",{"href":782,"dataGaName":783,"dataGaLocation":563},"https://handbook.gitlab.com/handbook/company/culture/all-remote/","remote",{"title":785,"items":786},"Connect",[787,792,797,802,807,812],{"text":788,"config":789},"GitLab Services",{"href":790,"dataGaName":791,"dataGaLocation":563},"/services/","services",{"text":793,"config":794},"Contribute",{"href":795,"dataGaName":796,"dataGaLocation":563},"https://contributors.gitlab.com","contribute",{"text":798,"config":799},"Community",{"href":800,"dataGaName":801,"dataGaLocation":563},"/community/","community",{"text":803,"config":804},"Forum",{"href":805,"dataGaName":806,"dataGaLocation":563},"https://forum.gitlab.com/","forum",{"text":808,"config":809},"Events",{"href":810,"dataGaName":811,"dataGaLocation":563},"/events/","events",{"text":813,"config":814},"Partners",{"href":815,"dataGaName":816,"dataGaLocation":563},"/partners/","partners",{"config":818,"title":821,"text":822,"link":823},{"background":819,"textColor":820},"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":824,"config":825},"Read the latest",{"href":826,"dataGaName":827,"dataGaLocation":563},"/whats-new/","whats new",{"text":829,"config":830,"menu":832},"Company",{"dataNavLevelOne":831},"company",{"type":614,"columns":833},[834],{"items":835},[836,841,847,849,854,859,864,869,874,879],{"text":837,"config":838},"About",{"href":839,"dataGaName":840,"dataGaLocation":563},"/company/","about",{"text":842,"config":843,"footerGa":846},"Jobs",{"href":844,"dataGaName":845,"dataGaLocation":563},"/jobs/","jobs",{"dataGaName":845},{"text":808,"config":848},{"href":810,"dataGaName":811,"dataGaLocation":563},{"text":850,"config":851},"Leadership",{"href":852,"dataGaName":853,"dataGaLocation":563},"/company/team/e-group/","leadership",{"text":855,"config":856},"Handbook",{"href":857,"dataGaName":858,"dataGaLocation":563},"https://handbook.gitlab.com/","handbook",{"text":860,"config":861},"Investor relations",{"href":862,"dataGaName":863,"dataGaLocation":563},"https://ir.gitlab.com/overview/default.aspx","investor relations",{"text":865,"config":866},"Trust Center",{"href":867,"dataGaName":868,"dataGaLocation":563},"/security/","trust center",{"text":870,"config":871},"AI Transparency Center",{"href":872,"dataGaName":873,"dataGaLocation":563},"/ai-transparency-center/","ai transparency center",{"text":875,"config":876},"Newsletter",{"href":877,"dataGaName":878,"dataGaLocation":563},"/company/contact/#contact-forms","newsletter",{"text":880,"config":881},"Press",{"href":882,"dataGaName":883,"dataGaLocation":563},"/press/","press",{"text":885,"config":886,"menu":887},"Contact us",{"dataNavLevelOne":831},{"type":614,"columns":888},[889],{"items":890},[891,896,901],{"text":892,"config":893},"Talk to sales",{"href":894,"dataGaName":895,"dataGaLocation":563},"/sales/","talk to sales",{"text":897,"config":898},"Support portal",{"href":899,"dataGaName":900,"dataGaLocation":563},"https://support.gitlab.com/hc/en-us","support portal",{"text":902,"config":903},"Customer portal",{"href":904,"dataGaName":905,"dataGaLocation":563},"https://customers.gitlab.com/customers/sign_in/","customer portal",{"close":907,"login":908,"suggestions":915},"Close",{"text":909,"link":910},"To search repositories and projects, login to",{"text":911,"config":912},"gitlab.com",{"href":577,"dataGaName":913,"dataGaLocation":914},"search login","search",{"text":916,"default":917},"Suggestions",[918,920,924,926,930,934],{"text":594,"config":919},{"href":599,"dataGaName":594,"dataGaLocation":914},{"text":921,"config":922},"Code Suggestions (AI)",{"href":923,"dataGaName":921,"dataGaLocation":914},"/solutions/code-suggestions/",{"text":630,"config":925},{"href":632,"dataGaName":630,"dataGaLocation":914},{"text":927,"config":928},"GitLab on AWS",{"href":929,"dataGaName":927,"dataGaLocation":914},"/partners/technology-partners/aws/",{"text":931,"config":932},"GitLab on Google Cloud",{"href":933,"dataGaName":931,"dataGaLocation":914},"/partners/technology-partners/google-cloud-platform/",{"text":935,"config":936},"Why GitLab?",{"href":607,"dataGaName":935,"dataGaLocation":914},{"freeTrial":938,"mobileIcon":943,"desktopIcon":948,"secondaryButton":951},{"text":939,"config":940},"Start free trial",{"href":941,"dataGaName":568,"dataGaLocation":942},"https://gitlab.com/-/trials/new/","nav",{"altText":944,"config":945},"Gitlab Icon",{"src":946,"dataGaName":947,"dataGaLocation":942},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203874/jypbw1jx72aexsoohd7x.svg","gitlab icon",{"altText":944,"config":949},{"src":950,"dataGaName":947,"dataGaLocation":942},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203875/gs4c8p8opsgvflgkswz9.svg",{"text":952,"config":953},"Get Started",{"href":954,"dataGaName":955,"dataGaLocation":942},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com/get-started/","get started",{"freeTrial":957,"mobileIcon":961,"desktopIcon":963},{"text":958,"config":959},"Learn more about GitLab Duo",{"href":599,"dataGaName":960,"dataGaLocation":942},"gitlab duo",{"altText":944,"config":962},{"src":946,"dataGaName":947,"dataGaLocation":942},{"altText":944,"config":964},{"src":950,"dataGaName":947,"dataGaLocation":942},{"button":966,"mobileIcon":971,"desktopIcon":973},{"text":967,"config":968},"/switch",{"href":969,"dataGaName":970,"dataGaLocation":942},"#contact","switch",{"altText":944,"config":972},{"src":946,"dataGaName":947,"dataGaLocation":942},{"altText":944,"config":974},{"src":975,"dataGaName":947,"dataGaLocation":942},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1773335277/ohhpiuoxoldryzrnhfrh.png",{"freeTrial":977,"mobileIcon":982,"desktopIcon":984},{"text":978,"config":979},"Back to pricing",{"href":712,"dataGaName":980,"dataGaLocation":942,"icon":981},"back to pricing","GoBack",{"altText":944,"config":983},{"src":946,"dataGaName":947,"dataGaLocation":942},{"altText":944,"config":985},{"src":950,"dataGaName":947,"dataGaLocation":942},{"title":987,"titleMobile":988,"button":989,"config":994},"Duo Agent Platform delivers 400% ROI, per new Forrester Consulting study.","400% ROI: Forrester TEI for GitLab Duo",{"text":605,"config":990},{"href":991,"dataGaName":992,"dataGaLocation":993},"https://about.gitlab.com/blog/gitlab-duo-agent-platform-delivers-400-percent-roi/","forrester-tei-dap-banner","global-banner",{"layout":995,"disabled":541},"release",{"data":997},{"text":998,"source":999,"edit":1005,"contribute":1010,"config":1015,"items":1020,"minimal":1230},"Git is a trademark of Software Freedom Conservancy and our use of 'GitLab' is under license",{"text":1000,"config":1001},"View page source",{"href":1002,"dataGaName":1003,"dataGaLocation":1004},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/","page source","footer",{"text":1006,"config":1007},"Edit this page",{"href":1008,"dataGaName":1009,"dataGaLocation":1004},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/content/","web ide",{"text":1011,"config":1012},"Please contribute",{"href":1013,"dataGaName":1014,"dataGaLocation":1004},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/CONTRIBUTING.md/","please contribute",{"twitter":1016,"facebook":1017,"youtube":1018,"linkedin":1019},"https://twitter.com/gitlab","https://www.facebook.com/gitlab","https://www.youtube.com/channel/UCnMGQ8QHMAnVIsI3xJrihhg","https://www.linkedin.com/company/gitlab-com",[1021,1068,1122,1166,1198],{"title":710,"links":1022,"subMenu":1037},[1023,1027,1032],{"text":1024,"config":1025},"View plans",{"href":712,"dataGaName":1026,"dataGaLocation":1004},"view plans",{"text":1028,"config":1029},"Why Premium?",{"href":1030,"dataGaName":1031,"dataGaLocation":1004},"/pricing/premium/","why premium",{"text":1033,"config":1034},"Why Ultimate?",{"href":1035,"dataGaName":1036,"dataGaLocation":1004},"/pricing/ultimate/","why ultimate",[1038],{"title":1039,"links":1040},"Contact Us",[1041,1044,1046,1048,1053,1058,1063],{"text":1042,"config":1043},"Contact sales",{"href":894,"dataGaName":573,"dataGaLocation":1004},{"text":897,"config":1045},{"href":899,"dataGaName":900,"dataGaLocation":1004},{"text":902,"config":1047},{"href":904,"dataGaName":905,"dataGaLocation":1004},{"text":1049,"config":1050},"Status",{"href":1051,"dataGaName":1052,"dataGaLocation":1004},"https://status.gitlab.com/","status",{"text":1054,"config":1055},"Terms of use",{"href":1056,"dataGaName":1057,"dataGaLocation":1004},"/terms/","terms of use",{"text":1059,"config":1060},"Privacy statement",{"href":1061,"dataGaName":1062,"dataGaLocation":1004},"/privacy/","privacy statement",{"text":1064,"config":1065},"Cookie preferences",{"dataGaName":1066,"dataGaLocation":1004,"id":1067,"isOneTrustButton":544},"cookie preferences","ot-sdk-btn",{"title":610,"links":1069,"subMenu":1078},[1070,1074],{"text":1071,"config":1072},"DevSecOps platform",{"href":592,"dataGaName":1073,"dataGaLocation":1004},"devsecops platform",{"text":1075,"config":1076},"AI-Assisted Development",{"href":599,"dataGaName":1077,"dataGaLocation":1004},"ai-assisted development",[1079],{"title":1080,"links":1081},"Topics",[1082,1087,1092,1097,1102,1107,1112,1117],{"text":1083,"config":1084},"CICD",{"href":1085,"dataGaName":1086,"dataGaLocation":1004},"/topics/ci-cd/","cicd",{"text":1088,"config":1089},"GitOps",{"href":1090,"dataGaName":1091,"dataGaLocation":1004},"/topics/gitops/","gitops",{"text":1093,"config":1094},"DevOps",{"href":1095,"dataGaName":1096,"dataGaLocation":1004},"/topics/devops/","devops",{"text":1098,"config":1099},"Version Control",{"href":1100,"dataGaName":1101,"dataGaLocation":1004},"/topics/version-control/","version control",{"text":1103,"config":1104},"DevSecOps",{"href":1105,"dataGaName":1106,"dataGaLocation":1004},"/topics/devsecops/","devsecops",{"text":1108,"config":1109},"Cloud Native",{"href":1110,"dataGaName":1111,"dataGaLocation":1004},"/topics/cloud-native/","cloud native",{"text":1113,"config":1114},"AI for Coding",{"href":1115,"dataGaName":1116,"dataGaLocation":1004},"/topics/devops/ai-for-coding/","ai for coding",{"text":1118,"config":1119},"Agentic AI",{"href":1120,"dataGaName":1121,"dataGaLocation":1004},"/topics/agentic-ai/","agentic ai",{"title":1123,"links":1124},"Solutions",[1125,1127,1129,1134,1138,1141,1145,1148,1150,1153,1156,1161],{"text":654,"config":1126},{"href":649,"dataGaName":654,"dataGaLocation":1004},{"text":643,"config":1128},{"href":626,"dataGaName":627,"dataGaLocation":1004},{"text":1130,"config":1131},"Agile development",{"href":1132,"dataGaName":1133,"dataGaLocation":1004},"/solutions/agile-delivery/","agile delivery",{"text":1135,"config":1136},"SCM",{"href":639,"dataGaName":1137,"dataGaLocation":1004},"source code management",{"text":1083,"config":1139},{"href":632,"dataGaName":1140,"dataGaLocation":1004},"continuous integration & delivery",{"text":1142,"config":1143},"Value stream management",{"href":682,"dataGaName":1144,"dataGaLocation":1004},"value stream management",{"text":1088,"config":1146},{"href":1147,"dataGaName":1091,"dataGaLocation":1004},"/solutions/gitops/",{"text":692,"config":1149},{"href":695,"dataGaName":696,"dataGaLocation":1004},{"text":1151,"config":1152},"Small business",{"href":701,"dataGaName":702,"dataGaLocation":1004},{"text":1154,"config":1155},"Public sector",{"href":707,"dataGaName":708,"dataGaLocation":1004},{"text":1157,"config":1158},"Education",{"href":1159,"dataGaName":1160,"dataGaLocation":1004},"/solutions/education/","education",{"text":1162,"config":1163},"Financial services",{"href":1164,"dataGaName":1165,"dataGaLocation":1004},"/solutions/finance/","financial services",{"title":715,"links":1167},[1168,1170,1172,1174,1177,1179,1182,1184,1186,1188,1190,1192,1194,1196],{"text":728,"config":1169},{"href":730,"dataGaName":731,"dataGaLocation":1004},{"text":733,"config":1171},{"href":735,"dataGaName":736,"dataGaLocation":1004},{"text":738,"config":1173},{"href":740,"dataGaName":741,"dataGaLocation":1004},{"text":743,"config":1175},{"href":745,"dataGaName":1176,"dataGaLocation":1004},"docs",{"text":766,"config":1178},{"href":768,"dataGaName":769,"dataGaLocation":1004},{"text":1180,"config":1181},"What's new",{"href":826,"dataGaName":827,"dataGaLocation":1004},{"text":761,"config":1183},{"href":763,"dataGaName":764,"dataGaLocation":1004},{"text":780,"config":1185},{"href":782,"dataGaName":783,"dataGaLocation":1004},{"text":788,"config":1187},{"href":790,"dataGaName":791,"dataGaLocation":1004},{"text":793,"config":1189},{"href":795,"dataGaName":796,"dataGaLocation":1004},{"text":798,"config":1191},{"href":800,"dataGaName":801,"dataGaLocation":1004},{"text":803,"config":1193},{"href":805,"dataGaName":806,"dataGaLocation":1004},{"text":808,"config":1195},{"href":810,"dataGaName":811,"dataGaLocation":1004},{"text":813,"config":1197},{"href":815,"dataGaName":816,"dataGaLocation":1004},{"title":829,"links":1199},[1200,1202,1204,1206,1208,1210,1214,1219,1221,1223,1225],{"text":837,"config":1201},{"href":839,"dataGaName":831,"dataGaLocation":1004},{"text":842,"config":1203},{"href":844,"dataGaName":845,"dataGaLocation":1004},{"text":850,"config":1205},{"href":852,"dataGaName":853,"dataGaLocation":1004},{"text":855,"config":1207},{"href":857,"dataGaName":858,"dataGaLocation":1004},{"text":860,"config":1209},{"href":862,"dataGaName":863,"dataGaLocation":1004},{"text":1211,"config":1212},"Sustainability",{"href":1213,"dataGaName":1211,"dataGaLocation":1004},"/sustainability/",{"text":1215,"config":1216},"Diversity, inclusion and belonging (DIB)",{"href":1217,"dataGaName":1218,"dataGaLocation":1004},"/diversity-inclusion-belonging/","Diversity, inclusion and belonging",{"text":865,"config":1220},{"href":867,"dataGaName":868,"dataGaLocation":1004},{"text":875,"config":1222},{"href":877,"dataGaName":878,"dataGaLocation":1004},{"text":880,"config":1224},{"href":882,"dataGaName":883,"dataGaLocation":1004},{"text":1226,"config":1227},"Modern Slavery Transparency Statement",{"href":1228,"dataGaName":1229,"dataGaLocation":1004},"https://handbook.gitlab.com/handbook/legal/modern-slavery-act-transparency-statement/","modern slavery transparency statement",{"items":1231},[1232,1235,1238],{"text":1233,"config":1234},"Terms",{"href":1056,"dataGaName":1057,"dataGaLocation":1004},{"text":1236,"config":1237},"Cookies",{"dataGaName":1066,"dataGaLocation":1004,"id":1067,"isOneTrustButton":544},{"text":1239,"config":1240},"Privacy",{"href":1061,"dataGaName":1062,"dataGaLocation":1004},[1242],{"id":1243,"title":7,"body":540,"config":1244,"content":1246,"description":540,"extension":1250,"meta":1251,"navigation":544,"path":1252,"seo":1253,"stem":1254,"__hash__":1255},"blogAuthors/en-us/blog/authors/rachel-nienaber.yml",{"template":1245},"BlogAuthor",{"name":7,"config":1247},{"headshot":1248,"ctfId":1249},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1749667065/Blog/Author%20Headshots/rnienaber-headshot.jpg","rnienaber","yml",{},"/en-us/blog/authors/rachel-nienaber",{},"en-us/blog/authors/rachel-nienaber","cJVawFoacNTReiIKOIg1Num__PkvtVJo3aAdUzsKdGQ",[1257,1266,1274],{"title":1258,"description":1259,"heroImage":1260,"category":536,"date":1261,"authors":1262,"slug":1265,"externalUrl":540},"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",[1263,1264],"Mathias Ewald","Martin Paloncy, Edgeless Systems","confidential-ai-for-gitlab-self-hosted",{"title":1267,"description":1268,"heroImage":1269,"category":536,"date":1270,"authors":1271,"slug":1273,"externalUrl":540},"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",[1272],"Lysanne Pinto","green-devops-carbon-measurement-cicd-pipeline",{"title":1275,"description":1276,"heroImage":1277,"category":536,"date":1278,"authors":1279,"slug":1281,"externalUrl":540},"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",[1280],"Paul Meresanu","how-to-build-ci-cd-observability-at-scale",{"promotions":1283},[1284,1298,1310,1322],{"id":1285,"categories":1286,"header":1288,"text":1289,"button":1290,"image":1295},"ai-modernization",[1287],"ai","Is AI achieving its promise at scale?","Quiz will take 5 minutes or less",{"text":1291,"config":1292},"Get your AI maturity score",{"href":1293,"dataGaName":1294,"dataGaLocation":769},"/assessments/ai-modernization-assessment/","modernization assessment",{"config":1296},{"src":1297},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138786/qix0m7kwnd8x2fh1zq49.png",{"id":1299,"categories":1300,"header":1302,"text":1289,"button":1303,"image":1307},"devops-modernization",[1301,1106],"product","Are you just managing tools or shipping innovation?",{"text":1304,"config":1305},"Get your DevOps maturity score",{"href":1306,"dataGaName":1294,"dataGaLocation":769},"/assessments/devops-modernization-assessment/",{"config":1308},{"src":1309},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138785/eg818fmakweyuznttgid.png",{"id":1311,"categories":1312,"header":1314,"text":1289,"button":1315,"image":1319},"security-modernization",[1313],"security","Are you trading speed for security?",{"text":1316,"config":1317},"Get your security maturity score",{"href":1318,"dataGaName":1294,"dataGaLocation":769},"/assessments/security-modernization-assessment/",{"config":1320},{"src":1321},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138786/p4pbqd9nnjejg5ds6mdk.png",{"id":1323,"paths":1324,"header":1327,"text":1328,"button":1329,"image":1334},"github-azure-migration",[1325,1326],"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":1330,"config":1331},"See how GitLab compares to GitHub",{"href":1332,"dataGaName":1333,"dataGaLocation":769},"/compare/gitlab-vs-github/github-azure-migration/","github azure migration",{"config":1335},{"src":1309},{"header":1337,"blurb":1338,"button":1339,"secondaryButton":1344},"Start building faster today","See what your team can do with the intelligent orchestration platform for DevSecOps.\n",{"text":1340,"config":1341},"Get your free trial",{"href":1342,"dataGaName":568,"dataGaLocation":1343},"https://gitlab.com/-/trial_registrations/new?glm_content=default-saas-trial&glm_source=about.gitlab.com/","feature",{"text":1042,"config":1345},{"href":894,"dataGaName":573,"dataGaLocation":1343},1786803774380]