<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Klarna Engineering - Medium]]></title>
        <description><![CDATA[Disrupting the financial sector starts and ends with products that work, are easy to use and stable day after day. The Engineering competence is pivotal in creating, maintaining and developing the Klarna experience. - Medium]]></description>
        <link>https://engineering.klarna.com?source=rss----86090d14ab52---4</link>
        <image>
            <url>https://cdn-images-1.medium.com/proxy/1*TGH72Nnw24QL3iV9IOm4VA.png</url>
            <title>Klarna Engineering - Medium</title>
            <link>https://engineering.klarna.com?source=rss----86090d14ab52---4</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Thu, 16 Apr 2026 12:19:59 GMT</lastBuildDate>
        <atom:link href="https://engineering.klarna.com/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Beyond Prompting: How Algorithmic Evolution Doubled our Training Speed]]></title>
            <link>https://engineering.klarna.com/beyond-prompting-how-algorithmic-evolution-doubled-our-training-speed-8f874af3080d?source=rss----86090d14ab52---4</link>
            <guid isPermaLink="false">https://medium.com/p/8f874af3080d</guid>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[artificial-intelligence]]></category>
            <category><![CDATA[software-engineering]]></category>
            <dc:creator><![CDATA[Rex Lin]]></dc:creator>
            <pubDate>Mon, 30 Mar 2026 12:47:12 GMT</pubDate>
            <atom:updated>2026-03-30T13:23:31.718Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*u7RbgujVkRJlJUfkC4U_jQ.jpeg" /></figure><p>By <a href="https://medium.com/u/874312cffcd2">Rex Lin</a> and <a href="https://medium.com/u/ca8844e068e7">Valeria Verzi</a> (Klarna Engineering), with <a href="https://medium.com/u/cb93502b860d">Anant Nawalgaria</a> (Google)</p><p>We knew our training pipeline could be faster — significantly faster.</p><p>At Klarna, one of our largest models (a transformer trained on vast streams of payment events and shared like infrastructure across many internal systems) is on a tight training loop. Speed is money at our scale. The opportunity for improvement was not in the hyperparameters, but in the plumbing: the way numbers moved between processors, the way memory was allocated, the way the model performed its most basic mathematical operations.</p><p>The challenge was scale. An engineer might try five or ten structural rewrites. A particularly ambitious one, armed with an AI coding assistant, might push to a hundred. But the full search space, the universe of possible combinations of precision formats, data pipelines, attention mechanisms, and gradient strategies, numbered in the thousands.</p><p>We partnered with Google to apply a different kind of tool. Instead of trying to prompt our way to a solution, we handed the problem to AlphaEvolve, which treats code optimization the way evolution treats organisms: generate candidates, test them, keep the fittest, repeat. Over three weeks and nearly 6,000 candidate programs, it doubled our training speed and, unexpectedly, produced a better model in the process.</p><h3>The Machine That Writes Machines</h3><p>AlphaEvolve is not a chatbot for code. You don’t interact with it through prompts. Instead, you build a sandbox around it.</p><p>The engineer’s job is to define what can change in the code and what cannot (passing only structural code snippets to the system, never customer data) to specify the metric that matters, and to set the constraints that must never be violated. You write hints in the form of code comments. You craft error messages that help the system learn when it fails. Then you step back.</p><p>The system takes over from there. It generates a candidate program, runs it, measures the result, scores it. The best candidates survive<strong> </strong>and seed the next generation. Candidates that violate constraints or fall below the quality threshold are discarded. This happens thousands of times, with no human reviewing individual outputs. The engineer shapes the environment; the machine explores it at a scale no person could match.</p><h3>Why Speed Matters When You Train a Thousand Times</h3><p>Scaling one of our largest models is not theoretical; it’s a daily challenge. With over 114 million customers and more than 3.4 million transactions each day, the data we process is constantly growing.</p><p>We run our training pipeline hundreds to thousands of times, depending on the stage of scaling, across different configurations and dataset sizes. Every minute shaved off a single run compounds across hundreds of iterations, directly reducing cloud-computing costs and accelerating development cycles.</p><p>The question was whether the code itself could be structurally reorganized to run faster, without sacrificing the model’s predictive accuracy or violating our strict requirement that every training run be perfectly reproducible. In regulated financial services, if you can’t reproduce the exact result, you can’t audit it, and you can’t deploy it.</p><h3>What Evolution Discovered</h3><p>The early generations found the obvious wins.</p><p>Mixed-precision training. Asynchronous data transfers between CPU and GPU. It instantiated high-precision tracking variables directly on the device, accumulating metrics entirely on the GPU without ever talking to the CPU, and pulling the final result only once at the end of the epoch, eliminating microscopic synchronization stalls. These are optimizations a seasoned engineer would eventually try, and they pushed throughput from 49 to about 72 samples per second.</p><p>Then we made a change that altered the trajectory of the search: we opened up more of the codebase. Initially, only the training loop was marked as evolvable. When we exposed the model’s forward pass and the data pipeline, AlphaEvolve began making deeper structural rewrites.</p><p>It stripped away many of the framework’s abstractions. It discarded PyTorch’s default Transformer objects, replacing them with a direct loop calling scaled dot-product attention kernels. By doing so, it realized it no longer needed to manually cache causal masks or pass dummy tensors: it let the bare-metal C++ kernels handle it.</p><p>It applied the same approach to the data pipeline. Instead of a standard setup where the data loader creates and pads new tensors for every batch, it pre-allocated the entire dataset into a single, massive, continuous block of memory before training even starts. It turned a complex data loader into a raw memory stream where batches were just continuous memory slices.</p><p>These are cross-cutting optimizations. Getting them right requires reasoning about numerical precision, memory layout, and computation simultaneously, the kind of optimization that is hard for humans not because the individual pieces are complex, but because you have to hold all of them in your head at once.</p><p>Combined, the changes brought throughput<strong> </strong>to roughly 97 samples per second under deterministic production constraints. That is roughly double the baseline.</p><p>Model quality improved, too. The best programs ran twice as fast while producing measurably better predictions than the baseline. The ultimate value of this search wasn’t necessarily just reducing costs (evolutionary searches themselves are computationally expensive). The value was in time. When a production pipeline runs in half the time, the speed of engineering doubles.</p><h3>The Solution That Fired Itself</h3><p>Perhaps the most striking result was something the system did to its own work.</p><p>At a training scale of 10,000 customer samples, evolution designed an elaborate stability mechanism that it named (unprompted) PASC (Proactive Adaptive Stability Control). It wasn’t just a simple failure check. PASC introduced a dynamic threshold to monitor the raw, unscaled loss during gradient accumulation. If a single mini-batch spiked, PASC flagged the cycle and waited; if the optimizer later detected unstable gradients, it executed an aggressive reset, dropping the gradients to save the training run. Some evolved versions even adjusted their aggressiveness based on the smoothed exponential moving average of the loss. At that scale, PASC appeared in every top-performing program.</p><p>Then we scaled up to 100,000 customer samples, still far from production scale, but enough to change the regime. Evolution dropped it. Simpler programs with fixed settings outperformed, revealing a broader pattern: mechanisms that stabilize small-scale training can become pure overhead as scale increases.</p><p>The system had not just found optimizations. It had found that one of its own best ideas was no longer needed, and discarded it. It arrived at the same lesson engineers typically learn over years of experience: that complexity is not always an asset.</p><h3>Defining the Right Sandbox</h3><p>The project required strict constraint engineering.</p><p>We did not enforce our determinism requirement from the start. The single largest throughput gain we found — a leap from 72 to 143 samples per second — turned out to rely on non-deterministic hardware shortcuts, aggressively enabling TensorFloat-32 and CuDNN benchmarking. Multiple runs had been spent exploring a path that could never be deployed. But this constraint ultimately became a catalyst. Once we forced the system into strict determinism, shutting off the hardware-level shortcuts, AlphaEvolve was forced to invent the architectural rewrites (the memory stream and the bare-metal attention bypass) to claw its way back up to 97 samples per second.</p><p>We also learned the importance of early stopping. Our largest experiment logged 631 consecutive evaluations with no improvement. Throughput had effectively capped. In hindsight, we learned that without a strict plateau threshold, the system will continue exploring marginal paths.</p><p>The choice of underlying language model mattered more than we expected. Early runs used an older model that produced syntactically broken or crashing programs roughly two-thirds of the time. Switching to a newer model pushed the success rate to between 86 and 97 percent, transforming the economics of the search.</p><h3>Beyond the Training Loop</h3><p>At the scale we operate — millions of customers and continuously growing datasets — these throughput improvements from this single project translate into a significant compounding effect on iteration speed and compute efficiency. And that was just one training loop for one model.</p><p>We see the same pattern elsewhere in our business: a clearly defined objective, hard constraints, and a search space of code-level decisions too large for any human to explore. We are now experimenting with AlphaEvolve on other high-stakes modeling and simulation problems where constraints are tight and the implementation details dominate runtime.</p><p>The broader implication extends beyond our work. For years, the conversation around AI and software engineering has centered on generation — what can the machine write when you tell it what you want? This project suggests a different, and possibly more consequential, question.</p><p>The future isn’t just what you can prompt an AI to write. It is what algorithmic evolution discovers when you let machines continuously redefine the limits of your work.</p><p><strong><em>Acknowledgement</em></strong></p><p><em>This project was a collaboration between the Klarna team, including Rex Lin, Valeria Verzi, Goran Dizdarevic and Sudhakar Periyasamy; the AI for Science team at Google Cloud, including Kartik Sanu, Laurynas Tamulevičius, Nicolas Stroppa, Chris Page, Skandar Hannachi, Vishal Agarwal, John Semerdjian, and Anant Nawalgaria; and our partners within Google Cloud Account team: Nikola Rubil, Karl Helling and Google DeepMind.</em></p><p>Follow<a href="https://engineering.klarna.com"> Klarna Engineering</a> for more.</p><p>AlphaEvolve is developed by Google DeepMind. Learn more at<a href="https://deepmind.google/discover/blog/alphaevolve-a-gemini-powered-coding-agent-for-designing-advanced-algorithms/"> deepmind.google</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8f874af3080d" width="1" height="1" alt=""><hr><p><a href="https://engineering.klarna.com/beyond-prompting-how-algorithmic-evolution-doubled-our-training-speed-8f874af3080d">Beyond Prompting: How Algorithmic Evolution Doubled our Training Speed</a> was originally published in <a href="https://engineering.klarna.com">Klarna Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How I stopped worrying and learned to love Cloud Inventory]]></title>
            <link>https://engineering.klarna.com/how-i-stopped-worrying-and-learned-to-love-cloud-inventory-723cd3c49d46?source=rss----86090d14ab52---4</link>
            <guid isPermaLink="false">https://medium.com/p/723cd3c49d46</guid>
            <category><![CDATA[engineering-management]]></category>
            <category><![CDATA[cloud-inventory-software]]></category>
            <category><![CDATA[change-management]]></category>
            <category><![CDATA[technical-change]]></category>
            <dc:creator><![CDATA[Maxim Savin]]></dc:creator>
            <pubDate>Fri, 06 Jun 2025 06:38:06 GMT</pubDate>
            <atom:updated>2025-06-06T07:51:47.682Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*30rilqotOLrVUbOA_wmYHQ.jpeg" /></figure><p>A long time ago, as a punishment for his crimes, Hades, the king of the underworld, made Sisyphus roll a huge enchanted boulder endlessly up a steep hill. Since then, many tech companies have learned to do that at scale by the hardships of cloud configuration management.</p><p>Consider an Engineer who wants to ensure that the data that moves through their system is encrypted along the way. This is a noble goal, and to achieve it they must identify every classic load balancer in their AWS environment to replace it with an application load balancer that enforces encryption in transit. Now imagine doing that at the scale of a company like Klarna, where teams collectively own more than a thousand AWS accounts? Add to this a multitude of other configuration challenges — databases that have not been deployed in a multi-availability zone set-up, missing Cloudwatch logs, expired digital certificates, systems running on unsupported framework versions — the list is endless. Identifying and rectifying violating cloud assets often feels like an endless game of whack-a-mole played blindfolded. This is the steep price tech companies pay to operate their systems securely and confidently, day by day.</p><p><strong><em>Klarna Engineering Platform (KEP)</em></strong> has been on a mission to facilitate configuration management for Klarna Engineers. After a few iterations we have built an ecosystem of Klarna services designed to collect, normalize, map, and serve data on ICT assets within Klarna’s cloud infrastructure. We call this system <strong><em>Cloud Inventory</em></strong>.</p><p>Over the last few months Klarna has:</p><ul><li>Rolled out over 100 automated controls enhancing every aspect of our configuration management (security, governance, and operational excellence), each control aimed to help system owners to identify and fix violations quickly.</li><li>Reduced the lead time of rolling out a control from several weeks to a matter of minutes</li><li>And as a result, successfully completed several large-scale cloud infrastructure optimization projects, such as a company-wide clean-up of RDS and EBS snapshots, without any incidents.</li></ul><p>In what follows, I will walk you through the foundation and the building blocks of <strong><em>Cloud Inventory</em></strong> as well as share some of the use cases it enables.</p><p><strong>In the beginning there was a SystemID</strong></p><p>It is often simple statements that are most important. Let’s begin with a fundamental statement about configuration management that the whole Klarna technology rests on: <strong><em>every system at Klarna must have a SystemID</em></strong>. A SystemID is a unique identifier of a system. It is hard to give a “scientific” definition of what the scope for a given SystemID should be, but we are following some guiding principles:</p><ul><li>A SystemID should not cover multiple things that are naturally handled by different teams</li><li>A SystemID should cover things that are logically developed as a unit which interact through non-published APIs, loosely defined as “code base”</li></ul><p>SystemID is required in several key processes of the software development lifecycle. For example, without it, one cannot create an access group or set up an AWS account. This simple yet powerful concept helps to define the boundaries of a system. Klarna maintains a dedicated systems registry in order to store and manage the lifecycle of SystemIDs. In the context of Cloud Inventory, System Owners are required to tag their cloud assets with SystemID tags (preferably by configuration), and it enables <strong><em>everything</em></strong> — ownership, accountability, governance.</p><p><strong>Cloud Inventory</strong></p><p>Now, onto the main secret, which is quite simple! As the whole Universe is based on a concept of graph theory where love is an edge, no wonder graphs are so helpful in configuration management. We employ a graph database solution to build a model of our cloud inventory featuring teams, systems and related ICT assets. For example, the graph example below represents a system “Klarna App” along with its assets and dependencies.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*PJJjcezSvOEpnXEv" /><figcaption>Pic 1. A system and its assets represented in a graph</figcaption></figure><p>This representation includes both cloud resources owned by the system — such as instances, databases, security groups, load balancers, VPC endpoints — as well as ICT assets external to our cloud providers, such as artifact repositories, threat models, user access groups, experimentation platform features, Kafka topics, and more. The most valuable aspect is that all these assets, residing in various siloed sources of truth across the company, are unified into <strong><em>a single graph with established relationships </em></strong>— all thanks to the little thing called SystemID. This also explains why we do not tend to rely on AWS-specific solutions like AWS Config — they are severely limited since they can only involve AWS data but also lack context when it comes to change management (more on that below).</p><p>How do we achieve this? There are two main components to Cloud Inventory:</p><ul><li>A graph database. We currently use a <a href="https://www.jupiterone.com/">third-party platform <em>JupiterOne</em></a><em> </em>which provides us a database that has direct integrations with our cloud providers. The database acts like a “giant cache” of Klarna’s cloud inventory, fully refreshed every few hours.</li><li>A set of ETL jobs. We have built synchronization jobs that run at specified polling intervals to set up custom integrations with our internal sources of truth — such as organizational data and the systems registry. These integrations are established through dedicated S3 buckets with distributed ownership, where different teams own various parts of the inventory.</li></ul><p>The central question that the Cloud Inventory solution enables us to answer is: <strong><em>“What is the current state of what we own as a company in the cloud?”.</em></strong> With the support of the graph query language, we can break down this question into specific queries, obtaining granular-level answers.</p><p>Below, I will present a use case illustrating how we assist Klarna system owners in optimizing their inventory — leading to operational cost savings and fewer incidents.</p><p><strong>So what is a target spec?</strong></p><p>Every engineer’s goal is to make sure their systems run reliably, securely and efficiently. Suppose a Klarna team runs a system with an RDS instance. This team has a dashboard displaying reports, such as one suggesting that <strong><em>“All production databases must be enabled for multiple availability zones”</em></strong>. Teams are expected to act on these reports within a specified SLA.</p><p>We call these reports “<strong><em>target specifications</em></strong>” because they define the target state which we expect an asset to achieve. Every target specification has several qualities:</p><ul><li>It has context — every target specification will have a short, concise and relatively human friendly description of what is expected of the team and why it is important to address it.</li><li>Evidence collection is automated — scoring whether an asset meets the expectation or deviates from it is expressed as a simple query</li><li>Evidence is precise and verifiable — evidence of an asset meeting or deviating from the expectation is on the exact configuration item (e.g. the exact database instance)</li><li>It is actionable — it explains how to achieve the target in a user-friendly way. The goal is that an Engineer should have everything they need to act on a target spec just by having a glance at it.</li><li>It is configured as code. Every target specification is essentially an <em>.md file </em>in a repository, meaning that anyone with basic Git knowledge can contribute and propose changes to both asset identification methods and the instructions provided to asset owners.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*nCQ_OGH7ODRPtlc6pC1AAw.png" /></figure><p>As you’ve probably noticed, target specifications are just organized and scheduled queries to the graph database, identifying deviations from the target state and exposing them to system owners.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*BrU5WgszqDFYEwmu" /><figcaption>Pic 3. How a target spec looks like in the repo</figcaption></figure><p>And what about impact? In the past few months, Klarna has released more than 100 target specifications covering nearly every aspect of configuration management — from database operations and tagging to logs and encryption. Target specifications have scaled well and are on track to fully replace manual surveys and reviews previously used for governance. They have proven highly effective. For instance, in enabling Multi-AZ for RDS instances, the number of identified violations has halved and continues to decline steadily.</p><p>Another straightforward example is a target spec for untagged database snapshots older than 90 days. At the time of its introduction, Klarna had roughly 1,500 such snapshots in its cloud inventory. One month later, this number dropped to about 500 as system owners proactively reduced waste, resulting in €10k — €20k monthly savings on the AWS bill from a single target spec.</p><p>Target specifications powered by Cloud Inventory have significantly enhanced Klarna’s security, reliability, and performance.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*FTMB90RF40RtHpzS" /><figcaption>Pic 4. A steady decline in the number of violating assets after activation</figcaption></figure><p><strong>Lessons Learned</strong></p><p>We’ve been working on Cloud Inventory out for a bit more than a year and here are some of the learnings so far:</p><ul><li><strong>Ownership matters.</strong> Since Cloud Inventory touches nearly every source of truth within the company, it was crucial to define the owners of the running integrations and data in the graph. Clear ownership is key to successful scaling and incident resolution and without it, things are doomed to deteriorate over time.</li><li>Change management starts with being <strong>clear on expectations</strong>. The set format of target specifications forced us to formulate expectations in a concise and clear manner, increasing the likelihood of teams acting on violations. We also learned that even small changes with a limited scope are best conducted via target specs, as they can be scaled very gracefully and teams benefit from having every kind of change request on a single dashboard.</li><li><strong>Focus on the data model</strong> when dealing with graphs. We quickly learned that the complexity of AWS integration alone is substantial. Scaling Cloud Inventory to 20+ different integrations was an opportunity to introduce chaos. Therefore we dedicated considerable time to aligning data standards (e.g., entity classes and types) and naming conventions to ensure Cloud Inventory scaled gracefully.</li><li><strong>Simplification is key.</strong> Overall, in the case of target specifications, we chose to opt for a very straightforward, almost “one-size-fits-all” model targeting every type of asset and it suited us very well.</li><li><strong>Show value early.</strong> Demonstrating immediate direct value was essential to convince our customers within Klarna to integrate their data sources with Cloud Inventory. Initially, the central team drove the integration process by selecting and implementing sync jobs with the most relevant data sources. However, after a few successful cases that proved the value of established integrations (such as the ability to set up relevant target specs), we began to see data source owners proactively proposing integrations. We’ve transitioned from a “supplier push” to a “customer pull” in our rollout.</li></ul><p><strong>Next steps</strong></p><p>Although Cloud Inventory is now an integral part of the Klarna Engineering Platform, our journey is far from over. Our current goals to advance it further include:</p><ul><li><strong>Expanding data sources</strong>. We still have some data sources not yet covered, which we are eager to bring in to enable more transparency and control. Our current focus includes ingesting an inventory of Jenkins instances, team on-call schedules, and code repositories.</li><li><strong>Leveraging AI</strong>. We are actively using Klarna’s internal AI Assistant to help teams to validate any manual inputs to the graph’s entities. For example, AI is already helping to reason about and cross-check information on system attributes (such as availability, integrity, confidentiality classifications) and system dependencies. In the future, we aim to empower our users with AI-driven support for managing the data model and constructing queries to the graph.</li><li><strong>Making better use of positive evidence</strong>. We aim to expand the perspective of target specifications as automated controls, integrating “good evidence” in addition to violations. This will not only act as positive motivation for system owners but also align with risk control frameworks and facilitate regulatory compliance.</li></ul><p>By continuously improving Cloud Inventory, we strive to set new benchmarks in cloud configuration management, driving operational excellence, and assisting Klarna system owners in making smart choices about their cloud assets. I am excited to see what comes next!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=723cd3c49d46" width="1" height="1" alt=""><hr><p><a href="https://engineering.klarna.com/how-i-stopped-worrying-and-learned-to-love-cloud-inventory-723cd3c49d46">How I stopped worrying and learned to love Cloud Inventory</a> was originally published in <a href="https://engineering.klarna.com">Klarna Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Learnings from a Klarna Engineer on feature development]]></title>
            <link>https://engineering.klarna.com/learnings-from-a-klarna-engineer-on-feature-development-9780c7870f3c?source=rss----86090d14ab52---4</link>
            <guid isPermaLink="false">https://medium.com/p/9780c7870f3c</guid>
            <category><![CDATA[engineering]]></category>
            <category><![CDATA[klarna]]></category>
            <category><![CDATA[learning-to-code]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[software-engineer]]></category>
            <dc:creator><![CDATA[Julien Avezou]]></dc:creator>
            <pubDate>Mon, 14 Apr 2025 09:05:53 GMT</pubDate>
            <atom:updated>2025-04-14T09:05:53.398Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*lgqElr8phgb6uLYQxwqSOg.jpeg" /></figure><p><em>In the world of FinTech, where regulations and innovation collide, my team at Klarna implemented new ways for users to recover access to their Klarna Card and Klarna balance accounts on their device. This posed a series of challenges with a whole range of aspects to consider from product, usability, security, scalability and regulatory. With the feature now released, I would like to share key learnings from working on this complex feature.</em></p><p><strong>Documentation is Key</strong></p><p>Starting to document early on in the project is crucial. We generated documents at all stages of our project in order to gather feedback at a technical, product and design level. This helped in aligning with teams, enabling smooth collaboration, and setting a solid foundation for feature development and delivery.</p><p>These documents allow close and continuous collaboration between stakeholders from both product, technical and design perspectives from the early stages of feature discovery to the start of implementation. However documentation doesn’t stop there, once the implementation is done, it is equally as important to capture in writing your feature from both product and technical aspects, supported by architectural diagrams and swimlanes, so that stakeholders within the company have a clear reference when interacting with the feature in the future.</p><p><strong>Always communicate</strong></p><p>Establishing open communication channels, such as dedicated Slack channels for internal stakeholders, and providing regular updates in order to foster collaboration. Promptly raising blockers and organizing group discussions with colleagues from various competences ensures transparency and helps in addressing challenges effectively. Having an open channel to discuss and update on progress also helped streamline our communication and avoid unnecessary meetings. Having an open channel also serves as an accountability mechanism and a great way to keep track of the conversations and topics over time. The dedicated channel also provides an easy way to share documents and highlight the important ones for quick and easy access by adding them to the channel bookmarks. Our team saved countless hours spent in meetings by setting up and maintaining a dedicated Slack channel from the start.</p><p><strong>Squash biases with Bug Bashes</strong></p><p>A Bug Bash is a structured testing event where team members and stakeholders come together to identify software bugs collaboratively based on a set of instructions and guidelines. A Bug Bash serves as a valuable session to spot hard-to-find bugs resulting from tunnel vision and owner bias. By leveraging the diverse talents within the company and inviting representatives from various stakeholder and non-stakeholder groups, we identified critical issues early in the process, enhancing overall product quality. For example, one stakeholder tested a flow in the app that their team owns as an integrator, which we didn’t cover on our end in our testing. This led to the discovery of a major bug that we fixed in time before the release.</p><p><strong>Beyond Engineering, think Product</strong></p><p>Combine the product and ownership mindset with engineering to ensure alignment is kept between development efforts with the product roadmap. Understanding user needs and product goals ensures that engineering decisions are driven by a customer-centric approach. Through taking ownership of the product, we organized regular demos with stakeholders. These touchpoints were extremely valuable to us, keeping us accountable while also getting early feedback in the process. A better understanding of the product also allowed us to better grasp the potential value and impact of our feature and how to measure impact after the release, in terms of metrics and KPIs. With this mentality, we also better understood the needs of all kinds of stakeholders including internal ones. We baked in mechanisms for easier internal testing and improving Developer experience within the company as a whole. An area often overlooked when building new features.</p><p><strong>Pragmatism in Execution</strong></p><p>Striking a balance between quality and efficiency is vital. Having a clear implementation strategy, understanding the release process, and being accountable for milestones helps in delivering high-quality features within set timelines. As an example, we started with the in-app screens while the technical discovery was still ongoing, so that we already had screens to work with when adding the logic. We also took the decision to implement in-memory databases for our models before actually persisting the data once we were fully confident in our data handling and relationships between the different models, thus allowing us to move faster and avoid subsequent unnecessary database migrations.</p><p><strong>Teach, Support, Learn, Repeat</strong></p><p>Understand early on your own strengths and weaknesses and how they fit within the dynamics of the team. This way you can optimize the time spent by supporting the rest of the team with what you already know while also sharing this knowledge by teaching others. This can be done via pair programming sessions, hosting frequent and concise knowledge sharing sessions, and fostering a culture of collaborative code reviews. If everyone shares the same mentality, you have the opportunity to learn from others, so seize this moment to expand your knowledge, learn new things, improve on your weaknesses or gaps in knowledge. For example, I learned about different backend strategies to encapsulate logic within service functions and learned to work with redux toolkit for the first time in the client.</p><p><strong>Be smart about your time management</strong></p><p>To stay focused on the implementation of complex features while managing daily tasks and duties, developing a structured approach is essential. A strategy used, such as the 3/3/3 method can be particularly effective.<em> (Oliver Burkeman, author of “Four Thousand Weeks: Time Management for Mortals.”) </em>Begin your day with a focused 3-hour deep dive into feature development, followed by accomplishing 3 urgent tasks and then addressing 3 maintenance tasks. This cyclical routine enables you to prioritize your core feature work while ensuring that essential daily tasks are consistently managed. There are days where it is difficult to respect this structure, due to incidents occurring, open investigations or workshops, but this is completely fine. This time management technique only serves as a general path to follow, not as a rigid rule to abide by. It can sometimes be beneficial to break the routine and have a mob programming session or hackathon to dive deeper into a certain topic.</p><p><strong>Don’t overlook code hygiene and maintenance</strong></p><p>Adhering to the revered “Leave the code cleaner than how you found it” principle, inspired by the Boy Scout rule outlined in Clean Code, embodies a crucial philosophy for developers. Actively seek opportunities to enhance code quality through refactoring, thereby making it more organized. For instance, our team invested efforts in converting our JavaScript codebase over to TypeScript when files were touched as part of the feature development. We also ensured that existing routes comply with our internal guidelines and software best practices. These represent proactive steps toward maintaining a high standard of code cleanliness. To emphasize this point further, our team experimented with dedicating 2 days each month to maintenance work.</p><p><strong>Don’t forget to enjoy the ride</strong></p><p>Last but not least, don’t forget to have fun while learning! Working on a big feature demands a high level of effort and can exert a lot of pressure and stress. Even so, embrace the challenges and be grateful for the opportunities to learn and grow alongside your colleagues. We experienced a major incident following the initial release which was stressful. However we transformed this into an opportunity to dig deeper into the existing code to understand and solve the issue, which also led us to solving longer standing issues.</p><p><em>This article unraveled the intricacies of developing the Account Recovery feature at Klarna, shedding light on the learnings gathered in the process. Embracing these key takeaways helped my team and I pave the way for success in navigating this complex feature, within a fast-paced FinTech landscape such as Klarna. I hope these learnings can also serve you when implementing your own features.</em></p><p><em>Enjoyed the post and want to stay updated on our latest projects and advancements in engineering?</em></p><p><em>Join our Klarna Engineering community on Medium and LinkedIn.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=9780c7870f3c" width="1" height="1" alt=""><hr><p><a href="https://engineering.klarna.com/learnings-from-a-klarna-engineer-on-feature-development-9780c7870f3c">Learnings from a Klarna Engineer on feature development</a> was originally published in <a href="https://engineering.klarna.com">Klarna Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How micro should your microservices be?]]></title>
            <link>https://engineering.klarna.com/how-micro-should-your-microservices-be-9ae7507a625c?source=rss----86090d14ab52---4</link>
            <guid isPermaLink="false">https://medium.com/p/9ae7507a625c</guid>
            <category><![CDATA[microservices]]></category>
            <category><![CDATA[software-architecture]]></category>
            <category><![CDATA[klarna]]></category>
            <category><![CDATA[modular-monolith]]></category>
            <dc:creator><![CDATA[Raya Rizk]]></dc:creator>
            <pubDate>Mon, 24 Mar 2025 07:22:27 GMT</pubDate>
            <atom:updated>2025-03-24T07:22:27.194Z</atom:updated>
            <content:encoded><![CDATA[<h3>Our journey towards striking the right balance</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*33z7mFsJHKrzSYblhVwCnw.jpeg" /></figure><p>The debate between monolithic and microservices architectures is a hot topic in software development. While monolithic systems are known for their simplicity and tightly integrated structure, they face challenges with scaling and flexibility. In contrast, microservices offer greater scalability and autonomy in development, but carry complexity in inter-service interactions. In this article, I’ll share our experience in navigating between these two paradigms while working on a recent project at Klarna, exploring different architectural decisions while addressing a fundamental question: what is the optimal granularity for a microservice?</p><h4>From monolith to microservices: the company is growing 🚀</h4><p>A monolithic architecture is often the natural starting point for businesses, serving well initially but revealing its limitations as organizations scale. Klarna was no exception. Like many in the IT industry, the company embraced the microservices paradigm alongside its rapid growth a few years ago.</p><p>In the payments domain, we are focused on offering customers various payment options, allowing them to choose between paying directly, later, over time, or through other tailored methods. This demand for diverse options led each payment method to evolve into a distinct microservice, managed by dedicated teams. Each payment service acts as a key orchestrator in the purchase flow, coordinating with other services to guide customers through the required steps until order completion.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*C1jBI0Tsbrr3QuQy2Qaqvw.png" /></figure><p>This adoption of microservices naturally aligned with the company’s organizational structure, offering team autonomy and the ability to scale services independently. Each service was self-contained, with its own database and code residing in a separate repository. By decoupling payment options into distinct services, we gained greater flexibility and enabled faster development cycles, as teams could focus on their respective components.</p><h4>Landing the distributed monolith: a complex setup 🏗️</h4><p>While the different payment options at Klarna serve unique purposes, they share underlying similarities. Regardless of the payment option a user selects, the end goal remains the same: “to pay”. Certain checks and steps are common across all payment options, such as user authentication, funding source collection, and risk assessment.</p><p>Since the microservices were handling similar functionalities, any new requirement had to be implemented across all services. Additionally, changes or maintenance needed to be executed multiple times by different teams. This led to numerous instances of duplicated code, challenging maintenance, scalability issues, and the unexpected cost of managing multiple services.</p><p>This situation raises critical questions: Do we really require microservices? Or has our excessive division led to the creation of a distributed monolith?</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*bO8HqBFHtZG91nncVGhvjg.png" /></figure><p>Microservices excel when applied to truly independent business domains with distinct scaling needs, separate release cycles, and autonomous teams. However, our use case was different — we encountered shared business logic, interdependent features, and similar scaling patterns. This, unfortunately, resulted in us creating a distributed monolith, with an ever-increasing complexity due to multiple codebases, databases, and deployment pipelines. The organizational impact was significant, as teams spent more time coordinating changes than delivering value, while struggling with the cognitive load of managing multiple services.</p><p>While microservices may appear well-suited for diverse scenarios, they are not a one-size-fits-all solution. Sometimes, even the best-intentioned implementations can lead to unforeseen costs. Adopting microservices, or any solution for that matter, can address certain challenges but may also introduce new, complex ones.</p><h4>From microrepos to monorepo: a recovery attempt 🚑</h4><p>In an effort to address the issues we faced with our microservices setup, we attempted to share common functionalities across services by reverting to a monorepo structure while keeping the services separate. The monorepo aimed to centralize code management and reduce redundancy, offering easier collaboration and alignment across teams. However, this quickly resulted in a monorepo cluttered with duplicate code, making it challenging to navigate and understand. Extracting common functions was both difficult and error-prone. Although the functionalities were similar, slight variations in their implementation complicated the process, making it anything but straightforward. Consequently, the cost and effort required to manage and maintain this setup remained high.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*D1mqmh0N9mOHHcjamitumw.png" /></figure><p>While monorepos can facilitate code sharing and cohesiveness, they require strong governance and standardization across teams to be effective. Merely consolidating code without addressing fundamental architectural challenges proved insufficient.</p><h4>From distributed monolith to modular monolith: a better future ☀️</h4><p>Faced with the challenges of a distributed monolith, what options do we have? Should we simply revert to the traditional monolith we had, with all its known limitations? Not necessarily.</p><p>Instead, by carefully considering both business needs and technical contexts, and following Domain-Driven Design (DDD) principles, we can identify a more balanced solution. DDD emphasizes structuring software around core business domains, recognizing that the ideal size of a service often aligns with a broader business domain or entity rather than focusing on granular features. For instance, instead of concentrating on individual “payment method options”, we consider “payments” as a cohesive whole. This shift in perspective allows us to consolidate common functions and organize the system into well-defined modules that focus on shared features, resulting in a more appropriately structured architecture.</p><p>In practice, our modular approach goes beyond simply separating the available payment options (“pay now”, “pay later”, “pay over time”, etc.) into distinct modules. Instead, we break down the application based on features. This results in clearly defined modules addressing specific functions — such as authentication, funding sources management, and risk assessment, among others. Each module represents a specific business domain with clear boundaries and responsibilities, enabling better organization and maintenance of the system.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*RynJh-yIkTFDMnRd0ai-uQ.png" /></figure><p>By dividing the large system into smaller, manageable modules within the same application and limiting interactions across their boundaries, we achieved the desired balance: one service, one database, and a structured, clean codebase. Each module operates within a well-defined context, enabling isolated development and testing while eliminating the overhead of remote calls and the duplicated functionality that is common in microservices. In other words: thoughtful modularization combines the simplicity of a monolithic architecture with the flexibility of loosely coupled components.</p><p>The benefits extend beyond code organization. The modular architecture enhances observability through standardized logs, metrics, and dashboards, eliminating the complexity of managing numerous service-specific monitoring solutions. This unified strategy simplifies troubleshooting while ensuring consistency across the entire system. Moreover, these well-defined modules become natural candidates for future microservices if needed, making the modular monolith an excellent foundation for incremental architecture evolution.</p><p>Although this approach has clear benefits, it also brings challenges. The modular monolith presents an increased risk of a single point of failure; if the application crashes, it can impact the entire system. Additionally, managing a unified codebase across multiple teams in a single monorepo requires clear standards and practices for coding, testing, and deployment. Coordinating development efforts, with numerous commits and simultaneous changes, requires robust communication and aligned release cycles.</p><h4>The power of modularity: striking the right balance ⚖️</h4><p>While we consolidated the payment services into a modular monolith, this single service itself remains a key microservice in the purchase flow at Klarna. It continues to both integrate with and be integrated by many other services.</p><p>A significant challenge remains in creating a universal payment flow that accommodates different payment options across various markets, each with distinct functionalities and market-specific requirements. The solution here lies in customization through configurability. By developing one configurable flow, we can create a versatile set of features and steps that function like puzzle pieces or modular building blocks. These components can then be assembled and tailored through various configurations to efficiently support diverse payment options.</p><p>The migration to the single service is still ongoing, yet the new solution is already proving its effectiveness. By leveraging configurability, we were able to add new features to existing payment options and launch new payment options in various markets within days with minimal development resources — simply by adjusting configurations and running tests, many of which passed on the first attempt. Previously, such deployments would take weeks or even months, requiring coordination across multiple teams and extensive testing to ensure a unified solution. This streamlined approach has significantly reduced both time to market and resource requirements.</p><p>In terms of load, the service already handles over a million orders daily, demonstrating its robustness and scalability. This capability was put to the ultimate test during Black Friday, Klarna’s most challenging day for peak load and purchase volume. The system not only withstood the stress test but set a new record by processing one-third of all Klarna transactions that took place that day. Given this proven performance and significantly lower resource usage, the financial advantages become clear — why incur the overhead costs of microservices when a dynamic modular monolith can handle such scale efficiently?</p><p>We look forward to completing the full migration, which will enable streamlined, unified processing for all Klarna payments while maintaining optimal resource utilization.</p><h4>Key takeaways: optimizing architecture choices 🎯</h4><p>Understanding the nuances between different architectural styles is essential for making informed decisions. The following table offers a side-by-side comparison of microservices and modular monolith approaches, highlighting their strengths, challenges, and typical use cases.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/853/1*FSw9UQDk0FsP7qZxINeyjQ.png" /></figure><p>In software development, cyclical trends are inevitable. As projects and organizations evolve, growth may necessitate splitting services again. By establishing a robust framework and embracing meaningful modularization — guided by Domain-Driven Design (DDD) principles and its focus on core business domains — teams can adapt their architecture to meet changing requirements without compromising system coherence or maintainability. The experience gained from modularization has revealed a key advantage: modules can be seamlessly extracted into microservices when needed. This flexibility enables teams to work within well-defined bounded contexts while maintaining the agility to restructure the system as necessary. Ultimately, this approach provides a sustainable path for continuous growth and innovation, allowing teams to navigate software development cycles with greater confidence and efficiency.</p><p><strong>Acknowledgement</strong></p><p><em>Many thanks to all the contributors for their support on this project, especially Alessandro Dal Bello, Batıkan Türkmen, Carlo Micieli, Francesco Maria Chiarenza, Mikael Vessgård, Niklas Peil, and Sanchi Goyal.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=9ae7507a625c" width="1" height="1" alt=""><hr><p><a href="https://engineering.klarna.com/how-micro-should-your-microservices-be-9ae7507a625c">How micro should your microservices be?</a> was originally published in <a href="https://engineering.klarna.com">Klarna Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The fellowship of the forgotten]]></title>
            <link>https://engineering.klarna.com/the-fellowship-of-the-forgotten-d341045a6123?source=rss----86090d14ab52---4</link>
            <guid isPermaLink="false">https://medium.com/p/d341045a6123</guid>
            <category><![CDATA[erlang]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[klarna]]></category>
            <category><![CDATA[postgresql]]></category>
            <dc:creator><![CDATA[Onno Vos Dev]]></dc:creator>
            <pubDate>Wed, 26 Feb 2025 09:04:27 GMT</pubDate>
            <atom:updated>2025-03-12T11:20:52.248Z</atom:updated>
            <content:encoded><![CDATA[<h3>How we migrated from Mnesia to Postgres with zero downtime</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/1*03pOkSaJMc28EH1uzbH08g.jpeg" /></figure><p>Back in December 2004, an Erlang application was born called KRED (referring to the freshly-started company called Kreditor, now known as Klarna). KRED is one of the “servicing systems” at Klarna and keeps track of consumer debt (among other things). It was powered by <a href="https://www.erlang.org/doc/apps/mnesia/mnesia.html">Mnesia</a> and consisted of a cluster of 7 nodes, each holding a full copy of the database on disk. The data was replicated using a custom replication mechanism built in-house by Klarna. One node was elected as the leader and its database was considered the source of truth in the system. All database transactions were executed on the leader and writes were replicated to the rest of the nodes, the so-called followers.</p><p>The Mnesia database was around 15 TB and at its peak in 2018 around 1.3 TB was held in memory at all times. Considering that few suppliers were selling hardware with such specs, it’s easy to claim the crown of one of the biggest Mnesia databases in terms of in-memory storage, that was running in production. The rest of the data was offloaded to disk using <a href="https://github.com/klarna/mnesia_eleveldb">mnesia_eleveldb</a>.</p><p>KRED has been a stable workhorse at Klarna so why change a winning concept?</p><p>Get ready, for a two part blog post where we’ll first go through our journey of how we went about this and secondly, how we made Mnesia behave just like Postgres and implemented our version serializable isolation level on top of Postgres!</p><h4><strong>How the journey started</strong></h4><p>Three engineers, sat down in a bar in Stockholm, Sweden and asked this question:</p><blockquote>‘When Klarna truly takes off, will KRED survive? Assuming “no”, and presented with a blanco check, how would we tackle this problem?’</blockquote><p>The answer quickly revolved around the issues of running Mnesia on an even larger cluster and with leveldb compaction hitting some hot tables during peak times. One can only imagine how that problem would just continue to get worse over time.</p><p>Considering the three engineers had worked on KRED for a long time, scaling KRED was an intriguing thought and the evening was concluded with an agreement to explore this plan in depth further. While some afterwork talks never lead to anything productive, this one did!</p><p>The trio had several sessions together and drafted a plan on how to solve KRED’s scaling issues by sharding the system across multiple clusters. An ambitious plan and certainly a challenging one. But hey, shoot for the stars, aim for the moon, right?</p><p>The deal was simple: give us a blank check, a few engineers, a couple years of runway we will execute on the plan with the newly formed team. Well, I wouldn’t be writing this article if that meeting didn’t go well. The target was:</p><blockquote>“Scale KRED in whatever way you see fit, it may be sharding, it may be something else but scale it, that’s the goal!”</blockquote><p>A team of 6 Engineers, a manager and a product owner were assigned to the task and it was time to start hacking!</p><h4><strong>Investigation phase</strong></h4><p><strong>Sharding KRED<br></strong>Since our goal was to: “Scale KRED in whatever way you see fit”, our first task would be to investigate exactly “what way” would be “the right way”. Seeing as the team already had a plan on sharding KRED on paper, we started investigating whether or not this plan was actually feasible.</p><p>The idea was simple: each KRED cluster would elect its own leader, use Mnesia, leveldb and run the exact same KRED codebase and follow the same release process. Each cluster would be of a limited database size to avoid hitting limits such as number of database locks obtained per second, number of transactions committed per second, amount of bytes written per transaction and the amount of disk space and RAM required by each instance. Each of these were limits that KRED would eventually hit one way or another.</p><p>After a thorough investigation the idea was put on ice. The reason was simple: there were too many complexities and missing pieces of the puzzle to make this happen. Sharding KRED required a lot of surrounding systems to be adjusted to operate against more than one KRED cluster and none of them were ready for such a change at the time. Furthermore, several critical pieces that were required weren’t even built so it was decided, Klarna was not (yet!) ready for a sharded KRED.</p><p><strong>Moving the database out of the application nodes<br></strong>One rainy day in November 2019, the team decided to do some mob programming just to see if it was possible to run KRED on a SQL-database rather than Mnesia. The rationale was simple: using a 3rd party SQL-database would avoid a lot of the issues we’ve seen on Mnesia and perhaps, migrating to a SQL-database would be the answer?</p><p>We’d be getting:</p><ul><li>Fully managed — ergo a lot less maintenance overhead;</li><li>Cost effective — using a single database instead rather than having seven full copies would be a lot more cost effective;</li><li>Reliable — despite us not having too many difficulties with the reliability of Mnesia, we were one of the biggest users of Mnesia and mnesia_eleveldb. Hence, we did not have the luxury of having the community find bugs and issues before they hit us like we would when moving to Postgres.</li><li>Ability to independently scale the application nodes from the database;</li><li>Ability to leverage modern database features such as efficient secondary indexes, rich query language and schema evolution;</li><li>Standardized tooling around the operational aspects such as backups and deployment.</li></ul><h4>Simple plans are never simple</h4><p>In its essence the plan was simple: introduce a follower node running Postgres as the database backend and slowly push it towards the leader role one step at a time. Once the leader is backed by Postgres, the Mnesia nodes simply become followers and we can replace them with KRED nodes that no longer hold a database on disk, but talk directly to a Postgres database.</p><p>Just like our original sharding plan, this plan sounded simple, but in practice there were a lot of moving parts involved and plenty of unknowns to be solved. The key to success in this project was to break each step to the end goal up into smaller epics where each should bring value to KRED regardless of whether or not the end state would even be achieved. Ergo, even if we wouldn’t be able to migrate KRED to Postgres in the end, we should end up with a better KRED either way. At any point in time, we should be able to pull the brakes and still have a net positive effect on KRED. Considering the large amount of unknowns of a project of this size and risk, this has been key throughout the past years in order to not feel overwhelmed by the end state but rather stay focused on smaller deliverables where each contributed to our end state. Only at the very end of the project, the deliverables should be allowed to grow in size and be more and more focused towards the actual migration. And only in the very last step, we should have a point-of-no-return type of moment.</p><h4>Our master plan</h4><p><strong>The compatibility layer known as kdb<br></strong>KRED business logic has not been allowed to interact with Mnesia directly for many years. Instead, all calls went through a wrapper that we controlled called kdb. The original purpose of kdb was to implement our in-house data replication mechanism but it doubled perfectly as a compatibility layer during this migration. We decided to keep the external API of kdb largely intact but behind the scenes allowed the storage of data to be either in Postgres or Mnesia.</p><p>With this approach we could minimize the changes in the business layers. A few minor changes had to be made however such as introducing multi-read and multi-write functionality. This was trivial to implement on Mnesia as you’d simply iterate the 10 keys or records and call out to Mnesia one by one. This was still fast as the data is local to the OS process. On Postgres however, reading 10 records from a single select was much faster than performing 10 selects one-by-one.</p><p>The key here was to make sure that both Mnesia and Postgres behave the same way and hence lots of tests, including <a href="https://propertesting.com/">property based</a> tests, were written to ensure that both performed equally.</p><p><strong>Introduction of types<br></strong>Mnesia stores untyped records (tuples) whereas Postgres requires types to build its columns. At the very beginning of this journey we simply created a key-value Postgres where both the key and value were a <a href="https://www.erlang.org/doc/apps/erts/erl_ext_dist.html">term_to_binary/1</a> representation. The key being the actual key and the value being the entire record. Naturally this was limiting to us in many ways but one of the main limitations was that <a href="https://www.erlang.org/doc/apps/erts/match_spec.html">match specifications</a> could not be executed, and in order to do any kind of filtering, the entire table contents had to be brought back into memory for Erlang side filtering. Hence, the journey began to add (and enforce!) types to all of our records.</p><p>This was achieved by iterating the full 15+ TB (at the time) database and for each field in each record, determining its type. Each time a new type was discovered for a particular field, we combined these types in order to determine a type that would fit both of these types. Let’s look at a few examples to make this clear.</p><p>Given the following three records:</p><pre>#purchase{id = 1234, amount = 100, description = &quot;shoes&quot;}<br>#purchase{id = 123456, amount = 200, description = &quot;&quot;}<br>#purchase{id = 12345678987654321234, amount = 300, description = undefined}</pre><p>Given that we iterate all 3 records, we can see that an id of the first record can be placed in an int2 as it is within the range of <em>-32768</em> to <em>+32767</em>. The second record can be placed in an int4 range (<em>-2147483648</em> to <em>+2147483647</em>). At this point we would upgrade it’s type to int4 since both id’s would fit in this type. The fourth record however would only fit in a numeric datatype so we had to upgrade the <em>#purchase.id</em> field to a numeric datatype in order to fit all possible ids.</p><p>The amounts were more straightforward as all of them would fit in an int2 range. Considering that amounts however are somewhat arbitrary, we decided that such fields should be upgraded to a higher int range in order to not run into trouble for example an int8 in order to allow really large purchases to not crash the database. Since amounts always have to be present this would end up being a non nullable int8.</p><p>The description is more interesting again as here we see three types, a string, an empty string and the atom undefined. In this case we can treat undefined as a null value and hence this would result in a nullable string.</p><p>Erlang however does not have a null concept and <a href="https://github.com/epgsql/epgsql">epgsql</a> treats undefined as nulls. KRED was inconsistent in treating undefined as if it were a null value. In KRED <em>null</em>, <em>undefined</em> or <em>[]</em> were all used to indicate a null value in records. Eventually, we ended up having to treat each of these as nulls and keeping track for each field (if nullable) what its null value should be. On the way to Postgres, we could transform the null value to undefined in order to store a null value and on its way back from Postgres transform it back to the null value that the application is expecting.</p><p><strong>Replication from Mnesia to Postgres, and back again<br></strong>One of the key aspects to this migration was the ability to switch back and forth between Postgres as leader and Mnesia as a leader. This meant that replication between Postgres ↔ Mnesia had to work in both directions. We wanted to have the ability to switch back to Mnesia if we saw any unforeseen issues with Postgres as the leader. Sounds easy enough on paper but it turned out to be quite the headache in practice.</p><p>The first step was to implement a replication mechanism from Mnesia to Postgres. Considering we already had a transaction log published out of KRED which Mnesia followers used to update their database with, this initially seemed fairly straightforward. As everything in this project, it turned out not to be. One of the problems we encountered was the added latency of Postgres caused the replication to be lagging and not keeping up with the sheer volume of transactions coming out of KRED.</p><p>We ended up building a pipelined replication mechanism where a worker process was spun up which was responsible for building up and flushing a batch of transactions. A worker took transactions and batched them up. Once a configurable batch size was reached it would start flushing the transactions to the database.</p><p>Two distinct modes were available for the replication mechanism: high throughput mode, and low latency mode.</p><p>In the high throughput mode, we would give up consistency of the database in favor of import speed. The node would not accept any traffic and all it had to do was import transactions as fast as it could in order to catch up again.</p><p>Once caught up the node would automatically switch back to low latency mode and traffic would be allowed on this node again. In low latency mode two strategies existed.</p><p>One strategy was to split big transactions into several smaller ones and handle them in parallel. This was possible since the Mnesia replication did a similar thing where a single transaction would be committed on a per table basis rather than as a single snapshot. With this strategy, all operations on a single table were committed on their own rather than as a single unit.</p><p>The second strategy was to batch several smaller transactions into a bigger one and handle them serially. This allowed us to use multi-delete and multi-write as well as get rid of obsolete operations. Ergo, if a record was written twice in quick succession, only the last write would be written. Similarly, if a record was written but subsequently deleted, only the delete would be performed.</p><p>During low latency mode, workers would maintain a record-level locking on the Erlang-side (local to the node). Only those records where the worker could acquire a lock would be sent to the database. Records where no lock could be acquired, would be sent once a lock was released by one of the previous workers. Only once all locks were acquired and all database operations were performed, would the transaction be committed.</p><p>With all this in place, we were able to serve a Postgres follower with about a 20ms lag. A totally acceptable amount of lag in our case.</p><p>Now that we had replication from the Mnesia leader to the Postgres follower in place, what would come next? We obviously also needed replication from Postgres to Mnesia for when it was time to take over as a leader role. As previously mentioned it had to be possible to switch back and forth between an Mnesia leader and Postgres leader. This had to be possible with no downtime as switches happen regularly during maintenance or on the rare occasion due to server failure.</p><p>Two methods of achieving this were put forward as possible candidates. First, using logical replication where we could change the Mnesia followers to use logical replication in order to obtain a stream of all database events that happened. Essentially mimicking the replication mechanism that we had in place for Mnesia to Mnesia nodes.</p><p>Another method would be to use prepared transactions and transmit a transaction log across the cluster after having prepared a transaction using rpc calls just as before. Since, once a transaction has been prepared, it is extremely unlikely to fail. Again, our Mnesia based replication used a similar methodology so whatever worked for the past 15 years, surely would work for a transition period of a few months at most.</p><p>Both of these approaches came with their pros and cons. Logical replication would result in us having to rewrite large parts of the replication logic. An area of the code that has been fairly stable for many years without requiring a lot of attention. Changing this up 180 degrees would be very risky. Luckily it would have minimal impact on the Postgres leader node. Using prepared transactions would have little to no impact on the Mnesia followers while instead the performance penalty on the Postgres leader would be considerably higher.</p><p>In the end we opted for using prepared transactions due to its minimal risk as well as being close to the current method that was used for Mnesia replication. Surely you can see a trend here, can’t you?</p><p><strong>Validating the replication<br></strong>Ok, so we had a replication mechanism in place between Mnesia and Postgres. But how do we ensure that this replication works as intended? Naturally, we had plenty of tests to cover for this, including a bunch of tests that ran an entire cluster of KRED nodes to ensure that everything worked as expected in a clustered fashion. But we wanted more than that.</p><p>In the early days, we regularly compared our Mnesia backups with Postgres snapshots (each DB, row-by-row). Similar to <a href="https://github.com/udacity/pgverify">pgverify</a> which can compare CockroachDB with Postgres. Considering the size of the database, this operation took about 7 days before we’d get back a yay or nay on whether or not anything went awry.</p><p>What if we could validate the replication by keeping track of all the keys that were written, updated or deleted and ensuring that the same state on the leader exists on the followers? Well, considering that all our database operations were going through the kdb layer it was trivial to record the keys in a log and have a separate process that would rpc across the cluster to determine that the state ends up converging and being the same.</p><p>So given a transaction that performs the following operations:</p><pre>write(table1, key1, value1)<br>write(table2, key2, value2)<br>delete(table1, key3)<br>update(table2, key4, newvalue4)</pre><p>We logged the following structure to a <a href="https://www.erlang.org/doc/apps/kernel/disk_log.html">disk_log</a>:</p><pre>[ {key1, table1, write, Timestamp}<br>, {key2, table2, write, Timestamp}<br>, {key3, table1, delete, Timestamp}<br>, {key4, table2, write, Timestamp}<br>]</pre><p>We would then read the log in real time from another process and use <em>rpc:multicall</em> to check with all nodes across the cluster that the two writes as well as the update are propagated correctly and the same value existed in the database and the delete is propagated and the record should no longer be present anywhere.</p><p>As it turned out, this process was extremely good at finding false positives. Jokes aside after some tweaks, we had this running for almost a year before our final switch and it has helped us with the confidence needed that all databases (Mnesia replicas) as well as Postgres were always up to date and equal to each other. The feeling of confidence was well worth the few false alarms we got from it.</p><h4>The final switch</h4><p>After having run as the leader for some months, the day has finally come to switch off the Mnesia nodes and fully run on Postgres. All the runbooks for any possible foreseeable disaster were in place and we were all ready. We gathered a bunch of KRED teams into a Google Hangout and started the shutdown of the Mnesia nodes. Can you spot the timestamp when all Mnesia nodes were shut off on the below graph?</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/598/0*0cxXDxOOsS43r5Hq" /></figure><p>No? Well neither could we. The final shutdown of all Mnesia nodes happened at 10:00:41 to be precise. No change in traffic was observed and everything kept on sailing smoothly. Honestly, after 3 years of work this was probably among the most anticlimactic changes we’ve ever made.</p><p>In this context anticlimactic was a good thing! We had officially migrated from Mnesia to Postgres with zero downtime! Time to party? Certainly! And what a party that was! But… we had one itsy-bitsy bit of cleanup to do, namely we really wanted to get rid of prepared transactions.</p><h4>The last migration</h4><p>There are two methods for exporting data out of KRED. First one, there’s a specific node that exports specific tables to a Postgres database for business intelligence purposes. This node was destined to remain on Mnesia due to the massive rewrite it would take to move this specific node over to Postgres too.</p><p>The second method is exporting domain events in the form of Avro messages exported to Kafka. Both of these methods of exporting data use the transaction log, each in their own way. For event emission we used a specific data structure that was inserted into the transaction log which was used to atomically append events for a specific transaction to the transaction log.</p><p>Remember how we leveraged two phase commits in order to publish a transaction log across the cluster? Well considering the performance penalty of this, we wanted to move away from this as soon as possible and use plain logical replication instead.</p><p>One slightly unknown feature in Postgres was introduced in Postgres 9.6 (<a href="https://github.com/postgres/postgres/commit/3fe3511d">3fe3511d</a>) that allows passing arbitrary messages in either text or bytea format to logical decoding plugins via WAL. In essence, this would allow us to inject the transaction log entry into the WAL log and migrate the above two use cases to simply pick that message from a logical decoding stream and allow us to stop using prepared transactions. Testing showed that we could shave off roughly 20ms from each database transaction by moving away from prepared transactions so this was a price well worth paying. Unfortunately the pgoutput plugin only added support for this in Postgres 14 (<a href="https://github.com/postgres/postgres/commit/ac4645c0157fc5fcef0af8ff571512aa284a2cec">ac4645c</a>) so an upgrade of our Aurora Postgres from 13 to 14 was required.</p><p>Another change was required with the <a href="https://github.com/SIfoxDevTeam/epgl/issues/3">Erlang pgoutput decoder plugin</a> in order to support this new message format. Once in place, we could insert arbitrary transaction logs into Postgres WAL which could replace the transaction log that was replicated over Erlang RPC and subsequently remove our need to use prepared transactions.</p><h4>Final notes</h4><p>A refactor of this size is certainly not a trivial one. There’s plenty more that cannot be covered in this blog or else I’d be taking way too much of your time. There are a few key takeaways though that certainly can hold true and can help you tackle a project of this size.</p><ol><li><strong>Create a proof of concept </strong>— It may sound trivial but getting early feedback whether or not something even remotely appears possible is key here. It doesn’t have to be pretty, it just has to compile (if applicable) and “kinda run”. Whatever code you write for this PoC, consider it throwaway.</li><li><strong>Break it down into bite sized chunks</strong> — Sure, you’re not gonna know all the bites you’re gonna have to bite through, but it’ll help put things into perspective. Ensure that each bite sized chunk is a positive change to the codebase. The further you go, the more of a “point of no return” may be acceptable but I’d argue that at least 75% of our changes were changes that would prove useful regardless of whether or not we switched to Postgres in the end.</li><li><strong>Solid testing strategy </strong>— I’ll dive more into this in part 2 of this blog post but every single change we made, was tested and could be proven to be correct. We had plenty of metrics and tests to ensure our business code was kept intact and performant. We also had real-time monitoring to ensure that nothing broke along the way.</li><li><strong>Do not stop </strong>— Sh*t happens and it’s ok. Hardly any problems are unsolvable. But with the right attitude, patience and knowledge, projects such as these can be turned into a great success story!</li></ol><p>In <a href="https://engineering.klarna.com/guardians-of-consistency-caf10252313e">part2</a> we will focus on database isolation levels and how we managed to make Postgres behave like Mnesia and how we ensured our implementation of serializable on top of Postgres met the requirements for serializable isolation level.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d341045a6123" width="1" height="1" alt=""><hr><p><a href="https://engineering.klarna.com/the-fellowship-of-the-forgotten-d341045a6123">The fellowship of the forgotten</a> was originally published in <a href="https://engineering.klarna.com">Klarna Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Automating the Klarna Card Ownership Fees System using AWS Step Functions]]></title>
            <link>https://engineering.klarna.com/automating-the-klarna-card-ownership-fees-system-using-aws-step-functions-346ce7094278?source=rss----86090d14ab52---4</link>
            <guid isPermaLink="false">https://medium.com/p/346ce7094278</guid>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[klarna]]></category>
            <category><![CDATA[serverless]]></category>
            <category><![CDATA[step-functions]]></category>
            <category><![CDATA[automation]]></category>
            <dc:creator><![CDATA[Michel Neumann]]></dc:creator>
            <pubDate>Thu, 02 May 2024 07:42:27 GMT</pubDate>
            <atom:updated>2024-05-02T07:42:27.827Z</atom:updated>
            <content:encoded><![CDATA[<p><strong>This article outlines how my team and I applied automation using AWS Step Functions and CloudFormation on a system to charge the monthly fee for Klarna Cards, enabling us to transform a previously manual routine into a self-sufficient, scheduled workflow. The initiative significantly streamlined operations and reduced maintenance cost.</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*nr6R-4LVUt5jK0yVl49t8g.jpeg" /></figure><h4><strong>Introduction</strong></h4><p>In early 2023, Klarna introduced monthly fees for Klarna Cards in the US. In the Card &amp; Banking domain, two teams, including myself as engineer, developed this system within a tight four-month deadline. Initially, the system required extensive manual operation, including a detailed checklist for engineers to follow to ensure successful executions. The teams launched, planning iterative improvements of that routine.</p><p>Months passed by without any advancement in refining the operation process nor automating any part of it. To provide an overview of what needed to be done by the teams to run the batch jobs:</p><ul><li>Designating an engineer to lead the monthly process, coordinated using JIRA tickets</li><li>Updating exemption lists and submitting pull requests to the code-base prior to initiating batch runs</li><li>Ensuring data integrity by performing Athena queries across three different production databases within the AWS Console</li><li>Manually initiating multiple batch jobs in a specified sequence with manual input of arguments in a live production setting</li><li>Awaiting termination of the jobs and conducting a thorough review of the outcomes of the final batch jobs for each market</li></ul><p>Overall, this routine took around three business days per month and required two engineers to approve code changes and review the results of the batch runs. Considering new markets where fees may be rolled out towards, this workflow posed a significant challenge to maintaining high-quality standards and preventing potential incidents.</p><h4><strong>Taking on The Challenge</strong></h4><p>We recognized that continuing with our current process was unsustainable and bound to cause issues down the line. When the topic got priority, I took the opportunity to lead the initiative, dedicating my time to investigate the problem and propose a viable solution.</p><p>I created a “Request for Comments” (RFC) document, a formal method employed by Klarna for proposing ideas, to outline potential solutions and gather ideas. During this process, discussions and constructive feedback were shared. We concluded on committing to AWS Step Functions, a service which allows orchestrating multiple services into server-less workflows.</p><p>To manage expectations and set project milestones, I developed a detailed timeline. I estimated the completion of different phases of the automation project, including the initial MVP and the final implementation. Additionally, I outlined a series of implementation and discovery tasks to be integrated into upcoming sprints.</p><h4><strong>System Overview</strong></h4><p>For better context, I am going to describe the Ownership Fee system as it operated before the introduction of automation. The system is distributed over two AWS accounts: one directly owned by the team and another shared account with resources used by the Klarna App.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*Y6IuSbmIfH_56oky" /><figcaption>Architecture diagram of the Ownership Fees before automation</figcaption></figure><p>CloudFormation and AWS CDK are utilized to deploy resources. Most notably are two Glue jobs and two SQS queues with one of them being a “Dead Letter Queue” (DLQ), containing messages that could not be processed. The Glue jobs are reading data from three distinct databases, each managed by a separate domain and housed in yet another AWS account.</p><p>A Python script running PySpark will preprocess, join, and transform the data to generate JSON records, each describing a customer’s card information for a given month. These records are written into the SQS queue and processed by an AWS Lambda function, which is deployed in the shared AWS account. The Lambda function implements a decision tree which results in either triggering a fee charge using a dependency system or an exemption for the customer from the fees for the current month. Decisions made by the Lambda function will be stored in a DynamoDB table within the same account.</p><h4><strong>Adding Automation</strong></h4><p>To implement automation we created a so-called “state machine”, which describes a sequence of event-driven steps where each step in the workflow is called a “state”. A state represents a unit of work that can call any AWS service or API. In our case these states would consist of Lambda functions and Glue job triggers to solve the issues of the manual routine mentioned previously. Finally, we will need to add a scheduler to trigger the workflow on a predetermined interval to avoid having an engineer to invoke the system manually by logging into AWS Console and launching the state machine.</p><p>Leveraging the existing use of AWS CloudFormation, a new Stack using CDK has been created, exclusively containing automation resources. A Stack describes a modular grouping of AWS resources that can be independently managed and altered without impacting other stacks. In this stack, the state machine has been defined.</p><pre>import { Stack, StackProps } from &#39;aws-cdk-lib&#39;<br>import { StateMachine } from &#39;aws-cdk-lib/aws-stepfunctions&#39;<br><br>export class KlarnaCardOwnershipFeesAutomationStack extends Stack {<br>  constructor(scope: Construct, id: string, props: StackProps) {<br>    super(scope, id, props)<br>  }<br><br>  private provision() {<br>    const stateMachine = new StateMachine(this, &#39;state-machine&#39;, {<br>      stateMachineName: `klarna-card-fees-automation`,<br>      role: stateMachineExecutionRole,<br>      definitionBody,<br>    })<br><br>    // More resources will follow here...<br>  }<br>}</pre><p>To integrate the execution of Lambda functions into the workflow, “LambdaInvoke” tasks needed to be created.</p><p>Because the Lambda is defined in the same CDK stack, we were able to reference it directly. However, it is also possible to invoke Lambda functions that are deployed elsewhere, for example by using their resource ARNs.</p><pre>import { Code, Function, Runtime } from &#39;aws-cdk-lib/aws-lambda&#39;<br>import { LambdaInvoke } from &#39;aws-cdk-lib/aws-stepfunctions-tasks&#39;<br><br>const lambda = new Function(this, &#39;lambda&#39;, {<br>  functionName: `klarna-card-fees-lambda`,<br>  code: Code.fromAsset(&#39;...&#39;),<br>  handler: &#39;index.handler&#39;,<br>  runtime: Runtime.NODEJS_20_X,<br>})<br><br>const lambdaInvoke = new LambdaInvoke(this, &#39;step-invoke-lambda&#39;, {<br>  lambdaFunction: lambda,<br>  resultSelector: {<br>    FeePeriod: JsonPath.stringAt(&#39;$.Payload&#39;),<br>  }<br>})</pre><p>To pass arguments in between states, tasks support specifying input and output selectors. This is a feature of the “Amazon States Language”, a JSON-based, structured language used to define state machines. The return value of the Lambda function (“Payload”) will be passed to the next state as a JSON record of the following format:</p><pre>{<br>  &quot;FeePeriod&quot;: &quot;2024-01&quot;<br>}</pre><p>Up next, the steps to trigger the Glue jobs were defined. The tasks reference the Glue jobs by their name. It is important to note that both the state machine and the Glue job must be deployed in the same region, as there is no support to invoke them across regions or accounts as of time of writing.</p><pre>import { GlueStartJobRun } from &#39;aws-cdk-lib/aws-stepfunctions-tasks&#39;<br><br>const startGlueJob = new GlueStartJobRun(this, &#39;step-fn-start-glue-job&#39;, {<br>  glueJobName: &#39;klarna-card-fees-glue-job&#39;, <br>  integrationPattern: IntegrationPattern.RUN_JOB,<br>  arguments: TaskInput.fromObject({<br>    &#39;--FEE_PERIOD&#39;: JsonPath.stringAt(&#39;$.FeePeriod&#39;),<br>    &#39;--MARKET&#39;: &quot;US&quot;,<br>  }),<br>})</pre><p>Accessing the state input, which in our case was returned by the previous Lambda task, can be achieved through a syntax known as “JSONPath”. With “$” pointing to the root of the input object, the “FeePeriod” property is selected, read as a string and passed as a Glue job run argument.</p><p>By setting the “integrationPattern” to “IntegrationPattern.RUN_JOB”, the execution of the Glue job is handled synchronously, meaning the state machine will pause until the Glue job has terminated.</p><p>Linking the previously defined steps together, concluded with a “Succeed” step to mark the execution as successful, forms the basis of the so-called “definition body” of the state machine.</p><pre>import { Succeed } from &#39;aws-cdk-lib/aws-stepfunctions&#39;<br><br>const definitionBody = DefinitionBody.fromChainable(<br>  lambdaInvoke.next(<br>    startGlueJob.next(<br>        new Succeed(this, &#39;automation-complete&#39;)<br>      )<br>    )<br>  )<br>)</pre><p>Due to the “least privilege principle” of the AWS Well-Architected framework, the state machine itself lacks permissions to execute the Lambda functions and Glue jobs. In alignment with best practices and to ensure the state machine operates within its required capabilities, an AWS IAM role has been created that allows the missing permissions. This role was added to the state machine’s configuration.</p><pre>const stateMachineExecutionRole = new Role(this, &#39;state-machine-execution-role&#39;, {<br>  assumedBy: new ServicePrincipal(`states.us-east-1.amazonaws.com`),<br>  roleName: `state-machine-execution-role`,<br>  inlinePolicies: [<br>    new Policy(this, &#39;state-machine-execution-role-policy&#39;, {<br>      statements: [<br>        new PolicyStatement({<br>          effect: Effect.ALLOW,<br>          actions: [&#39;lambda:InvokeFunction&#39;],<br>          resources: [`arn:aws:lambda:*:${account}:function:klarna-card-fees-lambda`],<br>        }),<br>        new PolicyStatement({<br>          effect: Effect.ALLOW,<br>          actions: [&#39;glue:StartJobRun&#39;],<br>          resources: [`arn:aws:glue:*:${account}:job/klarna-card-fees-glue-job`],<br>        }),<br>      ],<br>    }),<br>  ],<br>})</pre><p>With all components configured, deploying the stack is as simple as executing the “cdk deploy” command, after which the resources will be set up. Accessing the state machine is straightforward through the AWS Console under the “Step Functions” section.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*ujVQWmkldxlbRUNR" /><figcaption>Step Functions module in the AWS Console</figcaption></figure><p>A scheduled trigger to enable full automation is yet missing. For this, AWS EventBridge provides a solution to set up a CRON scheduler, which automatically triggers the state machines at specified intervals. An additional IAM role is required for the scheduler to have the necessary permissions to activate the state machine.</p><pre>import { CfnSchedule } from &#39;aws-cdk-lib/aws-scheduler&#39;<br><br>const schedulerExecutionRole = new Role(this, &#39;scheduler-execution-role&#39;, {<br>  assumedBy: new ServicePrincipal(&#39;scheduler.amazonaws.com&#39;),<br>  roleName: `klarna-card-fees-scheduler-role`,<br>})<br><br>const schedule = new CfnSchedule(this, &#39;automation-schedule&#39;, {<br>  name: `klarna-card-fees-automation-schedule`,<br>  scheduleExpression: &#39;cron(0 9 3 * ? *)&#39;, // Every 3rd at 9:00AM UTC<br>  state: &#39;ENABLED&#39;,<br>  target: {<br>    arn: stateMachine.stateMachineArn,<br>    roleArn: schedulerExecutionRole.roleArn,<br>  }<br>})<br><br>stateMachine.grantStartExecution(schedulerExecutionRole)</pre><p>By configuring the CRON schedule, enabling it, and designating the state machine as the target, the setup is complete. Following a redeployment, automation is fully in place, allowing the system to operate independently. To get notified about issues during the execution, we created custom monitors on Datadog with integration towards OpsGenie, to call-out an engineer if unexpected errors occur.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*5xYTmpI3Z9gGtrE-" /><figcaption>A CRON scheduler has been created using AWS EventBridge, targeting the state machine</figcaption></figure><p>A complete architecture diagram showcasing the addition of the entire automation framework is provided below.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*hYbvOtesND8vpAkl" /><figcaption>Complete architecture diagram including automation</figcaption></figure><h4><strong>Results</strong></h4><p>What were the advantages of solving this problem? Primarily, it led to a 12.5% monthly reduction in the team’s workload, freeing up resources for additional projects and initiatives. This decrease in workload translates into lower operational costs, as there’s no longer a need to dedicate engineer hours to system operation. By automating processes, the risk of human error was minimized, thereby reducing the likelihood of potential incidents.</p><p>Additionally, the team had the chance to reduce tech debt, reworking IAM roles by eliminating unnecessary permissions and improving the staging environment, enabling thorough testing of the automation process before its rollout to production.</p><p>I personally gained a lot of knowledge during this project which I gave back to my team and domain by creating documentation and hosting presentations to deep-dive into the technical aspects and challenges. Finally, working on this project greatly enhanced my proficiency with AWS, which ultimately led me to successfully acquire the AWS Solutions Architect Associate certification!</p><h4><strong>Summary</strong></h4><p>In conclusion, leveraging AWS Step Functions for automating tasks is highly beneficial for those who manage system components within AWS accounts that require regular execution or need to follow a specific sequence. This blog post has demonstrated the functionality of AWS Step Functions through a straightforward example. Yet, it is possible to architect more complex workflows, invoking other AWS resources, run processes in parallel, incorporate error management strategies, and much more.</p><p>A big thanks to all contributors and to my team for their extensive support, providing me the opportunity to drive this project!</p><p><em>Did you enjoy this post? Follow Klarna Engineering on </em><a href="https://engineering.klarna.com/"><em>Medium</em></a><em> and </em><a href="https://www.linkedin.com/showcase/klarna-engineering/"><em>LinkedIn</em></a><em> to stay updated on more articles like this.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=346ce7094278" width="1" height="1" alt=""><hr><p><a href="https://engineering.klarna.com/automating-the-klarna-card-ownership-fees-system-using-aws-step-functions-346ce7094278">Automating the Klarna Card Ownership Fees System using AWS Step Functions</a> was originally published in <a href="https://engineering.klarna.com">Klarna Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Peak Season 2023 : How Klarna achieved consistent success]]></title>
            <link>https://engineering.klarna.com/peak-season-2023-how-klarna-achieved-success-consistently-53f673d2d58d?source=rss----86090d14ab52---4</link>
            <guid isPermaLink="false">https://medium.com/p/53f673d2d58d</guid>
            <dc:creator><![CDATA[Anu Sasidharan]]></dc:creator>
            <pubDate>Fri, 01 Mar 2024 08:52:56 GMT</pubDate>
            <atom:updated>2024-03-01T08:55:36.339Z</atom:updated>
            <content:encoded><![CDATA[<h3>Peak Season 2023 : How Klarna achieved consistent success</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*xpDm6nPYCFXzuHnuq0uNHQ.jpeg" /></figure><h3>Introduction</h3><p>This article summarizes how Klarna consistently achieved its Peak Season goals during 2023.</p><p><strong>In 2023</strong>, we aimed higher. We were committed to continual improvement, building on the progress made in <a href="https://engineering.klarna.com/black-friday-at-klarna-how-engineering-teams-got-ready-for-the-most-important-time-of-the-year-d7700137c64a">2022</a>. Our primary focus was to deliver the best possible experiences to our customers. During the Black Friday sale, we broke our own records, a testament to our commitment and capability.</p><p>1. Klarna’s systems showed resilience with zero critical or major incidents, and we saw a 30% reduction in overall incidents compared to 2022.</p><p>2. We optimized the management of resources, leading to optimal cloud costs.</p><p>3. We paid special attention to our engineer’s experience, ensuring a smooth Peak Season preparation. This improved efficiency, and lessened the workload.</p><p>The topics this article covers:</p><ul><li>Peak Season at Klarna</li><li>Factors contributing to our success</li><li>Approaches to Peak Season essentials</li><li>Lessons learned</li></ul><h3>Peak Season at Klarna</h3><p>The most important time of the year for Klarna is the ‘Peak Season.’ It starts with the busy week of Black Friday and ends with the sales at the end of the year. Peak Season gets busy because of Holidays and Festivals, Sales and Discounts, and seasonal necessities. During this time, e-commerce and fintech companies buzz with heightened activity, with Klarna being a significant player amidst them.</p><h4>Why is the Peak Season important for Klarna?</h4><p>Klarna’s mission is to give shoppers around the world easy, safe, and ‘smoooth’ ways to pay. We handle an average of over 2 million purchases every day, serving over 150 million active shoppers at more than 450,000 sellers in 45 countries. On Black Friday, we handle more than 3 times the usual daily purchases.</p><p>Big sales events, especially flash sales, mean our systems have to manage a lot of traffic. Flash Sales are quick discounts or promotions from stores that get buyers excited. This rush adds to the already busy season, and our systems have to quickly handle more than 8 times the usual activity. We have even seen this go up to 40 times for a big merchant, all while still providing customers with a delightful experience.</p><h3>What were the Key challenges in getting ready for the Peak Season?</h3><p>Klarna is a large organization with many teams working in different domains and functions. For the Peak Season, all systems directly impacting the shopping frenzy and the systems that are supporting these frontline systems efficiently, were involved. This included over 450 systems, 180 teams, and 17 domains prepared for the Peak Season.</p><p>The challenges were:</p><p>1. Making sure the 450+ systems were ready to manage the increased loads smooothly, especially during flash sales,</p><p>2. Ensuring the security of these systems from potential attacks,</p><p>3. Aligning key decisions and strategies across the teams,</p><p>4. Communicating decisions, timelines, processes, and best practices effectively.</p><h3>Key Success factors</h3><p>Klarna’s decentralized structure lets teams work together and make decisions quickly, helping respond to the market’s needs. This focus on innovation and customer needs ensures smoooth shopping experiences.</p><p>For Peak Season management, we have implemented an efficient organizational structure designed for our distributed environment, with clearly defined roles and responsibilities. Good teamwork, particularly central coordination, was integral to our success during the 2023 Peak Season.</p><ul><li><strong>Teams/System Owners</strong>, being the key players, maintained their systems to meet the required standards.</li><li>The <strong>Continuous Readiness Team</strong> centrally coordinated the necessary preparations by making appropriate decisions and developing effective tools, processes, and practices to ensure operational readiness.</li><li>The <strong>Steering Committee</strong>, which includes Yaron Shaer (our CTO), Domain Leaders, and Architects, oversaw and approved the Continuous Readiness Team’s decisions.</li><li><strong>Domain Readiness Leads</strong> , took the lead in their areas and provided useful feedback to the Readiness Team, helping to identify and handle potential issues early.</li><li><strong>Business Developers</strong> worked closely with merchants to give important flash sale details.</li></ul><p>Klarna’s Engineering Platform laid a solid foundation for the System Owners to ensure optimal performance of their systems. In 2023, we focused on effectively distributing responsibilities across team, domain, and central levels, designing Peak Season requirements, and creating tools for compliance checks.</p><p>We have specified two types of readiness configurations: non-negotiable requirements, which are measured objectively, and team-managed configurations, which are evaluated subjectively under the oversight of the respective domain.</p><ul><li>We implemented <strong>a readiness tool</strong> which automatically checked non-negotiable compliance requirements. This tool alerts System Owners to maintain alignment with peak season and continuous requirements. It employed 56 rules examining system readiness across categories such as resilience, availability, databases, performance, and system capacity. Readiness dashboards enabled the Central Team to monitor and ensure system alignment. The tool assigned readiness scores to systems, teams, groups, domains, and Klarna as a whole. Our goal was to achieve a 100% score by October 31, 2023, thus ensuring readiness while allowing for unplanned contingencies.</li><li><strong>Readiness reviews</strong> were conducted to prevent overlooking any aspect, especially subjective assessments. System Owners underwent a comprehensive checklist review, which domain architects approved. Critical systems, which form the backbone of the purchase flow, were reviewed by a central team of architects and engineering leaders.</li><li>Under the oversight of the experts from our cloud providers, We conducted <strong>DDOS fire drills</strong> on publicly exposed systems to identify potential vulnerabilities that could lead to attacks during peak times.</li></ul><p>We’ve empowered our teams in the following ways:</p><ul><li><strong>Traffic Capacity Predictions</strong>: We introduced Kapacity (<strong>K</strong>larna C<strong>apacity</strong>), our in-house tool, to help teams project minute-by-minute request volumes using past data and merchant predictions. Kapacity provides growth metrics and extra capacity for unexpected increases, allowing System Owners to easily access data, estimate incoming requests, and make informed decisions about resource allocation. This resolves a pain point from 2022, when more than half of our systems had to make independent predictions based on central metrics. Now, with Kapacity, we offer predictions for every system and service.</li><li><strong>Performance Testing Tools &amp; Framework</strong>: Customized to Klarna’s engineering needs, our centralized performance testing framework streamlines the development, building, and execution of performance tests, ensuring our services can handle heavy loads. System Owners are guided by comprehensive best practices to guarantee a consistent user experience and confirm that their test parameters fulfill well-defined SLO requirements. The tool is capable of monitoring the success of tests conducted by the systems.</li><li><strong>Best Practices &amp; Guidelines</strong>: We provide guidance in several areas including Lambda Readiness,Databases, handling dependencies (internal and third parties), capacity reservations, monitoring, observability, runbooks, etc.</li></ul><h3>A Closer Look at Peak Season Essentials</h3><p>For the inquisitive readers who are keen to delve deeper into the approaches for the key preparations for Peak Season, this section is designed with you in mind. Some of these topics have been touched upon in previous sections, yet here we deep dive into the nitty-gritty aspects.</p><p>To guarantee a seamless and delightful customer experience during the 2023 Peak Season, preparations and detailed planning were undertaken, focusing primarily on the following essential areas:</p><p>1. Comprehensive Performance Testing</p><p>2. Proficient Management of Flash Sales</p><p>3. DDOS Readiness preparedness</p><p><strong>Approach for Performance Testing</strong>: Understanding the importance of system efficiency in handling elevated traffic, particularly during flash sales, extensive performance testing has been carried out in alignment with Klarna’s specific requirements. Based on direct traffic dependencies, the distinctive nature of flash sales, and trends observed from historical data, the Klarna Infrastructure capacity has been segregated into two levels:</p><ul><li><strong>FLAS Capacity</strong> (Fast, Large Spike): Designed to accommodate sudden, substantial surges of activity often triggered by flash sales, campaigns, or incidents. In such scenarios, traffic can spike drastically within a two-minute duration, necessitating a system robust enough to manage these increments without relying on auto-scaling, which might be too slow to react.</li><li><strong>Baseline Capacity</strong>: This constitutes the maximum capacity needed to support the regular daily traffic (excluding FLAS events). It’s structured to comfortably endure peak daily volumes, boasting an automatic auto-scaling functionality for consistent performance.</li></ul><p>The systems anticipated to face FLAS events have undergone <strong>load tests</strong> (where performance is gauged against expected loads), <strong>spike tests</strong> (assessing the system’s handling of sudden load increments, typically due to flash sales), and <strong>overload tests</strong>(designed to ascertain the load point at which a system fails or exhibits significant degradation). Conversely, the Baseline systems only needed to conduct load and overload tests.</p><p>Moreover, it’s integral to test underlying components like dependencies and databases — this ensures comprehensive system performance.</p><ul><li><strong>Failover tests</strong> play a critical role too. Failover is an automatic switching from the primary system to a backup system, initiated when a fault or failure is detected. Swiftly configuring databases and their corresponding clients for rapid failover is crucial for maintaining system resilience and overall performance, especially during unexpected events. For instance, if a sudden traffic surge occurs and the database isn’t primed for quick failover, notable slowdowns may transpire, or the system might become completely unresponsive. Such a scenario could culminate in downtime, potentially compromising data integrity or inciting data loss if handled incorrectly. Equipping your configuration with fast failover is thus a priority to avoid such disruptions, further ensuring a seamless user experience even under significant loads.We employed strategic retries, exponential backoffs, and robust exception handling. Additionally, we use specified query timeouts to maintain optimal speed for both indexed and non-indexed lookups. These measures all contribute to smoother recovery of database operations, thereby preserving application performance integrity.</li></ul><p><strong>Approach for Managing Flash sales</strong> : Flash sales management is divided into two categories: Managed Flash Sales and Unmanaged Flash Sales.</p><ul><li><strong>Managed Flash Sales</strong> are reported through a dedicated process by a Business Developer, who has direct contact with the Merchant. This report triggers an automated notification to the Continuous Readiness team. The team subsequently reviews the merchant’s historical data and projected peak capacity. The information is then compared against the existing rate limit for the relevant merchant category. This evaluation assists in managing intense sales activities during periods when partners are anticipated to exceed their standard rate limit. This vital procedure protects the Klarna platform from potential overloads that could negatively impact all partners. If a rate limit adjustment is needed due to a flash sale, the Continuous Readiness team requests a temporary rate limit increase from a central team which manages rate limit for all merchants. This decision is based on Klarna’s set capacity levels and a comprehensive understanding of the situation. Impacted teams, including Accountable Leads and On-Call members, are subsequently notified. The Business Developer or the Key Account Manager who reported the Flash Sale, along with the merchant’s solution engineer, is also informed. All pertinent stakeholders join a direct message group where they receive further details about the flash sale, such as the schedule, impacted countries, expected peak times, and other important considerations. Significant flash sales are monitored centrally to provide continuous support during the event.</li><li>On the other hand, <strong>Unmanaged Flash Sales</strong> are unpredictable. This means that any sudden load during these sales is managed by the FLAS capacity predictions provided by the Kapacity (prediction tool), which also includes a safety buffer. During peak season, particularly on Black Friday, continuous central monitoring is in place to ensure prompt support should any issues arise. To further ensure operational continuity, Technical Managers from our cloud providers were also on standby for support.</li></ul><p><strong>Approach for DDOS readiness</strong> : Our procedure for ensuring DDOS readiness involved enabling central platform level protection around several aspects in addition to the fire drill conducted for identified public endpoints. This exercise was led by Case Taintor (Competence Group Lead of Klarna Engineering Platform), in collaboration with our networking, security teams and cloud providers. The fire drill was planned with the aim of boosting confidence, identifying areas for improvement, validating processes, and gaining practice.</p><p>The fire drill exercise included a simulated synthetic test, reviewing setups such as WAF rules, and providing targeted advice. This drill aided system owners in identifying necessary actions on CloudFront configurations, alerting mechanisms, and origin setups. Moreover, it helped update essential checklists and procedures in the Runbook.</p><h3>Lessons Learned: Aspiring for Continual Growth</h3><p>One of the leadership principles that resonates profoundly with me is ‘start small and learn fast.’ At Klarna, we have the daring to experiment with cutting-edge technology trends, which accelerates our learning and fosters innovation. The accomplishment of each Peak Season is a tribute to the collaborative efforts of all the teams within the readiness scope. Our most recent Peak Season has set a new standard, thanks to its unprecedentedly high-quality delivery.</p><p><strong>In the future</strong>,</p><ul><li>We aim to further enhance our engineers’ efficiency by lessening the efforts needed for Peak Season preparations as well as in other operations throughout the year.</li><li>We will emphasize customer delight as the cornerstone of Klarna, rooted in our customer-centric philosophy. Therefore, persistent performance testing is of utmost importance, ensuring the reliability and optimum performance of Klarna’s systems. We currently boast a suite of impressive tools, and our goal is to foster a culture that encourages performance-driven development.</li><li>As the premier AI-powered bank, we are committed to leveraging AI to enhance efficiency and maintain an unwavering focus on quality.</li><li>We also anticipate increasing our dependency management with third-party systems to further augment readiness.</li></ul><p><strong>In conclusion</strong>, Klarna’s Peak Season 2023 was a game-changer, setting new standards in planning, preparedness, and execution. Excellence in Peak Season is no more a goal, but a standard that Klarna strives to elevate with each passing year, promising a future of seamless shopping experiences for our customers. In the face of ever-evolving challenges, Klarna’s steadfast commitment to customer delight, championed by a spirit of continual growth and innovation, continues to guide us towards stellar trajectories of success.</p><p><em>Did you enjoy this post and want to stay updated on our latest projects and advancements in the engineering field? Join the Klarna Engineering community on </em><a href="https://engineering.klarna.com/"><em>Medium</em></a>, <a href="https://www.meetup.com/klarna-engineering-stockholm/"><em>Meetup.com</em></a> <em>and </em><a href="https://www.linkedin.com/showcase/klarna-engineering/"><em>LinkedIn</em></a><em>.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=53f673d2d58d" width="1" height="1" alt=""><hr><p><a href="https://engineering.klarna.com/peak-season-2023-how-klarna-achieved-success-consistently-53f673d2d58d">Peak Season 2023 : How Klarna achieved consistent success</a> was originally published in <a href="https://engineering.klarna.com">Klarna Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Stop Misusing ROC Curve and GINI: Navigate Imbalanced Datasets with Confidence]]></title>
            <link>https://engineering.klarna.com/stop-misusing-roc-curve-and-gini-navigate-imbalanced-datasets-with-confidence-5edec4c187d7?source=rss----86090d14ab52---4</link>
            <guid isPermaLink="false">https://medium.com/p/5edec4c187d7</guid>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[data-science]]></category>
            <category><![CDATA[imbalanced-data]]></category>
            <dc:creator><![CDATA[Angel Igareta]]></dc:creator>
            <pubDate>Thu, 09 Nov 2023 09:22:53 GMT</pubDate>
            <atom:updated>2023-11-09T09:22:53.046Z</atom:updated>
            <content:encoded><![CDATA[<h4>Discover how the Precision-Recall curve can provide a more robust metric for binary classification in data science and machine learning.</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*5ARZVGrXbPENwcSBjPRavw.jpeg" /></figure><p><strong>Imagine stepping into the complex world of binary classification problems. As a Senior Data Scientist at </strong><a href="https://medium.com/u/a214eb632ed5"><strong>Klarna</strong></a><strong>, this is my day-to-day reality. Binary classification is a cornerstone of data science, with applications touching everything from credit default predictions to medical diagnoses and spam detection. Yet, these problems come with their own unique set of challenges.</strong></p><p>Metrics such as the GINI coefficient and ROC_AUC often serve as our compass in this maze. They are widely trusted and used for evaluating models. But here’s the catch: they might not always point us in the right direction. Can we rely on them blindly, or do we need to dig deeper?</p><p>The path gets even more challenging when we encounter imbalanced datasets. In such cases, the effectiveness of our trusted metrics can be seriously compromised.</p><p>In this post, I invite you to join me on a journey to explore these metrics in greater depth. We will question their effectiveness, understand their limitations, and reveal alternatives that could prove to be more reliable navigational tools in the world of binary classification problems.</p><h3>Understanding Model Predictions and Metrics</h3><p>To truly grasp the nuances of model evaluation, let’s start by setting the stage with a real-world scenario that we often encounter at Klarna.</p><p>Imagine we’re tasked with predicting customer loan defaults. We have two categories to consider — paid or default. However, in our scenario, the default rate is a mere 2%. This is a classic case of data imbalance, and it’s exactly the kind of challenge we’re up against.</p><p>To evaluate our model’s performance in this scenario, we need to understand its predictions. We break these down into four distinct outcomes, also known as the confusion matrix:</p><ol><li><strong>True Positives (TP):</strong> These are the customers who our model correctly identifies as defaulters.</li><li><strong>False Positives (FP):</strong> These are the customers who our model incorrectly flags as defaulters. Such errors can result in losses of customer lifetime value.</li><li><strong>True Negatives (TN):</strong> These are the customers who our model correctly identifies as non-defaulters.</li><li><strong>False Negatives (FN):</strong> These are the customers who our model incorrectly flags as non-defaulters. Such errors can lead to financial losses due to the average loss and recovery rate.</li></ol><p>With these categories in place, we can delve into the metrics we use to measure our model’s performance. In real-world applications, Data Scientists often aim to provide the best model within certain constraints, such as a range of risk profiles (like default rates). They don’t fixate on a single threshold. The Underwriting teams then pick a threshold based on the company’s current risk appetite and objectives over a given period.</p><p>Hence, we’ll skip the popular measures like Accuracy or F1-Score and instead we’ll focus on <strong>threshold-agnostic metrics</strong>, which gauge the model’s ability to distinguish between categories, regardless of the chosen threshold.</p><h4>ROC Curve</h4><p>Let’s delve deeper into one of the most widely used threshold-agnostic metrics — ROC_AUC, which stands for Receiver Operating Characteristic Area Under the Curve. This metric uses a graphical representation to provide a comprehensive view of our model’s predictive capabilities.</p><p>The ROC curve plots True Positive Rate (TPR) on the y-axis and False Positive Rate (FPR) on the x-axis. This gives us a clear view of the trade-off between TPR and FPR at different thresholds of the confusion matrix, offering a holistic understanding of our model’s performance across varying cutoff points.</p><figure><img alt="This graphical representation details an example of a Receiver Operating Characteristic Area Under the Curve (ROC_AUC). The graph plots the True Positive Rate (TPR) on the y-axis against the False Positive Rate (FPR) on the x-axis. The graph features several curves, each representing a different classifier’s performance. The ‘random classifier’ curve, which represents a baseline model, is also highlighted. The curves of better performing models are closer to the top left corner." src="https://cdn-images-1.medium.com/max/1024/0*ojQ6_b8E8vDaK4eV" /><figcaption>ROC Curve Illustration: Comparing Classifier Performances from Best to Worst</figcaption></figure><p>But what exactly are TPR and FPR? Let’s break it down:</p><ol><li><strong>True Positive Rate (TPR):</strong> This is the proportion of actual defaulters that our model correctly identifies. Mathematically, TPR is calculated as TP / (TP + FN)<strong>. </strong>Another way to think about it is, “If a customer defaults, what’s the chance our model will catch it?”</li><li><strong>False Positive Rate (FPR):</strong> This is the proportion of actual non-defaulters that our model mistakenly identifies as defaulters. Mathematically, FPR is calculated as FP / (FP + TN). You can interpret it as, “If a customer didn’t default, what’s the likelihood our model incorrectly marks them as a defaulter?”</li></ol><p>The area under the ROC curve gives us the ROC_AUC score. A higher score indicates that our model is not only accurate in its predictions (high TPR) but also minimizes false alarms (low FPR) across all thresholds in the confusion matrix.</p><h4>GINI Coefficient</h4><p>Derived from ROC_AUC, the GINI coefficient provides a simple yet insightful measure of a model’s performance. It ranges from 0, which signifies a model with no discriminative power, to 1, indicating perfect discrimination between classes. The formula for calculation is: GINI = 2 * ROC_AUC — 1.</p><p>For stakeholders, the GINI coefficient offers a quick, single-figure snapshot of model effectiveness. Unlike the ROC_AUC, which requires a deeper understanding of true and false positives and negatives, the GINI coefficient provides a more immediate sense of model performance, making it a popular choice for non-technical audiences.</p><blockquote>However, both the ROC_AUC and the GINI coefficient share a common pitfall.</blockquote><p>While they excel in evaluating models for balanced datasets, they can be misleading when dealing with imbalanced datasets. This is because they don’t take into account the ratio of the positive and negative classes.</p><p>Consequently, these metrics can yield a high score even when the model is performing poorly on the minority class. In an unbalanced situation, a model may resort to always predicting the majority class to achieve a high score. This strategy, though it inflates the performance metrics, fails to provide any meaningful insight into the minority class, which could be critical in scenarios like fraud detection or rare disease diagnosis where the minority class is of greater interest.</p><p>Yet, there’s no need for concern. We have a remedy for this situation: Enter PR_AUC. This metric provides a more reliable evaluation for imbalanced datasets, which we’ll explore next.</p><h4>Precision Recall Curve</h4><p>Let’s delve into PR_AUC (Precision-Recall Area Under the Curve). Like the ROC curve, it’s a graphical representation of model performance. But it uses Precision and Recall.</p><p>Let’s break down these two components:</p><ol><li><strong>Precision:</strong> The ratio of true positive outcomes (correctly identified defaulters in our case) to all positive outcomes predicted by the model. It’s calculated as TP / (TP + FP). You can view it as, “When our model flags a customer as a defaulter, what’s the probability they really are?”</li><li><strong>Recall (or True Positive Rate):</strong> Same component we discussed in the context of ROC curve. The proportion of actual defaulters correctly identified by the model. It’s the answer to, “Out of all the customers who defaulted, what portion did our model manage to identify?”</li></ol><p>The PR curve, with Precision on the y-axis and Recall on the x-axis, illustrates the trade-off between these two metrics for different threshold values, akin to the ROC curve’s TPR and FPR trade-off view.</p><figure><img alt="This graphical representation details an example of a Precision Recall Area Under the Curve (PR_AUC). The graph plots the Precision on the y-axis against the Recall (True Positive Rate) on the x-axis. The graph features several curves, each representing a different classifier’s performance. The ‘random classifier’ curve, which represents a baseline model, is also highlighted. The curves of better performing models are closer to the top right corner." src="https://cdn-images-1.medium.com/max/1024/0*1z69voTBb04MIzig" /><figcaption>PR Curve Illustration with a target incidence of 0.1: Comparing Classifier Performances from Best to Worst.</figcaption></figure><p>The PR_AUC score, derived from the area under the PR curve, indicates the model’s accuracy (high precision) and its ability to detect a large portion of actual defaulters (high recall). However, the score’s dependence on the target incidence (proportion of positive cases in the dataset) may make it less intuitive for those who are not well-versed in the model evaluation.</p><blockquote>The PR_AUC score offers a detailed insight into the model’s performance, especially on imbalanced datasets, proving to be an invaluable metric for data scientists and analysts.</blockquote><h4>Difference Between ROC_AUC and PR_AUC</h4><p>Even though they both serve as powerful tools for visualizing a model’s performance, they differ in their underlying metrics and how they interpret a model’s effectiveness.</p><p>Here’s a brief refresher on the metrics used in both curves:</p><p><strong>ROC_AUC</strong></p><ul><li><strong>True Positive Rate (TPR):</strong> TP / (TP + FN)</li><li><strong>False Positive Rate (FPR):</strong> FP / (FP + TN)</li></ul><p><strong>PR_AUC</strong></p><ul><li><strong>Precision:</strong> TP / (TP + FP)</li><li><strong>Recall:</strong> TP / (TP + FN)</li></ul><p>The impact of imbalanced datasets becomes critical when the negative class significantly outnumbers the positive class, leading to an abundance of TNs. This imbalance can distort metrics like ROC_AUC, which incorporates the FPR in its calculation.</p><blockquote>A high number of TNs can result in a misleadingly low FPR, even with many False Positives, thereby inflating the ROC_AUC score and painting an overly optimistic picture of the model’s performance.</blockquote><p>PR_AUC, unlike ROC_AUC, doesn’t consider <strong>TN</strong>. It focuses on the model’s precision and recall, providing a more realistic evaluation of performance on imbalanced datasets by concentrating on the minority class.</p><p>As we’ve unraveled these two powerful metrics, you’re now equipped with the knowledge to discern which one is best suited for your unique dataset and business problem.</p><p>Next, we’ll apply these concepts to a practical problem, bringing these metrics to life with real-world data.</p><h3>Practical Illustration: The Tale of Two Models</h3><p>To bring these concepts to life, let’s consider two models trained on the same imbalanced dataset. We have two contenders in the ring:</p><ul><li><strong>Model A:</strong> A Logistic Regression model, simple yet effective.</li><li><strong>Model B:</strong> A Gradient Boosting model, known for its precision and handling of complex datasets.</li></ul><p>Both models were trained on a dataset with a 2% default rate, a classic case of imbalance, and tested on a set of 10,000 customers.</p><p>As explained before, performance metrics are usually calculated by integrating over all possible thresholds, not just a single point. However, for simplicity in this illustration, we will consider the errors given a fixed threshold and a simplified ROC_AUC and PR_AUC calculations.</p><p>Following the evaluation of the models, the performance of each can be summarized as such:</p><p><strong>Model A’s Performance Card</strong></p><pre>| Confusion Matrix      | Predicted Non-Defaulters | Predicted Defaulters |<br>|-----------------------|:------------------------:|:--------------------:|<br>| Actual Non-Defaulters |           9600 (TN)      |         200 (FP)     |<br>| Actual Defaulters     |           100 (FN)       |         100 (TP)     |</pre><p><strong>Model B’s Performance Card</strong></p><pre>| Confusion Matrix      | Predicted Non-Defaulters | Predicted Defaulters |<br>|-----------------------|:------------------------:|:--------------------:|<br>| Actual Non-Defaulters |           9700 (TN)      |         100 (FP)     |<br>| Actual Defaulters     |           100 (FN)       |         100 (TP)     |</pre><p>We now proceed to calculate the simplified formula for the ROC and PR area under the curves for both models (assuming just one data point).</p><p><strong>Model A’s Performance Metrics</strong></p><ul><li><strong>ROC_AUC</strong>: Approximately 49%, calculated as TPR * (1 — FPR) = 0.5 * (1–0.0204) = 0.4898.</li><li><strong>PR_AUC</strong>: Approximately 17%, calculated as Precision * Recall = 0.3333 * 0.5 = 0.16665</li></ul><p><strong>Model B’s Performance Metrics</strong></p><ul><li><strong>ROC_AUC</strong>: Approximately 49%, calculated as TPR * (1 — FPR) = 0.5 * (1–0.0102) = 0.4949.</li><li><strong>PR_AUC</strong>: Approximately 25%, calculated as Precision * Recall = 0.5 * 0.5 = 0.25.</li></ul><h4>Comparison</h4><p>When comparing both models, if we were to just look at the ROC_AUC scores, which are 49% for both Model A and Model B, we might erroneously conclude that both models perform identically.</p><p>However, the ROC_AUC metric, with its high sensitivity to True Negatives, can distort our understanding, especially when dealing with imbalanced datasets like ours. Neglecting this subtlety could result in rejecting good customers, consequently missing out on their potential lifetime value.</p><blockquote>Despite Model A incorrectly predicting 100 more customers as defaulters than Model B, the identical ROC_AUC scores would suggest equivalent performance.</blockquote><p>This is where the PR_AUC metric comes into play. With scores of 17% for Model A and 25% for Model B, this metric, with its emphasis on False Positives, unveils a different scenario — Model B outperforms Model A in correctly classifying the minority class.</p><h3>Conclusions</h3><p>In this post, we explored the <strong>complexities of model evaluation for binary classification problems with imbalanced datasets</strong>. We discussed the limitations of popular metrics like ROC_AUC and GINI, which can be misleading in such scenarios, and introduced <strong>PR_AUC as a more reliable alternative</strong>.</p><p>Through a practical example, we demonstrated how metric choice impacts the interpretation of model performance. We highlighted the importance of selecting a metric that aligns with your dataset and problem to ensure accurate model evaluation.</p><p>In conclusion, the key takeaway is the <strong>importance of understanding your data and the problem at hand</strong>. Choosing the right metric that aligns with your dataset and problem can lead to more accurate measurements of your model’s performance and provide meaningful insights. Remember, there’s no one-size-fits-all solution in data science. It’s all about finding the right tool for the job.</p><p><strong><em>Angel Igareta,</em></strong><em> Senior Data Scientist <br></em><a href="https://medium.com/@angeligareta">Medium</a> | <a href="https://www.linkedin.com/in/angeligareta/">LinkedIn</a> | <a href="https://github.com/angeligareta">GitHub</a></p><p><em>Did you enjoy this post and want to stay updated on our latest projects and advancements in the engineering field? Join the Klarna Engineering community on </em><a href="https://engineering.klarna.com/"><em>Medium</em></a>, <a href="https://www.meetup.com/klarna-engineering-stockholm/"><em>Meetup.com</em></a> <em>and </em><a href="https://www.linkedin.com/showcase/klarna-engineering/"><em>LinkedIn</em></a><em>.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=5edec4c187d7" width="1" height="1" alt=""><hr><p><a href="https://engineering.klarna.com/stop-misusing-roc-curve-and-gini-navigate-imbalanced-datasets-with-confidence-5edec4c187d7">Stop Misusing ROC Curve and GINI: Navigate Imbalanced Datasets with Confidence</a> was originally published in <a href="https://engineering.klarna.com">Klarna Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Overcoming the Hurdle of Unformatted Input: What I Learned From Building a ChatGPT Add-On for…]]></title>
            <link>https://engineering.klarna.com/building-a-chatgpt-add-on-my-journey-to-streamlined-communication-in-google-workspace-60e73ec00084?source=rss----86090d14ab52---4</link>
            <guid isPermaLink="false">https://medium.com/p/60e73ec00084</guid>
            <category><![CDATA[productivity]]></category>
            <category><![CDATA[langchain]]></category>
            <category><![CDATA[google-workspace]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[chatgpt]]></category>
            <dc:creator><![CDATA[Mikael Wulfcrona]]></dc:creator>
            <pubDate>Mon, 16 Oct 2023 09:50:38 GMT</pubDate>
            <atom:updated>2023-10-16T10:12:04.027Z</atom:updated>
            <content:encoded><![CDATA[<h3><strong>Overcoming the Hurdle of Unformatted Input: What I Learned From Building a ChatGPT Add-On for Google Workspace</strong></h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*BFWrDMBi1DomSGQzkI9bVQ.jpeg" /></figure><p><strong>While ChatGPT is an incredibly powerful language model, it still struggles with unformatted input</strong>. <strong>As a tech nerd and passionate data scientist, I recently embarked on a journey to create an add-on for Google Workspace that could seamlessly interact with data stored in Sheets. Follow along as I share some of the main challenges, learnings, and solutions that eventually allowed me to integrate ChatGPT into Google’s productivity tools for textual interactions.</strong></p><p><strong>Unleashing the Potential of ChatGPT</strong><br>I was one of many people who was very impressed with the early versions of ChatGPT, released at the end of last year. Its capability to generate human-like responses fascinated me. However, I struggled to integrate it into my daily work. Having to switch tabs and copy-pasting text from different documents kept me away from really adopting this new tool.</p><p>Determined to overcome this hurdle, I dove into the world of Google Workspace add-ons. You know where the work with text actually happens. Armed with enthusiasm and basic JavaScript knowledge, I got into building my first add-on. Having never built an add-on before, I forked Google’s own example repo and started working.</p><p><strong>Working with the Google API<br></strong>As I delved into the project, I realized integrating ChatGPT via API calls was relatively straightforward. OpenAI provided excellent documentation, and Google Apps Script offered built-in features to make API calls. The main challenges, actually, laid on the Google side. Extracting text from different apps within the Google Workspace ecosystem initially baffled me. I spent hours unraveling the intricacies of the Google API, determined to find a way to seamlessly access and manipulate data.</p><p><strong>Overcoming GPT’s Limitations with Raw Data in Google Sheets</strong><br>As I progressed, I encountered more roadblocks. Firstly, the infamous token limit. Some document were quite long and simply not possible to fit into ChatGPT. While there are great workarounds for this, deploying third party libraries to the add-on codebase proved difficult. This was further complicated by a hard 60 second execution time limit for Google add-ons.</p><p>Secondly, and maybe the toughest one, ChatGPT struggles when faced with raw data in Google Sheets (.CSV). ChatGPT requires formatted input to generate meaningful responses. This posed a significant obstacle to building an add-on that could interact with data stored in Sheets.</p><p>Undeterred, I devised a two-step solution. First, I created a data preprocessing module within the add-on. This allowed me to parse and transform raw data from Google Sheets into a format compatible with ChatGPT. I extracted the relevant information, structured the data, and generated prompts that ChatGPT could understand. Second, I developed an output formatting module that presented ChatGPT’s responses in a user-friendly manner, right within the familiar Google Sheets interface.</p><p><strong>Conclusion</strong><br>My journey to build a ChatGPT add-on for Google Workspace proved to be a rewarding endeavour. Overcoming the different challenges associated with the Google API and ChatGPT’s limitations made me realize that we are only scratching the surface of what’s possible to do with these cutting-edge AI models. Today, fortunately, the add-on has been released internally at Klarna to help my colleagues enhance their productivity within the Google Workspace ecosystem.</p><p>This is where the future of digital communication lies. I’m sure that with determination and creativity, anyone can embark on similar projects and make a positive impact on how we interact in the digital realm.</p><p><strong>What’s next? Adding LangChain to Enhance Data Processing</strong><br>But my journey doesn’t end here. To take the ChatGPT add-on to even greater heights, I’m planning to integrate LangChain, a powerful Python library. By leveraging its robust toolset, I will overcome the limitations I faced with raw data in Google Sheets and solve the issue of limited tokens. This will greatly enhance data processing and user interaction within the add-on.</p><p><strong><em>Mikael Wulfcrona,</em></strong><em> Author<br></em><strong><em>ChatGPT</em></strong><em>, Co-author and editor</em></p><p><em>Did you enjoy this post and want to stay updated on our latest projects and advancements in the engineering field? Join the Klarna Engineering community on </em><a href="https://engineering.klarna.com/"><em>Medium</em></a><em> and </em><a href="https://www.linkedin.com/showcase/klarna-engineering/"><em>LinkedIn</em></a><em>.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=60e73ec00084" width="1" height="1" alt=""><hr><p><a href="https://engineering.klarna.com/building-a-chatgpt-add-on-my-journey-to-streamlined-communication-in-google-workspace-60e73ec00084">Overcoming the Hurdle of Unformatted Input: What I Learned From Building a ChatGPT Add-On for…</a> was originally published in <a href="https://engineering.klarna.com">Klarna Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Introducing native E2E testing: Learnings from the Senior Engineering Program for Women]]></title>
            <link>https://engineering.klarna.com/introducing-native-e2e-testing-learnings-from-the-senior-engineering-program-for-women-4c49cda2122c?source=rss----86090d14ab52---4</link>
            <guid isPermaLink="false">https://medium.com/p/4c49cda2122c</guid>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[e2e-testing]]></category>
            <category><![CDATA[gender-equality]]></category>
            <category><![CDATA[klarna]]></category>
            <category><![CDATA[appium]]></category>
            <dc:creator><![CDATA[Joana Melo]]></dc:creator>
            <pubDate>Fri, 08 Sep 2023 12:35:44 GMT</pubDate>
            <atom:updated>2023-09-11T13:42:08.374Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*_3PMhXpbg4yGqtO5t4VQvQ.jpeg" /></figure><p><strong>I made company-wide impact by successfully delivering the introduction of native end-to-end (E2E) testing in mini versions of the Klarna app. The goal was to have automated feature regression tests in our pipelines. I developed this as part of a program for senior engineering women, and today, I want to share the insights and learnings I gained from this experience.</strong></p><h3>Creating fair and equal opportunities for women</h3><p>How do we offer women equal and fair opportunities in an industry dominated by men?</p><p>Well, there are many ways to work on this topic. One that piqued my curiosity was Klarna’s <strong>Senior Engineering Program for Women</strong> (<strong>SEPW</strong>).</p><p>As you might wonder as well, my initial thoughts on it as with any other initiatives like this came with a lot of reservations:</p><blockquote>Is this fair? Is this the best way for me to ensure that I’m being fairly evaluated? Will it look like I am being brought to a speedlane towards an easy promotion if I happen to get one because I’m a woman? Are we going to get treated like tokens? Is this all just a marketing strategy to promote? Am I being part of and legitimizing something that has no real content and value for my career or other women? What will everyone think?</blockquote><p>When we are faced to join initiatives related to gender gap improvements, we might fall into the trap of having all the perfect and right answers before we take risks, or we can accept that there will never be the perfectly carved, impactful and life-changing solution at our doorstep.</p><p>We can only experiment and learn from the results to make better decisions as we help evolving into a hopefully more gender-fair world.</p><p>As a woman in engineering, I understand the issues, but I don’t claim to have all the answers. And that’s ok.</p><h3>The program</h3><p>The SEPW is a way for Klarna to acknowledge and accelerate the professional development of promising engineers and promote diversity within engineering. The 6 month program is designed with the individual’s growth as the main focus, and based on four themes: Execution, Collaboration and Communication, Technology, and Influence.</p><p>It includes coaching sessions with exercises such as presenting systems architecture and solutions, and the participants are selected with support from the engineering leads of the department, lasting around 6 months, to a group of 6–8 women. You can find more information on this <a href="https://www.klarna.com/careers/our-competences/engineering/">here</a>.</p><p><a href="https://www.klarna.com/careers/our-competences/engineering/">Engineering jobs at Klarna | Klarna Careers</a></p><h3>Going for it</h3><p>I was offered a spot on the program and after reflecting on the questions I had, I decided to accept. The very first immediate valuable point was that I reached out to past participants to hear about their experience in the program, and the collaboration and support started right there.</p><p>Then came figuring out how to balance it with my team’s day-to-day work, in order to adjust the goals and size of the project based on that.</p><p>I created a list of projects, defined their impact, duration and effort and asked for feedback from key people.</p><h3>Finding a project</h3><p>From my own experience working on the frontend at Klarna and by talking to many other engineers that do the same, a clear pain that most of these teams have became clear: the testing and validation of the release candidate that goes into the app each week.</p><p>A lot of manual testing goes into it, which means testing our feature flows (mostly the same each week) in two platforms — iOS and Android. While we do have many tools for different types of automated testing, we were missing one to allow real end to end testing in native apps. One that could truly replace much of, or all release testing.</p><p>There were actually multiple attempts in the past to bring native E2E tests to the app for the overall teams along the years, but for a mix of different reasons, they never came to fruition.</p><p>After I discovered this opportunity, I took upon myself to work on introducing support to E2E testing on the native platforms.</p><p>Along the way, I have crossed paths and worked together with engineers that work on:</p><ul><li>the core frontend tooling, reliability and pipelines of the app</li><li>other teams and departments that were also interested in and working closely with tests, or tooling, or help out the core teams and have a lot of know-how</li><li>end to end testing as a new initiative on its own</li><li>something else but wanted to help test the early stages of the solution</li></ul><p>I got the big picture of the state of many of the central pieces in the app, while also spotting redundant work that people were doing between teams and supported in bridging those efforts.</p><p>This helped me figure out a more precise plan and goals for myself as well.</p><h3>Technical challenges and improvements</h3><p>To bring this project to life, I introduced several changes to our tooling, a few of which I will be illustrating below.</p><h4>Typescript and globals</h4><p>I discovered that through different products included in our monorepo, a few Typescript globals were defined, including Jest and JQuery. Since I wanted to include Typescript support in the test definitions, and we were instead using Mocha and Appium, syntax like “expect” or “$” was colliding with the predefined globals. Since these were used in several places, it would be a challenge in itself to migrate the projects to use local definitions.</p><blockquote>A big takeaway from this is to always avoid defining globals as there are other libraries that you might want to use in your project later that might have the same syntax commands as your current ones.</blockquote><p>To address this, the first step was to exclude the Appium tests folders from the global <em>tsconfig</em>:</p><pre>&quot;exclude&quot;: [&quot;(…)/__appium__/**/*&quot;]</pre><p>In the global clients tsconfig I added Appium test files to the exclusion list:</p><pre>{<br>  &quot;exclude&quot;: [&quot;(…)/__appium__/**/*&quot;] <br>}</pre><p>Then, in the Appium tooling code, I included only the local files and tests, and also had to ignore the Jest types:</p><pre>{<br>  &quot;include&quot;: [&quot;(…)/__appium__/**/*”, “./**/*&quot;],<br>  &quot;exclude&quot;: [&quot;node_modules/@types/jest&quot;]<br>}</pre><p>In our deprecated Appium tooling, we had centralized tests in a single folder. Since collocation of tests was also introduced, meaning each test file would be able to live next to its feature and be moved around together, and features still needed to be using the global <em>tsconfig</em>, each feature’s tests folder was required to include a tsconfig extending the tooling <em>tsconfig</em> as follows:</p><pre>{<br>  &quot;extends&quot;: &quot;(…)/appium/tsconfig.json&quot;<br>}</pre><h4>Naming conventions</h4><p>I have also defined a specific namespace for Appium tests:</p><pre>&quot;app/__appium__/**/*&quot;</pre><p>And in order to help the evaluation and discoverability by our tooling, including it in the <em>.eslintrc.js</em> in the same format:</p><pre>&quot;**/*.appium-spec&quot;</pre><h4>Sub dependencies nightmare</h4><p>Since I moved the Appium tests from a separate standalone subproject with its own configuration and local dependencies to a central root location in the repo, and by consequence included this tool’s dependencies in the root package.json, a few issues came up with multiple versions of some common sub dependencies used already as well by the rest of the app. This is protected to avoid dependency redundancy in the project, and we use <a href="https://github.com/oblador/diglett">Diglett</a> for detecting these. Since several of these sub dependencies were very common and used already by other areas of our app, it would be nearly impossible to version bump and match all of them in the scope of this project, so I added the needed entries to <em>diglettignore.txt</em>.</p><h3>The coaching</h3><p>While working on this project, the SEPW provided coaching sessions. Internal and external presenters shared their expertise, allowing me to apply this knowledge to my project and beyond. Additionally, we as participants had opportunities to present our progress and practice on leadership soft skills, while receiving valuable feedback from coaches and fellow women in the program.</p><p>In the scope of these networking sessions, I talked to other women at my level about their work, their SEPW project, and the various engineering, work and gender related challenges they face. We shouldn’t underestimate the power this type of connection brings.</p><h3>Outcomes</h3><p>At the end of the program, I delivered on my set goal: working E2E tests in native in feature apps — with efficient runs and less friction in mind, for an enhanced developer experience and speedier pipelines. Ultimately, using this brings a lot more trust and robustness in any changes we make in our and others’ features, knowing that these will not introduce a corner case bug that we only detect the following week while doing release testing once again.</p><p>Along with all of this I also brought test collocation, in the features, so that we can scale to many teams easily, and more importantly, have a clear view of ownership and accountability. This has also become the reference for doing the same effort of co-locating our functioning central Cypress tests, and the testing ground to the challenges of that migration, which will save the core teams’ precious implementation time.</p><p>I have gained valuable skills in technologies such as <a href="https://webdriver.io/">Webdriver.io</a>, <a href="http://appium.io/docs/en/2.0/">Appium</a>, <a href="https://mochajs.org/">Mocha</a>, and <a href="https://github.com/allure-framework/allure-js">Allure</a>.</p><p>And ultimately, by promoting these achievements in our Slack channels, I saw more and more people interested and courageous to introduce these types of tests in their own features, reaching out to me for support setting up their local environment.</p><h3>Learnings</h3><p>I was able to show what I can do, while improving important tools and my own skills along the way.</p><p>I have learned how our core tooling code is set up, and was able to contribute with both suggestions and concrete actions.</p><p>I have realized that this type of initiative offers opportunities that may not otherwise be accessible due to various factors, including personal drive, team context, and yes, unconscious gender bias.</p><p>What is also very motivating is that, as I finished the program and my project, other women who were curious about the program or already joined a new iteration of it, reached out to me asking for opinions, support and advice — reinforcing the importance of being/having a role model and reference as an engineer.</p><p>And of course, I am now also part of a network of fellow engineers from different parts of the company that has participated in the program and the different coaching sessions, as well as the organizers who are clearly invested in the topic of diversity and gender equity. A network with an amazing sense of belonging and shared experiences.</p><p>The visibility of my participation on the program has allowed me to promote the initiative, my project and support for Women at Klarna. But also beyond, to effectively contribute to making the program better, and being part of company-wide initiatives that impact the work on the gender gap.</p><p>If I could have any advice for my past self, I would say “<em>It will be challenging. It will be completely unfamiliar territory. That’s why you have to go for it!</em>”.</p><p><em>Did you enjoy this post? Follow Klarna Engineering on </em><a href="https://engineering.klarna.com/"><em>Medium</em></a><em> and </em><a href="https://www.linkedin.com/showcase/klarna-engineering/"><em>LinkedIn</em></a><em> to stay updated on more articles like this.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=4c49cda2122c" width="1" height="1" alt=""><hr><p><a href="https://engineering.klarna.com/introducing-native-e2e-testing-learnings-from-the-senior-engineering-program-for-women-4c49cda2122c">Introducing native E2E testing: Learnings from the Senior Engineering Program for Women</a> was originally published in <a href="https://engineering.klarna.com">Klarna Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>