[{"data":1,"prerenderedAt":794},["ShallowReactive",2],{"/en-us/blog/scaling-down-how-we-prototyped-an-image-scaler-at-gitlab":3,"navigation-en-us":37,"banner-en-us":437,"footer-en-us":447,"blog-post-authors-en-us-Matthias Käppler":689,"blog-related-posts-en-us-scaling-down-how-we-prototyped-an-image-scaler-at-gitlab":704,"assessment-promotions-en-us":745,"next-steps-en-us":784},{"id":4,"title":5,"authorSlugs":6,"body":8,"categorySlug":9,"config":10,"content":14,"description":8,"extension":26,"isFeatured":12,"meta":27,"navigation":28,"path":29,"publishedDate":20,"seo":30,"stem":34,"tagSlugs":35,"__hash__":36},"blogPosts/en-us/blog/scaling-down-how-we-prototyped-an-image-scaler-at-gitlab.yml","Scaling Down How We Prototyped An Image Scaler At Gitlab",[7],"matthias-kppler",null,"unfiltered",{"slug":11,"featured":12,"template":13},"scaling-down-how-we-prototyped-an-image-scaler-at-gitlab",false,"BlogPost",{"title":15,"description":16,"authors":17,"heroImage":19,"date":20,"body":21,"category":9,"tags":22},"Scaling down: How we shrank image transfers by 93%","Our approach to delivering an image scaling solution to speed up GitLab site\nrendering",[18],"Matthias Käppler","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749664102/Blog/Hero%20Images/gitlab-values-cover.png","2020-11-02","The [Memory](https://handbook.gitlab.com/handbook/engineering/data-engineering/data-frameworks/database-framework/doc/strategy/)\nteam recently shipped an improvement to our image delivery functions that drastically reduces the amount of data we serve to clients. Learn here how we went from knowing nothing about [Golang](https://golang.org/) and image scaling to a working on-the-fly image scaling solution built into [Workhorse](https://gitlab.com/gitlab-org/gitlab-workhorse).\n\n## Introduction\n\nImages are an integral part of GitLab. Whether it is user and project avatars, or images embedded in issues and comments, you will rarely load a GitLab page that does not include images in some way shape or form.\n\nWhat you may not be aware of is that despite most of these images appearing fairly small when presented on the site, until recently we were always serving them in their original size.\n\nThis meant that if you would visit a merge request, then all user avatars that appeared merely as thumbnails in sidebars or comments would be delivered by the GitLab application in the same size they were uploaded in, leaving it to the browser rendering engine to scale them down as necessary.\nThis meant serving megabytes of image data in a single page load, just so the frontend would throw most of it away!\n\nWhile this approach was simple and served us well for a while, it had several major drawbacks:\n\n- **Perceived latency suffers.** The perceived latency is the time that\npasses between a user requesting content, and that content actually becoming visible or being ready to engage with.\n  If the browser has to download several megabytes of image data, and then has to furthermore scale down those images to fit the cells they are rendered into, the user experience unnecessarily suffers.\n- **Egress traffic cost.** On gitlab.com, we store all images in object\nstorage, specifically GCS (Google Cloud Storage). This means that our Rails app first needs to resolve an image entity to a GCS bucket URL where the binary data resides, and have the client download the image through that endpoint. This means that for every image served, we cause traffic from GCS to the user that we have to pay for, and the more data we serve, the higher the cost.\n\nWe therefore took on the challenge to both improve rendering performance and reduce traffic costs by implementing [an image scaler that would downscale images](https://gitlab.com/groups/gitlab-org/-/epics/3822)\n\nto a requested size before delivering them to the client.\n\n### Phase 1: Understanding the problem\n\nThe first problem is always: understand the problem! What is the status quo exactly? How does it work?\n\nWhat is broken about it? What should we focus on?\n\nWe had a pretty good idea of the severity of the problem, since we regularly run performance tests through [sitespeed.io](https://www.sitepeed.io) that highlight performance problems on our site.\n\nIt had identified images sizes as one of the most severe issues:\n\n![sitespeed performance test](https://gitlab.com/groups/gitlab-org/-/uploads/a06d8bfde802995c577afca843be7e96/Bildschirmfoto_2020-07-15_um_11.45.44.png)\n\nTo better inform a possible solution, an essential step was to [collect enough data](https://gitlab.com/gitlab-org/gitlab/-/issues/227387)\n\nto help identify the areas we should focus on. Here are some of the highlights:\n\n- **Most images requested are avatars.** We looked at the distribution of\nrequests for certain types of images.\n  We found that about 70% of them were for avatars, while the remaining 30% accounted for embedded images.\n  This suggested that any solution would have the biggest reach if we focused on avatars first. Within the avatar cohort we found that about 62% are user avatars, 22% are project avatars, and 16% are group avatars, which isn't surprising.\n- **Most avatars requested are PNGs or JPEGs.** We also looked at the\ndistribution of image formats. This is partially affected by our upload pipeline and how images are processed (for instance, we always crop user avatars and store them as PNGs)\n  but we were still surprised to see that both formats made up 99% of our avatars (PNGs 76%, JPEGs 23%). Not much love for GIFs here!\n- **We serve 6GB of avatars in a typical hour.** Looking at a representative\nwindow of 1 hour of GitLab traffic, we saw almost 6GB of data move over the wire, or 144GB a day. Based on experiments with downscaling a representative user avatar, we estimated that we could reduce this to a mere 13GB a day on average, saving 130GB of bandwidth each day!\n\nThis was proof enough for us that there were significant gains to be made here. Our first intuition was: could this be done by a CDN? Some modern CDNs like Cloudflare [already support image resizing](https://support.cloudflare.com/hc/en-us/articles/360028146432-Understanding-Cloudflare-Image-Resizing)\n\nin some of their plans. However, we had two major concerns about this:\n\n1. **Supporting our self-managed customers.** While gitlab.com is the\nlargest GitLab deployment we know of, we have hundreds of thousands of customers who run their own GitLab installation. If we were to only resize images that pass through a CDN in front of gitlab.com, none of those customers would benefit from it.\n1. **Pricing woes.** While there are request budgets based on your CDN plan,\nwe were worried about the operational cost this would add for us and how to reliably predict it.\n\nWe therefore decided to look for a solution that would work for all GitLab users, and that would be more under our own control, which led us to phase 2: experimentation!\n\n### Phase 2: Experiments, experiments, experiments!\n\nA frequent challenge for [our team (Memory)](https://handbook.gitlab.com/handbook/engineering/data-engineering/data-frameworks/database-framework/doc/strategy/)\n\nis that we need to venture into parts of GitLab's code base that we are unfamiliar with, be it with the technology, the product area, or both. This was true in this case as well. While some of us had some exposure to image scaling services, none of us had ever built or integrated one.\n\nOur main goal in phase 2 was therefore to identify what the possible approaches to image scaling were, explore them by researching existing solutions or even building proof-of-concepts (POCs), and grade them based on our findings. The questions we asked ourselves along the way were:\n\n- **When should we scale?** Upfront during upload or on-the-fly when an\nimage is requested?\n\n- **Who does the work?** Will it be a dedicated service? Can it happen\nasynchronously in Sidekiq?\n\n- **How complex is it?** Whether it's an existing service we integrate, or\nsomething we build ourselves, does implementation or integration complexity justify its relatively simple function?\n- **How fast is it?** We shouldn't forget that we set out to solve a\nperformance issue. Are we sure that we are not making the server slower by the same amount of time we save in the client?\n\nWith this in mind, we identifed [multiple architectural approaches](https://gitlab.com/groups/gitlab-org/-/epics/3979) to consider, each with their own pros and cons. These issues also doubled as a form of [architectural decision log](https://github.com/joelparkerhenderson/architecture_decision_record#what-is-an-architecture-decision-record)\n\nso that decisions for or against an approach are recorded.\n\nThe major approaches we considered are outlined next.\n\n#### Static vs. dynamic scaling\n\nThere are two basic ways in which an image scaler can operate: it can either create thumbnails of an existing image ahead of time, e.g. during the original upload as a background job. Or it can perform that work on demand, every time an image is requested. To make a long story short: while it took a lot of back and forth, and even though we had [a working\nPOC](https://gitlab.com/gitlab-org/gitlab/-/issues/232616), we eventually discarded the idea of scaling statically, at least for avatars. Even though [CarrierWave](https://github.com/carrierwaveuploader/carrierwave) (the Ruby uploader we employ) has an integration with MiniMagick and is able to perform that kind of work, it suffered from several issues:\n\n1. **Maintenance heavy.** Since image sizes may change over time, a strategy\nis needed to backfill sizes that haven't been computed yet. This raised questions especially for self-managed customers where we do not control the GitLab installation.\n1. **Statefulness.** Since thumbnails are created alongside the original\nimage, it was unclear how to perform cleanups should they become necessary, since CarrierWave does not store these as separate database entities that we could easily query.\n1. **Complexity.** The POC we created turned out to be more complex than\nanticipated and felt like we were shoehorning this feature onto existing code. This was exacerbated by the fact that at the time we were running a very old version of CarrierWave that was already a maintenance liability, and upgrading it would have added scope creep and delays to an already complex issue.\n1. **Flexibility.** The actual scaler implementation in CarrierWave is\nburied three layers down the Ruby dependency stack, and it was difficult to replace the actual scaler binary (which would become a problem when trying to secure this solution as we will see in a moment.)\n\nFor these reasons we decided to scale images on-the-fly instead.\n\n### Dynamic scaling: Workhorse vs. dedicated proxy\n\nWhen scaling images on-the-fly the question becomes: where? Early on there was a suggestion to use [imgproxy](https://github.com/imgproxy/imgproxy), a \"fast and secure standalone server for resizing and converting remote images\".\n\nThis sounded tempting, since it is a \"batteries included\" offering, it's free to use, and it is a great way to isolate the task of image scaling from other production work loads, which has benefits around security and fault isolation.\n\nThe main problem with imgproxy was exactly that, however: a standalone server.\n\n[Introducing a new service to\nGitLab](https://docs.gitlab.com/ee/development/adding_service_component.html#adding-a-new-service-component-to-gitlab)\n\nis a complex task, since we strive to appear as a [single application](https://handbook.gitlab.com/handbook/product/categories/gitlab-the-product/single-application/)\nto the end user, and documenting, packaging, configuring, running and monitoring a new service just for rescaling images seemed excessive.\n\nIt therefore wasn't in line with our prerogative of focusing on the [minimum viable change](https://handbook.gitlab.com/handbook/product/product-principles/#the-minimal-viable-change-mvc).\n\nMoreover, imgproxy had significant overlap with existing architectural components at GitLab, since we already run a reverse proxy:\n[Workhorse](https://gitlab.com/gitlab-org/gitlab-workhorse).\n\nWe therefore decided that the fastest way to deliver an MVC was to build out this functionality in Workhorse itself. Fortunately we found that we already had an established pattern for dealing with special, performance sensitive workloads, which meant that we could learn from existing solutions for similar problems (such as image delivery from remote storage), and we could lean on its existing integration with the Rails application for request authentication and running business logic such as validating user inputs, which helped us tremendously to focus on the actual problem: scaling images.\n\nThere was a final decision to make, however: scaling images is a very different kind of workload from serving ordinary requests, so an open question was how to integrate a scaler into Workhorse in a way that would not have knock-on effects on other tasks Workhorse processes need to execute.\n\nThe two competing approaches discussed were to either shell out to an executable that performs the scaling, or run a [sidecar process](https://docs.microsoft.com/en-us/azure/architecture/patterns/sidecar#:~:text=Sidecars%20are%20supporting%20processes%20or,fate%20of%20its%20parent%20application.)\n\nthat would take over image scaling work loads from the main Workhorse process.\n\n### Dynamic scaling: Sidecar vs. fork-on-request\n\nThe main benefit of a sidecar process is that it has its own life-cycle and memory space, so it can be tuned separately from the main serving process, which improves fault isolation.\nMoreover, you only pay the cost for starting the process once. However, it also comes with additional overhead: if the sidecar dies, something has to restart it, so we would have to look at process supervisors such as `runit` to do this for us, which again comes with a significant amount of configuration overhead. Since at this point we weren't even sure how costly it would be to serve image scaling requests, we let our MVC principle guide us and decided to first explore the simpler fork-on-request approach, which meant shelling out to a dedicated scaler binary on each image scaling request, and only consider a sidecar as a possible future iteration.\n\nForking on request was [explored as a\nPOC](https://gitlab.com/gitlab-org/gitlab/-/issues/230519)\n\nfirst, and was quickly made production ready and deployed behind a feature toggle. We initially ended up settling on [GraphicsMagick](http://www.graphicsmagick.org/)\n\nand its `gm` binary to perform the actual image scaling for us, both because it is a battle tested library, but also because there was precedent at GitLab to use it for existing features, which allowed us to ship a solution even faster.\n\nThe overall request flow finally looked as follows:\n\n```mermaid\n\nsequenceDiagram\n    Client->>+Workhorse: GET /image?width=64\n    Workhorse->>+Rails: forward request\n    Rails->>+Rails: validate request\n    Rails->>+Rails: resolve image location\n    Rails-->>-Workhorse: Gitlab-Workhorse-Send-Data: send-scaled-image\n    Workhorse->>+Workhorse: invoke image scaler\n    Workhorse-->>-Client: 200 OK\n```\n\nThe \"secret sauce\" here is the `Gitlab-Workhorse-Send-Data` header synthesized by Rails. It carries all necessary parameters for Workhorse to act on the image, so that we can maintain a clean separation between application logic (Rails) and serving logic (Workhorse).\n\nWe were fairly happy with this solution in terms of simplicity and ease of maintenance, but we still had to verify whether it met our expectations for performance and security.\n\n### Phase 3: Measuring and securing the solution\n\nDuring the entire development cycle, we frequently measured the performance of the various appraoches we tested, so as to understand how they would affect request latency and memory use.\n\nFor latency tests we relied on [Apache\nBench](https://httpd.apache.org/docs/2.4/programs/ab.html), since recalling our initial mission, we were mostly interested in reducing the request latency a user might experience.\n\nWe also ran benchmarks encoded as Golang tests that specifically [compared different scaler implementations](https://gitlab.com/ayufan/image-resizing-test)\n\nand how performance changed with different image formats and image sizes. We learned a lot from these tests, especially where we would typically lose the most time, which often was in encoding/decoding an image, and not in resizing an image per se.\n\nWe also took security very seriously from the start. Some image formats such as SVGs are notorious for remote code execution attacks, but there were other concerns such as\nDOS-ing the service with too many scaler requests or PNG compression bombs. We therefore put very strict requirements in place around what sizes (both dimensionally but also in bytes) and formats we will accept.\n\nUnfortunately one fairly severe issue remained that turned out to be a deal breaker with our simple solution: `gm` is a complex piece of software, and shelling out to a 3rd party binary written in C still leaves the door open for a number of security issues. The decision was to [sandbox the binary](https://gitlab.com/groups/gitlab-org/-/epics/4373)\n\ninstead, but this turned out to be a lot more difficult than anticipated. We evaluated but discarded multiple approaches to sandboxing such as via `setuid`, `chroot` and `nsjail`, as well as building a custom binary on top of [seccomp](https://en.wikipedia.org/wiki/Seccomp).\n\nHowever, due to performance, complexity or other concerns we discarded all of them in the end.\n\nWe eventually decided to sarifice some performance for the sake of protecting our users as best we can and wrote a scaler binary in Golang, based on an existing [imaging](https://github.com/disintegration/imaging)\n\nlibrary, which had none of these issues.\n\n### Results, conclusion and outlook\n\nIn roughly two months we took an innocent sounding but in fact complex topic, image scaling, and went from \"we know nothing about this\" to a fully functional solution that is now running on gitlab.com.\n\nWe faced many headwinds along the way, in part because we were unfamiliar with both the topic and the technology behind Workhorse (Golang), but also because we underestimated the challenges of delivering an image scaler that will be both fast and secure, an often difficult trade-off. A major lesson learned for us is that security cannot be an afterthought; it has to be part of the design from day one and must be part of informing the approach taken.\n\nSo was it a success? Yes! While the feature didn't have as much of an impact on overall perceived client latency as we had hoped, we still dramatically improved a number of metrics.\nFirst and foremost, the dreaded \"properly size image\" reminder that topped our sitepeed metrics reports is resolved. This is also evident in the average image size processed by clients, which for image heavy pages fell off a cliff (that's good -- lower is better here):\n\n![image size metric](https://gitlab.com/groups/gitlab-org/-/uploads/b453aedaf2132db1292898508fd6a0c1/Bildschirmfoto_2020-10-06_um_07.02.56.png)\n\nSite-wide we saw a staggering **93% reduction** in image transfer size of page content delivered to clients.\n\nThese gains also translate into savings for GCS egress traffic, and hence\nDollar cost savings, by an equivalent amount.\n\nA feature is never done of course, and there are a number of things we are looking to improve in the future:\n\n- Improving metrics and observability\n\n- Improving performance through more aggressive caching\n\n- Adding support for WebP and other features such as image blurring\n\n- Supporting content images embedded into GitLab issues and comments\n\nThe Memory team meanwhile will slowly step back from this work, however, and hand it over to product teams as product requirements evolve.",[23,24,25],"workflow","testing","performance","yml",{},true,"/en-us/blog/scaling-down-how-we-prototyped-an-image-scaler-at-gitlab",{"ogTitle":15,"ogImage":19,"ogDescription":16,"ogSiteName":31,"noIndex":12,"ogType":32,"ogUrl":33,"title":15,"canonicalUrls":33,"description":16},"https://about.gitlab.com","article","https://about.gitlab.com/blog/scaling-down-how-we-prototyped-an-image-scaler-at-gitlab","en-us/blog/scaling-down-how-we-prototyped-an-image-scaler-at-gitlab",[23,24,25],"DxxkJQQJQAuTo3kkLar0obCIPIBBkm4hSEOldGyklp0",{"data":38},{"logo":39,"freeTrial":44,"sales":49,"login":54,"items":59,"search":367,"minimal":398,"duo":417,"pricingDeployment":427},{"config":40},{"href":41,"dataGaName":42,"dataGaLocation":43},"/","gitlab logo","header",{"text":45,"config":46},"Get free trial",{"href":47,"dataGaName":48,"dataGaLocation":43},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com&glm_content=default-saas-trial/","free trial",{"text":50,"config":51},"Talk to sales",{"href":52,"dataGaName":53,"dataGaLocation":43},"/sales/","sales",{"text":55,"config":56},"Sign in",{"href":57,"dataGaName":58,"dataGaLocation":43},"https://gitlab.com/users/sign_in/","sign in",[60,87,182,187,288,348],{"text":61,"config":62,"cards":64},"Platform",{"dataNavLevelOne":63},"platform",[65,71,79],{"title":61,"description":66,"link":67},"The intelligent orchestration platform for DevSecOps",{"text":68,"config":69},"Explore our Platform",{"href":70,"dataGaName":63,"dataGaLocation":43},"/platform/",{"title":72,"description":73,"link":74},"GitLab Duo Agent Platform","Agentic AI for the entire software lifecycle",{"text":75,"config":76},"Meet GitLab Duo",{"href":77,"dataGaName":78,"dataGaLocation":43},"/gitlab-duo-agent-platform/","gitlab duo agent platform",{"title":80,"description":81,"link":82},"Why GitLab","See the top reasons enterprises choose GitLab",{"text":83,"config":84},"Learn more",{"href":85,"dataGaName":86,"dataGaLocation":43},"/why-gitlab/","why gitlab",{"text":88,"left":28,"config":89,"link":91,"lists":95,"footer":164},"Product",{"dataNavLevelOne":90},"solutions",{"text":92,"config":93},"View all Solutions",{"href":94,"dataGaName":90,"dataGaLocation":43},"/solutions/",[96,120,143],{"title":97,"description":98,"link":99,"items":104},"Automation","CI/CD and automation to accelerate deployment",{"config":100},{"icon":101,"href":102,"dataGaName":103,"dataGaLocation":43},"AutomatedCodeAlt","/solutions/delivery-automation/","automated software delivery",[105,109,112,116],{"text":106,"config":107},"CI/CD",{"href":108,"dataGaLocation":43,"dataGaName":106},"/solutions/continuous-integration/",{"text":72,"config":110},{"href":77,"dataGaLocation":43,"dataGaName":111},"gitlab duo agent platform - product menu",{"text":113,"config":114},"Source Code Management",{"href":115,"dataGaLocation":43,"dataGaName":113},"/solutions/source-code-management/",{"text":117,"config":118},"Automated Software Delivery",{"href":102,"dataGaLocation":43,"dataGaName":119},"Automated software delivery",{"title":121,"description":122,"link":123,"items":128},"Security","Deliver code faster without compromising security",{"config":124},{"href":125,"dataGaName":126,"dataGaLocation":43,"icon":127},"/solutions/application-security-testing/","security and compliance","ShieldCheckLight",[129,133,138],{"text":130,"config":131},"Application Security Testing",{"href":125,"dataGaName":132,"dataGaLocation":43},"Application security testing",{"text":134,"config":135},"Software Supply Chain Security",{"href":136,"dataGaLocation":43,"dataGaName":137},"/solutions/supply-chain/","Software supply chain security",{"text":139,"config":140},"Software Compliance",{"href":141,"dataGaName":142,"dataGaLocation":43},"/solutions/software-compliance/","software compliance",{"title":144,"link":145,"items":150},"Measurement",{"config":146},{"icon":147,"href":148,"dataGaName":149,"dataGaLocation":43},"DigitalTransformation","/solutions/visibility-measurement/","visibility and measurement",[151,155,159],{"text":152,"config":153},"Visibility & Measurement",{"href":148,"dataGaLocation":43,"dataGaName":154},"Visibility and Measurement",{"text":156,"config":157},"Value Stream Management",{"href":158,"dataGaLocation":43,"dataGaName":156},"/solutions/value-stream-management/",{"text":160,"config":161},"Analytics & Insights",{"href":162,"dataGaLocation":43,"dataGaName":163},"/solutions/analytics-and-insights/","Analytics and insights",{"title":165,"items":166},"GitLab for",[167,172,177],{"text":168,"config":169},"Enterprise",{"href":170,"dataGaLocation":43,"dataGaName":171},"/enterprise/","enterprise",{"text":173,"config":174},"Small Business",{"href":175,"dataGaLocation":43,"dataGaName":176},"/small-business/","small business",{"text":178,"config":179},"Public Sector",{"href":180,"dataGaLocation":43,"dataGaName":181},"/solutions/public-sector/","public sector",{"text":183,"config":184},"Pricing",{"href":185,"dataGaName":186,"dataGaLocation":43,"dataNavLevelOne":186},"/pricing/","pricing",{"text":188,"config":189,"link":191,"lists":195,"feature":275},"Resources",{"dataNavLevelOne":190},"resources",{"text":192,"config":193},"View all resources",{"href":194,"dataGaName":190,"dataGaLocation":43},"/resources/",[196,229,247],{"title":197,"items":198},"Getting started",[199,204,209,214,219,224],{"text":200,"config":201},"Install",{"href":202,"dataGaName":203,"dataGaLocation":43},"/install/","install",{"text":205,"config":206},"Quick start guides",{"href":207,"dataGaName":208,"dataGaLocation":43},"/get-started/","quick setup checklists",{"text":210,"config":211},"Learn",{"href":212,"dataGaLocation":43,"dataGaName":213},"https://university.gitlab.com/","learn",{"text":215,"config":216},"Product documentation",{"href":217,"dataGaName":218,"dataGaLocation":43},"https://docs.gitlab.com/","product documentation",{"text":220,"config":221},"Best practice videos",{"href":222,"dataGaName":223,"dataGaLocation":43},"/getting-started-videos/","best practice videos",{"text":225,"config":226},"Integrations",{"href":227,"dataGaName":228,"dataGaLocation":43},"/integrations/","integrations",{"title":230,"items":231},"Discover",[232,237,242],{"text":233,"config":234},"Customer success stories",{"href":235,"dataGaName":236,"dataGaLocation":43},"/customers/","customer success stories",{"text":238,"config":239},"Blog",{"href":240,"dataGaName":241,"dataGaLocation":43},"/blog/","blog",{"text":243,"config":244},"Remote",{"href":245,"dataGaName":246,"dataGaLocation":43},"https://handbook.gitlab.com/handbook/company/culture/all-remote/","remote",{"title":248,"items":249},"Connect",[250,255,260,265,270],{"text":251,"config":252},"GitLab Services",{"href":253,"dataGaName":254,"dataGaLocation":43},"/services/","services",{"text":256,"config":257},"Community",{"href":258,"dataGaName":259,"dataGaLocation":43},"/community/","community",{"text":261,"config":262},"Forum",{"href":263,"dataGaName":264,"dataGaLocation":43},"https://forum.gitlab.com/","forum",{"text":266,"config":267},"Events",{"href":268,"dataGaName":269,"dataGaLocation":43},"/events/","events",{"text":271,"config":272},"Partners",{"href":273,"dataGaName":274,"dataGaLocation":43},"/partners/","partners",{"backgroundColor":276,"textColor":277,"text":278,"image":279,"link":283},"#2f2a6b","#fff","Insights for the future of software development",{"altText":280,"config":281},"the source promo card",{"src":282},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758208064/dzl0dbift9xdizyelkk4.svg",{"text":284,"config":285},"Read the latest",{"href":286,"dataGaName":287,"dataGaLocation":43},"/the-source/","the source",{"text":289,"config":290,"lists":292},"Company",{"dataNavLevelOne":291},"company",[293],{"items":294},[295,300,306,308,313,318,323,328,333,338,343],{"text":296,"config":297},"About",{"href":298,"dataGaName":299,"dataGaLocation":43},"/company/","about",{"text":301,"config":302,"footerGa":305},"Jobs",{"href":303,"dataGaName":304,"dataGaLocation":43},"/jobs/","jobs",{"dataGaName":304},{"text":266,"config":307},{"href":268,"dataGaName":269,"dataGaLocation":43},{"text":309,"config":310},"Leadership",{"href":311,"dataGaName":312,"dataGaLocation":43},"/company/team/e-group/","leadership",{"text":314,"config":315},"Team",{"href":316,"dataGaName":317,"dataGaLocation":43},"/company/team/","team",{"text":319,"config":320},"Handbook",{"href":321,"dataGaName":322,"dataGaLocation":43},"https://handbook.gitlab.com/","handbook",{"text":324,"config":325},"Investor relations",{"href":326,"dataGaName":327,"dataGaLocation":43},"https://ir.gitlab.com/","investor relations",{"text":329,"config":330},"Trust Center",{"href":331,"dataGaName":332,"dataGaLocation":43},"/security/","trust center",{"text":334,"config":335},"AI Transparency Center",{"href":336,"dataGaName":337,"dataGaLocation":43},"/ai-transparency-center/","ai transparency center",{"text":339,"config":340},"Newsletter",{"href":341,"dataGaName":342,"dataGaLocation":43},"/company/contact/#contact-forms","newsletter",{"text":344,"config":345},"Press",{"href":346,"dataGaName":347,"dataGaLocation":43},"/press/","press",{"text":349,"config":350,"lists":351},"Contact us",{"dataNavLevelOne":291},[352],{"items":353},[354,357,362],{"text":50,"config":355},{"href":52,"dataGaName":356,"dataGaLocation":43},"talk to sales",{"text":358,"config":359},"Support portal",{"href":360,"dataGaName":361,"dataGaLocation":43},"https://support.gitlab.com","support portal",{"text":363,"config":364},"Customer portal",{"href":365,"dataGaName":366,"dataGaLocation":43},"https://customers.gitlab.com/customers/sign_in/","customer portal",{"close":368,"login":369,"suggestions":376},"Close",{"text":370,"link":371},"To search repositories and projects, login to",{"text":372,"config":373},"gitlab.com",{"href":57,"dataGaName":374,"dataGaLocation":375},"search login","search",{"text":377,"default":378},"Suggestions",[379,381,385,387,391,395],{"text":72,"config":380},{"href":77,"dataGaName":72,"dataGaLocation":375},{"text":382,"config":383},"Code Suggestions (AI)",{"href":384,"dataGaName":382,"dataGaLocation":375},"/solutions/code-suggestions/",{"text":106,"config":386},{"href":108,"dataGaName":106,"dataGaLocation":375},{"text":388,"config":389},"GitLab on AWS",{"href":390,"dataGaName":388,"dataGaLocation":375},"/partners/technology-partners/aws/",{"text":392,"config":393},"GitLab on Google Cloud",{"href":394,"dataGaName":392,"dataGaLocation":375},"/partners/technology-partners/google-cloud-platform/",{"text":396,"config":397},"Why GitLab?",{"href":85,"dataGaName":396,"dataGaLocation":375},{"freeTrial":399,"mobileIcon":404,"desktopIcon":409,"secondaryButton":412},{"text":400,"config":401},"Start free trial",{"href":402,"dataGaName":48,"dataGaLocation":403},"https://gitlab.com/-/trials/new/","nav",{"altText":405,"config":406},"Gitlab Icon",{"src":407,"dataGaName":408,"dataGaLocation":403},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203874/jypbw1jx72aexsoohd7x.svg","gitlab icon",{"altText":405,"config":410},{"src":411,"dataGaName":408,"dataGaLocation":403},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203875/gs4c8p8opsgvflgkswz9.svg",{"text":413,"config":414},"Get Started",{"href":415,"dataGaName":416,"dataGaLocation":403},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com/compare/gitlab-vs-github/","get started",{"freeTrial":418,"mobileIcon":423,"desktopIcon":425},{"text":419,"config":420},"Learn more about GitLab Duo",{"href":421,"dataGaName":422,"dataGaLocation":403},"/gitlab-duo/","gitlab duo",{"altText":405,"config":424},{"src":407,"dataGaName":408,"dataGaLocation":403},{"altText":405,"config":426},{"src":411,"dataGaName":408,"dataGaLocation":403},{"freeTrial":428,"mobileIcon":433,"desktopIcon":435},{"text":429,"config":430},"Back to pricing",{"href":185,"dataGaName":431,"dataGaLocation":403,"icon":432},"back to pricing","GoBack",{"altText":405,"config":434},{"src":407,"dataGaName":408,"dataGaLocation":403},{"altText":405,"config":436},{"src":411,"dataGaName":408,"dataGaLocation":403},{"title":438,"button":439,"config":444},"See how agentic AI transforms software delivery",{"text":440,"config":441},"Watch GitLab Transcend now",{"href":442,"dataGaName":443,"dataGaLocation":43},"/events/transcend/virtual/","transcend event",{"layout":445,"icon":446},"release","AiStar",{"data":448},{"text":449,"source":450,"edit":456,"contribute":461,"config":466,"items":471,"minimal":678},"Git is a trademark of Software Freedom Conservancy and our use of 'GitLab' is under license",{"text":451,"config":452},"View page source",{"href":453,"dataGaName":454,"dataGaLocation":455},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/","page source","footer",{"text":457,"config":458},"Edit this page",{"href":459,"dataGaName":460,"dataGaLocation":455},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/content/","web ide",{"text":462,"config":463},"Please contribute",{"href":464,"dataGaName":465,"dataGaLocation":455},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/CONTRIBUTING.md/","please contribute",{"twitter":467,"facebook":468,"youtube":469,"linkedin":470},"https://twitter.com/gitlab","https://www.facebook.com/gitlab","https://www.youtube.com/channel/UCnMGQ8QHMAnVIsI3xJrihhg","https://www.linkedin.com/company/gitlab-com",[472,519,573,617,644],{"title":183,"links":473,"subMenu":488},[474,478,483],{"text":475,"config":476},"View plans",{"href":185,"dataGaName":477,"dataGaLocation":455},"view plans",{"text":479,"config":480},"Why Premium?",{"href":481,"dataGaName":482,"dataGaLocation":455},"/pricing/premium/","why premium",{"text":484,"config":485},"Why Ultimate?",{"href":486,"dataGaName":487,"dataGaLocation":455},"/pricing/ultimate/","why ultimate",[489],{"title":490,"links":491},"Contact Us",[492,495,497,499,504,509,514],{"text":493,"config":494},"Contact sales",{"href":52,"dataGaName":53,"dataGaLocation":455},{"text":358,"config":496},{"href":360,"dataGaName":361,"dataGaLocation":455},{"text":363,"config":498},{"href":365,"dataGaName":366,"dataGaLocation":455},{"text":500,"config":501},"Status",{"href":502,"dataGaName":503,"dataGaLocation":455},"https://status.gitlab.com/","status",{"text":505,"config":506},"Terms of use",{"href":507,"dataGaName":508,"dataGaLocation":455},"/terms/","terms of use",{"text":510,"config":511},"Privacy statement",{"href":512,"dataGaName":513,"dataGaLocation":455},"/privacy/","privacy statement",{"text":515,"config":516},"Cookie preferences",{"dataGaName":517,"dataGaLocation":455,"id":518,"isOneTrustButton":28},"cookie preferences","ot-sdk-btn",{"title":88,"links":520,"subMenu":529},[521,525],{"text":522,"config":523},"DevSecOps platform",{"href":70,"dataGaName":524,"dataGaLocation":455},"devsecops platform",{"text":526,"config":527},"AI-Assisted Development",{"href":421,"dataGaName":528,"dataGaLocation":455},"ai-assisted development",[530],{"title":531,"links":532},"Topics",[533,538,543,548,553,558,563,568],{"text":534,"config":535},"CICD",{"href":536,"dataGaName":537,"dataGaLocation":455},"/topics/ci-cd/","cicd",{"text":539,"config":540},"GitOps",{"href":541,"dataGaName":542,"dataGaLocation":455},"/topics/gitops/","gitops",{"text":544,"config":545},"DevOps",{"href":546,"dataGaName":547,"dataGaLocation":455},"/topics/devops/","devops",{"text":549,"config":550},"Version Control",{"href":551,"dataGaName":552,"dataGaLocation":455},"/topics/version-control/","version control",{"text":554,"config":555},"DevSecOps",{"href":556,"dataGaName":557,"dataGaLocation":455},"/topics/devsecops/","devsecops",{"text":559,"config":560},"Cloud Native",{"href":561,"dataGaName":562,"dataGaLocation":455},"/topics/cloud-native/","cloud native",{"text":564,"config":565},"AI for Coding",{"href":566,"dataGaName":567,"dataGaLocation":455},"/topics/devops/ai-for-coding/","ai for coding",{"text":569,"config":570},"Agentic AI",{"href":571,"dataGaName":572,"dataGaLocation":455},"/topics/agentic-ai/","agentic ai",{"title":574,"links":575},"Solutions",[576,578,580,585,589,592,596,599,601,604,607,612],{"text":130,"config":577},{"href":125,"dataGaName":130,"dataGaLocation":455},{"text":119,"config":579},{"href":102,"dataGaName":103,"dataGaLocation":455},{"text":581,"config":582},"Agile development",{"href":583,"dataGaName":584,"dataGaLocation":455},"/solutions/agile-delivery/","agile delivery",{"text":586,"config":587},"SCM",{"href":115,"dataGaName":588,"dataGaLocation":455},"source code management",{"text":534,"config":590},{"href":108,"dataGaName":591,"dataGaLocation":455},"continuous integration & delivery",{"text":593,"config":594},"Value stream management",{"href":158,"dataGaName":595,"dataGaLocation":455},"value stream management",{"text":539,"config":597},{"href":598,"dataGaName":542,"dataGaLocation":455},"/solutions/gitops/",{"text":168,"config":600},{"href":170,"dataGaName":171,"dataGaLocation":455},{"text":602,"config":603},"Small business",{"href":175,"dataGaName":176,"dataGaLocation":455},{"text":605,"config":606},"Public sector",{"href":180,"dataGaName":181,"dataGaLocation":455},{"text":608,"config":609},"Education",{"href":610,"dataGaName":611,"dataGaLocation":455},"/solutions/education/","education",{"text":613,"config":614},"Financial services",{"href":615,"dataGaName":616,"dataGaLocation":455},"/solutions/finance/","financial services",{"title":188,"links":618},[619,621,623,625,628,630,632,634,636,638,640,642],{"text":200,"config":620},{"href":202,"dataGaName":203,"dataGaLocation":455},{"text":205,"config":622},{"href":207,"dataGaName":208,"dataGaLocation":455},{"text":210,"config":624},{"href":212,"dataGaName":213,"dataGaLocation":455},{"text":215,"config":626},{"href":217,"dataGaName":627,"dataGaLocation":455},"docs",{"text":238,"config":629},{"href":240,"dataGaName":241,"dataGaLocation":455},{"text":233,"config":631},{"href":235,"dataGaName":236,"dataGaLocation":455},{"text":243,"config":633},{"href":245,"dataGaName":246,"dataGaLocation":455},{"text":251,"config":635},{"href":253,"dataGaName":254,"dataGaLocation":455},{"text":256,"config":637},{"href":258,"dataGaName":259,"dataGaLocation":455},{"text":261,"config":639},{"href":263,"dataGaName":264,"dataGaLocation":455},{"text":266,"config":641},{"href":268,"dataGaName":269,"dataGaLocation":455},{"text":271,"config":643},{"href":273,"dataGaName":274,"dataGaLocation":455},{"title":289,"links":645},[646,648,650,652,654,656,658,662,667,669,671,673],{"text":296,"config":647},{"href":298,"dataGaName":291,"dataGaLocation":455},{"text":301,"config":649},{"href":303,"dataGaName":304,"dataGaLocation":455},{"text":309,"config":651},{"href":311,"dataGaName":312,"dataGaLocation":455},{"text":314,"config":653},{"href":316,"dataGaName":317,"dataGaLocation":455},{"text":319,"config":655},{"href":321,"dataGaName":322,"dataGaLocation":455},{"text":324,"config":657},{"href":326,"dataGaName":327,"dataGaLocation":455},{"text":659,"config":660},"Sustainability",{"href":661,"dataGaName":659,"dataGaLocation":455},"/sustainability/",{"text":663,"config":664},"Diversity, inclusion and belonging (DIB)",{"href":665,"dataGaName":666,"dataGaLocation":455},"/diversity-inclusion-belonging/","Diversity, inclusion and belonging",{"text":329,"config":668},{"href":331,"dataGaName":332,"dataGaLocation":455},{"text":339,"config":670},{"href":341,"dataGaName":342,"dataGaLocation":455},{"text":344,"config":672},{"href":346,"dataGaName":347,"dataGaLocation":455},{"text":674,"config":675},"Modern Slavery Transparency Statement",{"href":676,"dataGaName":677,"dataGaLocation":455},"https://handbook.gitlab.com/handbook/legal/modern-slavery-act-transparency-statement/","modern slavery transparency statement",{"items":679},[680,683,686],{"text":681,"config":682},"Terms",{"href":507,"dataGaName":508,"dataGaLocation":455},{"text":684,"config":685},"Cookies",{"dataGaName":517,"dataGaLocation":455,"id":518,"isOneTrustButton":28},{"text":687,"config":688},"Privacy",{"href":512,"dataGaName":513,"dataGaLocation":455},[690],{"id":691,"title":692,"body":8,"config":693,"content":695,"description":8,"extension":26,"meta":699,"navigation":28,"path":700,"seo":701,"stem":702,"__hash__":703},"blogAuthors/en-us/blog/authors/matthias-kppler.yml","Matthias Kppler",{"template":694},"BlogAuthor",{"name":18,"config":696},{"headshot":697,"ctfId":698},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1749670351/Blog/Author%20Headshots/mkaeppler-headshot.jpg","mkaeppler",{},"/en-us/blog/authors/matthias-kppler",{},"en-us/blog/authors/matthias-kppler","ifYoCrN_DXBz-4NxWtEPrkhCLzL27iwXIaXEy_ogYFc",[705,715,730],{"content":706,"config":713},{"title":707,"description":708,"authors":709,"heroImage":19,"date":711,"body":712,"category":9},"CEO Shadow Takeaways from Jacie","Recap of my experience in the CEO Shadow Program.",[710],"Jacie Bandur","2021-05-18","\n\n{::options parse_block_html=\"true\" /}\n\n\nHi! I’m Jacie Bandur. I completed GitLab’s CEO Shadow program from 2021-04-26 through 2021-05-07. It was a really enlightening experience. I generally work in Learning and Development and consider myself a lifelong learner. I can’t even explain how much I learned in such a short about of time. I learned a lot about the business. I learned a lot about the product. But learned even more about the importance of iteration in everything we do.\n\n### Qualifications to Participate\n\nI wanted to start this off with touching on qualifications to participate in the program.\n\nI am the type of person that has gone through most of my life thinking I’m not qualified for things. I’m not qualified for that job, that promotion, that program. The list goes on and on.\n\nWhen I saw the [CEO Shadow program](/blog/ceo-shadow-impressions-takeaways/) kick off in 2019, I really wanted to participate. I was a little intimidated. Who wouldn’t be, spending 2 weeks with the CEO of any company? But time passed and all the sudden it was 2021 and I had not taken any steps to participating in the program.\n\nIf you are sitting there waiting for someone to tell you that you are qualified to participate in this program, I’m not big on giving “pep talks,” but here’s me telling you - You are qualified for this program. There’s never going to be a good or perfect time to do it. Tell your manager you want to do the CEO Shadow program. Stop waiting. Sign up today.\n\nNote: Take a look at the [eligibility](https://handbook.gitlab.com/handbook/ceo/shadow/#eligibility) section of the CEO Shadow page for more information on signing up.\n\n### Pre-Program Tips\n\nThere are many things recommended for shadows to do pre-program outlined on the CEO Shadow handbook page. As I was going through the program there were things that I thought helped me (or would have helped me).\n\nHere are my top 6 recommendations:\n\n1. Make sure your team knows you will be unavailable for 2 weeks. This isn’t a program that can or should be done alongside your normal day to day work. I found catching up from the 2 weeks away kind of difficult because I was trying to keep up on what was going on and I had a bunch of half done things.\n1. Talk with people who have done the shadow program - schedule at least 3 coffee chats with CEO Shadow Alumni.\n1. Have food that is easy to eat quickly. Sid’s meetings are back to back most days, so you will have small amounts of time to eat throughout the day. Sid does eat during calls, which you are welcome to do, too, but if you are taking notes, it is difficult to eat. And this will make you realize why speedy meetings are so important!\n1. Listen to the [Executive Leadership LinkedIn Learning course](https://www.linkedin.com/learning/executive-leadership/).\n1. Be prepared to ask questions. When doing the program virtually, there isn’t a ton of time for asking questions, so when one would come up, I would add it to a note on my computer and ask if there was ever time with just the shadows and Sid.\n1. Take at least 1 day off after the program. Take even a couple of days off if you can! This is recommended on the handbook page, but I can’t stress this enough.\n\n\n### Takeaways\n\n**Group Conversations**\n\nI’ve been at GitLab for almost 4 years. When I joined, I made it a point to attend as many GC’s as I could. I had gotten out of the habit of attending Group Conversations. After attending them again for 2 weeks, I realized how important they are to understand better what is going on across the business. Everything in the organization is so intertwined. It’s helpful to understand what other teams are working on and succeeding in.\n\n**Feedback**\n\nWe should all be giving and receiving feedback often. We have a whole [handbook page on giving and receiving feedback](https://handbook.gitlab.com/handbook/people-group/guidance-on-feedback/). Read the handbook page and watch the videos, as well. Practice giving feedback. I recommend using the [1-1 agenda](https://handbook.gitlab.com/handbook/leadership/1-1/suggested-agenda-format/) Sid uses, because Feedback is an essential piece of that agenda, and it makes feedback more of a routine thing.\n\n**Biggest Takeaway**\n\nWe have an incredible team here at GitLab, from Engineering to Product to Sales to People and all the groups in between. There are so many great ideas. I observed the constant reinforcement by Sid to start with something small and build on it. You can ALWAYS make something more complex. It’s hard to go back to something more simple when you start with something complex.\n\nA couple of quotes that I heard from Sid during the program that reinforced this point:\n\n- “Every complex system evolves from a simple system that worked.”\n- “It’s very clear what is the simple solution. We can always make it more complicated as we go on.”\n\nI know they are very similar, but they happened in different meetings on different days, so the point was reinforced repeatedly.\n\nDuring the program, I reflected on the projects that I’am working on. How many of them am I trying to do too much on before releasing. Probably all of them. When I’m working on projects in the future, I will break them down into smaller, more doable chunks. Iteration is hard - it’s a skill to be practicing constantly.\n\n\n### Overall\n\nOverall, the program was really insightful and impactful. If you haven’t participated in it yet, I cannot encourage you enough to do so!\n",{"slug":714,"featured":12,"template":13},"ceo-shadow-recap",{"content":716,"config":728},{"title":717,"description":718,"authors":719,"heroImage":721,"date":722,"body":723,"category":9,"tags":724},"Why I love contributing to GitLab","Making small meaningful changes is what it's all about.",[720],"Austin Regnery","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749679501/Blog/Hero%20Images/new-feature.png","2021-05-11","It was mid-morning on a Tuesday in February, and I had 10 minutes in between meetings. So I decided to try and solve a pain point of mine.\nYou see, I had to memorize this HTML snippet to create a collapsible section in GitLab Issue descriptions and comments, but I kept forgetting it. Was it `summary` or `section`? I could never remember.\n```html\n\u003Cdetails>\n\u003Csummary>Insert Title\u003C/summary>\nHidden content\n\u003C/details>\n```\nEven though it is not vanilla Markdown, GitLab knows how to interpret some HTML. I used this formatting trick fairly often since full-page screenshots can occupy a lot of screen space, which leads to excessive scrolling.\nSo I decided to poke around our codebase to see how the other Markdown shortcuts worked. To my surprise, it was pretty straightforward. Each shortcut had a simple text input that mapped to each button. This implementation was simple to replicate since I just needed to copy/paste and replace a few words.\n![Image of Vue and Haml files with editor shortcuts](https://about.gitlab.com/images/blogimages/why-i-love-contributing-to-gitlab/vue-haml.png){: .shadow}\nThe Vue and Haml files with the new shortcut\n\nI started a branch and began hacking away at the code. Now, I would never call myself a Software Engineer, but I like to try and make things from time to time. I was able to add a new shortcut to the toolbar to insert this code snippet for me in less than 10 minutes. No more memorizing! Making contributions like this is what makes working at GitLab so special.\nNow, it wasn't ready for production, but I at least had something that worked. I shared it with my UX colleagues in Slack, and it started to gain traction with several up-votes and few constructive comments on how to make it better.\nWith the functionality flushed out, a few other designers helped me get a better icon added to our SVG library. Using clear iconography is critical for communicating information more clearly.\n| Initial Icon | Final Icon |\n| - | - |\n| ![SVG of chevron right icon](https://about.gitlab.com/images/blogimages/why-i-love-contributing-to-gitlab/chevron-right.svg) | ![SVG of details block icon](https://about.gitlab.com/images/blogimages/why-i-love-contributing-to-gitlab/details-block.svg) |\n\nThe last thing to do was resolve my failing tests, and I had several teammates help me do that.\n![Gif of the shortcut being used](https://about.gitlab.com/images/blogimages/why-i-love-contributing-to-gitlab/demo.gif)\n\nToday [this change](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/54938) merged! Now I solved a pain point for me and others. It took a few months to go from idea to production, but the effort was super low. I'd say the return on my initial investment, 10 minutes, is super high.\n> Having a direct impact on a product was never an option for me before joining GitLab.\n\n![Image of participants in the Merge Request](https://about.gitlab.com/images/blogimages/why-i-love-contributing-to-gitlab/participants.png)\n\n\nThank you to everyone that helped me deploy this\n",[725,726,727],"UX","product","AWS",{"slug":729,"featured":12,"template":13},"why-i-love-contributing-to-gitlab",{"content":731,"config":743},{"title":732,"description":733,"authors":734,"heroImage":736,"date":722,"body":737,"category":9,"tags":738},"Placebo Lines on the Pipeline Graph","Have you noticed the connecting lines missing on your pipelines lately? Here's why",[735],"Sam Beckham","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749679507/Blog/Hero%20Images/ci-cd.png","\n\n{::options parse_block_html=\"true\" /}\n\n\n\nHave you ever pressed the close door button on the elevator, in the hope that you'll save a few precious seconds?\nOr got frustrated at the person stood next to you at the cross-walk, neglecting to press the button?\nWell, maybe they know something you don't, or perhaps you know this already.\nMany buttons in our society lie to us.\n[David McRaney](https://youarenotsosmart.com/2010/02/10/placebo-buttons/) dubbed these, \"Placebo buttons\" and they're everywhere.\nThose elevator doors won't close any faster and the cross-walk button has no effect on the lights.\nThe only lights they control are the lights on the buttons themselves.\nThey give you the feedback you crave, but that's all they're doing.\n\nThese placebos aren't constrained to the physical world, they're prevalent in [UI design](/blog/the-evolution-of-ux-at-gitlab/) too.\nFrom literal placebo buttons like [YouTube's downvote](https://www.quora.com/Does-downvoting-a-comment-on-YouTube-even-do-anything), to more subtle effects like Instagram always [pretending to work](https://www.fastcompany.com/1669788/the-3-white-lies-behind-instagrams-lightning-speed), or progress bars that have a [fixed animation](https://www.theatlantic.com/technology/archive/2017/02/why-some-apps-use-fake-progress-bars/517233/).\nThey're everywhere if you know where to look.\n\nAt GitLab, we created a placebo of our own in one of our core features; the pipeline graph.\n\nThose of you who have used our pipeline graph, will be familiar with its appearance.\nThere's a series of jobs, grouped by stages, connected by a series of lines depicting the relationships between the jobs.\nBut these lines might be lying to you.\nThese lines are indiscriminately drawn between each job in a stage, regardless of their relationship.\nThese lines are placebos.\n\n![The old pipeline rendering with lines connecting every job in a stage](https://about.gitlab.com/images/blogimages/placebo-lines_old-graph.png)\n\nThis wasn't a problem to begin with.\nA basic pipeline has several jobs across a handful of stages.\nJobs in each stage would run parallel to each other, but each stage would run sequentially.\nIn the image shown above, all the jobs in the test stage would trigger at the same time. Once those jobs had finished, all the jobs in the build stage would trigger.\nWe used rudimentary CSS to draw lines connecting each job in one stage to each job in the next.\nThese lines weren't calculated based on their connections, but still reflected the story they were telling.\n\nSince the introduction of `needs` relationships in [v12.2](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/47063), pipelines got a bit more complicated.\nNow you could configure a job in a later stage to trigger as soon as a job in an earlier stage completed.\nLooking at our old example, we could set the API deployment to run as soon as our spec tests passed.\nThis skips the remaining tests and the entire build stage, turning our lines into pretty little liars.\n\nWe had many internal discussions about these lines, and how to show the relationships between jobs.\nThere's the [`needs` visualization](https://docs.gitlab.com/ee/ci/directed_acyclic_graph/#needs-visualization), which does an excellent job of displaying these relationships, but the main pipeline graph was still inaccurate.\nFor the past few months, we've been [refactoring the pipeline graph](https://gitlab.com/gitlab-org/gitlab/-/issues/276949), giving it a new lease of life and fixing some of its issues along the way.\nOne of those issues were the faked lines.\nIn the new version, we can accurately draw lines between jobs.\nLines that actually depict the relationships jobs have with each other.\nNow the lines no-longer lie!\n\n![The newer pipeline graph showing the correct needs links between jobs](https://about.gitlab.com/images/blogimages/placebo-lines_new-graph.png)\n\nThe above image shows an unreleased version of the pipeline graph.\nYou can see the lines drawn between the jobs to show that the `deploy:API` job can start as soon as the `rspec` job is successful.\nSomething the old lines (shown earlier in this post) would have been unable to depict.\n\nOne unfortunate downside of this is that these lines can be quite expensive to calculate.\nThey're actual DOM nodes, drawn deliberately and placed precisely.\nOn smaller graphs this isn't a problem, but some of our initial tests have found pipelines with a potential 8000+ job connections.\nThat kind of calculation would grind the browser to a halt, and nobody wants that.\n\nAt GitLab, we believe in boring solutions.\nWe make the simple change that sets us on the path towards where we want to be.\nShip it, get feedback, and iterate.\nSo that's what we did.\nIn the first phase of this rollout, we shipped the new pipeline graph with no lines connecting the jobs.\nWe don't have to worry about the expensive calculations, and we still get to roll out the refactored pipeline graph.\n\n![The current (v13.11) pipeline graph showing no links between jobs](https://about.gitlab.com/images/blogimages/placebo-lines_current-graph.png)\n\nWe know some of you will miss them, but fear not.\nBoring solutions are just technical debt if you don't iterate on them.\nSo the [improved lines are coming](https://gitlab.com/groups/gitlab-org/-/epics/4509) in a future release, along with several other improvements to the pipeline graph.\nWe're already starting to roll out the new [Job Dependencies](https://gitlab.com/gitlab-org/gitlab/-/issues/298973) view which shows the jobs in a (much closer to) execution order.\nStay tuned for more updates, and watch [Sarah Groff Hennigh Palermo's talk](https://www.youtube.com/watch?v=R2EKqKjB7OQ) for the technical side of this effort and a deeper dive into some of the decisions we made.\n",[739,740,741,742],"CI","frontend","agile","design",{"slug":744,"featured":12,"template":13},"placebo-lines-on-the-pipeline-graph",{"promotions":746},[747,761,772],{"id":748,"categories":749,"header":751,"text":752,"button":753,"image":758},"ai-modernization",[750],"ai-ml","Is AI achieving its promise at scale?","Quiz will take 5 minutes or less",{"text":754,"config":755},"Get your AI maturity score",{"href":756,"dataGaName":757,"dataGaLocation":241},"/assessments/ai-modernization-assessment/","modernization assessment",{"config":759},{"src":760},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138786/qix0m7kwnd8x2fh1zq49.png",{"id":762,"categories":763,"header":764,"text":752,"button":765,"image":769},"devops-modernization",[726,557],"Are you just managing tools or shipping innovation?",{"text":766,"config":767},"Get your DevOps maturity score",{"href":768,"dataGaName":757,"dataGaLocation":241},"/assessments/devops-modernization-assessment/",{"config":770},{"src":771},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138785/eg818fmakweyuznttgid.png",{"id":773,"categories":774,"header":776,"text":752,"button":777,"image":781},"security-modernization",[775],"security","Are you trading speed for security?",{"text":778,"config":779},"Get your security maturity score",{"href":780,"dataGaName":757,"dataGaLocation":241},"/assessments/security-modernization-assessment/",{"config":782},{"src":783},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138786/p4pbqd9nnjejg5ds6mdk.png",{"header":785,"blurb":786,"button":787,"secondaryButton":792},"Start building faster today","See what your team can do with the intelligent orchestration platform for DevSecOps.\n",{"text":788,"config":789},"Get your free trial",{"href":790,"dataGaName":48,"dataGaLocation":791},"https://gitlab.com/-/trial_registrations/new?glm_content=default-saas-trial&glm_source=about.gitlab.com/","feature",{"text":493,"config":793},{"href":52,"dataGaName":53,"dataGaLocation":791},1772652078936]