<?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"><channel><title><![CDATA[Marco Pedroza Technical Blog]]></title><description><![CDATA[Marco Pedroza Technical Blog]]></description><link>https://mpedroza.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Tue, 10 Nov 2026 12:15:23 GMT</lastBuildDate><atom:link href="https://mpedroza.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Building a High-Performance Event-Driven Rate Limiter with OpenResty, Valkey(Redis) & Prometheus]]></title><description><![CDATA[A Practical Guide to protect your spring boot applications from a large number of concurrent requests.]]></description><link>https://mpedroza.hashnode.dev/building-a-high-performance-event-driven-rate-limiter-with-openresty-valkey-redis-prometheus</link><guid isPermaLink="true">https://mpedroza.hashnode.dev/building-a-high-performance-event-driven-rate-limiter-with-openresty-valkey-redis-prometheus</guid><category><![CDATA[Devops]]></category><category><![CDATA[valkey]]></category><category><![CDATA[#prometheus]]></category><category><![CDATA[Springboot]]></category><category><![CDATA[Java]]></category><category><![CDATA[software development]]></category><category><![CDATA[software architecture]]></category><dc:creator><![CDATA[Marco Pedroza]]></dc:creator><pubDate>Mon, 09 Nov 2026 22:17:08 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6af21e1aa1bd141a01e3881f/96a54ca1-731e-4d53-b7d4-8a50bc0d0ec6.gif" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Probably you may have hear this <strong>“rate limiter”</strong> term in the past given that it is very popular in software development and DevOps. In this article we are going to explore a <strong>fully working example</strong> that you can run locally.</p>
<p>Let’s start by defining what Rate Limiter actually is.</p>
<p>A rate limiter is a tool that the developers eventually will need in their web facing application. It does not matter if it’s a commercial application or an open source app because you need to protect it from abusive use or seasons with exceptionally high traffic.</p>
<p>For example your application may experience an attack called <a href="https://community.owasp.org/attacks/Denial_of_Service"><u>DDoS(Distributed Denial of Service)</u></a> which is that one or more users are sending a large number of request to your application in a short period of time and as a result your application may cease to be available to legitimate users. Here is where a rate limiter is very useful because you can add rules that detect this and block those malicious requests, examples of rules could be checking by: IP address, location, JWT token or other attributes.</p>
<p>Another situation which is NOT an attack but it is very possible to occur is in high traffic sites that for example in some seasons like Christmas or Thanks Giving the traffic may be higher than expected and the infrastructure is not being able to handle it, in this case we may need to implement a rate limiter to reject requests when our infrastructure reached the threshold that we defined, allowing more requests might compromise our infrastructure.</p>
<p>By now we already covered what a rate limiter is and described a couple use cases, but obviously there might be more scenarios because as everything in software development it depends on your use case.</p>
<p>For this article we are going to focus in a possible solution when the infrastructure is overloaded, the scenario will be this one:</p>
<blockquote>
<p><em>The rate limiter is going to be activated when the CPU usage of our spring-boot-solr-movies-search-service is over 80% in the last 30 seconds.</em></p>
</blockquote>
<p>In order to generate high traffic to the spring boot application we are going to use Apache JMeter which is a tool to create load tests. At the end of this article you will find a section called <strong>Rate Limiter in Action</strong> which includes a link to the github repository that has all the code.</p>
<p>Let’s start for the high level view of the system that we are going to use in this article:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af21e1aa1bd141a01e3881f/b606bc4f-1d2c-426a-a565-cbae8fbb9a27.png" alt="Rate Limiter Flow" style="display:block;margin:0 auto" />

<h3><a href="https://openresty.org/en/"><strong><u>Openresty</u></strong></a></h3>
<p>It is a web platform that run a NGINIX server but with some additional features such as enhaced version of LuajIT, one of the cool things is that allows us to manipulate the request prior to reaching our services.</p>
<p>We use this container as our API Gateway, if you are not familiar with an API Gateway it is basically the entrypoint to your API, in this step is where you normally validate if the incoming request should reach or not the backend service.</p>
<p>In the example that we are reviewing in this article the api gateway validates if denies access to the backend service and returns a HTTP 503 response code or allow the request to reach the backend service.</p>
<p>The gateway decides to ALLOW or REJECT by checking if Valkey has this key: <strong>spring_boot_solr_movies_search_service_high_cpu_usage</strong> as true.</p>
<p>Here is a code snippet with what I just described.</p>
<pre><code class="language-shell">            access_by_lua_block {
                local valkey = require "resty.redis";
                local val = valkey:new()

                val:set_timeout(1000)

                local ok, err = val:connect("valkey.dns.podman",6379)

                if not ok then
                    ngx.status = ngx.HTTP_INTERNAL_SERVER_ERROR
                    ngx.say('{"error": "Error while trying to connect to valkey: ' .. err .. '"}')
                    return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
                end

                local context_path = ngx.var.uri

                local route_map = {}
                    route_map["/api/movies"] = "http://spring_boot_mongodb_movies_service_pool"
                    route_map["/api/movies/search"] = "http://spring_boot_solr_movies_search_service_pool"
                    route_map["/api/movies/semantic-search"] = "http://spring_boot_solr_movies_search_service_pool"
                    route_map["/api/movies/recommendations"] = "http://spring_boot_solr_movies_search_service_pool"


                local matched_backend_service = route_map[context_path]

                if matched_backend_service then

                     local valkey_keys_map = {}
                     valkey_keys_map["/api/movies"] = "spring_boot_mongodb_movies_service_high_cpu_usage"
                     valkey_keys_map["/api/movies/search"] = "spring_boot_solr_movies_search_service_high_cpu_usage"
                     valkey_keys_map["/api/movies/semantic-search"] = "spring_boot_solr_movies_search_service_high_cpu_usage"
                     valkey_keys_map["/api/movies/recommendations"] = "spring_boot_solr_movies_search_service_high_cpu_usage"


                    local matched_valkey_key = valkey_keys_map[context_path]

                    local res, err = val:get(matched_valkey_key)

                    if res and res ~= ngx.null and res == "true" then
                        ngx.status = 503
                        ngx.say('{"error": "Service Unavailable: ' .. context_path .. '"}')
                        val:set_keepalive(1000,200)
                        return ngx.exit(503)
                    end

                    ngx.var.target_backend = matched_backend_service

                else
                    ngx.status = 404
                    ngx.say('{"error": "The following path: ' .. context_path .. ' not found in API Gateway"}')
                    val:set_keepalive(1000,200)
                    return ngx.exit(404)
                end

                val:set_keepalive(10000,200)
            }
</code></pre>
<blockquote>
<p><em>Script file path: spring-boot-examples-pb/spring-boot-search-platform-example/openresty/nginx.conf.</em></p>
</blockquote>
<p>As you can see in the code snippet basically what we do is create a valkey instance so Openresty can connect to valkey and retrieve the keys based on the context path.</p>
<p>You may wonder why do I choose Openresty and not simply go with Spring Cloud Gateway. Here is why:</p>
<ul>
<li>Memory Usage: Here Openresty is superior to the Spring Cloud Gateway. Spring Cloud Gateway requires the JVM which introduces heavy memory usage, on the other side Openresty runs on top NGINIX and LuaJIT which does not consume a lot of memory.</li>
</ul>
<h3><a href="https://github.com/mpedroza-mx/spring-boot-examples-pb/tree/main/spring-boot-search-platform-example"><strong><u>spring-boot-solr-movies-search-service</u></strong></a></h3>
<p>This is the spring boot application that is used as the backend service. The application provides the following endpoints:</p>
<ul>
<li><a href="https://medium.com/@mpedroza-mx/building-a-modern-search-platform-using-spring-boot-mongodb-apache-kafka-and-apache-solr-d45ebcba6747?sharedUserId=mpedroza-mx"><u>/api/movies/search</u></a></li>
<li><a href="https://medium.com/@mpedroza-mx/how-to-implement-semantic-search-with-ollama-spring-boot-and-solr-a-comprehensive-tutorial-9ab5910a3e26?sharedUserId=mpedroza-mx"><u>/api/movies/semantic-search</u></a></li>
<li><a href="https://medium.com/@mpedroza-mx/building-a-local-rag-system-with-spring-boot-ollama-and-apache-solr-a-practical-guide-approach-306106df8bd5?sharedUserId=mpedroza-mx"><u>/api/movies/recommendations</u></a></li>
</ul>
<p>The endpoints are very self explanatory, I will not make a detailed description of those endpoints because they are out of the scope of this article, but if you are interested in terms such as semantic search, RAG Systems. I have created some articles about that, the link to those documents are in the previous listed endpoints.</p>
<h3><strong>prometheus-podman-exporter</strong></h3>
<p>Basically this container is going to help us to collect the metrics from the other containers that are being used in the docker-compose.yml file.</p>
<p>It is important to mention that we are running podman rootless, because we are following the least privilege principle, it is a security principle that states that you only grant the minimum permissions needed to a user, program to perform a specific task.</p>
<p>The metrics collected here will be used by prometheus. In order to allow podman-exporter collect the metrics we need to make the following configurations:</p>
<pre><code class="language-yaml">   podman-exporter:
    container_name: podman-exporter
    image: quay.io/navidys/prometheus-podman-exporter:v2.0.0
    security_opt:
      - "label=type:container_runtime_t"
    userns_mode: keep-id
    ports:
      - "9882:9882"
    environment:
      - CONTAINER_HOST=unix:///run/user/501/podman/podman.sock
      - HOME=/tmp
    volumes:
      - /run/user/501/podman/podman.sock:/run/user/501/podman/podman.sock:ro
    networks:
      - search_network
</code></pre>
<blockquote>
<p><em>File Path: spring-boot-examples-pb/spring-boot-search-platform-example/docker-compose.yaml</em></p>
</blockquote>
<p>The critical configurations are these ones:</p>
<ul>
<li>security_opt</li>
<li>userns_mode</li>
<li>volumes</li>
<li>environment</li>
</ul>
<p>All four together are what allow podman-exporter securely access the host’s rootless podman socket.</p>
<p>One more thing here if you take a look to the environment and the volumes they are using the user 501, this change from system to system in order to know the user in your computer, you can run the following command:</p>
<pre><code class="language-shell">        podman system connection list
</code></pre>
<p>The output of this command will let you know if you are running podman in rootless mode. The rootless mode uses the <strong>core user</strong>.</p>
<h3><a href="https://valkey.io/"><strong><u>valkey</u></strong></a></h3>
<p>It is the open source version of REDIS. Valkey is a high performance key value datastore which is great for distributed environments. In our use case we only have one instance of spring-boot-solr-movies-search-service but in real production microservices environments you will normally have several instances.</p>
<h3><a href="https://prometheus.io/"><strong><u>Prometheus</u></strong></a><strong>/</strong><a href="https://prometheus.io/docs/alerting/latest/alertmanager/"><strong><u>alertManager</u></strong></a></h3>
<p>I will explain these 2 components together because they work very close. Prometheus acts as our metrics hub, it will pull the metrics from the different components and will define the alerts using something called promQL which is a query language that allows you to work with the metrics.</p>
<p>For example here we can see the alert associated to the data from the podman-exporter</p>
<pre><code class="language-yaml">- name: podman-exporter
 rules:
   - alert: spring_boot_solr_movies_search_service_high_cpu_usage
 expr: sum(rate(podman_container_cpu_seconds_total[30s])) by (id) * 100 * on(id) podman_container_info{name="spring-boot-search-platform-example-spring-boot-solr-movies-search-service-1"} &gt; 80
 for: 30s
 labels:
   container_stats: spring_boot_solr_movies_search_service_cpu_usage
   instance: '{{ $labels.instance }}'
   severity: critical
 annotations:
   summary: "spring-boot-solr-movies-search-service CPU usage higher than 80%"
   description: "The instance *{{ $labels.instance }}* CPU usage has been higher than 80% for more than 30 seconds"
</code></pre>
<blockquote>
<p><em>File Path: spring-boot-examples-pb/spring-boot-search-platform-example/prometheus/alert.yml</em></p>
</blockquote>
<p>The alert will be triggered if the CPU usage from the spring-boot-solr-movies-search-service is higher than 80% in the last 30 seconds, this alert will be send to the alertManager and will decide how to handle it.</p>
<p>Here are the configurations used by the alertmanager</p>
<pre><code class="language-yaml">global:
 smtp_smarthost: 'mailpit:1025'
 smtp_from: 'search-platform@example.com'
 smtp_require_tls: false

route:
 group_by: ['service','severity']
 group_wait: 5s
 group_interval: 30s
 repeat_interval: 4h
 receiver: 'email-receiver'

 routes:
   - receiver: 'webhook-receiver'
     matchers:
       - container_stats =~ "spring_boot_mongodb_movies_service_cpu_usage|spring_boot_solr_movies_search_service_cpu_usage"
templates:
 - '/etc/alertmanager/templates/*.tmpl'

receivers:
 - name: 'email-receiver'
   email_configs:
     - to: 'support-team@example.com'
       send_resolved: true
       require_tls: false
       headers:
         subject: '[{{ .Status | toUpper }}] Service Alert: {{ .CommonLabels.alertname }}'
       html: '{{ template "email.custom" . }}'
 - name: 'webhook-receiver'
   webhook_configs:
     - url: 'http://rate-limit-service:5050/api/alerts'
       send_resolved: true
</code></pre>
<blockquote>
<p><em>File Path: spring-boot-examples-pb/spring-boot-search-platform-example/prometheus-alert-manager/alertmanager.yml</em></p>
</blockquote>
<p>For this alert there are 2 receivers</p>
<ul>
<li>Email</li>
<li>Webhook</li>
</ul>
<p>This is great for real PRODUCTION scenarios because it notifies the support team but also activates the rate limiter through the webhook, basically what the webhook does is that it sends an http request to this endpoint: <a href="http://rate-limit-service:5050/api/alerts"><strong><u>http://rate-limit-service:5050/api/alerts</u></strong></a><strong>,</strong> which is a spring boot application.</p>
<blockquote>
<p><em>This interaction between the prometheus/alertmanager using the webhook to notify the rate-limit-service applicaiton is the well-known event driven approach. This approach is by far more efficient than the method polling data approach.</em></p>
</blockquote>
<p>If you are interested in learning more about the process of receiving the alerts by email I wrote the following article:</p>
<p><a href="https://medium.com/@mpedroza-mx/from-metrics-to-alerts-monitoring-spring-boot-app-with-prometheus-and-alertmanager-246377c338c6?sharedUserId=mpedroza-mx"><u>From Metrics to Alerts: Monitoring Spring Boot App with Prometheus and Alertmanager</u></a></p>
<h3><strong>MailPit</strong></h3>
<p>It is an email testing tool which is great for this case given that we are doing local testing in this article, for other environments such as DEV, QA and PROD you will need another approach like a real SMTP server.</p>
<h3><strong>rate-limit-service</strong></h3>
<p>This is a normal spring boot REST API that uses this maven dependency:</p>
<pre><code class="language-xml">&lt;dependency&gt;
   &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
   &lt;artifactId&gt;spring-boot-starter-data-redis-reactive&lt;/artifactId&gt;
&lt;/dependency&gt;
</code></pre>
<blockquote>
<p><em>File Path: spring-boot-examples-pb/spring-boot-search-platform-example/rate-limit-service/pom.xml</em></p>
</blockquote>
<p>This maven dependency has the tools that we need to communicate with Valkey by creating ReactiveRedistTemplate as we can see here</p>
<pre><code class="language-java">@Bean
public ReactiveRedisTemplate&lt;String, Boolean&gt; reactiveRedisTemplate(ReactiveRedisConnectionFactory reactiveRedisConnectionFactory) {
   StringRedisSerializer keySerializer = new StringRedisSerializer();
   GenericToStringSerializer valueSerializer = new GenericToStringSerializer&lt;&gt;(Boolean.class);




   RedisSerializationContext serializationContext = RedisSerializationContext
           .&lt;String, Boolean&gt;newSerializationContext()
           .key(RedisSerializationContext.SerializationPair.fromSerializer(keySerializer))
           .value(RedisSerializationContext.SerializationPair.fromSerializer(valueSerializer))
           .hashKey(RedisSerializationContext.SerializationPair.fromSerializer(keySerializer))
           .hashValue(RedisSerializationContext.SerializationPair.fromSerializer(keySerializer))
           .build();


   return new ReactiveRedisTemplate&lt;&gt;(reactiveRedisConnectionFactory, serializationContext);
}
</code></pre>
<blockquote>
<p><em>File Path: spring-boot-examples-pb/spring-boot-search-platform-example/rate-limit-service/src/main/java/org/learning/spring/rate_limit_service/config/ValkeyConfig.java</em></p>
</blockquote>
<p>The application in the AlertService class will process the alerts that it receives from the Prometheus/Alert Manager and will create the rate limit key(s) in valkey using the alertName as key.</p>
<p>Here is how the key is created in valkey:</p>
<pre><code class="language-java">    public void handleAlert(PrometheusAlertRequestBody prometheusAlertRequestBody) {


   prometheusAlertRequestBody.getAlerts().forEach(alert -&gt; {
       if (alert.getStatus().equals("firing")) {


           Boolean rateLimitEnabled = reactiveRedisTemplate.opsForValue().set(alert.getLabels().getAlertName(), true, Duration.ofMinutes(4))
                   .block();


          if (rateLimitEnabled != null &amp;&amp; rateLimitEnabled){
              LOGGER.info("Enable rate limit for: {}", alert.getLabels().getAlertName());
          }else{
              LOGGER.error("Error while trying to activate the rate limit for alert {}", alert.getLabels().getAlertName());
          }
       } else if (alert.getStatus().equals("resolved")) {
           LOGGER.info("Deactivate rate limit for alert: {}", alert.getLabels().getAlertName());
       }
   });


}
</code></pre>
<blockquote>
<p><em>File Path: spring-boot-examples-pb/spring-boot-search-platform-example/rate-limit-service/src/main/java/org/learning/spring/rate_limit_service/service/AlertService.java</em></p>
</blockquote>
<p>We can see that if the alert is in <strong>firing</strong> status it is going to create the rate limit key with TTL(Time to live) of 4 Minutes, this means that during 4 minutes Openresty will respond with HTTP 503 codes blocking the access to spring-boot-solr-movies-search-service.</p>
<p>As a separate note if you are curious about the OpenAPI specification for the alerManager you can find it here:</p>
<p><a href="https://prometheus.io/docs/alerting/latest/alerts_api/"><u>https://prometheus.io/docs/alerting/latest/alerts_api/</u></a></p>
<h3><a href="https://jmeter.apache.org/"><strong><u>Apache JMeter</u></strong></a></h3>
<p>As mentioned earlier in this article JMeter it is a load test tool that we will use to simulate 1000 concurrent users progressively during 5 minutes. Here you can see the configurations:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af21e1aa1bd141a01e3881f/016f2aca-0f1f-4497-8756-db865c6d0fc5.png" alt="Thread Group" style="display:block;margin:0 auto" />

<p>You can find the load test file here:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af21e1aa1bd141a01e3881f/6543bd99-a094-4b17-9a7b-80d790537395.png" alt="Load Test Script Location" style="display:block;margin:0 auto" />

<p>Apache JMeter will send many http requests to this endpoint:</p>
<p><a href="http://openresty:80/api/movies/search"><u>http://openresty</u>:80/api/movies/search</a></p>
<p>HTTP Method: POST</p>
<p>HTTP Request Body:</p>
<pre><code class="language-json">{
  "query": "Future",
  "filters": [
    {
      "field": "genres",
      "operator": "IN",
      "values": ["Comedy","Drama"]
    },
    {
      "field": "title",
      "operator": "EQUALS",
      "value": "Future"
    },
    {
      "field": "tomatoes.critic.rating",
      "operator": "RANGE",
      "from": "7",
      "to": "8"
    }
  ]
}
</code></pre>
<h3><a href="https://grafana.com/docs/grafana/latest/fundamentals/getting-started/"><strong><u>Grafana</u></strong></a></h3>
<p>It is a visualization tool that is used in this system to help us see the traffic and CPU usage associated with the spring-boot-solr-movies-search-service application. If you want to know more about the configuration needed for grafana and how to create dashboards you can take a look to the following article that I created:</p>
<p><a href="https://medium.com/@mpedroza-mx/from-metrics-to-dashboards-monitoring-spring-boot-app-with-prometheus-and-grafana-45ed64302762?sharedUserId=mpedroza-mx"><u>From Metrics to Dashboards: Monitoring Spring Boot App with Prometheus and Grafana</u></a></p>
<h3><strong>Rate Limiter In Action</strong></h3>
<p>We are going to see how we can test this.</p>
<p>1. Open a terminal and clone this project:</p>
<p><a href="https://github.com/mpedroza-mx/spring-boot-examples-pb">https://github.com/mpedroza-mx/spring-boot-examples-pb</a></p>
<p>2. Navigate to this folder: <strong>spring-boot-search-platform-example</strong> and execute the following command:</p>
<pre><code class="language-shell">     podman compose --profile MONITORING --profile=LOAD_TEST  up --build -d
</code></pre>
<p>3. Once all containers are up and running you can check the <strong>spring-boot-solr-movies-search-service</strong> CPU and traffic and see the rate limit in action, for this you will need to connect to prometheus, grafana and mailpit. You should see something like this:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af21e1aa1bd141a01e3881f/f3974f65-9194-4196-880d-025231bfc050.png" alt="Rate Limit In Action Visualization" style="display:block;margin:0 auto" />

<p>In the previous image we can see that there was a peak in the traffic and CPU usage but then at some point it ended abruptly that it is when the rate limiter was activated. We can also see that prometheus triggered the alert and mailpit received the notification email.</p>
<p>If you want to see this locally you can access these urls:</p>
<ul>
<li>Grafana: <a href="http://localhost:3000"><u>http://localhost:3000</u></a></li>
<li>Prometheus: <a href="http://localhost:9090/alerts"><u>http://localhost:9090/alerts</u></a></li>
<li>MailPit: <a href="http://localhost:8025"><u>http://localhost:8025</u></a></li>
</ul>
<blockquote>
<p><em>Note: If you are asked for a user and password to connect to your local grafana use admin/admin</em></p>
</blockquote>
<p>4. When the load test is completed we now can check the index.html file in the following path:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af21e1aa1bd141a01e3881f/576f414f-80ef-44e3-8d26-e2d1ddea278f.png" alt="Index.html location" style="display:block;margin:0 auto" />

<p>The report should look something like this:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af21e1aa1bd141a01e3881f/a40e8526-4407-49da-aa55-01ddb630b902.png" alt="" style="display:block;margin:0 auto" />

<p>We can see that there are a lot of errors because Openresty rejected all the requests after the CPU usage for the spring-boot-solr-movies-search-service reached 80%.</p>
<h2><strong>Conclusion</strong></h2>
<ul>
<li>We learned in this article that there is more than one reason why our applications may need a rate limiter.</li>
<li>We learned that as part of a good rate limit strategy we need to apply observability features in order to know what is happening with the application.</li>
<li>We learned one of the best strategies to react to specific events, and that it is using webhooks which allows us to follow the event-driven approach.</li>
</ul>
<p>I hope that this article give you some ideas for your use case or you find it interesting</p>
<p>👋 Thanks for reading!</p>
<h2><strong>Tools used in this tutorial:</strong></h2>
<ul>
<li>macOs 26.5.2</li>
<li>openjdk 21.0.10</li>
<li>Spring Boot 4.0.0.</li>
<li>mongod v8.0.26</li>
<li>Apache Solr 10.0.0</li>
<li>podman version 5.8.1</li>
<li>Docker Compose version 5.3.1</li>
<li>Grafana 13.0</li>
<li>prometheus v3.13.1</li>
<li>alertmanager v0.33.1</li>
<li>podman-exporter v2.0.0</li>
<li>mailpit v1.30</li>
<li>jmeter 5</li>
<li>valkey 9.1.2</li>
<li>openresty 1.31-alpine</li>
</ul>
<h2><strong>Computer specs used with this application</strong></h2>
<ul>
<li>Apple M4 Pro</li>
<li>Memory 24GB</li>
</ul>
<p><strong>Note: The configurations and code shown in this example are intended for learning and local development, not for production environments.</strong></p>
]]></content:encoded></item></channel></rss>