<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Juan Torchia</title>
    <description>The latest articles on DEV Community by Juan Torchia (@jtorchia).</description>
    <link>https://dev.to/jtorchia</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F885942%2F099b05dc-1940-49f6-a022-9c6a392bb405.jpg</url>
      <title>DEV Community: Juan Torchia</title>
      <link>https://dev.to/jtorchia</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jtorchia"/>
    <language>en</language>
    <item>
      <title>gRPC shows up when REST starts breaking under its own weight</title>
      <dc:creator>Juan Torchia</dc:creator>
      <pubDate>Sat, 10 Oct 2026 12:00:20 +0000</pubDate>
      <link>https://dev.to/jtorchia/grpc-shows-up-when-rest-starts-breaking-under-its-own-weight-4oab</link>
      <guid>https://dev.to/jtorchia/grpc-shows-up-when-rest-starts-breaking-under-its-own-weight-4oab</guid>
      <description>&lt;p&gt;Picture this: you've got fifteen microservices talking to each other. Each one exposes its own REST API, each one has its own JSON with its own naming conventions, and every time someone changes a field in the payments service, three other teams find out when everything blows up in production. You document with Swagger, but the Swagger goes stale. You write HTTP clients by hand, or generate something with OpenAPI that works "more or less." And when you need service A to ask service B something ten thousand times a second, the overhead of JSON + HTTP/1.1 starts hurting your infrastructure bill.&lt;/p&gt;

&lt;p&gt;That specific pain — inter-service communication with contracts that break silently and performance that doesn't scale — is exactly what gRPC comes to solve. It's not a trend. Google designed it to solve that exact problem inside its own infrastructure, and then released it as an open source project. Today it shows up in three independent awesome lists, which, in the curation system I've been building out through this series, is a strong signal: it's not noise, it's real community consensus.&lt;/p&gt;

&lt;p&gt;If you've been working with REST your whole career (like I spent a good chunk of time managing infrastructure before diving fully into development), the first time you see gRPC in action it changes how you think. It's not "another HTTP framework." It's a different paradigm for how your services talk to each other.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/grpc/grpc" rel="noopener noreferrer"&gt;gRPC&lt;/a&gt; is a Remote Procedure Call framework that runs over HTTP/2 and uses Protocol Buffers (protobuf) as its default serialization mechanism. Instead of exposing REST endpoints and letting every client build its own request, you define a contract — a &lt;code&gt;.proto&lt;/code&gt; file — and from that you generate client and server code in whatever language you need. Go, Java, Python, C++, Node, whatever. Same contract, multiple languages, and the field names and types are enforced by the generated code instead of living only in a doc someone forgot to update.&lt;/p&gt;

&lt;p&gt;Here's what a basic contract looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight protobuf"&gt;&lt;code&gt;&lt;span class="c1"&gt;// pedidos.proto&lt;/span&gt;
&lt;span class="c1"&gt;// We define the service and the messages it will exchange&lt;/span&gt;
&lt;span class="na"&gt;syntax&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"proto3"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;pedidos&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// The service exposes an RPC method, as if it were a remote function&lt;/span&gt;
&lt;span class="kd"&gt;service&lt;/span&gt; &lt;span class="n"&gt;PedidosService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;rpc&lt;/span&gt; &lt;span class="n"&gt;ObtenerPedido&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PedidoRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;returns&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PedidoResponse&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Messages are like DTOs, but typed and versionable&lt;/span&gt;
&lt;span class="kd"&gt;message&lt;/span&gt; &lt;span class="nc"&gt;PedidoRequest&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="na"&gt;id_pedido&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;message&lt;/span&gt; &lt;span class="nc"&gt;PedidoResponse&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="na"&gt;id_pedido&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="na"&gt;monto_total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="na"&gt;estado&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From that file, the protobuf compiler (&lt;code&gt;protoc&lt;/code&gt;) automatically generates serialization classes and client/server stubs. If you come from the Java world (like me, where I started my career as a developer), this is going to sound similar to what SOAP did with WSDL, but without the dead weight of XML and with noticeably better performance.&lt;/p&gt;

&lt;p&gt;On the server side in Java, the implementation looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// We implement the service automatically generated from the .proto&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PedidosServiceImpl&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;PedidosServiceGrpc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;PedidosServiceImplBase&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;obtenerPedido&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PedidoRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; 
                               &lt;span class="nc"&gt;StreamObserver&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;PedidoResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;responseObserver&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// The real logic would go here, querying the database&lt;/span&gt;
        &lt;span class="nc"&gt;PedidoResponse&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;PedidoResponse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newBuilder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setIdPedido&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getIdPedido&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setMontoTotal&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1500.50&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setEstado&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"CONFIRMADO"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// onNext sends the response, onCompleted closes the stream&lt;/span&gt;
        &lt;span class="n"&gt;responseObserver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;onNext&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;responseObserver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;onCompleted&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice there's no JSON anywhere. The payload is serialized in binary, which means smaller payloads and faster parsing. And since it runs over HTTP/2, you get multiplexing of requests over a single TCP connection, native bidirectional streaming, and header compression — the structural reasons gRPC tends to outperform REST under high load.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it's on the list
&lt;/h2&gt;

&lt;p&gt;The consensus signal — three independent awesome lists mentioning it — isn't a coincidence. gRPC solves a problem that comes up fast with microservices: the lack of strong contracts between services. With REST + JSON, the contract is a convention that lives in devs' heads and in documentation that goes stale. With gRPC, the contract is generated code. If you break a field, you find out at compile time, not in production on a Friday at six in the evening.&lt;/p&gt;

&lt;p&gt;The other big reason is performance. Code generation eliminates manual serialization boilerplate — you don't write JSON parsers by hand, you don't maintain duplicate DTOs in every language. And binary protobuf over HTTP/2 is the combination that gives gRPC an edge over traditional REST under high load, which is exactly the scenario where microservices start to struggle.&lt;/p&gt;

&lt;p&gt;It's the kind of tool that doesn't shine in a five-minute demo, but becomes indispensable when you have dozens of services talking to each other at real volume. That's where the JVM ecosystem (if you work with Spring Boot, there's solid support via grpc-spring-boot-starter) benefits from this approach versus maintaining fifteen hand-built REST clients.&lt;/p&gt;

&lt;h2&gt;
  
  
  When NOT to use it
&lt;/h2&gt;

&lt;p&gt;Now, let's be honest: gRPC isn't free, and this is the part people skip. Protobuf and the async/streaming model are a different mental model from request/response REST — if your team hasn't touched this before, budget real ramp-up time for it, not an afternoon. Debugging is also more tedious: you can't just hit a gRPC endpoint with curl like you did with REST, you need extra tools like &lt;a href="https://github.com/fullstorydev/grpcurl" rel="noopener noreferrer"&gt;grpcurl&lt;/a&gt; or &lt;a href="https://github.com/bloomrpc/bloomrpc" rel="noopener noreferrer"&gt;BloomRPC&lt;/a&gt; to inspect traffic.&lt;/p&gt;

&lt;p&gt;It also doesn't make sense if you're going to consume your API directly from a browser (you need gRPC-Web with an intermediate proxy, another layer of complexity) or if you're building a public API that third parties will consume without any control over their stack — there, REST + OpenAPI is still more friendly. If your system is two or three services with low traffic, you're probably adding complexity you don't need. For those cases, stick with REST or look at lighter alternatives like &lt;a href="https://github.com/trpc/trpc" rel="noopener noreferrer"&gt;tRPC&lt;/a&gt; if you're in a TypeScript monorepo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;gRPC is the kind of tool you shouldn't adopt because it's trendy, but because you have the specific problem it solves: inter-service communication at scale, with strong contracts and performance that matters. That's why it made it through this series' filters — it's not hype, it's real technical consensus, and it comes with a real learning cost you should plan for, not skip.&lt;/p&gt;

&lt;p&gt;If you liked this analysis, this is just entry #13 of &lt;strong&gt;Awesome Curated: The Tools&lt;/strong&gt;, where I go through tools that passed our curation system's filter. If you're into infrastructure and runtimes, you might also like the post on &lt;a href="https://juanchi.dev/en/blog/nodejs-runtime-that-changed-backend-forever" rel="noopener noreferrer"&gt;Node.js and its event loop&lt;/a&gt;, or if you're into networking, &lt;a href="https://juanchi.dev/en/blog/sniffnet-monitor-network-traffic-without-tcpdump" rel="noopener noreferrer"&gt;Sniffnet for monitoring network traffic without losing your mind&lt;/a&gt;. The full series is at &lt;a href="https://juanchi.dev/en/blog/series/awesome-curated-tools" rel="noopener noreferrer"&gt;/blog/series/awesome-curated-tools&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article was originally published on &lt;a href="https://juanchi.dev/en/blog/grpc-when-rest-isnt-enough-microservices" rel="noopener noreferrer"&gt;juanchi.dev&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>english</category>
      <category>java</category>
      <category>rpc</category>
      <category>http2</category>
    </item>
    <item>
      <title>gRPC no es moda: es el contrato que REST nunca te dio</title>
      <dc:creator>Juan Torchia</dc:creator>
      <pubDate>Sat, 10 Oct 2026 12:00:16 +0000</pubDate>
      <link>https://dev.to/jtorchia/grpc-no-es-moda-es-el-contrato-que-rest-nunca-te-dio-549j</link>
      <guid>https://dev.to/jtorchia/grpc-no-es-moda-es-el-contrato-que-rest-nunca-te-dio-549j</guid>
      <description>&lt;p&gt;Imaginate esto: tenés quince microservicios hablando entre sí. Cada uno expone su API REST, cada uno tiene su propio JSON con sus propias convenciones de nombres, y cada vez que alguien cambia un campo en el servicio de pagos, tres equipos más se enteran cuando todo explota en producción. Documentás con Swagger, pero el Swagger se desactualiza. Escribís clientes HTTP a mano, o generás algo con OpenAPI que funciona "más o menos". Y cuando necesitás que el servicio A le pregunte algo al servicio B diez mil veces por segundo, el overhead de JSON + HTTP/1.1 empieza a dolerte en la factura de infraestructura.&lt;/p&gt;

&lt;p&gt;Ese dolor específico — comunicación inter-servicios con contratos que se rompen en silencio y performance que no escala — es el que gRPC viene a resolver. No es una moda. Google lo diseñó para resolver ese problema puntual dentro de su propia infraestructura, y después lo liberó como proyecto open source. Hoy aparece en tres awesome lists independientes, lo cual en el sistema de curación que vengo laburando en esta serie es señal fuerte: no es ruido, es consenso real de la comunidad.&lt;/p&gt;

&lt;p&gt;Si vos venís de laburar con REST toda la vida (como yo laburé un buen tiempo administrando infraestructura antes de meterme de lleno al desarrollo), la primera vez que ves gRPC en acción te cambia la cabeza. No es "otro framework HTTP". Es un paradigma distinto para pensar cómo se hablan tus servicios.&lt;/p&gt;

&lt;h2&gt;
  
  
  Qué hace
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/grpc/grpc" rel="noopener noreferrer"&gt;gRPC&lt;/a&gt; es un framework de Remote Procedure Calls que corre sobre HTTP/2 y usa Protocol Buffers (protobuf) como mecanismo de serialización por default. En vez de exponer endpoints REST y que cada cliente arme su propio request, definís un contrato — un archivo &lt;code&gt;.proto&lt;/code&gt; — y de ahí se genera código cliente y servidor en el lenguaje que necesites: Go, Java, Python, C++, Node. El mismo contrato, múltiples lenguajes, menos espacio para que cada equipo interprete el JSON a su manera.&lt;/p&gt;

&lt;p&gt;Mirá cómo se ve un contrato básico:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight protobuf"&gt;&lt;code&gt;&lt;span class="c1"&gt;// pedidos.proto&lt;/span&gt;
&lt;span class="c1"&gt;// Definimos el servicio y los mensajes que va a intercambiar&lt;/span&gt;
&lt;span class="na"&gt;syntax&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"proto3"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;pedidos&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// El servicio expone un método RPC, como si fuera una función remota&lt;/span&gt;
&lt;span class="kd"&gt;service&lt;/span&gt; &lt;span class="n"&gt;PedidosService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;rpc&lt;/span&gt; &lt;span class="n"&gt;ObtenerPedido&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PedidoRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;returns&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PedidoResponse&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Los mensajes son como DTOs, pero tipados y versionables&lt;/span&gt;
&lt;span class="kd"&gt;message&lt;/span&gt; &lt;span class="nc"&gt;PedidoRequest&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="na"&gt;id_pedido&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;message&lt;/span&gt; &lt;span class="nc"&gt;PedidoResponse&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="na"&gt;id_pedido&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="na"&gt;monto_total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="na"&gt;estado&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;De ese archivo, el compilador de protobuf (&lt;code&gt;protoc&lt;/code&gt;) genera automáticamente las clases de serialización y los stubs de cliente/servidor. Si vos venís del mundo Java (como yo, que arranqué ahí mi carrera como developer), esto te va a sonar parecido a lo que hacía SOAP con WSDL, pero sin el peso muerto de XML.&lt;/p&gt;

&lt;p&gt;Del lado del servidor en Java, la implementación queda así:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Implementamos el servicio generado automáticamente desde el .proto&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PedidosServiceImpl&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;PedidosServiceGrpc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;PedidosServiceImplBase&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;obtenerPedido&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PedidoRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; 
                               &lt;span class="nc"&gt;StreamObserver&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;PedidoResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;responseObserver&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Acá iría la lógica real, consultando la base de datos&lt;/span&gt;
        &lt;span class="nc"&gt;PedidoResponse&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;PedidoResponse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newBuilder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setIdPedido&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getIdPedido&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setMontoTotal&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1500.50&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setEstado&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"CONFIRMADO"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// onNext envía la respuesta, onCompleted cierra el stream&lt;/span&gt;
        &lt;span class="n"&gt;responseObserver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;onNext&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;responseObserver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;onCompleted&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fijate que no hay JSON por ningún lado. El payload va serializado en binario, lo cual en teoría significa payloads más chicos y parsing más rápido que un JSON equivalente. Y como corre sobre HTTP/2, tenés multiplexación de requests sobre una sola conexión TCP, streaming bidireccional nativo y header compression — las piezas que, en el paper y la documentación oficial de gRPC, explican la ventaja frente a REST + HTTP/1.1 en escenarios de tráfico alto. No tengo benchmarks propios corridos para este post; lo dejo como lo que es, una ventaja de diseño documentada por el proyecto, no una medición mía.&lt;/p&gt;

&lt;h2&gt;
  
  
  Por qué está en la lista
&lt;/h2&gt;

&lt;p&gt;La señal de consenso — tres awesome lists independientes mencionándolo — no es casualidad. gRPC apunta a un problema que cualquiera que laburó con microservicios a escala conoce: la falta de contratos fuertes entre servicios. Con REST + JSON, el contrato es una convención que vive en la cabeza de los devs y en una documentación que se desactualiza. Con gRPC, el contrato es código generado: si cambiaste un campo y rompiste algo, el compilador te lo marca, no lo descubrís en producción un viernes a las seis de la tarde.&lt;/p&gt;

&lt;p&gt;La otra razón es que el code generation elimina el boilerplate de serialización manual — no escribís parsers de JSON a mano, no mantenés DTOs duplicados en cada lenguaje. Eso es valor concreto, independiente de cuánto gane o no en performance tu caso puntual.&lt;/p&gt;

&lt;p&gt;Es el tipo de herramienta que no brilla en un demo de cinco minutos, pero que tiene sentido cuando tenés una arquitectura con decenas de servicios hablando entre sí. Ahí es donde el ecosistema JVM (si laburás con Spring Boot, hay soporte vía grpc-spring-boot-starter) se beneficia de este approach frente a mantener quince clientes REST hechos a mano.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cuándo NO usarlo
&lt;/h2&gt;

&lt;p&gt;Ahora, seamos honestos: gRPC no es gratis, y esto es lo que más me interesa dejar claro. Protobuf y los patrones async/streaming tienen una curva de entrada que no es trivial — si tu equipo nunca laburó con esto, contá con perder tiempo entendiendo por qué el stub generado no hace lo que esperabas en el primer intento. No tengo un número de días para darte porque depende del equipo, pero es una friccion real, no un detalle menor.&lt;/p&gt;

&lt;p&gt;El debugging distribuido también es más tedioso: no podés pegarle con curl a un endpoint gRPC como hacías con REST, necesitás herramientas extra como &lt;a href="https://github.com/fullstorydev/grpcurl" rel="noopener noreferrer"&gt;grpcurl&lt;/a&gt; o &lt;a href="https://github.com/bloomrpc/bloomrpc" rel="noopener noreferrer"&gt;BloomRPC&lt;/a&gt; para inspeccionar tráfico.&lt;/p&gt;

&lt;p&gt;Tampoco tiene sentido si tu API la vas a consumir directamente desde un browser (necesitás gRPC-Web con un proxy intermedio, otra capa de complejidad) o si estás armando una API pública que terceros van a consumir sin control sobre su stack — ahí REST + OpenAPI sigue siendo más amigable. Si tu sistema son dos o tres servicios con tráfico bajo, probablemente estés agregando complejidad que no necesitás. Para esos casos, quedate con REST o mirá alternativas más livianas como &lt;a href="https://github.com/trpc/trpc" rel="noopener noreferrer"&gt;tRPC&lt;/a&gt; si estás en un monorepo TypeScript.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cierre
&lt;/h2&gt;

&lt;p&gt;gRPC tiene sentido cuando tenés el problema específico que resuelve: comunicación inter-servicios a escala, con contratos fuertes y una curva de aprendizaje que estás dispuesto a pagar. No lo adoptaría solo porque aparece en tres awesome lists — esa señal te dice que la comunidad lo validó, no que tu equipo esté listo para el costo de entrada.&lt;/p&gt;

&lt;p&gt;Esto es la entrega #13 de &lt;strong&gt;Awesome Curated: The Tools&lt;/strong&gt;, donde voy desmenuzando herramientas que pasaron el filtro de nuestro sistema de curación. Si te interesa el lado de infraestructura y runtimes, capaz te cope también el post sobre &lt;a href="https://juanchi.dev/es/blog/nodejs-runtime-javascript-backend-event-loop-ecosystem" rel="noopener noreferrer"&gt;Node.js y su event loop&lt;/a&gt;, o si andás por el lado de redes, &lt;a href="https://juanchi.dev/es/blog/sniffnet-monitor-trafico-red-ui-accesible-rust" rel="noopener noreferrer"&gt;Sniffnet para monitorear tráfico sin volverte loco&lt;/a&gt;. La serie completa está en &lt;a href="https://juanchi.dev/es/blog/series/awesome-curated-tools" rel="noopener noreferrer"&gt;/blog/series/awesome-curated-tools&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Este artículo fue publicado originalmente en &lt;a href="https://juanchi.dev/es/blog/grpc-rpc-http2-microservicios-protobuf" rel="noopener noreferrer"&gt;juanchi.dev&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>spanish</category>
      <category>espanol</category>
      <category>java</category>
      <category>rpc</category>
    </item>
    <item>
      <title>Apache Spark won't melt down when your data doesn't fit in RAM anymore</title>
      <dc:creator>Juan Torchia</dc:creator>
      <pubDate>Thu, 08 Oct 2026 12:00:23 +0000</pubDate>
      <link>https://dev.to/jtorchia/apache-spark-wont-melt-down-when-your-data-doesnt-fit-in-ram-anymore-1gic</link>
      <guid>https://dev.to/jtorchia/apache-spark-wont-melt-down-when-your-data-doesnt-fit-in-ram-anymore-1gic</guid>
      <description>&lt;p&gt;Hey, before I start I need to make an honest clarification, because in this series transparency is sacred: the automated analysis I was handed for this post talked about a "memory layer for AI agents," vector DBs, and cross-session context persistence. That has nothing to do with what Apache Spark actually is. That description belongs to a different tool — probably something like Mem0 or similar — and it got crossed wrong in the curation pipeline. It happens, automated systems sometimes mix up metadata. That's why the human verdict still says "pending" — here it is, done by hand, with the real data.&lt;/p&gt;

&lt;p&gt;What I do have confirmed: Apache Spark shows up in &lt;strong&gt;3 independent awesome lists&lt;/strong&gt;, and the official repo description describes it as a framework with "micro-batch processing for streams" and "stateful exactly-once semantics" as a backend. In plain terms: it's a distributed engine built for workloads where a single server, no matter how much RAM you throw at it, becomes a bottleneck.&lt;/p&gt;

&lt;p&gt;Picture this: you have logs from an app generating 500 GB per day, and you need to calculate aggregate metrics — unique users, average latencies, anomaly detection — without waiting 14 hours for a Python script running on a single core to finish. That's where Spark stops being an academic curiosity and becomes the tool that saves your sprint.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;p&gt;Apache Spark is a distributed data processing engine, originally written in Scala, that runs on the JVM (yes, the JVM again — there's a reason I keep mentioning it in this series). It was born in Berkeley's AMPLab as a direct response to Hadoop MapReduce's limitations: Spark processes in memory instead of writing to disk at every intermediate step, which makes it orders of magnitude faster for iterative workloads (think ML algorithms, which need to pass over the same data again and again).&lt;/p&gt;

&lt;p&gt;The central concept is the &lt;strong&gt;RDD&lt;/strong&gt; (Resilient Distributed Dataset): a collection of data partitioned across the cluster's nodes, immutable, and able to rebuild itself if a node goes down (hence "resilient"). On top of that sit friendlier layers like DataFrames and Datasets, which give you a SQL/Pandas-style API, but distributed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scala"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Classic example: counting words in a giant dataset&lt;/span&gt;
&lt;span class="c1"&gt;// distributed across all nodes in the cluster&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.apache.spark.sql.SparkSession&lt;/span&gt;

&lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;spark&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;SparkSession&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;appName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ContadorDePalabras"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;getOrCreate&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;textFile&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;spark&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;read&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;textFile&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"hdfs://logs/*.txt"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;conteo&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="n"&gt;textFile&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;flatMap&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;linea&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;linea&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;split&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;" "&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;   &lt;span class="c1"&gt;// split into words&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;groupByKey&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;identity&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;                  &lt;span class="c1"&gt;// group by word&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;count&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;                               &lt;span class="c1"&gt;// count occurrences&lt;/span&gt;

&lt;span class="nv"&gt;conteo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;show&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
&lt;span class="nv"&gt;spark&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;stop&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The interesting part is that this exact same code runs the same way on your notebook with 4 GB of RAM as it does on a 200-node cluster on AWS EMR. Spark takes care of partitioning the data, distributing the tasks, and handling failures — you just describe &lt;em&gt;what&lt;/em&gt; you want to do, not &lt;em&gt;how&lt;/em&gt; to distribute it.&lt;/p&gt;

&lt;p&gt;The streaming part (the one the official repo description mentioned, "micro-batch processing for streams... stateful exactly-once semantics") is Spark Structured Streaming: instead of processing event by event like Kafka Streams or Flink, Spark groups events into micro-batches (every few seconds) and processes them with the same API you use for batch. That gives you "exactly-once" — no event gets processed twice or lost, even if a node explodes at the worst possible moment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Streaming in PySpark: counting events by time window
# reading from a Kafka topic in real time
&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;spark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;readStream&lt;/span&gt; \
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kafka&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; \
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kafka.bootstrap.servers&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;localhost:9092&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; \
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;subscribe&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;eventos&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; \
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;conteoPorVentana&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt; \
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;window&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1 minute&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; \
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conteoPorVentana&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;writeStream&lt;/span&gt; \
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;outputMode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;update&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; \
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;console&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; \
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;awaitTermination&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The project is open source under the Apache 2.0 license, maintained by the Apache Software Foundation, with official support for Scala, Java, Python (PySpark), and R. The repo is at &lt;a href="https://github.com/apache/spark" rel="noopener noreferrer"&gt;github.com/apache/spark&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it's on the list
&lt;/h2&gt;

&lt;p&gt;Showing up in 3 independent awesome lists is a decent signal — different curators, different angles, pointing at the same tool. It doesn't prove consensus across entire industries, but it's more than "trending on Twitter": it suggests the tool holds up across more than one context.&lt;/p&gt;

&lt;p&gt;What sets it apart from alternatives like Hadoop MapReduce (its direct predecessor, much slower because it writes to disk at every step) or Dask (more pythonic but with an ecosystem and maturity well below) is the combination of three things: in-memory speed, a unified API that serves both batch and streaming, and an ecosystem of integrated libraries (MLlib for distributed machine learning, GraphX for graphs, Spark SQL for database-style queries) that saves you from having to glue together five different tools.&lt;/p&gt;

&lt;p&gt;If you're coming from my history with infrastructure — Linux, networking, servers — you'll quickly understand why Spark earns my respect: it's not magic, it's distributed systems engineering done right, with two decades of academic papers behind it (the original RDD paper by Zaharia et al. is required reading if you like understanding the &lt;em&gt;why&lt;/em&gt;, not just the &lt;em&gt;what&lt;/em&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  When NOT to use it
&lt;/h2&gt;

&lt;p&gt;Now for the honest part, because in this series we don't sell smoke: if your dataset fits comfortably in a single machine's RAM (say, under 10-20 GB depending on the hardware), setting up a Spark cluster is like using a crane to lift a soda can. Pandas, Polars, or even DuckDB will give you faster results with a fraction of the operational complexity.&lt;/p&gt;

&lt;p&gt;It's also not the best option if you need real sub-second latency in streaming — there, Apache Flink beats it hands down because it processes event by event, not in micro-batches. And if your team doesn't have experience operating distributed clusters (YARN, Kubernetes, or Databricks as a managed layer), the maintenance cost can eat up any performance gain. Spark isn't "install and go": it requires understanding partitioning, shuffle, executor memory — things you learn the hard way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;This is post #13 of "Awesome Curated: The Tools," the series where we break down tools that passed the filter of our curation system — cross-referencing awesome lists, AI analysis, and a final human verdict. Spark is one of those tools that doesn't have the shine of something new, but that's still there, carrying the real weight of the industry, long after the hype moved on to something else.&lt;/p&gt;

&lt;p&gt;If you're interested in the infrastructure and systems side of things, check out the post on &lt;a href="https://juanchi.dev/en/blog/nodejs-runtime-that-changed-backend-forever" rel="noopener noreferrer"&gt;Node.js and the runtime that changed the backend&lt;/a&gt; or the one on &lt;a href="https://juanchi.dev/en/blog/sniffnet-monitor-network-traffic-without-tcpdump" rel="noopener noreferrer"&gt;Sniffnet for monitoring your network&lt;/a&gt;. And if you want to see the full arc of the series, the complete list is at &lt;a href="https://juanchi.dev/en/blog/series/awesome-curated-tools" rel="noopener noreferrer"&gt;/blog/series/awesome-curated-tools&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article was originally published on &lt;a href="https://juanchi.dev/en/blog/apache-spark-distributed-big-data-streaming-engine" rel="noopener noreferrer"&gt;juanchi.dev&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>english</category>
      <category>java</category>
      <category>bigdata</category>
      <category>spark</category>
    </item>
    <item>
      <title>Apache Spark procesa terabytes sin que se te derrita el cluster</title>
      <dc:creator>Juan Torchia</dc:creator>
      <pubDate>Thu, 08 Oct 2026 12:00:18 +0000</pubDate>
      <link>https://dev.to/jtorchia/apache-spark-procesa-terabytes-sin-que-se-te-derrita-el-cluster-258j</link>
      <guid>https://dev.to/jtorchia/apache-spark-procesa-terabytes-sin-que-se-te-derrita-el-cluster-258j</guid>
      <description>&lt;p&gt;Che, antes de arrancar tengo que hacer una aclaración honesta, porque en esta serie la transparencia es sagrada: el análisis automático que me pasaron para este post hablaba de "memory layer para AI agents", vector DBs y persistencia de contexto cross-session. Eso no tiene nada que ver con lo que es realmente Apache Spark. Esa descripción corresponde a otra herramienta — probablemente algo como Mem0 o similar — y se cruzó mal en el pipeline de curación. Pasa, los sistemas automáticos a veces mezclan metadata. Por eso el veredicto humano todavía dice "pendiente": acá está, hecho a mano, con la data real.&lt;/p&gt;

&lt;p&gt;Lo que sí tengo confirmado: Apache Spark aparece en &lt;strong&gt;3 awesome lists independientes&lt;/strong&gt; y tiene años de historia en producción en empresas que procesan volúmenes enormes de datos. La descripción oficial del repo lo define como un framework de "micro-batch processing for streams", con "stateful exactly-once semantics". En criollo: es el motor que entra en escena cuando tu volumen de datos supera lo que un solo servidor puede manejar en memoria, sin importar cuánta RAM le metas.&lt;/p&gt;

&lt;p&gt;Pensalo así: tenés logs de una app que generan 500 GB por día, y necesitás calcular métricas agregadas — usuarios únicos, latencias promedio, detección de anomalías — sin esperar 14 horas a que termine un script de Python corriendo en un solo core. Ahí es donde Spark deja de ser una curiosidad académica y se convierte en la herramienta que te salva el sprint.&lt;/p&gt;

&lt;h2&gt;
  
  
  Qué hace
&lt;/h2&gt;

&lt;p&gt;Apache Spark es un motor de procesamiento distribuido de datos, escrito originalmente en Scala, que corre sobre la JVM (sí, otra vez la JVM — por algo la menciono tanto en esta serie). Nació en el AMPLab de Berkeley como respuesta directa a las limitaciones de Hadoop MapReduce: Spark procesa en memoria en vez de escribir a disco en cada paso intermedio, lo que lo hace órdenes de magnitud más rápido para cargas de trabajo iterativas (pensá en algoritmos de ML, que necesitan pasar por los mismos datos una y otra vez).&lt;/p&gt;

&lt;p&gt;El concepto central es el &lt;strong&gt;RDD&lt;/strong&gt; (Resilient Distributed Dataset): una colección de datos particionada entre los nodos del cluster, inmutable, y que sabe reconstruirse sola si un nodo se cae (de ahí lo de "resilient"). Sobre eso se montan capas más amigables como DataFrames y Datasets, que te dan una API tipo SQL/Pandas pero distribuida.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scala"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Ejemplo clásico: contar palabras en un dataset gigante&lt;/span&gt;
&lt;span class="c1"&gt;// distribuido entre todos los nodos del cluster&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.apache.spark.sql.SparkSession&lt;/span&gt;

&lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;spark&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;SparkSession&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;appName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ContadorDePalabras"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;getOrCreate&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;textFile&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;spark&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;read&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;textFile&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"hdfs://logs/*.txt"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;conteo&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="n"&gt;textFile&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;flatMap&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;linea&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;linea&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;split&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;" "&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;   &lt;span class="c1"&gt;// separamos en palabras&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;groupByKey&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;identity&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;                  &lt;span class="c1"&gt;// agrupamos por palabra&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;count&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;                               &lt;span class="c1"&gt;// contamos ocurrencias&lt;/span&gt;

&lt;span class="nv"&gt;conteo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;show&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
&lt;span class="nv"&gt;spark&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;stop&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lo interesante es que ese mismo código corre igual en tu notebook con 4 GB de RAM que en un cluster de 200 nodos en AWS EMR. Spark se encarga de particionar los datos, distribuir las tareas, y manejar los fallos — vos solo describís &lt;em&gt;qué&lt;/em&gt; querés hacer, no &lt;em&gt;cómo&lt;/em&gt; distribuirlo.&lt;/p&gt;

&lt;p&gt;La parte de streaming (la que mencionaba la descripción oficial del repo, "micro-batch processing for streams... stateful exactly-once semantics") es Spark Structured Streaming: en vez de procesar evento por evento como Kafka Streams o Flink, Spark agrupa los eventos en micro-batches (cada pocos segundos) y los procesa con la misma API que usás para batch. Eso te da "exactly-once" — ningún evento se procesa dos veces ni se pierde, ni aunque un nodo explote en el peor momento.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Streaming en PySpark: contamos eventos por ventana de tiempo
# leyendo de un topic de Kafka en tiempo real
&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;spark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;readStream&lt;/span&gt; \
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kafka&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; \
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kafka.bootstrap.servers&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;localhost:9092&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; \
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;subscribe&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;eventos&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; \
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;conteoPorVentana&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt; \
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;window&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1 minute&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; \
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conteoPorVentana&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;writeStream&lt;/span&gt; \
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;outputMode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;update&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; \
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;console&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; \
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;awaitTermination&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El proyecto es open source bajo licencia Apache 2.0, mantenido por la Apache Software Foundation, con soporte oficial para Scala, Java, Python (PySpark) y R. El repo está en &lt;a href="https://github.com/apache/spark" rel="noopener noreferrer"&gt;github.com/apache/spark&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Por qué está en la lista
&lt;/h2&gt;

&lt;p&gt;Que aparezca en 3 awesome lists independientes es un dato a favor, aunque no deja de ser una métrica de curación, no una prueba de calidad técnica en sí misma. Lo que sí puedo decir con base en la documentación oficial y años de adopción documentada en la industria: la combinación de velocidad en memoria, una API unificada para batch y streaming, y un ecosistema de librerías integradas (MLlib para machine learning distribuido, GraphX para grafos, Spark SQL para queries estilo base de datos) es lo que lo diferencia de alternativas como Hadoop MapReduce (su predecesor directo, mucho más lento porque escribe a disco en cada paso) o Dask (más pythonico pero con un ecosistema y madurez muy por debajo).&lt;/p&gt;

&lt;p&gt;Si venís de mi historia con infraestructura — Linux, redes, servidores — vas a entender rápido por qué Spark me genera respeto: no es magia, es ingeniería de sistemas distribuidos bien hecha, con dos décadas de papers académicos atrás (el paper original de RDDs de Zaharia et al. es lectura obligada si te gusta entender el &lt;em&gt;por qué&lt;/em&gt;, no solo el &lt;em&gt;qué&lt;/em&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  Cuándo NO usarlo
&lt;/h2&gt;

&lt;p&gt;Ahora la parte honesta, porque en esta serie no vendemos humo: si tu dataset entra cómodo en la RAM de una sola máquina (digamos, menos de 10-20 GB dependiendo del hardware), montar un cluster de Spark es usar una grúa para levantar una lata de gaseosa. Pandas, Polars o incluso DuckDB te van a dar resultados más rápido y con una fracción de la complejidad operativa.&lt;/p&gt;

&lt;p&gt;Tampoco es la mejor opción si necesitás latencia sub-segundo real en streaming — ahí Apache Flink le gana de mano porque procesa evento por evento, no en micro-batches. Y si tu equipo no tiene experiencia operando clusters distribuidos (YARN, Kubernetes, o Databricks como capa managed), el costo de mantenimiento puede comerte cualquier ganancia de performance. Spark no es "instalá y listo": requiere entender particionamiento, shuffle, memoria de executors — cosas que se aprenden a los golpes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cierre
&lt;/h2&gt;

&lt;p&gt;Este es el post #13 de "Awesome Curated: The Tools", la serie donde desmenuzamos herramientas que pasaron el filtro de nuestro sistema de curación — cross-referencia entre awesome lists, análisis de IA, y veredicto humano final. Spark es de esas herramientas que no tienen el brillo de lo nuevo, pero que siguen ahí, aguantando el peso real de la industria, mucho después de que el hype se fue a otra parte.&lt;/p&gt;

&lt;p&gt;Si te interesó el lado de infraestructura y sistemas, dale una vuelta al post sobre &lt;a href="https://juanchi.dev/es/blog/nodejs-runtime-javascript-backend-event-loop-ecosystem" rel="noopener noreferrer"&gt;Node.js y el runtime que cambió el backend&lt;/a&gt; o al de &lt;a href="https://juanchi.dev/es/blog/sniffnet-monitor-trafico-red-ui-accesible-rust" rel="noopener noreferrer"&gt;Sniffnet para monitorear tu red&lt;/a&gt;. Y si querés ver el arco completo de la serie, la lista entera está en &lt;a href="https://juanchi.dev/es/blog/series/awesome-curated-tools" rel="noopener noreferrer"&gt;/blog/series/awesome-curated-tools&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Este artículo fue publicado originalmente en &lt;a href="https://juanchi.dev/es/blog/apache-spark-procesamiento-distribuido-big-data-streaming" rel="noopener noreferrer"&gt;juanchi.dev&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>spanish</category>
      <category>espanol</category>
      <category>java</category>
      <category>bigdata</category>
    </item>
    <item>
      <title>I Gave My VS Code Extension to a Cybersecurity Model and It Found an Infinite Hang</title>
      <dc:creator>Juan Torchia</dc:creator>
      <pubDate>Thu, 08 Oct 2026 01:01:59 +0000</pubDate>
      <link>https://dev.to/jtorchia/i-gave-my-vs-code-extension-to-a-cybersecurity-model-and-it-found-an-infinite-hang-5e03</link>
      <guid>https://dev.to/jtorchia/i-gave-my-vs-code-extension-to-a-cybersecurity-model-and-it-found-an-infinite-hang-5e03</guid>
      <description>&lt;p&gt;CertView is a VS Code extension I wrote to inspect certificates: you open a &lt;code&gt;.pem&lt;/code&gt;, a &lt;code&gt;.p12&lt;/code&gt;, a &lt;code&gt;.cer&lt;/code&gt;, and it shows you the subject, the dates, the fingerprint, all offline. It's published on the Marketplace. It parses binary files that anyone can send you, so it's attack surface, and until this week I'd never looked at it thinking like an attacker.&lt;/p&gt;

&lt;p&gt;I now have access to Daybreak Blue, OpenAI's defensive cyber model lane. I gave it my own code, the one I maintain, and asked it one concrete thing: assume I send you a malicious certificate and Juan opens it in the editor, what breaks? It found two ways to hang VS Code with a file smaller than a phone photo. I fixed both before writing this.&lt;/p&gt;

&lt;h2&gt;
  
  
  The PEM That Takes 88 Seconds to Do Nothing
&lt;/h2&gt;

&lt;p&gt;The PEM parser splits the file into blocks. For every line it read, it re-joined the entire accumulated block to measure its size. One &lt;code&gt;join&lt;/code&gt; per line. That's O(n²): double the lines, quadruple the work.&lt;/p&gt;

&lt;p&gt;I measured it on the actual code. A PEM block with many single-character lines, staying under the 256 KiB limit the plugin itself enforces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;39 KiB → 2.4 seconds&lt;/li&gt;
&lt;li&gt;78 KiB → 9.8 seconds&lt;/li&gt;
&lt;li&gt;156 KiB → 39.7 seconds&lt;/li&gt;
&lt;li&gt;234 KiB → &lt;strong&gt;87.7 seconds&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The size limit didn't help, because it was checked &lt;em&gt;after&lt;/em&gt; re-joining all the lines. The fix is a one-line idea: keep a length counter and do a single &lt;code&gt;join&lt;/code&gt; when closing the block. O(n) instead of O(n²).&lt;/p&gt;

&lt;h2&gt;
  
  
  The PKCS#12 That Never Ends
&lt;/h2&gt;

&lt;p&gt;This one is worse, and it's the mess that made me stop.&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;.p12&lt;/code&gt; file carries a number inside: how many iterations to use to derive the key from the password. It's a legitimate mechanism — more iterations, more expensive to brute-force. The problem is that CertView passed that number straight to the parsing library (&lt;code&gt;node-forge&lt;/code&gt;) exactly as it came from the file, with no cap, and the derivation runs synchronously: while it runs, VS Code doesn't respond.&lt;/p&gt;

&lt;p&gt;An attacker can declare a huge number. And here's the detail that turns it into a hang instead of a delay: &lt;code&gt;node-forge&lt;/code&gt; reads that number with &lt;code&gt;parseInt&lt;/code&gt;. A 128-byte integer of &lt;code&gt;0xFF&lt;/code&gt; inside the file converts, in JavaScript, into &lt;code&gt;Infinity&lt;/code&gt;. The derivation loop is &lt;code&gt;for (round = 0; round &amp;lt; iterations; round++)&lt;/code&gt;. With &lt;code&gt;iterations = Infinity&lt;/code&gt;, it's not that it takes a long time: it never ends.&lt;/p&gt;

&lt;p&gt;I verified it in one line of Node:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;f&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;Infinity&lt;/span&gt; &lt;span class="c1"&gt;// true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the worst part: CertView automatically tries the empty password as soon as you open the file, before asking you anything. So the attacker doesn't need to know any password. You open the &lt;code&gt;.p12&lt;/code&gt; to see what it is, and the editor freezes on you forever.&lt;/p&gt;

&lt;p&gt;The fix is a preflight: before touching the library, CertView now reads the iteration numbers directly from the file structure — no &lt;code&gt;parseInt&lt;/code&gt;, so there's no path to &lt;code&gt;Infinity&lt;/code&gt; — and rejects anything above 100,000. If the file asks for more, it doesn't get parsed.&lt;/p&gt;

&lt;h2&gt;
  
  
  What It Didn't Find, Which Also Matters
&lt;/h2&gt;

&lt;p&gt;Not everything was a finding. The model reviewed the webview (the view where certificate data is shown) and confirmed that hostile fields are properly escaped, that the Content-Security-Policy is restrictive, that passwords aren't logged or saved, and that encrypted private keys aren't even decrypted. Instead of inventing vulnerabilities to pad out a list, it said where the code was already fine. That's what makes it useful: a report that's pure findings doesn't let you know what it checked and ruled out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sol vs. Blue, Same Code
&lt;/h2&gt;

&lt;p&gt;I ran the same audit, with the exact same request word for word, on two models: GPT-5.6-Sol (the general-purpose one) and Daybreak Blue (the defensive one). Both read the code, neither invented anything. The difference wasn't that Blue "did something forbidden": it was depth. Sol saw that the PKCS#12 "freezes the host"; Blue also saw the &lt;code&gt;Infinity&lt;/code&gt; case, which is the difference between slow and non-terminating. And Blue found one more thing Sol didn't: a race window between measuring the file size and reading it.&lt;/p&gt;

&lt;p&gt;That's the honest part of the experiment. This was defensive work on my own code, and for that the general-purpose model also does the job. The specialized lane showed up in the detail, not in unlocking something the other refused to do.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Obstacle Wasn't the Model
&lt;/h2&gt;

&lt;p&gt;Two things I didn't expect, and I'm mentioning them because they're the part that doesn't make it into the announcement:&lt;/p&gt;

&lt;p&gt;To get the account enabled I had to verify identity and set a physical YubiKey as the sole login — one single key, the one that now opens all my work. It's not optional, and it makes sense: a model with the brakes taken off for security work is exactly what someone would want to use with a stolen account.&lt;/p&gt;

&lt;p&gt;And on the day I ran the audit, what slowed me down most wasn't either model: it was the tool's sandbox, which stopped being able to isolate the network on that machine and blocked all file reads. The model was ready; the plumbing around it wasn't. It's almost always like that.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to Do If You Use CertView
&lt;/h2&gt;

&lt;p&gt;Update to 0.5.1. Both vulnerabilities are fixed there, with tests that fail if either comes back. And if you're writing a parser for something sent to you from outside: measure your limits &lt;em&gt;before&lt;/em&gt; doing the expensive work, not after, and never pass a library a number that came from the file without a cap.&lt;/p&gt;

</description>
      <category>english</category>
      <category>experiments</category>
      <category>openai</category>
      <category>vscode</category>
    </item>
    <item>
      <title>Le di mi extensión de VS Code a un modelo de ciberseguridad y encontró un cuelgue infinito</title>
      <dc:creator>Juan Torchia</dc:creator>
      <pubDate>Thu, 08 Oct 2026 01:01:54 +0000</pubDate>
      <link>https://dev.to/jtorchia/le-di-mi-extension-de-vs-code-a-un-modelo-de-ciberseguridad-y-encontro-un-cuelgue-infinito-2hfi</link>
      <guid>https://dev.to/jtorchia/le-di-mi-extension-de-vs-code-a-un-modelo-de-ciberseguridad-y-encontro-un-cuelgue-infinito-2hfi</guid>
      <description>&lt;p&gt;CertView es una extensión de VS Code que escribí para inspeccionar certificados: abrís un &lt;code&gt;.pem&lt;/code&gt;, un &lt;code&gt;.p12&lt;/code&gt;, un &lt;code&gt;.cer&lt;/code&gt;, y te muestra el sujeto, las fechas, el fingerprint, todo offline. Está publicada en el Marketplace. Parsea archivos binarios que te puede mandar cualquiera, así que es superficie de ataque, y hasta esta semana nunca la había mirado pensando como atacante.&lt;/p&gt;

&lt;p&gt;Ahora tengo acceso a Daybreak Blue, el carril defensivo del modelo cyber de OpenAI. Le di mi propio código, el que mantengo yo, y le pedí una cosa concreta: asumí que te mando un certificado malicioso y Juan lo abre en el editor, ¿qué se rompe? Encontró dos formas de colgar VS Code con un archivo que pesa menos que una foto del celular. Las dos las arreglé antes de escribir esto.&lt;/p&gt;

&lt;h2&gt;
  
  
  El PEM que tarda 88 segundos en no hacer nada
&lt;/h2&gt;

&lt;p&gt;El parser de PEM separa el archivo en bloques. Por cada línea que leía, volvía a pegar todo el bloque acumulado para medir su tamaño. Un &lt;code&gt;join&lt;/code&gt; por línea. Eso es O(n²): si duplicás las líneas, cuadruplicás el trabajo.&lt;/p&gt;

&lt;p&gt;Lo medí sobre el código real. Un bloque PEM con muchas líneas de un solo carácter, por debajo del límite de 256 KiB que el propio plugin impone:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;39 KiB → 2,4 segundos&lt;/li&gt;
&lt;li&gt;78 KiB → 9,8 segundos&lt;/li&gt;
&lt;li&gt;156 KiB → 39,7 segundos&lt;/li&gt;
&lt;li&gt;234 KiB → &lt;strong&gt;87,7 segundos&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;El límite de tamaño no ayudaba, porque se chequeaba &lt;em&gt;después&lt;/em&gt; de volver a pegar todas las líneas. El arreglo es de una línea de idea: llevar un contador de largo y hacer un solo &lt;code&gt;join&lt;/code&gt; al cerrar el bloque. O(n) en lugar de O(n²).&lt;/p&gt;

&lt;h2&gt;
  
  
  El PKCS#12 que no termina nunca
&lt;/h2&gt;

&lt;p&gt;Este es peor, y es el quilombo que me hizo parar.&lt;/p&gt;

&lt;p&gt;Un archivo &lt;code&gt;.p12&lt;/code&gt; trae adentro un número: cuántas iteraciones usar para derivar la clave desde la contraseña. Es un mecanismo legítimo — más iteraciones, más caro de atacar por fuerza bruta. El problema es que CertView le pasaba ese número a la librería de parsing (&lt;code&gt;node-forge&lt;/code&gt;) tal cual venía del archivo, sin ningún tope, y la derivación corre de forma síncrona: mientras corre, VS Code no responde.&lt;/p&gt;

&lt;p&gt;Un atacante puede declarar un número enorme. Y acá está el detalle que lo vuelve un cuelgue y no una demora: &lt;code&gt;node-forge&lt;/code&gt; lee ese número con &lt;code&gt;parseInt&lt;/code&gt;. Un entero de 128 bytes de &lt;code&gt;0xFF&lt;/code&gt; dentro del archivo se convierte, en JavaScript, en &lt;code&gt;Infinity&lt;/code&gt;. El bucle de derivación es &lt;code&gt;for (round = 0; round &amp;lt; iteraciones; round++)&lt;/code&gt;. Con &lt;code&gt;iteraciones = Infinity&lt;/code&gt;, no es que tarda mucho: no termina jamás.&lt;/p&gt;

&lt;p&gt;Lo verifiqué en una línea de Node:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;f&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;Infinity&lt;/span&gt; &lt;span class="c1"&gt;// true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Y lo peor: CertView prueba la contraseña vacía automáticamente apenas abrís el archivo, antes de preguntarte nada. Así que el atacante no necesita saber ninguna contraseña. Vos abrís el &lt;code&gt;.p12&lt;/code&gt; para ver qué es, y el editor se te congela para siempre.&lt;/p&gt;

&lt;p&gt;El arreglo es un preflight: antes de tocar la librería, CertView ahora lee los números de iteraciones directamente de la estructura del archivo — sin &lt;code&gt;parseInt&lt;/code&gt;, así que no hay camino a &lt;code&gt;Infinity&lt;/code&gt; — y rechaza cualquiera por encima de 100.000. Si el archivo pide más, no se parsea.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lo que no encontró, que también importa
&lt;/h2&gt;

&lt;p&gt;No todo era un hallazgo. El modelo revisó el webview (la vista donde se muestran los datos del certificado) y confirmó que los campos hostiles se escapan bien, que la Content-Security-Policy es restrictiva, que las contraseñas no se loguean ni se guardan, y que las llaves privadas cifradas ni se descifran. En vez de inventar vulnerabilidades para llenar una lista, dijo dónde el código ya estaba bien. Eso es lo que lo hace útil: un reporte que es puro hallazgo no te deja saber qué revisó y descartó.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sol contra Blue, mismo código
&lt;/h2&gt;

&lt;p&gt;Corrí la misma auditoría, con el mismo pedido palabra por palabra, en dos modelos: GPT-5.6-Sol (el de uso general) y Daybreak Blue (el defensivo). Los dos leyeron el código, ninguno inventó nada. La diferencia no fue que Blue "hiciera algo prohibido": fue profundidad. Sol vio que el PKCS#12 "congela el host"; Blue vio además el caso &lt;code&gt;Infinity&lt;/code&gt;, que es la diferencia entre lento y no-terminante. Y Blue encontró una cosa más que Sol no: una ventana de carrera entre medir el tamaño del archivo y leerlo.&lt;/p&gt;

&lt;p&gt;Esa es la parte honesta del experimento. Esto era trabajo defensivo sobre mi propio código, y para eso el modelo de uso general también sirve. El carril especializado se notó en el detalle, no en destrabar algo que el otro se negaba a hacer.&lt;/p&gt;

&lt;h2&gt;
  
  
  El obstáculo real no fue el modelo
&lt;/h2&gt;

&lt;p&gt;Dos cosas que no esperaba, y que cuento porque son la parte que no sale en el anuncio:&lt;/p&gt;

&lt;p&gt;Para tener la cuenta habilitada tuve que validar identidad y dejar una YubiKey física como único login — una sola llave, la que ahora abre todo mi laburo. No es opcional, y tiene sentido: un modelo al que le sacaron frenos para trabajo de seguridad es exactamente lo que alguien querría usar con una cuenta robada.&lt;/p&gt;

&lt;p&gt;Y el día que corrí la auditoría, lo que más me frenó no fue ninguno de los dos modelos: fue el sandbox de la herramienta, que dejó de poder aislar la red en esa máquina y bloqueó toda lectura de archivos. El modelo estaba listo; el plumbing alrededor, no. Casi siempre es así.&lt;/p&gt;

&lt;h2&gt;
  
  
  Qué hacer si usás CertView
&lt;/h2&gt;

&lt;p&gt;Actualizá a la 0.5.1. Las dos vulnerabilidades están corregidas ahí, con tests que fallan si alguna vuelve. Y si vos escribís un parser de algo que te mandan de afuera: medí tus límites &lt;em&gt;antes&lt;/em&gt; de hacer el laburo caro, no después, y nunca le pases a una librería un número que vino del archivo sin un tope.&lt;/p&gt;

</description>
      <category>spanish</category>
      <category>espanol</category>
      <category>experimentos</category>
      <category>openai</category>
    </item>
    <item>
      <title>Docker HEALTHCHECK: what the command actually runs and what it doesn't tell you</title>
      <dc:creator>Juan Torchia</dc:creator>
      <pubDate>Tue, 06 Oct 2026 12:00:22 +0000</pubDate>
      <link>https://dev.to/jtorchia/docker-healthcheck-what-the-command-actually-runs-and-what-it-doesnt-tell-you-j3k</link>
      <guid>https://dev.to/jtorchia/docker-healthcheck-what-the-command-actually-runs-and-what-it-doesnt-tell-you-j3k</guid>
      <description>&lt;p&gt;A Docker HEALTHCHECK answers a single question: did the command you defined exit with code 0? Nothing more. It doesn't measure whether the app is "healthy" in any broad sense — it measures whether that specific command, at that specific moment, didn't fail.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://docs.docker.com/reference/dockerfile/#healthcheck" rel="noopener noreferrer"&gt;official Docker documentation&lt;/a&gt; says it plainly: the &lt;code&gt;HEALTHCHECK&lt;/code&gt; instruction tells Docker how to test a container to check that it's "still working." The example the docs themselves give is telling:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;HEALTHCHECK&lt;/span&gt;&lt;span class="s"&gt; --interval=5m --timeout=3s \&lt;/span&gt;
  CMD curl -f http://localhost/ || exit 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;curl -f http://localhost/&lt;/code&gt; tests one thing: that the web server answers something on the root path within 3 seconds. It doesn't test whether the database is up, whether the message queue has live workers, or whether the authentication service the app needs to log users in is responding. If curl gets any response at all — even an error page with a misconfigured 200 status — the healthcheck passes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three states and what triggers each one
&lt;/h2&gt;

&lt;p&gt;According to the docs, a container with a healthcheck defined has a health status in addition to its normal status. It starts as &lt;code&gt;starting&lt;/code&gt;. Every time the check passes, it moves to &lt;code&gt;healthy&lt;/code&gt;. After a certain number of consecutive failures, it moves to &lt;code&gt;unhealthy&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The command's exit code is the only thing that drives that state machine:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;0&lt;/code&gt;: healthy — the container is "healthy" according to that command&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;1&lt;/code&gt;: unhealthy — the container isn't working correctly&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;2&lt;/code&gt;: reserved, do not use&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's all Docker knows. It doesn't interpret the content of the response, it doesn't validate business logic, it doesn't check transitive dependencies. If your check command is &lt;code&gt;curl -f http://localhost/health&lt;/code&gt; and that endpoint returns &lt;code&gt;200 OK&lt;/code&gt; with a hardcoded JSON that never changes, you'll have a container that's forever &lt;code&gt;healthy&lt;/code&gt; even if the app's actual logic is broken.&lt;/p&gt;

&lt;h2&gt;
  
  
  Shallow liveness vs. real health
&lt;/h2&gt;

&lt;p&gt;Here's the distinction that matters when deciding what to put in the healthcheck: a shallow "liveness" check only confirms that the process responds — that it's not hung, that it hasn't entered an infinite loop, that the port is listening. A real health check validates that the dependencies the app needs to serve traffic actually work: database connection, access to critical external services, queue status if the app depends on them.&lt;/p&gt;

&lt;p&gt;The docs give the exact example of the problem a basic liveness check solves: "detecting cases such as a web server that is stuck in an infinite loop and unable to handle new connections, even though the server process is still running." That's exactly what it covers, no more, no less. If the process responds but can't write to the database because the connection dropped, a healthcheck that just curls &lt;code&gt;/&lt;/code&gt; will never find out — the server keeps answering with 200 on that path while every real request to the app fails.&lt;/p&gt;

&lt;p&gt;A HEALTHCHECK that only verifies the process responds gives a false sense of security if it doesn't validate the app's real dependencies. The &lt;code&gt;healthy&lt;/code&gt; status in &lt;code&gt;docker ps&lt;/code&gt; can coexist perfectly well with an app that can't complete a single useful operation.&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;stateDiagram-v2
  [*] --&amp;gt; starting
  starting --&amp;gt; healthy: exit 0
  starting --&amp;gt; unhealthy: consecutive failures
  healthy --&amp;gt; unhealthy: consecutive failures
  unhealthy --&amp;gt; healthy: exit 0&lt;/code&gt;&lt;/pre&gt;



&lt;h2&gt;
  
  
  What happens with start_period and retries
&lt;/h2&gt;

&lt;p&gt;Two &lt;code&gt;HEALTHCHECK&lt;/code&gt; options define how much patience Docker has before declaring &lt;code&gt;unhealthy&lt;/code&gt;: &lt;code&gt;--start-period&lt;/code&gt; and &lt;code&gt;--retries&lt;/code&gt;. The docs explain that the start period gives initialization time for containers that need to bootstrap — if the check fails during that window, it doesn't count toward the maximum number of retries. But if the check passes once during the start period, the container is considered started, and from then on every consecutive failure does count.&lt;/p&gt;

&lt;p&gt;This matters in a detail the docs don't highlight but that follows from the mechanics: if the app takes a while to come up and your start_period is short, you can end up counting as a "real failure" something that was actually "still starting up." The right tuning of start_period and retries depends on the app's actual bootstrap time — there's no universal value that works for every image.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it doesn't measure, even if the healthcheck is well written
&lt;/h2&gt;

&lt;p&gt;Not even the best &lt;code&gt;CMD&lt;/code&gt; inside &lt;code&gt;HEALTHCHECK&lt;/code&gt; solves this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;External dependencies you're not explicitly checking.&lt;/strong&gt; If the command doesn't touch the database, the healthcheck knows nothing about it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Partial degradation.&lt;/strong&gt; A service that responds slowly but within the timeout passes the same as one that responds instantly. The exit code has no gradients.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Business state.&lt;/strong&gt; If the app returns 200 but with corrupted data or an empty response where there should be content, and your check only validates the HTTP code, it passes all the same.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anything happening outside the container.&lt;/strong&gt; HEALTHCHECK runs inside the container, with whatever visibility it has from there. It doesn't see the state of the host, the external network, or other containers unless your command queries them directly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of these points gets solved by adding logic to the healthcheck command — not by changing how Docker interprets the result. If you need the check to reflect the service's real health, the work is in writing an endpoint or a script that actually tests the dependencies that matter, not in tweaking intervals or retries.&lt;/p&gt;

&lt;h2&gt;
  
  
  Original source
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.docker.com/reference/dockerfile/#healthcheck" rel="noopener noreferrer"&gt;Docker Docs — Dockerfile reference, HEALTHCHECK instruction&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;This article was originally published on &lt;a href="https://juanchi.dev/en/blog/docker-healthcheck-what-it-measures-and-what-it-doesnt" rel="noopener noreferrer"&gt;juanchi.dev&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>english</category>
      <category>docker</category>
      <category>devops</category>
      <category>dockercompose</category>
    </item>
    <item>
      <title>Docker HEALTHCHECK: qué comando corre y qué no te dice</title>
      <dc:creator>Juan Torchia</dc:creator>
      <pubDate>Tue, 06 Oct 2026 12:00:17 +0000</pubDate>
      <link>https://dev.to/jtorchia/docker-healthcheck-que-comando-corre-y-que-no-te-dice-423o</link>
      <guid>https://dev.to/jtorchia/docker-healthcheck-que-comando-corre-y-que-no-te-dice-423o</guid>
      <description>&lt;p&gt;Un HEALTHCHECK de Docker contesta una sola pregunta: ¿el comando que definiste salió con código 0? Nada más. No mide si la app está "sana" en ningún sentido amplio — mide si ese comando específico, en ese momento, no falló.&lt;/p&gt;

&lt;p&gt;La &lt;a href="https://docs.docker.com/reference/dockerfile/#healthcheck" rel="noopener noreferrer"&gt;documentación oficial de Docker&lt;/a&gt; lo dice sin vueltas: la instrucción &lt;code&gt;HEALTHCHECK&lt;/code&gt; le dice a Docker cómo testear un contenedor para chequear que "sigue funcionando". El ejemplo que da la propia doc es elocuente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;HEALTHCHECK&lt;/span&gt;&lt;span class="s"&gt; --interval=5m --timeout=3s \&lt;/span&gt;
  CMD curl -f http://localhost/ || exit 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ese &lt;code&gt;curl -f http://localhost/&lt;/code&gt; prueba una cosa: que el servidor web contesta algo en la ruta raíz dentro de 3 segundos. No prueba que la base de datos esté arriba, que la cola de mensajes tenga workers vivos, ni que el servicio de autenticación que la app necesita para loguear usuarios esté respondiendo. Si curl recibe cualquier respuesta — incluso una página de error con status 200 mal configurado — el healthcheck pasa.&lt;/p&gt;

&lt;h2&gt;
  
  
  Los tres estados y qué dispara cada uno
&lt;/h2&gt;

&lt;p&gt;Según la doc, un contenedor con healthcheck definido tiene un estado de salud además de su estado normal. Arranca en &lt;code&gt;starting&lt;/code&gt;. Cada vez que el chequeo pasa, pasa a &lt;code&gt;healthy&lt;/code&gt;. Después de una cierta cantidad de fallos consecutivos, pasa a &lt;code&gt;unhealthy&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;El exit code del comando es lo único que mueve esa máquina de estados:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;0&lt;/code&gt;: healthy — el contenedor está "sano" según ese comando&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;1&lt;/code&gt;: unhealthy — el contenedor no funciona correctamente&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;2&lt;/code&gt;: reservado, no usar&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Eso es todo lo que Docker sabe. No interpreta el contenido de la respuesta, no valida lógica de negocio, no chequea dependencias transitivas. Si tu comando de chequeo es &lt;code&gt;curl -f http://localhost/health&lt;/code&gt; y ese endpoint devuelve &lt;code&gt;200 OK&lt;/code&gt; con un JSON hardcodeado que nunca cambia, vas a tener un contenedor eternamente &lt;code&gt;healthy&lt;/code&gt; aunque la lógica real de la app esté rota.&lt;/p&gt;

&lt;h2&gt;
  
  
  Liveness superficial vs salud real
&lt;/h2&gt;

&lt;p&gt;Acá está la distinción que importa para decidir qué poner en el healthcheck: un chequeo de "liveness" superficial solo confirma que el proceso responde — que no está colgado, que no entró en loop infinito, que el puerto escucha. Un chequeo de salud real valida que las dependencias que la app necesita para servir tráfico funcionan: conexión a la base de datos, acceso a servicios externos críticos, estado de colas si la app depende de ellas.&lt;/p&gt;

&lt;p&gt;La doc da el ejemplo exacto del problema que resuelve un liveness check básico: "detectar casos como un servidor web atascado en un loop infinito e incapaz de manejar nuevas conexiones, aunque el proceso del servidor siga corriendo". Eso es exactamente lo que cubre, ni más ni menos. Si el proceso responde pero no puede escribir en la base de datos porque se cortó la conexión, un healthcheck que solo pega un &lt;code&gt;curl&lt;/code&gt; a &lt;code&gt;/&lt;/code&gt; nunca se va a enterar — el servidor sigue respondiendo con 200 en esa ruta mientras cada request real a la app falla.&lt;/p&gt;

&lt;p&gt;Un HEALTHCHECK que solo verifica que el proceso responde da una falsa sensación de seguridad si no valida las dependencias reales de la app. El estado &lt;code&gt;healthy&lt;/code&gt; en &lt;code&gt;docker ps&lt;/code&gt; puede convivir perfectamente con una app que no puede completar ninguna operación útil.&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;stateDiagram-v2
  [*] --&amp;gt; starting
  starting --&amp;gt; healthy: exit 0
  starting --&amp;gt; unhealthy: fallos consecutivos
  healthy --&amp;gt; unhealthy: fallos consecutivos
  unhealthy --&amp;gt; healthy: exit 0&lt;/code&gt;&lt;/pre&gt;



&lt;h2&gt;
  
  
  Qué pasa con start_period y los reintentos
&lt;/h2&gt;

&lt;p&gt;Dos opciones de &lt;code&gt;HEALTHCHECK&lt;/code&gt; definen cuánta paciencia tiene Docker antes de declarar &lt;code&gt;unhealthy&lt;/code&gt;: &lt;code&gt;--start-period&lt;/code&gt; y &lt;code&gt;--retries&lt;/code&gt;. La doc explica que el start period da tiempo de inicialización para contenedores que necesitan bootstrapear — si el chequeo falla durante esa ventana, no cuenta para el máximo de reintentos. Pero si el chequeo pasa una vez durante el start period, el contenedor se considera arrancado y desde ahí todos los fallos consecutivos sí cuentan.&lt;/p&gt;

&lt;p&gt;Esto importa en un detalle que la doc no resalta pero que se desprende de la mecánica: si la app tarda en levantar y tu start_period es corto, podés terminar contando como "fallo real" algo que en realidad era "todavía estoy arrancando". El ajuste correcto de start_period y retries depende del tiempo real de bootstrap de la app — no hay un valor universal que sirva para toda imagen.&lt;/p&gt;

&lt;h2&gt;
  
  
  Qué no mide, aunque el healthcheck esté bien escrito
&lt;/h2&gt;

&lt;p&gt;Ni el mejor &lt;code&gt;CMD&lt;/code&gt; dentro de &lt;code&gt;HEALTHCHECK&lt;/code&gt; resuelve esto:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dependencias externas que no estás chequeando explícitamente.&lt;/strong&gt; Si el comando no toca la base de datos, el healthcheck no sabe nada sobre ella.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Degradación parcial.&lt;/strong&gt; Un servicio que responde lento pero dentro del timeout pasa igual que uno que responde instantáneo. El exit code no tiene gradientes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Estado de negocio.&lt;/strong&gt; Si la app devuelve 200 pero con datos corruptos o una respuesta vacía donde debería haber contenido, y tu chequeo solo valida el código HTTP, pasa igual.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lo que pasa fuera del contenedor.&lt;/strong&gt; HEALTHCHECK corre dentro del contenedor, con la visibilidad que tiene desde ahí. No ve el estado del host, de la red externa, ni de otros contenedores salvo que tu comando los consulte directamente.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cada uno de estos puntos se resuelve agregando lógica al comando del healthcheck — no cambiando cómo Docker interpreta el resultado. Si necesitás que el chequeo refleje la salud real del servicio, el trabajo está en escribir un endpoint o un script que efectivamente pruebe las dependencias que importan, no en ajustar intervalos o reintentos.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fuente original
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.docker.com/reference/dockerfile/#healthcheck" rel="noopener noreferrer"&gt;Docker Docs — Dockerfile reference, instrucción HEALTHCHECK&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Este artículo fue publicado originalmente en &lt;a href="https://juanchi.dev/es/blog/docker-healthcheck-que-mide" rel="noopener noreferrer"&gt;juanchi.dev&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>spanish</category>
      <category>espanol</category>
      <category>docker</category>
      <category>devops</category>
    </item>
    <item>
      <title>Mem0 Doesn't Fix an Unbounded Agent, It Complements It</title>
      <dc:creator>Juan Torchia</dc:creator>
      <pubDate>Sun, 27 Sep 2026 12:00:20 +0000</pubDate>
      <link>https://dev.to/jtorchia/mem0-doesnt-fix-an-unbounded-agent-it-complements-it-259h</link>
      <guid>https://dev.to/jtorchia/mem0-doesnt-fix-an-unbounded-agent-it-complements-it-259h</guid>
      <description>&lt;p&gt;I have a hard limit configured in Cline: it doesn't touch files outside a specific folder, it doesn't run network commands without confirmation, and when the session gets long, the context starts piling up garbage. I already wrote about &lt;a href="https://juanchi.dev/en/blog/sniffnet-ai-agents-network-traffic-monitoring" rel="noopener noreferrer"&gt;setting explicit limits on Cline running on autopilot&lt;/a&gt; and about monitoring what an agent does without telling you. The question that kept nagging me after nailing down that scope restriction is a different one: is lost context in long sessions a problem you solve with more scope discipline, or do you actually need an external memory layer?&lt;/p&gt;

&lt;p&gt;That's where Mem0 comes in. It's a library that promises memory persistence for AI agents — it saves relevant facts from a conversation and retrieves them later, instead of re-injecting the entire raw history on every request. The pitch sounds good for the case I care about: a Cline session that runs for hours, on the same repo, where the agent repeats questions you already answered three times because the context filled up with noise and lost what mattered.&lt;/p&gt;

&lt;p&gt;My thesis, before getting into the weeds: Mem0 can help in long, repetitive sessions, where the same kind of information gets needed over and over. But it doesn't replace the first line of defense, which is still limiting what the agent can touch and how much context you feed it going in. A memory layer is a patch on top of a design problem, not a solution to the design problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Mem0 repo says and what it doesn't say
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://github.com/mem0ai/mem0" rel="noopener noreferrer"&gt;official Mem0 GitHub repo&lt;/a&gt; describes the tool as a memory layer that lets AI agents remember preferences, facts, and context across sessions, with support for multiple vector storage backends. The core idea is simple: instead of sending the entire conversation history to the model every time, Mem0 extracts and saves relevant snippets, and retrieves them when needed.&lt;/p&gt;

&lt;p&gt;What the repo doesn't say — and this matters — is how much it reduces token consumption in a real code-agent workflow like Cline, working on a specific repo, with tool calls (reading a file, running a command, writing a diff) interleaved with reasoning. The docs' examples are built for conversational chatbots, not for agents that execute actions on a filesystem. That difference isn't trivial: a code agent generates a different kind of context than a chatbot that just talks. Diffs, terminal output, file contents — that's not "conversation memory," that's operational state.&lt;/p&gt;

&lt;p&gt;So the question I'm asking isn't "does Mem0 work?" — the repo has activity, tests, and documented use cases. The question is "does Mem0 solve the specific context bloat problem in a code agent like Cline?", and the public evidence isn't enough to answer that with certainty.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where people get it wrong: install Mem0 and expect magic
&lt;/h2&gt;

&lt;p&gt;The common recipe I see floating around is: you grab Mem0, wire it up to the agent, and assume context bloat disappears because "now it has memory." The hidden cost of that recipe is that Mem0 adds a new layer with its own latency — every memory query involves a vector search, and that search isn't free in time or tokens if the retrieval prompt is verbose.&lt;/p&gt;

&lt;p&gt;The counterexample that convinces me not to buy the hype without proof: if the agent already has a badly defined scope — you tell it "fix the bug" without specifying which files it can touch, without telling it what NOT to do — no memory layer fixes that. The agent will keep reading extra files, running exploratory commands, generating context it doesn't need. Mem0 can help so session 5 doesn't repeat what was already established in session 1, but it doesn't stop session 1 from being a context disaster if the scope was wrong from the start.&lt;/p&gt;

&lt;p&gt;This connects directly to something I've been saying for a while: the underlying problem with autonomous agents isn't "missing memory," it's "missing limits." A well-written &lt;code&gt;.clinerules&lt;/code&gt;, with explicit restrictions on which folders it can touch and which commands are forbidden, fixes more context bloat than an external memory layer — because it attacks the cause, not the symptom.&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TD
  A[Long Cline session] --&amp;gt; B{Agent scope defined?}
  B --&amp;gt;|No| C[Context grows unchecked]
  B --&amp;gt;|Yes| D[Context bounded from the start]
  C --&amp;gt; E[Mem0 filters repeated noise]
  E --&amp;gt; F[Background bloat persists]
  D --&amp;gt; G[Mem0 adds real value in repetitive sessions]&lt;/code&gt;&lt;/pre&gt;



&lt;h2&gt;
  
  
  The experiment I'd design before adopting this
&lt;/h2&gt;

&lt;p&gt;I don't have production metrics of Mem0 integrated with Cline, and I'm not going to make any up. What I can lay out is the reproducible experiment I'd run before deciding whether it's worth it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Fixed test repo&lt;/strong&gt;: a small repo, with a known repetitive task (for example, adding the same type of endpoint three times with variations).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Baseline without Mem0&lt;/strong&gt;: run the task three times in separate Cline sessions, counting context tokens per session (Cline exposes this in its cost UI).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Same task with Mem0&lt;/strong&gt;: integrate Mem0 as a memory layer between sessions, repeat the three runs, and compare the input token count on the third run against the third run of the baseline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Success criteria&lt;/strong&gt;: if the third run with Mem0 uses noticeably fewer input context tokens than the third run without Mem0, there's a real signal. If the difference is marginal, or Mem0's query overhead cancels out the savings, the tool isn't solving the problem for that case.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I didn't run this experiment myself — this is the design I'd follow before writing a post claiming numbers. Anyone who wants to validate the thesis can clone the Mem0 repo and repeat these steps with their own Cline setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decision matrix: when it actually makes sense to bring in Mem0
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Situation&lt;/th&gt;
&lt;th&gt;Worth adding Mem0?&lt;/th&gt;
&lt;th&gt;What to check first&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Short sessions, single well-scoped task&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Scope already solves the bloat, memory is overhead with no return&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Long sessions with repetitive tasks in the same domain&lt;/td&gt;
&lt;td&gt;Possibly&lt;/td&gt;
&lt;td&gt;Measure tokens before/after with the experiment above&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent without &lt;code&gt;.clinerules&lt;/code&gt; or scope restrictions&lt;/td&gt;
&lt;td&gt;No, fix that first&lt;/td&gt;
&lt;td&gt;Fix the scope limit before adding a new layer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multiple agents sharing project context&lt;/td&gt;
&lt;td&gt;Possibly&lt;/td&gt;
&lt;td&gt;Check if the vector backend Mem0 uses is already in your stack&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;One-off project, no continuity between sessions&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Persistent memory adds nothing if there's no future session to use it&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Every row in this matrix is a criterion, not an absolute conclusion — the real outcome depends on the repo, the model, and how the agent is configured.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limits: what can't be concluded without actually running this
&lt;/h2&gt;

&lt;p&gt;Without production data or real logs from a Mem0 + Cline integration, I can't claim a token reduction percentage, an added latency figure, or whether the agent's response quality improves or gets worse with persistent memory. I also can't compare Mem0 against other context-compaction strategies (like periodic manual summaries) without running both in the same scenario.&lt;/p&gt;

&lt;p&gt;What I do stand by with the available public evidence: the Mem0 repo is designed and documented first for conversational agents, not for code agents with heavy operational state. That design gap is why I wouldn't adopt the tool without first running the experiment described above on my own repo.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Does Mem0 replace limiting the scope of an agent like Cline?&lt;/strong&gt;&lt;br&gt;
No. Mem0 manages what information persists across sessions, but it doesn't control what the agent can touch within a session. Scope is still defined with explicit rules, not memory.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does Mem0 reduce tokens on every request?&lt;/strong&gt;&lt;br&gt;
It can reduce tokens if it avoids re-injecting the full history, but it adds its own retrieval query. The net balance depends on the case — you have to measure it, not assume it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does Mem0 work with Cline out of the box?&lt;/strong&gt;&lt;br&gt;
The Mem0 repo isn't built specifically for code agents with tools like Cline. Integrating it takes adaptation work, it's not plug-and-play for that use case.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do I need a separate vector database to use Mem0?&lt;/strong&gt;&lt;br&gt;
Yes, Mem0 depends on a vector storage backend to index and retrieve memory. Check the repo's docs for supported options before adding new infrastructure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When does it NOT make sense to use a memory layer like this?&lt;/strong&gt;&lt;br&gt;
When the task is a single session, when the agent already has bounded scope and resolves quickly, or when the project has no continuity between runs. There, persistent memory is cost with no return.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I measure if Mem0 actually helps in my case?&lt;/strong&gt;&lt;br&gt;
With the three-run experiment described above: baseline without memory, same task with memory, comparison of input tokens on the repeated run. Without that measurement, any claim is speculation.&lt;/p&gt;

&lt;h2&gt;
  
  
  My take
&lt;/h2&gt;

&lt;p&gt;I'm not going to recommend Mem0 as a universal fix for something that, in most cases, is an agent design problem, not a memory shortage. If you're already limiting Cline's scope with clear rules — which folders it touches, which commands are forbidden, when you require confirmation — and you still feel context degrading in long, repetitive sessions, that's where Mem0 deserves the experiment. If you haven't put that limit in place first, installing a memory layer is putting makeup on a symptom.&lt;/p&gt;

&lt;p&gt;The concrete next step for anyone who wants to actually validate this: clone the repo, set up the three-run experiment on a test project, and measure. Without that, everything else is new-tool folklore — the same mistake I've been avoiding with other pieces of the agent stack, like I wrote about &lt;a href="https://juanchi.dev/en/blog/sniffnet-ai-agents-network-traffic-monitoring" rel="noopener noreferrer"&gt;monitoring the real traffic AI agents generate&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Original source:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mem0 GitHub: &lt;a href="https://github.com/mem0ai/mem0" rel="noopener noreferrer"&gt;https://github.com/mem0ai/mem0&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;This article was originally published on &lt;a href="https://juanchi.dev/en/blog/mem0-doesnt-fix-unbounded-agent-complements" rel="noopener noreferrer"&gt;juanchi.dev&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>english</category>
      <category>agentesia</category>
      <category>cline</category>
      <category>vscode</category>
    </item>
    <item>
      <title>Mem0 no arregla un agente sin límites, lo complementa</title>
      <dc:creator>Juan Torchia</dc:creator>
      <pubDate>Sun, 27 Sep 2026 12:00:16 +0000</pubDate>
      <link>https://dev.to/jtorchia/mem0-no-arregla-un-agente-sin-limites-lo-complementa-3f06</link>
      <guid>https://dev.to/jtorchia/mem0-no-arregla-un-agente-sin-limites-lo-complementa-3f06</guid>
      <description>&lt;p&gt;Tengo un límite duro configurado en Cline: no toca archivos fuera de una carpeta específica, no corre comandos de red sin confirmación, y cuando la sesión se pone larga, el contexto empieza a acumular basura. Ya escribí sobre &lt;a href="https://juanchi.dev/es/blog/sniffnet-monitoreo-trafico-red-agentes-ia" rel="noopener noreferrer"&gt;poner límites explícitos a Cline en autopilot&lt;/a&gt; y sobre monitorear lo que un agente hace sin avisar. La pregunta que me quedó picando después de esa restricción de scope es otra: ¿el problema de contexto perdido en sesiones largas se resuelve con más disciplina de scope, o hace falta una capa de memoria externa?&lt;/p&gt;

&lt;p&gt;Ahí entra Mem0. Es una librería que promete persistencia de memoria para agentes de IA — guarda hechos relevantes de una conversación y los recupera después, en lugar de reinyectar todo el historial crudo en cada request. La promesa suena bien para el caso que me interesa: una sesión de Cline que dura horas, con el mismo repo, donde el agente repite preguntas que ya respondiste tres veces porque el contexto se llenó de ruido y perdió lo importante.&lt;/p&gt;

&lt;p&gt;Mi tesis, antes de meterme en el detalle: Mem0 puede ayudar en sesiones largas y repetitivas, donde el mismo tipo de información se necesita una y otra vez. Pero no reemplaza la primera línea de defensa, que sigue siendo limitar qué puede tocar el agente y cuánto contexto le das de entrada. Una capa de memoria es un parche sobre un problema de diseño, no una solución al problema de diseño.&lt;/p&gt;

&lt;h2&gt;
  
  
  Qué dice el repo de Mem0 y qué no dice
&lt;/h2&gt;

&lt;p&gt;El &lt;a href="https://github.com/mem0ai/mem0" rel="noopener noreferrer"&gt;repositorio oficial de Mem0 en GitHub&lt;/a&gt; describe la herramienta como una capa de memoria que permite a los agentes de IA recordar preferencias, hechos y contexto entre sesiones, con soporte para múltiples backends de almacenamiento vectorial. La idea central es simple: en lugar de mandar todo el historial de conversación al modelo cada vez, Mem0 extrae y guarda fragmentos relevantes, y los recupera cuando hacen falta.&lt;/p&gt;

&lt;p&gt;Lo que el repo no dice — y esto es importante — es cuánto reduce el consumo de tokens en un flujo real de agente de código como Cline, trabajando sobre un repo específico, con llamadas a herramientas (leer archivo, ejecutar comando, escribir diff) intercaladas con razonamiento. Los ejemplos de la documentación están pensados para chatbots conversacionales, no para agentes que ejecutan acciones sobre un sistema de archivos. Esa diferencia no es menor: un agente de código genera contexto de una naturaleza distinta a un chatbot que solo conversa. Diffs, salidas de terminal, contenido de archivos — eso no es "memoria de conversación", es estado operacional.&lt;/p&gt;

&lt;p&gt;Entonces la pregunta que me hago no es "¿Mem0 funciona?" — el repo tiene actividad, tests, y casos de uso documentados. La pregunta es "¿Mem0 resuelve el problema específico de context bloat en un agente de código como Cline?", y ahí la evidencia pública no alcanza para responder con certeza.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dónde se equivoca la gente: instalar Mem0 y esperar magia
&lt;/h2&gt;

&lt;p&gt;La receta común que veo circular es: agarrás Mem0, lo conectás al agente, y asumís que el context bloat desaparece porque "ahora tiene memoria". El costo oculto de esa receta es que Mem0 agrega una capa nueva con su propia latencia — cada consulta a la memoria implica una búsqueda vectorial, y esa búsqueda no es gratis en tiempo ni en tokens si el prompt de recuperación es verboso.&lt;/p&gt;

&lt;p&gt;El contraejemplo que me convence de no comprar el hype sin pruebas: si el agente ya tiene un scope mal definido — le decís "arreglá el bug" sin especificar qué archivos puede tocar, sin decirle qué NO hacer — ninguna capa de memoria arregla eso. El agente va a seguir leyendo archivos de más, ejecutando comandos exploratorios, generando contexto que no necesita. Mem0 puede ayudar a que la sesión 5 no repita lo que ya se estableció en la sesión 1, pero no evita que la sesión 1 sea un desastre de contexto si el scope está mal puesto desde el arranque.&lt;/p&gt;

&lt;p&gt;Esto conecta directo con algo que ya vengo sosteniendo: el problema de fondo en agentes autónomos no es "falta memoria", es "falta límite". Un &lt;code&gt;.clinerules&lt;/code&gt; bien escrito, con restricciones explícitas de qué carpetas puede tocar y qué comandos están prohibidos, resuelve más context bloat que una capa de memoria externa — porque ataca la causa, no el síntoma.&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TD
  A[Sesion larga de Cline] --&amp;gt; B{Scope del agente definido?}
  B --&amp;gt;|No| C[Contexto crece sin control]
  B --&amp;gt;|Si| D[Contexto acotado desde el inicio]
  C --&amp;gt; E[Mem0 filtra ruido repetido]
  E --&amp;gt; F[Sigue habiendo bloat de fondo]
  D --&amp;gt; G[Mem0 suma valor real en sesiones repetitivas]&lt;/code&gt;&lt;/pre&gt;



&lt;h2&gt;
  
  
  El experimento que diseñaría antes de adoptar esto
&lt;/h2&gt;

&lt;p&gt;No tengo métricas productivas de Mem0 integrado con Cline, y no las voy a inventar. Lo que sí puedo dejar es el experimento reproducible que armaría antes de decidir si vale la pena:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Repo de prueba fijo&lt;/strong&gt;: un repo chico, con una tarea repetitiva conocida (por ejemplo, agregar el mismo tipo de endpoint tres veces con variaciones).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Baseline sin Mem0&lt;/strong&gt;: correr la tarea tres veces en sesiones separadas de Cline, contando tokens de contexto por sesión (Cline expone esto en su UI de costos).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Misma tarea con Mem0&lt;/strong&gt;: integrar Mem0 como capa de memoria entre sesiones, repetir las tres corridas, y comparar el conteo de tokens de entrada en la tercera corrida contra la tercera corrida del baseline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Criterio de éxito&lt;/strong&gt;: si la tercera corrida con Mem0 usa notablemente menos tokens de contexto de entrada que la tercera corrida sin Mem0, hay una señal real. Si la diferencia es marginal o el overhead de consulta a Mem0 compensa el ahorro, la herramienta no está resolviendo el problema para ese caso.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Este experimento no lo corrí yo — es el diseño que seguiría antes de escribir un post afirmando números. Cualquiera que quiera validar la tesis puede clonar el repo de Mem0 y repetir estos pasos con su propio setup de Cline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Matriz de decisión: cuándo tiene sentido meter Mem0
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Situación&lt;/th&gt;
&lt;th&gt;¿Vale meter Mem0?&lt;/th&gt;
&lt;th&gt;Qué mirar primero&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Sesiones cortas, tarea única y bien acotada&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;El scope ya resuelve el bloat, la memoria es overhead sin retorno&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sesiones largas con tareas repetitivas sobre el mismo dominio&lt;/td&gt;
&lt;td&gt;Posiblemente&lt;/td&gt;
&lt;td&gt;Medir tokens antes/después con el experimento de arriba&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agente sin &lt;code&gt;.clinerules&lt;/code&gt; ni restricciones de scope&lt;/td&gt;
&lt;td&gt;No, primero eso&lt;/td&gt;
&lt;td&gt;Arreglar el límite de scope antes de agregar una capa nueva&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Múltiples agentes compartiendo contexto de proyecto&lt;/td&gt;
&lt;td&gt;Posiblemente&lt;/td&gt;
&lt;td&gt;Evaluar si el backend vectorial que usa Mem0 ya está en el stack&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Proyecto de un solo uso, sin continuidad entre sesiones&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;La memoria persistente no aporta si no hay sesión futura que la use&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Cada fila de esta matriz es un criterio, no una conclusión absoluta — el resultado real depende del repo, del modelo y de cómo esté configurado el agente.&lt;/p&gt;

&lt;h2&gt;
  
  
  Límites: qué no se puede concluir sin correr esto en serio
&lt;/h2&gt;

&lt;p&gt;Sin datos productivos ni logs reales de una integración Mem0 + Cline, no puedo afirmar un porcentaje de reducción de tokens, ni un tiempo de latencia agregado, ni si la calidad de las respuestas del agente mejora o empeora con memoria persistente. Tampoco puedo comparar Mem0 contra otras estrategias de compactación de contexto (como resúmenes periódicos manuales) sin correr ambas en el mismo escenario.&lt;/p&gt;

&lt;p&gt;Lo que sí sostengo con la evidencia pública disponible: el repo de Mem0 está diseñado y documentado primero para agentes conversacionales, no para agentes de código con estado operacional pesado. Esa brecha de diseño es la razón por la que no adoptaría la herramienta sin antes correr el experimento descripto arriba en un repo propio.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;¿Mem0 reemplaza limitar el scope de un agente como Cline?&lt;/strong&gt;&lt;br&gt;
No. Mem0 gestiona qué información persiste entre sesiones, pero no controla qué puede tocar el agente dentro de una sesión. El scope se sigue definiendo con reglas explícitas, no con memoria.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;¿Mem0 reduce tokens en cada request?&lt;/strong&gt;&lt;br&gt;
Puede reducir tokens si evita reinyectar historial completo, pero agrega su propia consulta de recuperación. El balance neto depende del caso — hay que medirlo, no asumirlo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;¿Funciona Mem0 con Cline directamente?&lt;/strong&gt;&lt;br&gt;
El repo de Mem0 no está pensado específicamente para agentes de código con herramientas como Cline. Integrarlo requiere trabajo de adaptación, no es plug-and-play para ese caso de uso.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;¿Necesito una base vectorial separada para usar Mem0?&lt;/strong&gt;&lt;br&gt;
Sí, Mem0 depende de un backend de almacenamiento vectorial para indexar y recuperar memoria. Revisá la documentación del repo para las opciones soportadas antes de sumar infraestructura nueva.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;¿Cuándo NO tiene sentido usar una capa de memoria como esta?&lt;/strong&gt;&lt;br&gt;
Cuando la tarea es de una sola sesión, cuando el agente ya tiene scope acotado y resuelve rápido, o cuando el proyecto no tiene continuidad entre corridas. Ahí la memoria persistente es costo sin retorno.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;¿Cómo mido si Mem0 realmente ayuda en mi caso?&lt;/strong&gt;&lt;br&gt;
Con el experimento de tres corridas descripto arriba: baseline sin memoria, misma tarea con memoria, comparación de tokens de entrada en la corrida repetida. Sin esa medición, cualquier claim es especulación.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mi postura
&lt;/h2&gt;

&lt;p&gt;No voy a recomendar Mem0 como solución universal a algo que en la mayoría de los casos es un problema de diseño del agente, no de falta de memoria. Si ya limitás el scope de Cline con reglas claras — qué carpetas toca, qué comandos están prohibidos, cuándo pedís confirmación — y todavía sentís que el contexto se degrada en sesiones largas y repetitivas, ahí Mem0 merece el experimento. Si no pusiste ese límite primero, instalar una capa de memoria es maquillar un síntoma.&lt;/p&gt;

&lt;p&gt;El próximo paso concreto para cualquiera que quiera validar esto en serio: clonar el repo, armar el experimento de tres corridas en un proyecto de prueba, y medir. Sin eso, todo lo demás es folklore de herramienta nueva — el mismo error que ya vengo evitando con otras piezas del stack de agentes, como escribí sobre &lt;a href="https://juanchi.dev/es/blog/sniffnet-monitoreo-trafico-red-agentes-ia" rel="noopener noreferrer"&gt;monitorear el tráfico real que generan los agentes IA&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Fuente original:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mem0 GitHub: &lt;a href="https://github.com/mem0ai/mem0" rel="noopener noreferrer"&gt;https://github.com/mem0ai/mem0&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Este artículo fue publicado originalmente en &lt;a href="https://juanchi.dev/es/blog/mem0-memoria-agentes-ia-context-bloat-cline" rel="noopener noreferrer"&gt;juanchi.dev&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>spanish</category>
      <category>espanol</category>
      <category>agentesia</category>
      <category>cline</category>
    </item>
    <item>
      <title>Next.js v16.3.6 vs v15.5.26: what's in each branch, according to the changelog</title>
      <dc:creator>Juan Torchia</dc:creator>
      <pubDate>Sat, 26 Sep 2026 12:00:21 +0000</pubDate>
      <link>https://dev.to/jtorchia/nextjs-v1636-vs-v15526-whats-in-each-branch-according-to-the-changelog-578n</link>
      <guid>https://dev.to/jtorchia/nextjs-v1636-vs-v15526-whats-in-each-branch-according-to-the-changelog-578n</guid>
      <description>&lt;h2&gt;
  
  
  Hypothesis and scope
&lt;/h2&gt;

&lt;p&gt;Two Next.js releases on the same day, September 22, 2026: v16.3.6 and v15.5.26. The question I'm interested in answering isn't which one is "better," but something more operational: does the 15.x branch still get only security patches, or does it also get general features and fixes? If the answer is "security only," the criteria for deciding when to migrate changes completely.&lt;/p&gt;

&lt;p&gt;This isn't a benchmark or a performance measurement. It's a direct reading of the official release notes on GitHub, comparing the text content of each tag. Scope is limited: a single pair of releases, a single day. I can't generalize about the full lifecycle of the 15.x and 16.x branches from a single sample — that's flagged as a limitation below.&lt;/p&gt;

&lt;h2&gt;
  
  
  What each release note says
&lt;/h2&gt;

&lt;p&gt;Primary source, no interpretation in between:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;v16.3.6&lt;/strong&gt; — published September 22, 2026 at 17:15 UTC by maintainer eps1lon. The full text of the release reads:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"This release contains a security fix for GHSA-vcvr-r3jv-pc5j: Remote Code Execution in next/og ImageResponse"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Nothing else. A single item, one vulnerability identified by its GHSA code, affected component: &lt;code&gt;next/og&lt;/code&gt; &lt;code&gt;ImageResponse&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;v15.5.26&lt;/strong&gt; — published the same day, one minute earlier (17:14 UTC), same maintainer. The full text:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"This release contains additional security hardening for next/og. For more information, check out &lt;a href="https://nextjs.org/blog/nextjs-security-update-september-22-2026" rel="noopener noreferrer"&gt;https://nextjs.org/blog/nextjs-security-update-september-22-2026&lt;/a&gt;"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Also a single item. Same component (&lt;code&gt;next/og&lt;/code&gt;), same day, but the wording is different: "security hardening" instead of "security fix" with a specific GHSA. The 15.5.26 release note points to a Next.js blog post for more context, which isn't part of the material I could verify for this analysis — I'm not going to speculate about its content.&lt;/p&gt;

&lt;h2&gt;
  
  
  Direct comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;v16.3.6&lt;/th&gt;
&lt;th&gt;v15.5.26&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Publish date&lt;/td&gt;
&lt;td&gt;Sep 22, 2026, 17:15 UTC&lt;/td&gt;
&lt;td&gt;Sep 22, 2026, 17:14 UTC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Affected component&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;next/og&lt;/code&gt; ImageResponse&lt;/td&gt;
&lt;td&gt;&lt;code&gt;next/og&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nature of the change&lt;/td&gt;
&lt;td&gt;Specific fix with identified GHSA (RCE)&lt;/td&gt;
&lt;td&gt;Additional "hardening," no own GHSA in the text&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Number of changelog items&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;New features in this tag&lt;/td&gt;
&lt;td&gt;Not mentioned&lt;/td&gt;
&lt;td&gt;Not mentioned&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Commits from this tag to canary&lt;/td&gt;
&lt;td&gt;858&lt;/td&gt;
&lt;td&gt;5556&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The last data point in the table — commits from the tag to canary — doesn't measure development speed or future activity: it's just the current distance between that specific tag and the tip of canary at the moment GitHub was checked. I'm including it because it's public data visible on the release page, not because it predicts anything about the pace of either branch.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I CANNOT conclude with this evidence
&lt;/h2&gt;

&lt;p&gt;This pair of releases isn't enough to say whether 15.x receives &lt;em&gt;only&lt;/em&gt; security patches as a general policy. The two texts I have in front of me are specifically about &lt;code&gt;next/og&lt;/code&gt;, on the same day, probably coordinated as a response to the same vulnerability reported across different branches. That's consistent with "15.x gets security backports," but it doesn't prove that's &lt;em&gt;all&lt;/em&gt; it gets: it would take reviewing several previous releases in the 15.5.x series to see if general bugfixes without a security component ever went in.&lt;/p&gt;

&lt;p&gt;Nor can I state, with these two tags, what new features 16.3.6 brings compared to 15.5.26 in terms of product: neither text mentions features. If the 16.x branch has accumulated new functionality in other patches within the 16.3.x series, it's not documented in this specific release — reviewing the full changelog from 16.3.0 to 16.3.6, not just the last tag, would be necessary.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this means for a team that hasn't migrated to App Router 16
&lt;/h2&gt;

&lt;p&gt;With the evidence I have — just these two releases — the prudent criterion is this: if the team is still on 15.5.x and applied the &lt;code&gt;next/og&lt;/code&gt; patch, it's covered for this specific vulnerability. That confirms Vercel keeps the 15.x branch alive for security, at least in this concrete case. What it doesn't confirm is that teams can stay there indefinitely waiting for the next fix: there's no evidence in these two tags about how much longer the 15.5.x series will receive support, or whether it will incorporate anything beyond security hardening.&lt;/p&gt;

&lt;p&gt;The decision to force a migration to App Router 16 doesn't depend on this specific patch — it depends on which 16.x features the project needs and how much support time 15.x has left, a detail that isn't in these release notes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pending protocol for anyone who wants to dig deeper
&lt;/h2&gt;

&lt;p&gt;I didn't run this as part of this analysis; I'm leaving it as reproducible steps for anyone who wants to go beyond the two specific tags:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# View the full release history of 15.5.x from npm&lt;/span&gt;
npm view next versions &lt;span class="nt"&gt;--json&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s1"&gt;'"15\.5\.'&lt;/span&gt;

&lt;span class="c"&gt;# Compare the code diff between two specific tags&lt;/span&gt;
git log v15.5.20..v15.5.26 &lt;span class="nt"&gt;--oneline&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; packages/next/src/server/image-optimizer

&lt;span class="c"&gt;# Check whether any 15.5.x release brings features (not just fixes)&lt;/span&gt;
&lt;span class="c"&gt;# by searching for the term "Feature" in each GitHub release note&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Criterion for accepting or rejecting the hypothesis "15.x only receives security": if reviewing 5 or 6 previous releases in the 15.5.x series turns up an item that isn't a security fix or a critical bugfix, the hypothesis falls apart. With the two tags in this analysis, there isn't enough evidence either way — just a single data point from September 22, 2026.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Original source:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Next.js v16.3.6 release: &lt;a href="https://github.com/vercel/next.js/releases/tag/v16.3.6" rel="noopener noreferrer"&gt;https://github.com/vercel/next.js/releases/tag/v16.3.6&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Next.js v15.5.26 release: &lt;a href="https://github.com/vercel/next.js/releases/tag/v15.5.26" rel="noopener noreferrer"&gt;https://github.com/vercel/next.js/releases/tag/v15.5.26&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;This article was originally published on &lt;a href="https://juanchi.dev/en/blog/nextjs-v16-3-6-vs-v15-5-26-changelog-comparison" rel="noopener noreferrer"&gt;juanchi.dev&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>english</category>
      <category>nextjs</category>
      <category>react</category>
      <category>approuter</category>
    </item>
    <item>
      <title>Next.js v16.3.6 vs v15.5.26: qué contiene cada rama, según el changelog</title>
      <dc:creator>Juan Torchia</dc:creator>
      <pubDate>Sat, 26 Sep 2026 12:00:16 +0000</pubDate>
      <link>https://dev.to/jtorchia/nextjs-v1636-vs-v15526-que-contiene-cada-rama-segun-el-changelog-545j</link>
      <guid>https://dev.to/jtorchia/nextjs-v1636-vs-v15526-que-contiene-cada-rama-segun-el-changelog-545j</guid>
      <description>&lt;h2&gt;
  
  
  Hipótesis y alcance
&lt;/h2&gt;

&lt;p&gt;Dos releases de Next.js el mismo día, 22 de septiembre de 2026: v16.3.6 y v15.5.26. La pregunta que me interesa responder no es cuál es "mejor", sino algo más operativo: ¿la rama 15.x sigue recibiendo sólo parches de seguridad, o también features y fixes generales? Si la respuesta es "sólo seguridad", el criterio para decidir cuándo migrar cambia por completo.&lt;/p&gt;

&lt;p&gt;Esto no es un benchmark ni una medición de performance. Es una lectura directa de los release notes oficiales en GitHub, comparando el contenido textual de cada tag. Alcance acotado: un solo par de releases, un solo día. No puedo generalizar sobre el ciclo de vida completo de las ramas 15.x y 16.x a partir de una sola muestra — eso queda marcado como límite más abajo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Qué dice cada release note
&lt;/h2&gt;

&lt;p&gt;Fuente primaria, sin interpretación de por medio:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;v16.3.6&lt;/strong&gt; — publicado el 22 de septiembre de 2026 a las 17:15 UTC por el mantenedor eps1lon. El texto completo del release dice:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"This release contains a security fix for GHSA-vcvr-r3jv-pc5j: Remote Code Execution in next/og ImageResponse"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Nada más. Un solo ítem, una vulnerabilidad identificada por su código GHSA, componente afectado: &lt;code&gt;next/og&lt;/code&gt; &lt;code&gt;ImageResponse&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;v15.5.26&lt;/strong&gt; — publicado el mismo día, un minuto antes (17:14 UTC), mismo mantenedor. El texto completo:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"This release contains additional security hardening for next/og. For more information, check out &lt;a href="https://nextjs.org/blog/nextjs-security-update-september-22-2026" rel="noopener noreferrer"&gt;https://nextjs.org/blog/nextjs-security-update-september-22-2026&lt;/a&gt;"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;También un solo ítem. Mismo componente (&lt;code&gt;next/og&lt;/code&gt;), mismo día, pero la redacción es distinta: "security hardening" en lugar de "security fix" con un GHSA puntual. El release note de 15.5.26 remite a un blog post de Next.js para más contexto, que no está en el material que pude verificar para este análisis — no voy a especular sobre su contenido.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comparación directa
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;v16.3.6&lt;/th&gt;
&lt;th&gt;v15.5.26&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Fecha de publicación&lt;/td&gt;
&lt;td&gt;22 sep 2026, 17:15 UTC&lt;/td&gt;
&lt;td&gt;22 sep 2026, 17:14 UTC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Componente afectado&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;next/og&lt;/code&gt; ImageResponse&lt;/td&gt;
&lt;td&gt;&lt;code&gt;next/og&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Naturaleza del cambio&lt;/td&gt;
&lt;td&gt;Fix puntual con GHSA identificado (RCE)&lt;/td&gt;
&lt;td&gt;"Hardening" adicional, sin GHSA propio en el texto&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cantidad de ítems en el changelog&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Features nuevas en este tag&lt;/td&gt;
&lt;td&gt;No mencionadas&lt;/td&gt;
&lt;td&gt;No mencionadas&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Commits desde este tag hasta canary&lt;/td&gt;
&lt;td&gt;858&lt;/td&gt;
&lt;td&gt;5556&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;El último dato de la tabla — commits desde el tag hasta canary — no mide velocidad de desarrollo ni actividad futura: es sólo la distancia actual entre ese tag puntual y la punta de canary en el momento en que se consultó GitHub. Lo incluyo porque es un dato público visible en la página del release, no porque prediga nada sobre el ritmo de cada rama.&lt;/p&gt;

&lt;h2&gt;
  
  
  Qué NO puedo concluir con esta evidencia
&lt;/h2&gt;

&lt;p&gt;Este par de releases no alcanza para decir si 15.x recibe &lt;em&gt;sólo&lt;/em&gt; parches de seguridad como política general. Los dos textos que tengo delante son específicamente sobre &lt;code&gt;next/og&lt;/code&gt;, el mismo día, probablemente coordinados como respuesta a la misma vulnerabilidad reportada en distintas ramas. Eso es consistente con "15.x recibe backports de seguridad", pero no prueba que sea &lt;em&gt;lo único&lt;/em&gt; que recibe: haría falta revisar varios releases anteriores de la serie 15.5.x para ver si en algún momento entraron bugfixes generales sin componente de seguridad.&lt;/p&gt;

&lt;p&gt;Tampoco puedo afirmar, con estos dos tags, qué features nuevas trae 16.3.6 frente a 15.5.26 en términos de producto: ninguno de los dos textos menciona features. Si la rama 16.x acumula funcionalidad nueva en otros patches de la serie 16.3.x, no está documentado en este release puntual — sería necesario revisar el changelog de 16.3.0 a 16.3.6 completo, no sólo el último tag.&lt;/p&gt;

&lt;h2&gt;
  
  
  Qué implica para un equipo que no migró a App Router 16
&lt;/h2&gt;

&lt;p&gt;Con la evidencia que tengo — sólo estos dos releases — el criterio prudente es este: si el equipo sigue en 15.5.x y aplicó el parche de &lt;code&gt;next/og&lt;/code&gt;, está cubierto para esta vulnerabilidad puntual. Eso confirma que Vercel mantiene la rama 15.x viva para seguridad, al menos en este caso concreto. Lo que no confirma es que puedan quedarse ahí indefinidamente esperando la próxima corrección: no hay evidencia en estos dos tags sobre cuánto tiempo más va a recibir soporte la serie 15.5.x, ni sobre si va a incorporar algo más que hardening de seguridad.&lt;/p&gt;

&lt;p&gt;La decisión de forzar la migración a App Router 16 no depende de este parche puntual — depende de qué features de 16.x necesita el proyecto y de cuánto tiempo de soporte le quede a 15.x, dato que no está en estos release notes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Protocolo pendiente para quien quiera profundizar
&lt;/h2&gt;

&lt;p&gt;Esto no lo ejecuté como parte de este análisis, lo dejo como pasos reproducibles para quien quiera ir más allá de los dos tags puntuales:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Ver el historial completo de releases 15.5.x desde npm&lt;/span&gt;
npm view next versions &lt;span class="nt"&gt;--json&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s1"&gt;'"15\.5\.'&lt;/span&gt;

&lt;span class="c"&gt;# Comparar el diff de código entre dos tags específicos&lt;/span&gt;
git log v15.5.20..v15.5.26 &lt;span class="nt"&gt;--oneline&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; packages/next/src/server/image-optimizer

&lt;span class="c"&gt;# Revisar si algún release 15.5.x trae features (no solo fixes)&lt;/span&gt;
&lt;span class="c"&gt;# buscando el término "Feature" en cada release note de GitHub&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Criterio para aceptar o descartar la hipótesis "15.x sólo recibe seguridad": si al revisar 5 o 6 releases anteriores de la serie 15.5.x aparece algún ítem que no sea fix de seguridad o bugfix crítico, la hipótesis cae. Con los dos tags de este análisis, no hay evidencia suficiente en ningún sentido — sólo un dato puntual del 22 de septiembre de 2026.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Fuente original:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Next.js v16.3.6 release: &lt;a href="https://github.com/vercel/next.js/releases/tag/v16.3.6" rel="noopener noreferrer"&gt;https://github.com/vercel/next.js/releases/tag/v16.3.6&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Next.js v15.5.26 release: &lt;a href="https://github.com/vercel/next.js/releases/tag/v15.5.26" rel="noopener noreferrer"&gt;https://github.com/vercel/next.js/releases/tag/v15.5.26&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Este artículo fue publicado originalmente en &lt;a href="https://juanchi.dev/es/blog/nextjs-16-3-6-vs-15-5-26-diferencias" rel="noopener noreferrer"&gt;juanchi.dev&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>spanish</category>
      <category>espanol</category>
      <category>nextjs</category>
      <category>react</category>
    </item>
  </channel>
</rss>
