<?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: Carlos Arturo Castaño G.</title>
    <description>The latest articles on DEV Community by Carlos Arturo Castaño G. (@carlos_arturocastaog_).</description>
    <link>https://dev.to/carlos_arturocastaog_</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%2F4015461%2F7354aa68-baa2-4bf9-8706-7180b83e9845.jpg</url>
      <title>DEV Community: Carlos Arturo Castaño G.</title>
      <link>https://dev.to/carlos_arturocastaog_</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/carlos_arturocastaog_"/>
    <language>en</language>
    <item>
      <title>Cómo eliminé duplicados en la generación de números de factura sin usar locks</title>
      <dc:creator>Carlos Arturo Castaño G.</dc:creator>
      <pubDate>Sat, 04 Jul 2026 19:17:45 +0000</pubDate>
      <link>https://dev.to/carlos_arturocastaog_/como-elimine-duplicados-en-la-generacion-de-numeros-de-factura-sin-usar-locks-3mj8</link>
      <guid>https://dev.to/carlos_arturocastaog_/como-elimine-duplicados-en-la-generacion-de-numeros-de-factura-sin-usar-locks-3mj8</guid>
      <description>&lt;h1&gt;
  
  
  Cómo eliminé duplicados en la generación de números de factura sin usar locks
&lt;/h1&gt;

&lt;p&gt;Tenía un sistema de facturación en producción (Java/JAX-RS + MongoDB, corriendo en Tomcat) donde cada documento —factura, egreso, comprobante— necesita un número consecutivo único. Nada exótico. El problema apareció solo bajo carga: dos requests casi simultáneos generaban el mismo número.&lt;/p&gt;

&lt;h2&gt;
  
  
  El síntoma
&lt;/h2&gt;

&lt;p&gt;Un usuario reportó dos facturas con el mismo folio. En logs, ambas requests llegaron con 40ms de diferencia. El código hacía esto:&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;// Antes: lee el último número, le suma 1, guarda&lt;/span&gt;
&lt;span class="nc"&gt;Documento&lt;/span&gt; &lt;span class="n"&gt;ultimo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dao&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findLastByTipo&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"FACTURA"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;siguiente&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ultimo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getNumero&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;nuevoDocumento&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setNumero&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;siguiente&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;dao&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;save&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nuevoDocumento&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Clásico read-then-write sin atomicidad. Entre el &lt;code&gt;find&lt;/code&gt; y el &lt;code&gt;save&lt;/code&gt;, otro thread (u otro proceso, u otro pod si escalas horizontal) puede leer el mismo "último número" y generar el mismo consecutivo. Es el mismo problema que un debounce mal hecho en un botón físico: dos pulsos que deberían ser uno solo porque no hay una ventana atómica que los serialice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Por qué no usé locks
&lt;/h2&gt;

&lt;p&gt;Lock a nivel de aplicación (synchronized, semáforos) no sirve si vas a escalar a más de una instancia. Lock a nivel de base de datos (transacciones con bloqueo explícito) funciona pero añade latencia y complejidad de manejo de deadlocks que no necesitaba para este caso.&lt;/p&gt;

&lt;h2&gt;
  
  
  La solución: operación atómica nativa de Mongo
&lt;/h2&gt;

&lt;p&gt;MongoDB tiene &lt;code&gt;findOneAndUpdate&lt;/code&gt; con &lt;code&gt;$inc&lt;/code&gt;, que es atómico a nivel de documento. En vez de leer-calcular-guardar, le pides a Mongo que incremente y te devuelva el valor ya incrementado, en una sola operación indivisible.&lt;/p&gt;

&lt;p&gt;Colección dedicada:&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="c1"&gt;// colección: contadores&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;FACTURA&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;secuencia&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1042&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Código:&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="nc"&gt;Bson&lt;/span&gt; &lt;span class="n"&gt;filtro&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Filters&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;eq&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"_id"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"FACTURA"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;Bson&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Updates&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;inc&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"secuencia"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;FindOneAndUpdateOptions&lt;/span&gt; &lt;span class="n"&gt;opts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FindOneAndUpdateOptions&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;returnDocument&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ReturnDocument&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;AFTER&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;upsert&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;Document&lt;/span&gt; &lt;span class="n"&gt;contador&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;collection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findOneAndUpdate&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filtro&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;siguienteNumero&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;contador&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getInteger&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"secuencia"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esto es atómico incluso con N instancias de la aplicación pegándole a la misma base al mismo tiempo. Mongo serializa internamente las operaciones sobre el mismo &lt;code&gt;_id&lt;/code&gt;, así que no hay ventana donde dos requests lean el mismo valor.&lt;/p&gt;

&lt;h2&gt;
  
  
  La red de seguridad: índice único
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;findOneAndUpdate&lt;/code&gt; resuelve el 99% de los casos, pero no cuesta nada blindar el dato final con un índice único sobre el campo &lt;code&gt;numero&lt;/code&gt; dentro de la colección de documentos:&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="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;facturas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createIndex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;tipo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;numero&lt;/span&gt;&lt;span class="p"&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="na"&gt;unique&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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;Si por alguna razón (bug futuro, migración, dato legacy) se intenta insertar un duplicado, Mongo lo rechaza con un &lt;code&gt;E11000&lt;/code&gt; en vez de guardarlo silenciosamente. Prefiero un error explícito en logs a un duplicado silencioso que alguien descubre tres meses después en una auditoría.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resultado
&lt;/h2&gt;

&lt;p&gt;Cero duplicados desde que se desplegó, incluyendo picos de concurrencia reales (varios usuarios facturando al cierre de mes). El patrón se repite igual para cualquier secuencia que necesite unicidad garantizada bajo concurrencia: consecutivos de comprobantes, tickets, números de orden, lo que sea.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resumen para copiar y pegar
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Nunca generes consecutivos con &lt;code&gt;find&lt;/code&gt; + cálculo en memoria + &lt;code&gt;save&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Usa &lt;code&gt;findOneAndUpdate&lt;/code&gt; + &lt;code&gt;$inc&lt;/code&gt; sobre una colección de contadores dedicada.&lt;/li&gt;
&lt;li&gt;Blinda con índice único como última línea de defensa, no como solución principal.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>backend</category>
      <category>database</category>
      <category>java</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
