La base de datos de una plataforma no es un documento que se reescribe. Es una estructura que sostiene peso mientras hay gente de pie encima, y las tablas que contienen los saldos de los jugadores, el historial de rondas y las transacciones liquidadas son los muros de carga. Cambiar su forma es, por tanto, una cuestión sobre qué puede prometer la plataforma durante los minutos en que el cambio se está ejecutando, y no solo sobre cuál debería ser la nueva forma.
Por eso el trabajo de esquema en una plataforma regulada tiene un aspecto distinto del trabajo de esquema en un sitio web. Se espera de la plataforma que liquide rondas, respete límites y responda a preguntas sobre una transacción pasada mientras se añade una columna, se aplica una restricción y se reescriben varios millones de filas en el sitio. La disciplina de ingeniería que sigue consiste en hacerlo de forma deliberada: saber qué forma de sentencia toma qué bloqueo, escalonar el cambio para que ningún paso sea a la vez grande e irreversible, y conservar pruebas de que los datos que quedan al otro lado son los mismos datos.

El bloqueo es el cambio, no la sentencia
Toda operación de esquema es en realidad una decisión de bloqueo con una sintaxis adherida, y la disponibilidad de la plataforma la decide el bloqueo, no cuánto tarda la sentencia en planificarse.
PostgreSQL declara el comportamiento por defecto sin rodeos en su documentación de ALTER TABLE: «Se adquiere un bloqueo ACCESS EXCLUSIVE a menos que se indique explícitamente lo contrario». La misma página añade la frase que pilla desprevenidos a los equipos, porque se aplica a una sentencia que parece un solo cambio y se comporta como varios: «Cuando se dan varios subcomandos, el bloqueo adquirido será el más estricto de los que requiera cualquiera de los subcomandos». Combinar un cambio de metadatos barato con otro caro en una sola sentencia no promedia el coste; hereda el peor bloqueo de la lista y lo mantiene durante todo el comando.
La documentación también señala los casos en los que basta con un bloqueo más ligero. Añadir una clave foránea es uno de ellos: ADD FOREIGN KEY «solo requiere un bloqueo SHARE ROW EXCLUSIVE», aunque también toma uno sobre la tabla referenciada. Validar una restricción que ya se había añadido como NOT VALID es otro, y es la razón por la que el enfoque por etapas que se describe más abajo funciona siquiera.
Así que la primera regla práctica es un hábito de lectura más que una técnica: antes de ejecutar nada, consultar el nivel de bloqueo de la forma que se va a usar y dividir toda sentencia que mezcle un subcomando rápido con uno lento.
Qué ediciones reescriben la tabla y cuáles solo tocan los metadatos
La segunda pregunta es si el motor puede cambiar la definición sin reescribir las filas almacenadas. La documentación de MySQL sobre DDL en línea deja explícita la distinción, y merece la pena copiarla en el plan de cambio, porque sobre una tabla grande la diferencia se mide en segundos frente a horas.
Su documentación describe tres formas en que puede comportarse una operación. Una operación instantánea «solo modifica metadatos en el diccionario de datos», con un posible bloqueo de metadatos exclusivo y breve durante la ejecución y con DML concurrente permitido. Una operación en el sitio (in-place) cambia la tabla sin una copia completa, pero puede seguir necesitando reorganizar los datos. Una operación de copia reescribe la tabla.
| Cambio | Cómo lo clasifica el motor | Qué le cuesta a la plataforma |
|---|---|---|
| Añadir un índice secundario | En el sitio, sin reconstruir la tabla, DML concurrente permitido | El tiempo de construcción del índice, más el camino de escritura con el que compite |
Añadir el primer índice FULLTEXT |
En el sitio, pero no se permite DML concurrente | Un paso que bloquea las escrituras, no un paso en segundo plano |
| Añadir una clave primaria | En el sitio, reconstruye la tabla; el manual califica la reorganización de los datos de sustancial y costosa | Una reescritura de gran volumen con DML concurrente permitido |
| Eliminar una clave primaria | Reconstruye la tabla y no permite DML concurrente | Una interrupción de las escrituras mientras dura |
| Cambiar el tipo de un índice | Instantáneo posible, solo metadatos | Prácticamente gratis, así que hazlo pronto y por separado |
Dos detalles de esa misma documentación son fáciles de pasar por alto y ambos importan a un plan de cambio. El primero es que una cláusula LOCK es una aserción, no una preferencia: si pide un nivel menos restrictivo «del permitido para una operación DDL concreta, la sentencia falla con un error». Ese fallo es el resultado útil, porque llega antes del cambio y no durante él. El segundo tiene que ver con cuándo es realmente utilizable un índice: un índice secundario recién creado «contiene solo los datos confirmados en la tabla en el momento en que la sentencia CREATE INDEX … termina de ejecutarse», y por eso un índice corresponde al final de la secuencia y no al principio.
En PostgreSQL, el hábito equivalente se abre con el mismo razonamiento y otro vocabulario. Una forma que reescribe la tabla bajo un bloqueo ACCESS EXCLUSIVE se planifica como una migración; una forma que toma SHARE UPDATE EXCLUSIVE es un cambio rutinario. El plan de cambio debería decir por escrito cuál de las dos es, porque las dos reciben ventanas distintas, aprobaciones distintas e historias de reversión distintas.
Descomponer el cambio: expandir, rellenar, contraer
La manera de evitar un único paso grande e irreversible es dividir el cambio en tres que sean pequeños por separado, que es la forma que ya usan la mayoría de los equipos con experiencia aunque no le pongan nombre.
Expandir. Añadir la nueva columna, tabla o índice sin pedirle a la base de datos que demuestre nada sobre las filas existentes. La documentación de ALTER TABLE de PostgreSQL explica el mecanismo con claridad: una restricción añadida provoca normalmente un escaneo de la tabla para verificar que todas las filas existentes la satisfacen, pero «si se usa la opción NOT VALID, este escaneo, potencialmente largo, se omite». La restricción sigue aplicándose a todo lo que se escriba después, así que la plataforma deja de acumular incumplimientos nuevos de inmediato, mientras que las filas históricas quedan sin demostrar.
Rellenar (backfill). Mover los datos por lotes, en un proceso aparte, a un ritmo que elija la plataforma. La sección que sigue lo trata como la carga de trabajo que es.
Contraer. Una vez que las filas históricas se han demostrado válidas y el camino de lectura se ha trasladado, validar la restricción y después eliminar lo que ya no se necesita — y eliminarlo en un cambio posterior, no en el mismo.
El paso de validación es el que hace asequible la secuencia. Esa misma documentación afirma que la validación «no necesita bloquear las actualizaciones concurrentes, ya que sabe que otras transacciones aplicarán la restricción a las filas que inserten o actualicen; solo hay que comprobar las filas preexistentes. Por lo tanto, la validación adquiere únicamente un bloqueo SHARE UPDATE EXCLUSIVE sobre la tabla que se está modificando». Una clave foránea añade un bloqueo ROW SHARE sobre la tabla referenciada.
El mismo escalonado se aplica a una columna NOT NULL, cuyo comportamiento está documentado con más detalle de lo que un equipo suele suponer. SET NOT NULL se «comprueba normalmente durante el ALTER TABLE escaneando toda la tabla, a menos que se especifique NOT VALID»; el escaneo se omite por completo «si existe una restricción CHECK válida (y no se elimina en el mismo comando) que demuestre que no puede existir ningún NULL»; y si una restricción not-null ya se añadió como no válida, SET NOT NULL la valida en lugar de volver a comprobar el mundo entero. En la práctica eso significa: añadir el check como NOT VALID, validarlo y solo entonces marcar la columna como not null — tres pasos pequeños cuyo coste total es un único escaneo a un nivel que permite escrituras.
Crear el índice sin detener las escrituras — y poner precio a la concurrencia
Los índices son el punto donde un cambio de esquema se convierte más a menudo en una interrupción, porque una construcción de índice normal deja fuera las escrituras mientras dura y una tabla grande puede tardar horas. Todos los motores importantes ofrecen ya una forma de evitarlo, y cada uno cambia disponibilidad por trabajo total.
La documentación de CREATE INDEX de PostgreSQL describe la opción y su coste a la vez: CONCURRENTLY construye el índice «sin tomar ningún bloqueo que impida inserciones, actualizaciones o borrados concurrentes en la tabla; mientras que una construcción de índice estándar deja fuera las escrituras (pero no las lecturas) sobre la tabla hasta que termina». Después dice qué compra la plataforma con su presupuesto de tiempo: la construcción concurrente realiza dos escaneos, «debe esperar a que terminen todas las transacciones existentes que podrían modificar o usar el índice» y, por lo tanto, «requiere más trabajo total que una construcción de índice estándar y tarda bastante más en completarse».
Tres comportamientos documentados pertenecen al runbook, porque son los que sorprenden a la gente:
- Una construcción concurrente puede fallar y dejar algo atrás. El índice se registra primero como índice «no válido»; si surge un problema como un interbloqueo durante el escaneo, el comando «fallará, pero dejará atrás un índice ‘no válido’». Ese índice se ignora para consultas porque puede estar incompleto, pero «seguirá consumiendo sobrecarga de actualización» en cada escritura. La recuperación documentada consiste en eliminarlo y ejecutar de nuevo la construcción concurrente.
- Un índice único empieza a aplicarse antes de ser utilizable. La unicidad se aplica frente a otras transacciones desde el segundo escaneo en adelante, así que un incumplimiento «podría notificarse en otras consultas antes de que el índice esté disponible para su uso, o incluso en casos en los que la construcción del índice acabe fallando», y un índice único no válido sigue aplicándose después. Desplegar la restricción y el código que la satisface en el orden equivocado convierte esto en errores visibles sobre tráfico en vivo.
- Una construcción concurrente no es componible. Solo puede ejecutarse una construcción concurrente de índice sobre una tabla a la vez, la tabla no puede ver modificado su esquema mientras la construcción se ejecuta, y
CREATE INDEX CONCURRENTLYno puede ejecutarse dentro de un bloque de transacción. En una tabla particionada, las construcciones concurrentes no son compatibles directamente; la alternativa documentada es construir el índice de forma concurrente en cada partición y luego adjuntar el índice particionado, que es una operación que solo toca metadatos.
La última restricción es la que suele obligar a un despliegue coordinado de una versión posterior: si el índice no puede crearse en la misma transacción de migración que el resto del esquema, la construcción pasa a ser un paso operativo aparte con su propia monitorización.
Un backfill es una carga de trabajo, no un script
Reescribir filas históricas es la parte de un cambio de esquema que realmente consume capacidad de la plataforma, y la tentación es tratarla como un script puntual que se ejecuta hasta el final. En una plataforma de juego en vivo es una carga de trabajo que compite con las demás y que tiene un camino de vuelta.
El patrón de código abierto que más equipos toman prestado merece leerse por sus decisiones de diseño y no por su línea de comandos. gh-ost, de GitHub, describe la forma de un cambio en línea: crea una tabla fantasma a imagen de la original, migra esa tabla mientras está vacía, copia los datos de la original hacia ella «lentamente y de forma incremental», propaga al mismo tiempo los cambios que van ocurriendo y después, «en el momento adecuado», sustituye la original por la tabla fantasma. Dos de sus decisiones de diseño son las transferibles. Evita deliberadamente los triggers y lee el log binario en su lugar, porque, en palabras de sus autores, los triggers son la fuente de «muchas limitaciones y riesgos» en un camino de escritura con carga. Y su mecanismo de limitación es una pausa real y no un ritmo más lento: cuando limita, «realmente cesa las escrituras en el maestro: ni copias de filas ni procesamiento de eventos en curso», devolviendo la base de datos a su carga de trabajo original.
Esa última propiedad es lo que necesita un backfill en una plataforma regulada: un interruptor de apagado que funcione de inmediato, porque el desencadenante para usarlo suele ser una señal externa al backfill — la latencia de liquidación subiendo, el retraso de replicación escalando o una cola de soporte llenándose de apuestas fallidas. Un backfill que solo puede ralentizarse reiniciándolo no es uno que un operador pueda dejar en marcha con seguridad durante las horas punta.
Las propiedades que merece la pena escribir en el plan:
- Lotes acotados con un marcador de progreso confirmado. El trabajo debe poder reanudarse desde donde se detuvo sin reescribir filas que ya procesó, y debe poder detenerse entre lotes, no solo entre ejecuciones.
- Escrituras idempotentes. Un backfill que se ejecuta dos veces debe dejar el mismo resultado, porque el fallo más probable no es un valor equivocado sino un paso repetido tras uno interrumpido.
- Limitación guiada por la propia señal de salud de la plataforma, no por una espera fija. El retraso de replicación, la latencia de escritura y la profundidad de la cola de liquidación son las señales que dicen si la carga extra sale gratis justo ahora.
- Una condición de finalización que sea comprobable, como un recuento de filas que aún esperan el nuevo valor, en lugar de una línea de log que dice que el script ha terminado.
- Un orden deliberado entre el backfill y el corte. Si la nueva columna pasa a ser la fuente de verdad mientras el backfill sigue en marcha, la plataforma tiene dos escritores y un problema de conciliación.
Los límites de tiempo pertenecen al cambio
Una sentencia de esquema que espera es peor que una que falla, porque un cambio que espera un bloqueo está reteniendo recursos y sigue siendo un paso sin terminar en una secuencia que ya nadie está vigilando. Los timeouts son la forma en que la plataforma decide eso de antemano.
PostgreSQL documenta lock_timeout como un ajuste de sesión o de sentencia que aborta «cualquier sentencia que espere más tiempo del especificado mientras intenta adquirir un bloqueo sobre una tabla, un índice, una fila u otro objeto de la base de datos», y señala que el límite «se aplica por separado a cada intento de adquisición de bloqueo». Está desactivado por defecto, y la documentación aconseja explícitamente no configurarlo en el archivo de configuración del servidor «porque afectaría a todas las sesiones» — que es exactamente por lo que pertenece a las herramientas del cambio, acotado a la conexión que ejecuta la migración. También señala la trampa del orden: si statement_timeout está configurado con el mismo valor o con uno menor, siempre se disparará primero, así que un timeout de bloqueo elegido para proteger la plataforma puede quedar enmascarado por un timeout de sentencia elegido con otro propósito.
El ajuste relacionado es el que protege frente a la migración que se atasca sin llegar a fallar. idle_in_transaction_session_timeout termina cualquier sesión que quede inactiva dentro de una transacción abierta, y la documentación da la razón operativa: puede usarse para garantizar que las sesiones inactivas «no retengan bloqueos durante un periodo de tiempo irrazonable». Un proceso de cambio que abre una transacción, ejecuta un paso y después espera a otra cosa está reteniendo todo lo que haya bloqueado, y en una plataforma el coste lo pagan los jugadores cuya ronda no puede liquidarse.
El plan de cambio debería indicar por tanto, para cada paso: bajo qué ajustes de sesión se ejecuta, qué hace cuando se agota el tiempo y si el fallo deja la base de datos en un estado desde el que el paso siguiente pueda reanudar. Un timeout que deja un índice no válido o una restricción sin validar es un punto de control, no un desastre, siempre que el plan lo diga antes de que ocurra.
La verificación es una comparación, no una marca verde
Un cambio de esquema está terminado cuando los datos demuestran que está terminado. La evidencia es una comparación entre dos estados de la misma pregunta, tomados en dos momentos, y tiene que poder reproducirla alguien que no participó en el cambio.
| Qué se compara | Por qué es la que importa |
|---|---|
| Recuentos de filas por tabla y por partición | Es barato y detecta el lote truncado y la inserción duplicada |
| Sumas y recuentos sobre las columnas de dinero, agrupados como los agrupa el libro mayor | La comparación que pedirá un revisor de finanzas o de cumplimiento, y la que detecta un backfill que contó mal |
| Una suma de comprobación sobre un conjunto de claves definido, antes y después | Convierte «creemos que se ejecutó» en un valor que puede recalcularse |
| Rondas abiertas y transacciones sin liquidar en la frontera | El estado con más probabilidad de quedar a medio migrar, porque estaba en vuelo cuando se ejecutó el cambio |
| El conjunto de filas que rechaza la nueva restricción | Un recuento de incumplimientos es a la vez una medida de progreso y una medida de riesgo |
| Lecturas por el camino nuevo frente al camino antiguo sobre las mismas filas | Detecta el error de mapeo que deja ambos caminos individualmente válidos |
Dos hábitos hacen que esto sea asequible. El primero es la ventana de conciliación: al cambio le sigue un periodo en el que el libro mayor se compara igual que se compara después de cualquier otro evento de liquidación, que es donde ya se aplica la disciplina de conciliación de monederos. El segundo es que el camino antiguo siga siendo legible hasta que la comparación haya pasado, que es el contenido técnico de «expandir y contraer» y la razón por la que una columna eliminada se planifica en un cambio posterior.
Ninguna de estas comparaciones demuestra que una ronda pueda seguir interpretándose como se interpretó cuando se jugó. Esa propiedad merece enunciarse aparte, porque un cambio de esquema puede romperla sin hacer ruido: la disciplina de repetición determinista existe precisamente porque a veces hay que reconstruir una ronda pasada a partir de los datos tal como estaban, y un cambio que añade, renombra o reinterpreta una columna es un cambio en ese registro.
Decidir la reversión antes de que se ejecute la primera sentencia
Todo plan de cambio tiene un punto a partir del cual revertir es más caro que seguir adelante. Ponerle nombre a ese punto de antemano es la diferencia entre una decisión y una improvisación a las tres de la madrugada.
La parte reversible suele ser mayor de lo que los equipos suponen, por cómo se comportan las restricciones escalonadas. Una restricción NOT VALID añadida puede eliminarse. Un backfill puede detenerse y reiniciarse. Un índice recién construido puede eliminarse — y eliminar un índice, a diferencia de construirlo, es un cambio de metadatos que permite DML concurrente. La parte irreversible es estrecha e identificable: eliminar una columna o una tabla, y cualquier cambio que el nuevo camino de código ya haya leído y escrito.
Eso da a un plan de cambio una forma, más que una lista de comprobación:
- Expandir y rellenar son reversibles y pueden abandonarse en cualquier momento, dejando columnas extra y un índice extra que ocupan espacio pero no cambian nada.
- El corte es el paso reversible con esfuerzo: el código puede revertirse, pero solo mientras siga manteniéndose el camino antiguo.
- Contraer es el punto de no retorno y pertenece a un cambio aparte y posterior, con su propia aprobación, su propia ventana y su propia verificación — después del periodo de observación que defina el plan, no después de que termine la ventana de cambio.
La misma disciplina determina qué aspecto tiene un incidente. Un cambio fallido con un esquema no revertido es un incidente distinto de un cambio fallido que se abandonó limpiamente, y el plan de respuesta a incidentes es donde se escribe la diferencia entre ambos, junto con quién tiene permitido ordenar la reversión.
Ensayar contra una copia del tamaño real
Un cambio de esquema ensayado sobre una tabla vacía no demuestra casi nada, porque lo que se está probando es cómo se comporta la operación frente a la forma real de los datos.
El ensayo necesita tres propiedades. Tamaño: una copia de la tabla con el volumen de producción, porque la diferencia entre un cambio de metadatos instantáneo y uno que reescribe solo aparece cuando hay filas que reescribir. Concurrencia: escrituras ocurriendo mientras el cambio se ejecuta, porque un bloqueo invisible en una base de datos inactiva es el problema entero en una ocupada. Inyección: un fallo provocado deliberadamente — una sentencia abortada a mitad de backfill, una restricción que debería rechazar una fila, una construcción de índice que falla — porque el camino de recuperación es la parte del plan que nadie ha probado.
Para eso está el entorno no productivo, y los requisitos de entornos no productivos son su sitio adecuado. Un ensayo también puede tomar prestado el camino de restauración: un cambio ensayado contra una copia restaurada de la base de datos de producción pone a prueba tanto el cambio como la capacidad de restaurar la copia de seguridad de la que procede, que es la misma evidencia que exige la disciplina de verificación de copias de seguridad y restauración.
Cuando el cambio afecta a una tabla que pertenece a otro equipo — una tabla de integración con proveedores, un extracto para informes, una tabla que lee un socio — el ensayo es también el momento de confirmar que el cambio es compatible con sus lecturas. La guía de migración y corte de plataformas cubre la versión más amplia de ese problema: un traslado de plataforma es una secuencia de cambios coordinados, y el esquema es una de las cosas que se coordinan, no un detalle de implementación del traslado.
Convertirlo en evidencia de aceptación
El paquete que un revisor, un auditor o un comprador debería poder leer es corto, y cada elemento es comprobable en lugar de afirmado:
- El plan de cambio, indicando para cada paso la forma de la sentencia, el nivel de bloqueo que el motor documenta para ella y si la operación reescribe la tabla.
- La descomposición, mostrando que ningún paso reescribe a la vez una tabla grande y es irreversible.
- La especificación del backfill: tamaño de lote, marcador de progreso, comportamiento de reanudación, señal de limitación y condición de finalización.
- Los ajustes de sesión bajo los que se ejecuta cada paso, incluidos los timeouts de bloqueo y de transacción inactiva y qué ocurre cuando se disparan.
- La evidencia de comparación: los recuentos, sumas y sumas de comprobación tomados antes y después, con las consultas que los produjeron.
- El registro del ensayo, incluido el fallo inyectado y la recuperación observada.
- El punto de reversión con nombre, los pasos que siguen siendo reversibles y quién está autorizado a ordenar la reversión.
- La planificación del paso de contracción, mantenida aparte del cambio que lo hizo posible.
El trabajo que esto describe es en su mayor parte preparación, y por eso se comprime tan a menudo dentro de la propia ventana de cambio, donde resulta a la vez más caro y más visible para los jugadores. El hábito al que pertenece — especificar el comportamiento antes de construirlo y mantener la especificación al día cuando el sistema cambia — es el mismo que hay detrás de la disciplina de desarrollo de plataformas que este sitio aplica en otros lugares. En el caso de una base de datos, la especificación es inusual en un aspecto: tiene que seguir siendo cierta mientras se reescriben varios millones de filas por debajo, que es la única razón por la que merece la pena escribir la secuencia antes de ejecutarla.
Preguntas que se hace un equipo de plataforma
¿Podemos limitarnos a programar la migración en una hora tranquila?
Puedes hacerlo, y aun así merece la pena descomponer la sentencia. Una hora tranquila reduce la probabilidad de que alguien esté a mitad de ronda cuando se toma el bloqueo; no cambia lo que tarda una reescritura, y en una tabla lo bastante grande como para importar la reescritura dura más que cualquier hora tranquila que tenga la plataforma. Las dos mitigaciones son independientes: la ventana reduce el número de personas afectadas, y el cambio escalonado reduce aquello por lo que se ven afectadas. Un cambio que se apoya solo en la ventana falla la primera vez que la tabla crece más allá de ella.
¿Es seguro ejecutar CREATE INDEX CONCURRENTLY en cualquier momento?
Está diseñado para no bloquear inserciones, actualizaciones ni borrados concurrentes, y sigue siendo una operación más pesada que una construcción normal, porque realiza dos escaneos y espera a transacciones que podrían tocar el índice. Dos de sus comportamientos documentados deciden si es seguro en una hora concreta: puede fallar y dejar un índice no válido que consume sobrecarga de actualización hasta que se elimina, y un índice único aplica la unicidad desde su segundo escaneo en adelante, lo que significa que puede sacar a la luz incumplimientos sobre tráfico en vivo antes de ser utilizable. Así que la respuesta es una secuencia y no un sí: constrúyelo cuando se sabe que los datos están bien, vigila el estado no válido y trata el índice como un paso operativo aparte del resto del cambio.
¿Qué tiene que ser cierto realmente antes de eliminar la columna antigua?
Tres cosas, en orden. El camino nuevo debe ser el único escritor. La comparación sobre los datos de dinero y de rondas debe haber pasado a lo largo de un ciclo de liquidación completo, no solo con una comprobación puntual. Y debe haber transcurrido un periodo de observación definido sin que ningún consumidor siga leyendo la columna antigua — incluidos el extracto para informes, el feed del socio y ese panel del que nadie se acordaba. Hasta que se cumplan las tres, la columna antigua es un seguro barato: ocupa espacio y además es la única copia que queda de los datos con la forma que entendía el código antiguo.
¿Cómo sabemos que el backfill ha terminado de verdad?
No te sirvas de la ausencia de errores ni de una línea de log. Define una condición de finalización que la base de datos pueda responder, como el recuento de filas que aún conservan el valor anterior o que aún incumplen la nueva restricción, y exige que ese recuento llegue a cero y se mantenga ahí durante un ciclo de escritura completo después del corte. Después mantén ese mismo recuento como monitor durante un periodo definido, porque un camino de código que escribe el valor antiguo puede reintroducirse con la reversión de una versión no relacionada, y un backfill silencioso no es lo mismo que un backfill que sigue terminado.
¿Cuál es el primer paso útil más pequeño si nada de esto existe todavía?
Anota, para el próximo cambio que tengas que hacer de todos modos, los cuatro hechos que la documentación ya te dice: la forma de la sentencia, el bloqueo que toma, si reescribe la tabla y el punto a partir del cual el cambio ya no es reversible. Eso es una tarde de lectura y cambia el plan de inmediato, porque las dos decisiones que causan la mayor parte del daño — combinar un subcomando rápido con uno lento en una sola sentencia, y descubrir el punto de reversión a posteriori — se toman ambas en el momento en que se escribe el plan.








































