T3.4: Handles Schema Evolution

Knowledge Review - InterSystems ObjectScript Specialist

1. Agregar índices a propiedades existentes

Puntos Clave

  • Agregar un índice: Definir el índice en la definición de clase y compilar la clase
  • Construir el índice: Los índices nuevos sobre datos existentes requieren población vía `%BuildIndices()` o el comando SQL `BUILD INDEX`
  • Compilar solo no es suficiente: La clase se compila exitosamente, pero el global del índice permanece vacío hasta que se construya
  • %BuildIndices(): Método de clase para poblar uno o más índices; puede ejecutarse en segundo plano
  • CREATE INDEX: Alternativa SQL DDL que define y opcionalmente construye el índice
  • Escenarios de reconstrucción: Requerido después de cargas masivas de datos, recuperación de corrupción de datos o cambios de almacenamiento

Notas Detalladas

Descripción general

Cuando agrega un índice a una clase que ya contiene datos, la definición del índice se registra en tiempo de compilación, pero la estructura de datos del índice (las entradas de subíndice del global) no se pobla automáticamente. Debe construir explícitamente el índice para poblarlo con los datos existentes. No hacerlo significa que el optimizador usará el índice vacío y las consultas devolverán cero resultados (o resultados incompletos si algunas filas fueron insertadas después de que se definió el índice).

Agregar un índice vía definición de clase

Class MyApp.Patient Extends %Persistent
{
    Property Name As %String(MAXLEN = 100);
    Property SSN As %String(MAXLEN = 11);
    Property City As %String(MAXLEN = 50);

    // Índice existente
    Index SSNIdx On SSN [ Unique ];

    // NUEVO índice agregado a clase existente con datos
    Index CityIdx On City;
    Index NameIdx On Name;
}

Después de agregar la definición del índice y compilar la clase, debe construirlo:

// Construir todos los índices para la clase
DO ##class(MyApp.Patient).%BuildIndices()

// Construir solo índices específicos
DO ##class(MyApp.Patient).%BuildIndices($ListBuild("CityIdx", "NameIdx"))

// Construir con salida de progreso
DO ##class(MyApp.Patient).%BuildIndices(, 1)  // modo detallado

// Construir y purgar datos de índice existentes primero (reconstrucción limpia)
DO ##class(MyApp.Patient).%BuildIndices($ListBuild("CityIdx"), 1, 1)
// Parámetros: (indexList, verbose, purgeFirst)

Agregar un índice vía SQL DDL

-- CREATE INDEX define Y construye en un solo paso
CREATE INDEX CityIdx ON TABLE MyApp.Patient (City)

-- Crear un índice único
CREATE UNIQUE INDEX SSNIdx ON TABLE MyApp.Patient (SSN)

-- El enfoque SQL es frecuentemente más simple para operaciones de tipo DBA

Comando BUILD INDEX

-- Reconstruir un índice específico
BUILD INDEX CityIdx FOR TABLE MyApp.Patient

-- Reconstruir todos los índices para una tabla
BUILD INDEX FOR TABLE MyApp.Patient

Cuándo reconstruir índices

  • Después de agregar un nuevo índice: El escenario más común
  • Después de importación masiva de datos: Si los datos se cargaron vía operaciones a nivel de global omitiendo el mantenimiento de índices
  • Después de recuperación de datos: Si los datos se restauraron desde un respaldo y los índices pueden ser inconsistentes
  • Después de cambios en la definición de almacenamiento: Si el almacenamiento de la clase fue modificado manualmente
  • Degradación del rendimiento: Si un índice se fragmenta (aunque esto es raro con estructuras B-tree+)
// Patrón común: verificar si la reconstrucción es necesaria después de carga masiva
// Primero, purgar y reconstruir todos los índices
SET sc = ##class(MyApp.Patient).%BuildIndices(, 1, 1)
IF $$$ISERR(sc) {
    WRITE "Index build failed: ", $SYSTEM.Status.GetErrorText(sc), !
}

Referencias de Documentación

2. Impacto de cambiar el nombre o tipo de dato de una propiedad

Puntos Clave

  • Renombrar una propiedad: NO renombra la ranura de almacenamiento; los datos antiguos quedan huérfanos, la nueva propiedad se lee como vacía
  • Desajuste de definición de almacenamiento: El almacenamiento de clase mapea nombres de propiedad a posiciones de subíndice del global; renombrar rompe el mapeo
  • Cambiar tipo de dato: Los datos existentes pueden volverse inválidos para el nuevo tipo (ej., datos de cadena en una columna ahora entera)
  • Riesgo de pérdida de datos: Recompilar después de renombrar sin migración puede hacer inaccesibles los datos existentes
  • Se requiere migración: Las operaciones de renombrado requieren actualización de la definición de almacenamiento o scripts de migración de datos
  • SqlFieldName: Usar para cambiar el nombre de columna SQL sin cambiar el nombre de propiedad en ObjectScript

Notas Detalladas

Descripción general

InterSystems IRIS usa una definición de almacenamiento que mapea nombres de propiedad a posiciones específicas en la estructura de global subyacente. Cuando cambia el nombre o tipo de dato de una propiedad, la definición de almacenamiento y los datos existentes pueden desalinearse, conduciendo a pérdida o corrupción de datos. Entender estos riesgos es esencial para una evolución segura del esquema.

Cómo funciona el almacenamiento

Cuando una clase se compila por primera vez, el compilador genera una definición de almacenamiento que mapea cada propiedad a una posición de subíndice en el global de datos. Por ejemplo:

<!-- Definición de almacenamiento auto-generada (simplificada) -->
<Storage name="Default">
  <Data name="PatientDefaultData">
    <Value name="1"><Value>Name</Value></Value>
    <Value name="2"><Value>SSN</Value></Value>
    <Value name="3"><Value>City</Value></Value>
  </Data>
  <DataLocation>^MyApp.PatientD</DataLocation>
</Storage>

El global de datos podría verse así:

^MyApp.PatientD(1) = $LB("", "Smith, John", "123-45-6789", "Boston")
^MyApp.PatientD(2) = $LB("", "Jones, Mary", "987-65-4321", "Chicago")

Qué sucede cuando renombra una propiedad

Si renombra City a Location y recompila:

1. El compilador ve Location como una nueva propiedad y la asigna a la posición 4 2. La posición 3 (donde residen los datos de City) queda sin mapeo 3. Location lee de la posición 4, que está vacía para todas las filas existentes 4. Los datos antiguos de City en la posición 3 quedan efectivamente huérfanos

// ANTES del renombrado: City está en la posición 3
SET obj = ##class(MyApp.Patient).%OpenId(1)
WRITE obj.City  // "Boston" - lee de la posición 3

// DESPUÉS de renombrar City a Location y recompilar:
SET obj = ##class(MyApp.Patient).%OpenId(1)
WRITE obj.Location  // "" - lee de la posición 4 (¡vacía!)
// Los datos "Boston" aún existen en la posición 3 pero son inaccesibles

Enfoques seguros para renombrar

Opción 1: Editar la definición de almacenamiento - Actualizar manualmente el XML de almacenamiento para mapear Location a la posición 3 (donde residen los datos de City). Esto requiere editar el XML de la clase directamente.

Opción 2: Migración de datos - Escribir un script de migración que copie datos de la propiedad antigua a la nueva:

// Script de migración para renombrar City a Location
SET id = ""
FOR {
    SET id = $ORDER(^MyApp.PatientD(id))
    QUIT:id=""
    SET data = ^MyApp.PatientD(id)
    // Copiar posición 3 (antigua City) a posición 4 (nueva Location)
    SET $LIST(data, 4) = $LIST(data, 3)
    SET ^MyApp.PatientD(id) = data
}

Opción 3: Usar SqlFieldName - Si solo necesita cambiar el nombre de la columna SQL, mantenga el nombre de propiedad en ObjectScript y use la palabra clave SqlFieldName:

Property City As %String(MAXLEN = 50) [ SqlFieldName = Location ];
// ObjectScript usa obj.City, SQL usa la columna Location

Riesgos de cambiar tipo de dato

Cambiar el tipo de dato de una propiedad puede causar problemas con los datos existentes:

// Original: Property Age As %String;
// Datos almacenados: "twenty-five", "30", "unknown"

// Cambiado a: Property Age As %Integer;
// Ahora "twenty-five" y "unknown" fallan la validación
// Los datos de cadena existentes a nivel de global siguen ahí pero son inválidos

Al cambiar tipos de datos:

  • Ampliación (ej., %Integer a %String): Los valores de datos son compatibles, pero la colación del índice puede diferir%Integer usa por defecto la colación EXACT mientras que %String usa SQLUPPER, así que las propiedades indexadas pueden requerir una reconstrucción de índice después del cambio de tipo
  • Estrechamiento (ej., %String a %Integer): Arriesgado, los datos existentes pueden no cumplir
  • Completamente diferente (ej., %String a %Date): Requiere migración de datos

Enfoque más seguro vía SQL DDL: Usar ALTER TABLE ... ALTER COLUMN para cambiar el tipo de dato de una columna es frecuentemente la solución más segura y completa. Las operaciones SQL DDL manejan automáticamente las actualizaciones de almacenamiento, activan la invalidación de consultas en caché y revelan errores de validación — reduciendo el riesgo de desajustes de colación o entradas de índice huérfanas comparado con editar la definición de clase directamente.

-- Cambiar tipo de columna vía SQL (maneja almacenamiento + invalidación de caché)
ALTER TABLE MyApp.Patient ALTER COLUMN Age VARCHAR(10)

Referencias de Documentación

3. Purgar consultas en caché después de cambios de esquema

Puntos Clave

  • Consultas en caché: Los planes de consulta SQL se compilan y almacenan en caché para rendimiento; los cambios de esquema pueden invalidarlos
  • $SYSTEM.SQL.Purge(): Purga todas las consultas en caché en el namespace actual
  • Invalidación automática: Algunos cambios (como ALTER TABLE) invalidan automáticamente las consultas en caché relacionadas
  • Se necesita purga manual: Los cambios vía edición de definición de clase pueden no activar la invalidación automática
  • Impacto en rendimiento: La primera ejecución después de la purga es más lenta (recompilación); las ejecuciones subsiguientes usan la nueva caché
  • Portal de Administración: System Administration > SQL > Cached Queries para visualización y gestión

Notas Detalladas

Descripción general

Cuando una consulta SQL se ejecuta por primera vez (ya sea vía SQL Dinámico o SQL Embebido), InterSystems IRIS compila un plan de consulta y lo almacena en caché como una rutina optimizada. Esta consulta en caché se reutiliza en ejecuciones subsiguientes, proporcionando beneficios significativos de rendimiento. Sin embargo, cuando el esquema subyacente cambia (nuevos índices, columnas modificadas, estadísticas cambiadas), el plan en caché puede ser subóptimo o incluso incorrecto. Purgar fuerza la recompilación con el esquema actualizado.

Cuándo purgar consultas en caché

1. Después de agregar o eliminar índices: El optimizador de consultas puede elegir diferentes estrategias de acceso 2. Después de ejecutar TUNE TABLE (solo si los valores no cambiaron): TUNE TABLE recompila automáticamente las consultas en caché cuando las estadísticas cambian, pero si no cambiaron valores, las consultas en caché se dejan como están 3. Después de cambiar tipos de datos de propiedades: Los cambios de tipo de columna pueden afectar la validez del plan de consulta 4. Después de modificar el almacenamiento de clase: Los cambios directos en la definición de almacenamiento requieren regeneración del plan de consulta 5. Después de actualizar InterSystems IRIS: Las nuevas mejoras del optimizador pueden producir mejores planes

Métodos de purga

// Purgar TODAS las consultas en caché en el namespace actual
DO $SYSTEM.SQL.Purge()

// Purgar consultas en caché para una tabla/clase específica
DO $SYSTEM.SQL.PurgeForTable("MyApp.Patient")

// Purgar con más control vía utilidades %SYS.PTools
DO $SYSTEM.SQL.Purge(.count)
WRITE "Purged ", count, " cached queries", !
-- Enfoque SQL
PURGE CACHED QUERIES

Vía Portal de Administración

Navegar a System Administration > SQL y usar la pestaña Cached Queries para:

  • Ver todas las consultas en caché con su texto SQL y estadísticas de ejecución
  • Eliminar selectivamente consultas en caché individuales
  • Ver cuándo cada consulta fue compilada y ejecutada por última vez
  • Ver el plan de consulta para cada consulta en caché

Invalidación automática vs manual

La invalidación automática ocurre cuando:

  • Se usa ALTER TABLE para modificar una estructura de tabla
  • Se usa DROP INDEX o CREATE INDEX vía SQL DDL
  • Se ejecuta TUNE TABLE (recompila automáticamente las consultas en caché para la tabla cuando las estadísticas cambian)
  • Ciertos cambios a nivel de sistema que activan la recompilación de clase

Se necesita invalidación manual cuando:

  • Se editan definiciones de clase directamente en Studio/VS Code y se compilan
  • Se realizan operaciones masivas de datos que cambian la distribución de datos
  • Se hacen cambios en la definición de almacenamiento
// Mejor práctica después de cambios de esquema vía edición de clase:
// 1. Compilar la clase
DO $SYSTEM.OBJ.Compile("MyApp.Patient", "ck")

// 2. Reconstruir índices si se agregaron nuevos
DO ##class(MyApp.Patient).%BuildIndices()

// 3. Actualizar estadísticas de tabla (también recompila consultas en caché para esta tabla)
DO $SYSTEM.SQL.TuneTable("MyApp.Patient")

// 4. Si las ediciones de clase no activaron invalidación automática de caché,
//    purgar manualmente las consultas en caché para esta tabla
DO $SYSTEM.SQL.PurgeForTable("MyApp.Patient")

Planes congelados

Los planes congelados permiten bloquear un plan de consulta para que se retenga a través de compilaciones, TUNE TABLE e incluso actualizaciones del sistema. Un plan congelado no se recompila cuando cambios de esquema o TUNE TABLE normalmente activarían la recompilación.

¿Por qué congelar un plan?

  • Proteger un plan de consulta bien ajustado de ser reemplazado por un nuevo plan no probado después de cambios de esquema o actualizaciones de software
  • Asegurar rendimiento predecible de consultas en producción

Dos estrategias para usar planes congelados:

  • Optimista: Asumir que los cambios mejorarán el rendimiento. Congelar el plan actual como respaldo, descongelar, hacer el cambio, comparar. Si empeora, restaurar el plan congelado del respaldo.
  • Pesimista: Asumir que los cambios no mejorarán el rendimiento. Congelar el plan actual, hacer el cambio, re-ejecutar la consulta con %NOFPLAN (ignora el plan congelado) para comparar. Si no hay mejora, mantener el plan congelado.

Congelar y descongelar planes:

-- Congelar/descongelar todos los planes para una tabla
FREEZE PLANS FOR TABLE Sample.Person
UNFREEZE PLANS FOR TABLE Sample.Person

-- Congelar/descongelar todos los planes en un esquema o namespace
FREEZE PLANS FOR SCHEMA Sample
FREEZE PLANS

-- Ignorar un plan congelado para pruebas (estrategia pesimista)
SELECT %NOFPLAN * FROM Sample.Person WHERE Name = 'Smith'
// Interfaz programática vía $SYSTEM.SQL.Statement
DO $SYSTEM.SQL.Statement.FreezeRelation("Sample.Person")
DO $SYSTEM.SQL.Statement.UnfreezeRelation("Sample.Person")

// Por sentencia (requiere el hash de la sentencia de INFORMATION_SCHEMA.STATEMENTS)
DO $SYSTEM.SQL.Statement.FreezeStatement(hash)

Puede verificar el estado de congelación consultando INFORMATION_SCHEMA.STATEMENTS para la columna Frozen: Descongelado (0), Congelado/Explícito (1), Congelado/Actualización (2), o Descongelado/Paralelo (3). También puede usar EXPLAIN en una consulta específica.

Importante: Los planes congelados pueden convertirse en un problema después de la evolución del esquema. Si agrega un nuevo índice que mejoraría significativamente una consulta, el plan congelado no lo usará. Revise los planes congelados después de cambios importantes de esquema.

Implicaciones de rendimiento

  • Primera ejecución después de purga: Ligeramente más lenta ya que la consulta se recompila
  • Ejecuciones subsiguientes: Usan el nuevo plan optimizado, potencialmente mucho más rápidas si hay nuevos índices disponibles
  • Purga innecesaria: Evitar purgar en producción sin razón, ya que causa una caída breve de rendimiento
  • Purga dirigida: PurgeForTable() es preferible sobre Purge() en producción para minimizar el impacto
  • Planes congelados: No se ven afectados por purgas o TUNE TABLE — deben descongelarse explícitamente para beneficiarse de nueva optimización de consultas

Referencias de Documentación

Resumen de Preparación para el Examen

Conceptos críticos a dominar:

  1. Construcción de índices: Agregar una definición de índice y compilar NO lo pobla; debe llamar a `%BuildIndices()` o `BUILD INDEX`
  2. Mapeo de almacenamiento: Las propiedades se mapean a posiciones específicas en el global de almacenamiento; renombrar rompe el mapeo
  3. Pérdida de datos al renombrar: Renombrar una propiedad sin actualizar el almacenamiento o migrar datos hace inaccesibles los datos existentes
  4. SqlFieldName: Forma segura de cambiar el nombre de columna SQL sin afectar ObjectScript ni el almacenamiento
  5. Purga de consultas en caché: Los cambios de esquema pueden requerir `$SYSTEM.SQL.Purge()` para forzar la reoptimización; TUNE TABLE lo maneja automáticamente
  6. Planes congelados: Se retienen a través de compilaciones y TUNE TABLE; deben descongelarse explícitamente para beneficiarse de cambios de esquema
  7. Cambios de tipo de dato: La ampliación es generalmente segura para datos pero puede requerir reconstrucción de índice por diferencias de colación; el estrechamiento o cambio completo requiere migración; `ALTER TABLE ... ALTER COLUMN` es el enfoque más seguro y completo

Escenarios comunes de examen:

  • Explicar por qué un índice recién agregado no devuelve resultados hasta que se llama a `%BuildIndices()`
  • Identificar el riesgo de pérdida de datos cuando se renombra una propiedad y se recompila la clase
  • Elegir el enfoque correcto para renombrar una columna SQL de forma segura (SqlFieldName vs migración de datos)
  • Determinar cuándo las consultas en caché necesitan purgarse después de varios cambios de esquema
  • Ordenar los pasos para un cambio de esquema completo: compilar, construir índices, tune table, purgar consultas
  • Reconocer que ALTER TABLE activa invalidación automática de caché pero la edición de clase no

Recomendaciones de práctica:

  • Crear una clase con datos, agregar un índice, compilar y observar que las consultas no lo usan hasta que se construya
  • Renombrar una propiedad, recompilar y observar la pérdida de datos; luego inspeccionar la definición de almacenamiento
  • Usar `SqlFieldName` para cambiar un nombre de columna SQL y verificar que el acceso por ObjectScript y SQL aún funcione
  • Ejecutar una consulta, verificar la consulta en caché en el Portal de Administración, purgarla y observar la recompilación
  • Practicar el flujo completo de cambio de esquema: editar clase, compilar, construir índices, tune table, purgar consultas
  • Examinar el XML de almacenamiento antes y después de cambios de propiedad para entender el mapeo de posiciones

Report an Issue