Company Logo Tech Writers

tech writers

This is our blog for technology lovers! Here, softplayers and other specialists share knowledge that is fundamental for the development of this community.

Learn more
RFC 9745 and its impact on API governance
Tech Writers June 10, 2025

RFC 9745 and its impact on API governance

The Internet Engineering Task Force (IETF) - an open international organization that develops and promotes technical standards for the Internet, such as the TCP/IP, HTTP and DNS protocols - has just released RFC 9745, which defines a standardized way of reporting resource deprecation in the context of HTTP, which is especially relevant for APIs based on this protocol. In short, from now on, servers can inform their clients about the deprecation status of certain resources using the Deprecation response header, whose value is a date, in the past or future, indicating that the resource has already been or will be deprecated. Additionally, it is possible to use the link header to point to documentation and also Sunset, providing the date on which the resource will become unavailable. In this article we will evaluate the feasibility of applying this standard in the real world. Using best practices in API development, we will start with definition files that follow the OpenAPI Specification and end up with the KrakenD API gateway. Impacts As mentioned, this new standard is especially important for web APIs, that is, for APIs that adhere to the HTTP protocol, such as the famous REST. In this context, the RFC provides a means of bringing deprecation policies—once restricted to documentation or design time—to runtime. Therefore, the novelty has the potential to seriously mitigate integration failures, allowing developers to make the necessary adaptations with a comfortable lead time. By the way, it is worth remembering that we are entering the era of AI (with their agents, MCP servers, etc.), which only increases the impact of this new standard, as they can learn and adapt on their own when faced with depreciation signals. In the context of governance, the RFC also makes it possible for API gateway vendors (such as Kong, Tyk, KrakenD, Traefik, APISix, etc.) to consider the new standard during automated API deployment processes, especially when we think about APIOps based on OpenAPI specification. Let's see. The OpenAPI specification provides for the indication of deprecation of operations through the deprecated field. With this new RFC, it is simply natural to think about linking things, that is, making the deprecation indication present in the definition files match the configuration of the gateways, which, once running, start to inject the new response header in the appropriate operations. This improvement would take governance to the next level of quality! Proving the concept We will use the definition file that adheres to the OpenAPI Specification (OAS) to describe our API, we will build a parser in Go using libopenapi, we will rely on KrakenD as the API gateway and HttpBin as the backend. Full project details can be found in this repository. So, I'll just highlight the main points: The definition file (openapi.yaml) paths: (...) /users/{userId}: (...) delete: (...) deprecated: true Note that the user delete operation relies on the OAS standard field deprecated with the value true. Well, it is easy to see that we are faced with an impedance mismatch when we try to make this boolean interact with the new headers provided in RFC 9745, since these are much richer in information than that one. For reasons like this, OAS has extensions, which, in our case, will be used to describe the properties expected by the RFC as follows: paths: (...) /users/{userId}: (...) delete: (...) deprecated: true x-deprecated-at: "2025-06-30T23:59:59Z" x-deprecated-sunset: "2026-01-01T00:00:00Z" x-deprecated-link: https://api.example.com/deprecation-policy The Parser The parser's role is to read and interpret the openapi.yaml definition file, extract the relevant information for the gateway, and create the operations.json file, which will be embedded in the KrakenD image and consumed during its initialization, in an approach called flexible configuration. This is the result of operations.json: { "list": [ { "path": "/users", "method": "get", "backend": { "path": "/users", "host": "http://backend:8888" } }, { "path": "/users", "method": "post", "backend": { "path": "/users", "host": "http://backend:8888" } }, { "path": "/users/{userId}", "method": "get", "backend": { "path": "/users/{userId}", "host": "http://backend:8888" } }, { "path": "/users/{userId}", "method": "delete", "deprecated": { "at": "@1751327999", "link": "https://api.example.com/deprecation-policy", "sunset": "Thu, 01 Jan 2026 00:00:00 UTC" }, "backend": { "path": "/users/{userId}", "host": "http://backend:8888" } } ] } Notice that the parser has projected the extended OAS elements into the KrakenD configuration file, including doing the appropriate value conversions, like so: OAS KrakenD x-deprecated-at: deprecated.at: x-deprecated-link: deprecated.link: x-deprecated-sunset: deprecated.sunset: The Plugin Now that the gateway configuration has been properly generated from the definition file, our custom plugin comes into play. Its function is to identify deprecated API operations and insert RFC 9745 headers with the appropriate values. More details can be found in the article repository. But once the plugin was embedded in KrakenD, we got the following results: GET /users/1 DELETE /users/1 Note that only the second operation was deprecated (see operations.json) and the gateway correctly added the headers in the response. Conclusions The experiment showed the viability of the concept, that is, that it is possible to take depreciation policies beyond definition and documentation, being easily communicated at runtime. In this way, systems can adopt automated actions to communicate obsolescence to interested parties and significantly reduce the chances of integration failures. Although the OpenAPI Specification extensions have made this possible in the face of the insufficiency of the deprecated boolean, I imagine that the OpenAPI Initiative should include an improvement in future versions. Especially when I think that Eric Wilde, co-author of this RFC, is very active in the world of APIs. To the readers who have come this far, thank you very much. I hope these few words have added something to you and made your time worthwhile.

Embeddings: what they are and their applications
Tech Writers 27 May, 2025

Embeddings: what they are and their applications

We know that with the emergence of various technologies, there is a large increase in the number of terms we hear about, embeddings is one of them, but what are they? Embeddings, which in English means "to incorporate", is a term used in AI and Natural Language Processing (NLP). It refers to the process of "embedding" or "embedding" complex information (such as words, sentences, or documents) into a vector space. This means that data that would be difficult to process directly is transformed into a numerical form (vectors), which Machine Learning models can understand and use for tasks such as classification and semantic analysis. When combined with vector databases, they enable systems to analyze large volumes of unstructured data. This allows for the extraction of relevant information and complex queries quickly and efficiently. This data transformation technique is essential in building scalable solutions, as the vector representation facilitates the search and recovery of information, in addition to compressing the information and still maintaining the relationship with its original content. How it works We know that Embeddings are vectors for machine understanding based on texts, phases, documents. But how do we transform this information into vectors? Vectors are formed by using AI models trained to identify contexts, classifying them based on the approximation of the context in numbers, which typically range from -1 to 1. A value of 1 indicates the closest match, with thousands of comparison parameters. These models are typically trained on large volumes of text, identifying patterns of competition between words that appear frequently in similar contexts, such as "cat" and "animal." During training, the model learns to map these words to numeric vectors in a multidimensional space, so that words with related meanings or similar contexts are positioned closer to each other in this vector space. The goal is to make words or phrases with similar meanings closer together in the "space" of vectors. For example, "cat" and "dog" should be represented by close vectors, while "cat" and "car" will be further apart. Embedding example | Image: https://arize.com/blog-course/embeddings-meaning-examples-and-how-to-compute/ How is the similarity between two vectors calculated, comparing, for example, a text with several vectors from the trained model? Mathematically, the cosine similarity technique is normally used to compare two vectors. Cosine similarity provides a value in the range [-1,1], with 1 being the closest context value and -1 being the furthest [1] Cosine similarity equation | Image: Wikipedia Two vectors with 98% similarity based on the cosine of the angle between the vectors | Image: Richmond Alake Embeddings, in practice PDF analysis with QA (Question Answering): Embeddings are used in document analysis systems, such as PDFs, to perform Question and Answer (QA) tasks. Companies that deal with large volumes of documents, such as contracts or reports, can use embeddings to automatically locate relevant passages in a text. For example, when analyzing a PDF contract, embeddings allow you to semantically map the content and identify passages related to questions like "What is the validity period of this contract?" or "What are the customer's payment obligations?" A generative AI model can then use these snippets to interpret the context and generate natural language responses with greater accuracy. Product Recommendation (E-commerce): Platforms like Amazon and Netflix use embeddings to recommend products or movies based on users' preferences and past behaviors. For example, when recommending movies, embeddings are used to capture the style, genre, and features of the movies the user has watched, suggesting new content based on vector similarity. Sentiment Analysis (Customer Service): Companies use embeddings to analyze sentiment in customer feedback or messages. For example, when analyzing a set of social media comments or customer emails, embeddings help automatically identify whether the sentiment is positive, negative, or neutral, enabling a quick and appropriate response. Conclusion Embeddings have proven to be a powerful and growing tool in several industries, transforming the way we interact with unstructured data. Its ability to represent complex information numerically has led to improvements in document analysis systems, recommendations and even customer service. As a technology that is constantly evolving, it is expected that, over time, it will be increasingly integrated into intelligent and scalable solutions. Furthermore, with the trend towards reducing computational costs and the advancement of processing and storage infrastructures, it becomes increasingly viable to scale these solutions efficiently and at low cost.

How Karpenter optimized the management of our EKS infrastructure on AWS
Tech Writers 13 May, 2025

How Karpenter optimized the management of our EKS infrastructure on AWS

Companies face daily challenges in managing Kubernetes infrastructure, especially to maintain efficiency and reduce costs. Here at Softplan, we discovered a solution that transforms the way we manage our EKS clusters on AWS: Karpenter. Challenges in instance management Before talking about Karpenter, it is necessary to take a few steps back and explain a little about what node auto-scaling is. Suppose we have our cluster with some machines (instances) available running our workloads. What happens if there is a spike in usage in our applications and we need to launch more instances (replicas) of our pods? Without autoscaling, we would need to provision a node, instruct it to join our cluster so that our pods would be able to be started on this new instance. Remembering that provisioning an instance is not instantaneous, there is a whole bootstrapping of the machine, network configurations and many other things before it becomes fully available. Okay, we talked about peak users in our applications, but what about when there is idleness? Do we really want to leave these nodes standing with underutilized computing power? To resolve this and other issues, the concept of auto scalers comes into play. Auto Scalers Auto scaler implementations are basically responsible for node provisioning and consolidation. Here we are talking about horizontal scaling, that is, adding more machines to our cluster. There are several implementations of node autoscaling, but in this article the focus will be on the AWS implementation and why we decided to migrate to another solution. Below is a figure exemplifying how node autoscaling works: Figure 01: AWS autoscaling - Auto Scaling Groups When defining a scaling group in AWS we need to define several properties, such as the minimum/maximum number of node instances allowed for this group, resources used, disk type, network configurations (subnets, etc.) and many other details. For example, for a certain type of application that uses more CPU, we will configure a group that contains instance types with more CPU than memory. In the end we will possibly have some distinct groups for certain types of applications. Putting the pieces together – Cluster Auto Scaler In order for my cluster to be able to “talk” to my cloud provider (in this example AWS), we need a component called Cluster Auto Scaler, or CAS. This component was created by the community that maintains Kubernetes, and is available here. A default CAS configuration can be seen below, using helm for installation: nameOverride: cluster-autoscaler awsRegion: us-east-1 autoDiscovery: clusterName: my-cluster image: repository: registry.k8s.io/autoscaling/cluster-autoscaler tag: v1.30.1 tolerations: - key: infra operator: Exists effect: NoSchedule nodeSelector: environment: "infra" rbac: create: true serviceAccount: name: cluster-autoscaler annotations: eks.amazonaws.com/role-arn: "role-aws" extraArgs: v: 1 stderrthreshold: info With this configured and installed and our autoscaling groups created we have just enabled automatic management of our nodes! Why we decided to migrate to Karpenter Our use case here at Projuris is as follows: we have a development cluster and a production cluster. After migrating to Gitlab SaaS, we had a challenge of how to provision runners to execute our pipelines. It was decided that we would use the development cluster to provision these runners. In the “first version” we chose the auto scaler cluster because it was a simpler configuration and already met our production setup. But then we started to face some problems with this choice: Provisioning time: when starting a pipeline the machine provisioning time was a little slow. The big point is that the auto scaler cluster pays a “toll” to the cloud provider for provisioning a new node. Difficulty in configuring groups: as we have some pipeline “profiles”, this management became a little complicated, because for each new profile a new node group needs to be created. Cost: to mitigate the problem of slow startup of a new node, we had an “online” machine profile that was always on, even without executing any pipeline. What is Karpenter? It is an autoscaling cluster solution created by AWS, which promises the provisioning and consolidation of nodes always at the lowest possible cost. He is smart enough to know that, for example, when buying an on-demand machine on AWS, depending on the situation, it is more cost-effective than if it were a spot machine. And that's just one of the features of this tool. Karpenter also works with the idea of ​​“groups” of machines (which in the Karpenter world we call NodePools), but the difference is that we do this through CRDs (custom resource definitions) from Karpenter itself, that is, we have manifests within our cluster with all these configurations, eliminating the need for any node group created in AWS. pool. How did Karpenter help us overcome the challenges presented? Provisioning time: since Karpenter talks directly to the cloud provider's APIs, there is no need to pay the autoscaler cluster toll. We had many timeout issues when provisioning new nodes; after switching to Karpenter, this problem simply disappeared, precisely because provisioning is more efficient. Difficulty in configuring groups: with Karpenter's NodePools and NodeClass solution, this configuration became trivial, and most importantly, versioned in our version control on Gitlab. In other words, do you need to include a new machine profile in the NodePool? No problem, just one commit and Karpenter will already consider it in new provisioning. Cost: We were able to use machines, since now runners with similar characteristics are allocated to nodes that support the required memory and CPU requirements. In other words, we are really using all the computing power that that node provides. This also applies to node consolidation. With the cluster auto scaler, there were complex scripts to drain the nodes before consolidation. With Karpenter, this is configured in the NodePool in a very simplified way. A great argument for management to justify investing in this type of change is cost. Below we have a cost comparison using Cluster AutoScaler and Karpenter in January/25, where we achieved a total savings of 16%: Figure 02: Period from 01/01 to 15/01 with ClusterAutoScaler Figure 03: Period from 16/01 to 31/01 with Karpenter Final considerations The migration to Karpenter was a wise choice. We were able to simplify the management of our nodes with different profiles in a very simplified way. There is still room for some improvements, such as using a single NodePool to simplify things even further, and letting runners configure specific labels for the machine profile that should be provisioned for the runner (more at https://kubernetes.io/docs/reference/labels-annotations-taints/). References Karpenter (official doc): https://karpenter.sh/ Node Auto Scaling (official k8s doc): https://kubernetes.io/docs/concepts/cluster-administration/node-autoscaling/

The strength of collaboration and the customer as the protagonist: impacts of product evolution in the Group Softplan
Tech Writers 29 April, 2025

The strength of collaboration and the customer as the protagonist: impacts of product evolution in the Group Softplan

In the group Softplan, product evolution is an ongoing effort that involves cross-team collaboration and a deep commitment to the customer. In my role as Product Growth, I constantly exchange ideas with other teams and receive valuable feedback from customers, whether through analysis of how they use the product or through specific communication channels, such as email. These interactions give me a clear view of the impact that continuous product evolution has on the success of the company and the value delivered to customers. This article explores how cross-team collaboration and customer focus drive the evolution of our products, fostering the growth of the Group. Softplan and the success of those who use our solutions. Collaboration between teams: the engine of innovation Product development and improvement in the Group Softplan require continuous integration between different teams. Software solutions need to be effective and aligned with market demands. Although it is not directly part of defining the roadmap, my role allows me to bring valuable insights based on customer interactions and performance data. This directly contributes to the prioritization of development initiatives. Studies by Forbes indicate that companies that encourage internal collaboration are 4,5 times more likely to retain top talent and innovate more efficiently (Forbes on Collaboration). In the Group Softplan, effective collaboration is one of the pillars to ensure that customer needs are met quickly and efficiently. The product, marketing, growth and sales departments work together continuously, always seeking to align initiatives with market demands. This collaborative work, combined with the support of the Growth team in prioritizing initiatives, integrates different perspectives and areas of the company, allowing for constant adjustments to products and driving the creation of innovations based on these interactions between departments. Customer as the protagonist: the guide for our decisions In the Group Softplan, the customer is at the center of all decisions, especially in the Industry and Construction Unit, where the value of "customer as protagonist" guides our way of working. We use specific channels to collect continuous feedback, and these insights shape product initiatives. As pointed out by Salesforce, 80% of customers consider the experience offered by a company as important as its products and services (Salesforce State of the Connected Customer). In practice, this means that by listening to users and adjusting our products based on their demands, we strengthen the relationship and increase loyalty to our brand. An example of this was the recent feature update, based on customer feedback, which brought more diversified communications across product modules, aligning with identified needs. This customer-driven approach not only meets current needs, but also allows us to anticipate future demands. This ongoing process solidifies our role as a strategic partner for customers. Market Impact: Innovation and Growth The Group Softplan stands out in the market for its commitment to innovation and focus on concrete results. Adjusting our products based on direct customer feedback has a direct impact on the company’s growth and user satisfaction. As mentioned, the update of more diversified communications in product modules was a direct response to this feedback, highlighting how continuous communication with the customer guides the evolution of our solutions. According to PwC, companies that prioritize customer experience can see a 16% increase in revenue and greater customer retention (PwC Future of Customer Experience Survey). This reality also applies to the Group Softplan, where continuous adjustment and focus on customer needs help us deliver relevant solutions that stand out in the market. Strategic use of customer feedback not only improves the user experience, but also ensures that we are always one step ahead in terms of innovation and competitiveness. Come grow with us The Group Softplan stands out for listening to its customers and bringing its teams together to create solutions that drive business. The value of "customer as protagonist" is a practical guide and present in our journey of product evolution. We collaborate, innovate and adapt, always ensuring that customer needs are at the center of our decisions. If you value an environment that fosters collaboration and innovation, with opportunities for continuous learning and growth, the Group Softplan is the right place for you. Here, our values ​​and strategic objectives are reinforced by training and the opportunity to work on challenging projects that transform the software market. Join us and be part of a team that transforms the lives of customers and innovates the market. Visit our career page.

Digital Evolution in the Public Sector: B2G Product Management
Tech Writers 16 April, 2025

Digital Evolution in the Public Sector: B2G Product Management

In recent years, the public sector has expanded its digital services for citizens. The 2020 pandemic accelerated this trend, driving the modernization of bodies such as Courts of Justice, Public Ministries and Public Defenders' Offices. This transformation aims to improve the efficiency of public services and facilitate access for the population. Historically, the Information Technology areas of these organizations have adopted project management models that prioritize the delivery of defined scopes, with deadlines and teams limited to specific demands. However, the growing need for agility has driven the transition to product management. In this context, the concept of Business-to-Government (B2G) gains relevance, highlighting the importance of product management in offering innovative solutions to the government. As a Product Manager working on B2G products, my focus is to deliver solutions aligned with the needs of end users. Unlike the B2B sector, where there is a structured sales funnel, product management in the public sector requires the adoption of metrics and tools adapted to this ecosystem. Day-to-day life of Product Managers in the public sector Interactions with customers begin after the contract is signed, when the first contact occurs with the management group, formed by employees responsible for implementing the product. From this point, insights are gained into the needs of end users, allowing an initial understanding of the workflow. To prioritize the backlog, we use the RICE (Reach, Impact, Confidence, Effort) matrix, ensuring that decisions consider both contractual requirements and user needs. This prioritization occurs continuously, following the evolution of the product and established contracts. In the development cycle, we apply experimentation, prototyping and usability testing techniques with pilot groups. We collect quantitative and qualitative data to measure adoption and define improvements to product functionality. Example of RICE matrix With these premises in mind, we apply to pilot users, for example, experimentation techniques, prototyping, conducting usability tests for new functionalities. We also constantly collect quantitative and qualitative data on the journey they use, as adherence increases. Based on the metrics collected, we can define whether the main or additional features of our user's journey need to be improved. Example of quantitative insights with information from the user journey in organizing tasks using the MixPanel tool Example of using the INDECX tool for qualitative information about the product or functionality Product triad delivering efficient results Product management in the public sector requires a collaborative approach, integrating the technical team, user experience team and customer. This ongoing interaction strengthens strategic alignment and clarity about product evolution. The product roadmap is shared with the client to ensure transparency and predictability in deliveries. Softplan has established itself as a reference in the digital transformation of the public sector, generating positive impacts for citizens. Solutions such as the Justice Automation System (SAJ) provide efficiency and speed in public services. As Product Manager at Softplan, I contribute to the management of products aimed at the public sector. An example is the SAJ Defensorias, whose task panel was developed after business study and technical analysis based on the product triad. This panel centralizes daily activities, prioritizing tasks to be performed immediately and organizing completed ones for future reference. SAJ Solution Softplan Our goal is to offer intuitive and efficient products that meet the daily demands of public defenders and contribute to improving the provision of services to society. Defender's task panel in SAJ Defensorias Digital initiatives in the public sector have great growth potential, driven by product culture. Digital transformation is irreversible and will continue to evolve to meet society's expectations for more agile, efficient, and transparent services. Bibliographic References CAGAN, M. Inspired: How to Create Tech Products Customers Love. United States: Wiley, 2017. EIS, D. Modern Digital Product Management. Santa Catarina: Clube de Autores, 2022. TORRES, J. Software product management: How to increase your software's chances of success. São Paulo: Casa do Código, 2015. Software Product Manager Responsibilities in Software Management Cycle https://asperbrothers.com/blog/software-product-manager-responsibilities/The different types of 'Product Managers': which one am I and which one do I need?https://medium.com/@carlosbeneyto/types-of-product-manager-startup-3bb978f50d2f Rice Matrix: what it is and how to improve the prioritization of your projects: https://www.nomus.com.br/blog-industrial/matriz-rice/ Learn about NPS and customer satisfaction: https://blog.indecx.com.br/relatorios-de-nps-16-exemplos-com-indicadores-de-satisfacao-de-clientes/

.Net ThreadPool Exhaustion
Tech Writers March 25, 2025

.Net ThreadPool Exhaustion

More than once in my career I have come across this scenario: a .Net application frequently showing high response times. This high latency can have several causes, such as slow access to an external resource (a database or an API, for example), CPU usage reaching 100%, disk access overload, among others. I would like to add another possibility to the previous list, often overlooked: ThreadPool exhaustion. I will briefly show how the .Net ThreadPool works, and code examples where this can happen. Finally, I will demonstrate how to avoid this problem. The .Net ThreadPool The .Net Task-based asynchronous programming model is well known by the development community, but I believe that its implementation details are poorly understood - and it is in the details that the danger lies, as the saying goes. Behind the .Net Task execution mechanism there is a Scheduler, responsible, as its name suggests, for scheduling the execution of Tasks. Unless explicitly changed, the default .Net scheduler is the ThreadPoolTaskScheduler, which, as the name suggests, uses the default .Net ThreadPool to perform its work. The ThreadPool then manages, as expected, a pool of threads, to which it assigns the Tasks it receives using a queue. It is in this queue that the Tasks are stored until there is a free thread in the pool, and then start processing it. By default, the minimum number of threads in the pool is equal to the number of logical processors on the host. And here's the detail in how it works: when there are more Tasks to be executed than the number of threads on the host, pool, the ThreadPool can either wait for a thread to become free or create more threads. If it chooses to create a new thread and if the current number of threads in the pool is equal to or greater than the configured minimum number, this growth takes between 1 and 2 seconds for each new thread added to the pool. Note: Starting with .Net 6, improvements were introduced to this process, allowing for a faster increase in the number of threads in the ThreadPool, but the main idea still remains. Let's look at an example to make it clearer: suppose a computer has 4 cores. The minimum value of the ThreadPool will be 4. If all the Tasks that arrive quickly process their work, the pool may even have less than the minimum of 4 active threads. Now, imagine that 4 Tasks of slightly longer duration arrived simultaneously, thus using all the threads of the pool. When the next Task arrives in the queue, it will need to wait between 1 and 2 seconds, until a new thread is added to the queue. pool, and then leave the queue and start processing. If this new Task also has a longer duration, the next Tasks will wait in the queue again and will need to “pay the toll” of 1 to 2 seconds before they can start executing. If this behavior of new long-running Tasks continues for some time, the clients of this process will feel slow for any new Tasks that arrive at the ThreadPool queue. This scenario is called ThreadPool exhaustion (or ThreadPool starvation). This will occur until the Tasks finish their work and start returning threads to the pool, enabling the reduction of the queue of pending Tasks, or that the pool can grow enough to meet the current demand. This can take several seconds, depending on the load, and only then will the slowdown observed previously cease to exist. Synchronous vs. asynchronous code It is now necessary to make an important distinction about types of long-running work. Generally, they can be classified into 2 types: CPU/GPU-bound (CPU-bound or GPU-bound), such as the execution of complex calculations, or I/O-bound (I/O-bound), such as database access or network calls. In the case of CPU-bound tasks, except for algorithm optimizations, there is not much that can be done: you need to have enough processors to meet the demand. However, in the case of I/O-bound tasks, it is possible to free up the processor to respond to other requests while waiting for the I/O operation to finish. And this is exactly what the ThreadPool does when asynchronous I/O APIs are used. In this case, even if the specific task is still time-consuming, the thread will be returned to the pool and can serve another Task from the queue. When the I/O operation is finished, the Task will be requeued and then continue executing. For more details on how the ThreadPool waits for I/O operations to finish, click here. However, it is important to note that there are still synchronous I/O APIs, which cause the thread to block and prevent it from being released to the pool. These APIs - and any other type of call that blocks a thread before returning to execution - compromise the proper functioning of the ThreadPool, and may cause it to exhaust itself when subjected to sufficiently large and/or long loads. We can therefore say that the ThreadPool - and by extension ASP.NET Core/Kestrel, designed to operate asynchronously - is optimized for executing tasks of low computational complexity, with asynchronous bound I/O loads. In this scenario, a small number of threads is capable of processing a very high number of tasks/requests efficiently. Thread blocking with ASP.NET Core Let's see some code examples that cause threads to block pool, using ASP.NET Core 8. Note: These codes are simple examples, and are not intended to represent any particular practice, recommendation, or style, except for the points related to the ThreadPool demonstration specifically. To maintain identical behavior between examples, a request to a SQL Server database will be used that will simulate a workload that takes 1 second to return, using the WAITFOR DELAY statement. To generate a usage load and demonstrate the practical effects of each example, we will use siege, a free command-line utility designed for this purpose. In all examples, a load of 120 concurrent accesses will be simulated for 1 minute, with a random delay of up to 200 milliseconds between requests. These numbers are enough to demonstrate the effects on the ThreadPool without generating timeouts when accessing the database. Synchronous Version Let's start with a completely synchronous implementation: The DbCall action is synchronous, and the ExecuteNonQuery method of the DbCommand/SqlCommand is synchronous, so it will block the thread until the database returns. Below is the result of the load simulation (with the siege command used). You can see that we achieved a rate of 27 requests per second (Transaction rate), and an average response time (Response time) of around 4 seconds, with the longest request (Longest transaction) lasting more than 16 seconds – a very poor performance. Asynchronous Version – Attempt 1 Let’s now use an asynchronous action (returning Task ), but still use the synchronous ExecuteNonQuery method. Running the same load scenario as before, we have the following result. Note that the result was even worse in this case, with a request rate of 14 per second (compared to 27 for the completely synchronous version) and an average response time of more than 7 seconds (compared to 4 for the previous one). Asynchronous Version – Attempt 2 In this next version, we have an implementation that exemplifies a common – and not recommended – attempt to transform a synchronous I/O call (in our case, ExecuteNonQuery ) into an “asynchronous API”, using Task.Run. The result, after simulation, shows that the result is close to the synchronous version: request rate of 24 per second, average response time of more than 4 seconds and the longest request taking more than 14 seconds to return. Asynchronous Version – Attempt 3 Now the variation known as “sync over async”, where we use asynchronous methods, such as ExecuteNonQueryAsync in this example, but the .Wait() method of the Task returned by the method is called, as shown below. Both .Wait() and the .Result property of a Task have the same behavior: they cause the executing thread to block! Running our simulation, we can see below how the result is also bad, with a rate of 32 requests per second, an average time of more than 3 seconds, with requests taking up to 25 seconds to return. Not surprisingly, the use of .Wait() or .Result in a Task is discouraged in asynchronous code. Problem Solution Finally, let's look at the code created to work in the most efficient way, through asynchronous APIs and applying async / await correctly, following Microsoft's recommendation. We then have the asynchronous action, with the ExecuteNonQueryAsync call with await. The simulation result speaks for itself: request rate of 88 per second, average response time of 1,23 seconds and request taking a maximum of 3 seconds to return - numbers generally 3 times better than any previous option. The table below summarizes the results of the different versions, for a better comparison of the data between them. Code VersionRequest Rate ( /s)Average Time (s)Max Time (s)Synchronous27,384,1416,93Asynchronous114,337,9414,03Asynchronous224,904,5714,80Asynchronous332,433,5225,03Solution88,911,233,18 Workaround It is worth mentioning that we can configure the ThreadPool to have a minimum number of threads greater than the default (the number of logical processors). With this, he will be able to quickly increase the number of threads without paying that “toll” of 1 or 2 seconds. There are at least 3 ways to do this: by dynamic configuration, using the runtimeconfig.json file, by project configuration, by adjusting the ThreadPoolMinThreads property, or by code, by calling the ThreadPool.SetMinThreads method. This should be seen as a temporary measure, while the appropriate adjustments are not made to the code as shown above, or after appropriate prior testing to confirm that it brings benefits without performance side effects, as recommended by Microsoft. Conclusion ThreadPool exhaustion is an implementation detail that can have unexpected consequences. And they can be difficult to detect if we consider that .Net has several ways to obtain the same result, even in its best-known APIs – I believe motivated by years of evolution in the language and ASP.NET, always aiming at backward compatibility. When we talk about operating at increasing rates or volumes, such as going from dozens to hundreds of requests, it is essential to know the latest practices and recommendations. Furthermore, knowing one or another implementation detail can make a difference in avoiding scale problems or diagnosing them more quickly. Tech Writers. In a future article, we will explore how to diagnose ThreadPool exhaustion and identify the source of the problem in code from a running process.