Yeda AI Tips · #131

English

Impón la unicidad en la base de datos, no solo en el modelo

Las validaciones de Rails mienten bajo carga concurrente. La base de datos no. Un validates :email, uniqueness: true parece garantizar una sola fila por dirección — hasta que dos peticiones llegan en el mismo milisegundo y ambas pasan.

La carrera, paso a paso

El validador de unicidad ejecuta un SELECT para comprobar si ya existe un duplicado, y solo entonces hace el INSERT. Son dos sentencias separadas con un hueco entre ellas. Bajo concurrencia:

  1. La petición A hace la comprobación — no encuentra duplicado.
  2. La petición B hace la comprobación — tampoco encuentra duplicado.
  3. La petición A inserta.
  4. La petición B inserta.

Ambas pasaron la comprobación en el mismo instante, así que ambas se guardan y obtienes el duplicado que creías haber evitado. La guía de Rails lo dice con claridad: esta validación "no crea una restricción de unicidad en la base de datos, por lo que puede ocurrir un escenario en el que dos conexiones distintas creen dos registros con el mismo valor en una columna que pretendías que fuera única".

La solución: un índice único

Baja la invariante a la única capa que la evalúa de forma atómica. Agrega un índice único en una migración:

class AddUniqueIndexToUsersEmail < ActiveRecord::Migration[7.1]
  def change
    add_index :users, :email, unique: true
  end
end

Ahora el segundo INSERT de la carrera no puede ganar — la base de datos lo rechaza y lanza ActiveRecord::RecordNotUnique. La correctitud queda garantizada sin importar cuántas conexiones compitan. Para la unicidad con alcance (uniqueness: { scope: :account_id }), el índice debe cubrir ambas columnas: add_index :memberships, [:account_id, :user_id], unique: true.

Mantén ambas capas

La validación del modelo no está mal — simplemente no basta. Consérvala para lo que hace bien.

CapaFunciónDestaca en
validates ..., uniqueness: trueMensaje de error amigable por campo antes de guardarExperiencia de usuario
Índice único en la base de datosGarantía atómica bajo concurrenciaCorrectitud

La validación atrapa ~99% de los duplicados de forma barata y le entrega al usuario un mensaje limpio de "el email ya está en uso". El índice atrapa la carrera poco frecuente que la validación no puede ver. Dos capas, una invariante.

Movimientos avanzados

Recursos

¿Llevas Rails a carga real? Yeda AI diseña, audita y endurece sistemas en producción.

Habla con nosotros · Lee el blog