Rendimiento

¿Qué pasa cuando ejecutamos una consulta en SQL Server?

Para ojos inexpertos puede parecer trivial. Escribimos una SELECT, pulsamos Ejecutar y SQL Server nos devuelve filas, o al menos devuelve un error con una explicación bastante agresiva de por qué no puede ejecutar la consulta. Esa visión sirve para un usuario primerizo, pero se queda ridículamente corta en cuanto queremos entender rendimiento, bloqueos, estimaciones o por qué dos consultas casi idénticas terminan con comportamientos opuestos.

La realidad es menos mágica y mucho más de ingeniería de esa que nos gusta en este blog. Cuando ejecutamos una consulta, el motor no “lee tablas” sin más. Interpreta el lote, valida sintaxis y semántica, resuelve objetos y permisos, decide si reutiliza un plan o compila uno nuevo, estima cardinalidades, elige operadores físicos, reserva memoria, accede a páginas en memoria o disco, coordina concurrencia y devuelve resultados al cliente. Y todo eso ocurre en milisegundos cuando las cosas van bien. Cuando van mal, nos regala una tarde entera de entretenimiento buscando un problema que no vemos.

¿Parece complicado verdad? Pues espera que te lo explico con más detalle. Pero antes conviene fijar una idea: SQL es declarativo. Nosotros describimos el resultado que queremos, no la secuencia exacta de pasos para obtenerlo. Esa diferencia, que parece un detalle académico, es justo la que separa una explicación decente de un mito de pasillo.

El lote, la sesión y el contexto

Lo primero que recibe SQL Server no es una consulta aislada en una urna de cristal. Recibe un lote (batch) T-SQL enviado por una aplicación cliente, normalmente SSMS, un programa, un driver ODBC/OLE DB o cualquier otro consumidor que hable TDS. Ese lote viaja con un contexto de sesión que importa más de lo que muchos quisieran admitir. Incluye, entre otras cosas, la base de datos actual, el usuario, configuraciones regionales y de idioma, opciones SET, nivel de aislamiento, estado de la transacción y otros detalles que suelen parecer inocentes hasta que rompen la reutilización de planes.

Eso significa que el mismo texto no siempre equivale a la misma ejecución. Dos sesiones con distintas opciones SET pueden acabar con planes diferentes. Dos usuarios con permisos distintos pueden provocar resoluciones distintas. Incluso el contexto transaccional puede condicionar bloqueos, versionado y comportamiento del optimizador. Antes de tocar una sola página de datos, SQL Server ya está operando dentro de unas condiciones concretas. Esas condiciones cuentan luego cuando alguien jura que “la query es la misma” pero no va igual.

Antes de hablar de planes y operadores conviene pasar por la primera puerta del proceso. No porque sea la más compleja, sino porque mucha gente confunde “la consulta compila” con “la consulta está bien”. Son cosas muy distintas, por desgracia.

Parsear no es ejecutar

Una vez que tenemos el lote, la primera fase interna sería el parsing. El motor tokeniza el texto, valida la sintaxis y construye una representación inicial de la consulta. Aquí se detectan errores de sintaxis de esos que todos cometemos cuando estamos cansados: paréntesis mal cerrados, comas absurdas, palabras clave fuera de sitio o expresiones que no encajan en el lenguaje. Superar esta fase solo significa que escribimos algo comprensible para el parser. No significa que tenga sentido desde el punto de vista del motor ni, desde luego, que sea una buena idea.

Después entra el algebrizer, a veces llamado binder. Aquí SQL Server resuelve el significado real de lo que hemos escrito. Tiene que averiguar a qué tabla pertenece cada columna, expandir el *, validar alias, comprobar agregaciones, deducir tipos de datos, aplicar conversiones implícitas y verificar permisos. Es la fase en la que dejamos de pensar en cosas estéticas y empiezan los errores con sustancia: columnas inexistentes, referencias ambiguas, funciones mal usadas, objetos inaccesibles o expresiones incompatibles. También en este punto se van a comprobar los permisos del usuario sobre todos los objetos necesarios. 

Además, el motor construye una representación lógica de la consulta que ya no es puro texto, sino álgebra relacional. En otras palabras, transforma la sentencia en operaciones como proyección, selección, join o agregación. Todavía no ha decidido cómo ejecutar nada físicamente, pero ya sabe qué significa cada pieza y qué resultado lógico perseguimos. También aquí puede aparecer la parametrización automática, esa ayuda que a veces mejora la reutilización de planes y otras veces solo prepara el terreno para un precioso caso de parameter sniffing. La estabilidad nunca ha sido un valor central en la vida del DBA.

A partir de aquí llega una bifurcación clave. Si ya existe un plan reutilizable, SQL Server puede ahorrar mucho trabajo. Si no, toca compilar. Y ahí empieza la parte que realmente decide si una consulta va a tardar milisegundos o va a convertir TempDB en una zona de conflicto.

Caché de planes, optimización y estimaciones

Con la consulta ya entendida, SQL Server mira la caché de planes. Para esto, se usa un hash del texto de la consulta. Si encuentra un plan válido para ese hash y ese contexto, puede reutilizarlo. Esto es fundamental porque compilar cuesta CPU y tiempo. En cargas OLTP, donde una misma consulta se ejecuta miles de veces, la reutilización es parte del rendimiento.

Si no hay plan reutilizable, o si es necesaria una recompilación por cambios en estadísticas, metadatos o condiciones de ejecución, entra el optimizador. Aquí conviene matar un mito viejo: el optimizador no busca el plan perfecto, porque eso exigiría explorar demasiadas combinaciones. Busca un plan suficientemente bueno según su modelo de costes y dentro de un presupuesto finito de tiempo. Lo bastante listo para rendir, lo bastante práctico para no pasarse más tiempo pensando que ejecutando.

Para tomar decisiones usa estadísticas, histogramas, densidades, índices disponibles, restricciones, cardinalidades estimadas y reglas de transformación. Puede reordenar joins, empujar predicados, simplificar expresiones, eliminar operaciones redundantes y elegir entre Nested Loops, Hash Match o Merge Join, entre acceso por seek, scan o lookup, entre ejecución serie o paralela, y entre distintos tamaños de memory grant. Gran parte del rendimiento nace aquí, mucho antes de la primera lectura física.

El mito del orden lógico

Aquí encaja una distinción importante que suele explicarse mal. El famoso orden lógico de una consulta, ese FROM/JOIN, WHERE, GROUP BY, HAVING, SELECT, ORDER BY, sirve para entender el significado del lenguaje. Es útil para razonar sobre el resultado, no para describir el recorrido físico real. SQL Server no ejecuta la consulta siguiendo ese diagrama como quien monta un mueble sueco de esa famosa casa de muebles que estás pensando. El motor puede empujar filtros hacia abajo, reordenar joins y empezar por el conjunto más selectivo si las estimaciones y los índices lo hacen recomendable.

Eso significa que en una consulta entre Clientes y Pedidos, con un filtro muy selectivo sobre Clientes, el plan puede localizar primero esos pocos clientes con un Index Seek y solo después unir con Pedidos. Lógicamente seguimos teniendo un JOIN y un WHERE, pero físicamente el motor ha reorganizado el trabajo para reducir filas cuanto antes. No es una contradicción. Es exactamente lo que esperamos de un optimizador de consultas. Confundir orden lógico con plan físico es una de esas costumbres que sobreviven porque caben bien en una infografía simplona hecha con IA por un gurú de LinkedIn (no del trabajo real).

El problema, claro, es que el optimizador decide en función de estimaciones. Si estima bien, suele elegir razonablemente bien. Si estima mal, el plan puede ser impecable en apariencia y terrible en producción. Un Nested Loops sobre millones de filas, un memory grant insuficiente que termina en spill a TempDB o un paralelismo absurdo no suelen venir de la maldad del motor, sino de una mala predicción. Las estadísticas no son decoración. Son una de las pocas cosas que separan la ingeniería de la adivinación y las apuestas.

Una vez elegido el plan, empieza la ejecución de verdad. Y aquí también conviene aparcar otra simplificación: el plan no se ejecuta como solemos leer el dibujo en SSMS.

Del plan al motor de ejecución

Siempre te han dicho que los planes de ejecucion se leen de derecha a izquierda de abajo a arriba. Y es verdad hasta cierto punto. El motor de ejecución trabaja con un modelo de iteradores, normalmente en modo pull. Un operador superior pide una fila al operador inferior, que a su vez la pide al siguiente, y así sucesivamente. Ese “dame la siguiente fila” parece una trivialidad, pero explica muchas cosas. Explica por qué algunos operadores pueden empezar a devolver resultados casi de inmediato y otros bloquean hasta haber consumido buena parte de la entrada.

Un Top puede cortar el trabajo muy pronto. Un Sort necesita reunir todos los datos y ordenar antes de darle nada al siguiente operador. Un Hash Match puede requerir construir primero una tabla hash en memoria. Un agregado puede operar en streaming o bloquear, según el operador concreto y el plan. Y así con muchos operadores. Además, desde SQL Server 2012 en adelante y con más presencia en versiones modernas, algunas consultas pueden ejecutarse en modo batch, no fila a fila, con beneficios importantes en CPU y throughput. No todo circula del mismo modo por el plan, y esa diferencia se nota.

También aquí aparecen efectos derivados del memory grant. Si el optimizador concede memoria suficiente, un Sort o un Hash Match viven tranquilos. Si se queda corto, llegan los spills a TempDB y el plan empieza a pagar intereses. Y TempDB, como sabemos, siempre está encantada de recibir problemas ajenos en horario laboral.

Hasta ahora hemos hablado del motor relacional. Pero las filas no salen de una abstracción platónica. Alguien tiene que ir a buscarlas. Ahí entra el storage engine, que es donde las cosas pueden torcerse.

Buffer pool, páginas e I/O real

Cuando el plan necesita datos, el storage engine localiza las páginas necesarias. Si esas páginas ya están en memoria, dentro del buffer pool, puede trabajar con ellas inmediatamente. Si no lo están, debe solicitarlas al subsistema de almacenamiento para cargarlas en el buffer pool y, a partir de ahí, continuar la ejecución sobre esas páginas ya residentes en memoria. Esto es clave, el motor no va a pasar datos del disco duro a los resultados de tu consulta, siempre pasan por el buffer pool de la memoria RAM. Y ahí aparecen los costes reales de I/O, los tiempos de espera y toda la artillería interna que rara vez se explican bien, navegación por B-trees, páginas hoja, IAMs, read-ahead, latches y comprobaciones varias.

Un Index Seek no significa “esto será rápido” por definición. Significa que el motor puede navegar por la estructura del índice hasta un rango concreto. Si luego necesita hacer miles de lookups para recuperar columnas no cubiertas por el índice, el coste puede dispararse. Del mismo modo, un Scan no siempre es malo. A veces es la opción correcta si el volumen a leer es grande o si la selectividad no compensa una ruta aparentemente más elegante. El problema no es el nombre del operador, sino el contexto, las filas reales y el trabajo asociado.

La presencia o ausencia de datos en memoria también cambia por completo la película. Una consulta sobre páginas calientes puede correr como un tiro y la misma, minutos después, tener que pelear con lecturas físicas porque la caché cambió. Por eso las pruebas “me ha ido rápido una vez en mi portátil” tienen el valor técnico que suelen tener, muy poco.

Si la consulta solo leyera datos ya tendríamos bastante material, pero muchas veces también modifica. Y cuando modifica, el motor deja de ser solo lector y pasa a garantizar consistencia, aislamiento y durabilidad. Es decir, empieza la parte donde un UPDATE inocente se convierte en un pequeño proyecto de infraestructura.

Bloqueos, versionado y transaction log

Durante la ejecución, SQL Server coordina concurrencia. Adquiere bloqueos compartidos, exclusivos o de actualización, puede escalar bloqueos si la presión lo justifica y, si la base trabaja con READ_COMMITTED_SNAPSHOT o SNAPSHOT, puede recurrir al versionado de filas. Eso significa que una consulta puede tardar por lo que calcula, pero también por lo que espera. Esperas por locks, latches, I/O, memoria o CPU convierten muchas consultas lentas en consultas bloqueadas, contenidas o asfixiadas por el entorno.

Cuando hay escrituras, además, entra en juego el transaction log. Un INSERT, un UPDATE o un DELETE no consisten en cambiar valores y seguir adelante. Hay que registrar la operación, mantener índices, validar restricciones, comprobar claves externas, ejecutar triggers si existen y asegurar propiedades ACID. El COMMIT no es un adorno en estos casos. Es el momento en el que la durabilidad exige que el log esté donde tiene que estar. Si el subsistema de almacenamiento del log no acompaña, lo notaremos muy deprisa.

Por eso dos operaciones aparentemente modestas pueden tener costes muy distintos. Una actualización sobre una tabla con varios índices, triggers y claves foráneas puede mover bastante más trabajo interno del que sugiere una línea de T-SQL. Y no, “solo eran 500 filas” no impresiona a nadie cuando cada fila arrastra media docena de tablas referenciadas.

Todo esto nos lleva a la pregunta inevitable. Si la consulta es la misma, ¿por qué unas veces rinde bien y otras no? La respuesta, para fastidio de quienes esperan una causa única y limpia, es que la ejecución nunca depende solo del texto.

¿Por qué la misma query no siempre corre igual?

La misma consulta puede reutilizar un plan excelente o uno desastroso. Puede encontrar datos en memoria o tener que leerlos de disco, ejecutarse con poca concurrencia o dentro de una tormenta de bloqueos, recibir una estimación razonable o una completamente deformada por estadísticas antiguas, parámetros sesgados o distribuciones raras. También puede obtener un memory grant suficiente o terminar desbordando a TempDB desde el minuto uno.

Por eso el análisis serio siempre se apoya en evidencias: plan real, filas estimadas frente a reales, lecturas lógicas, tiempo de CPU, waits, uso de memoria, spills, recompilaciones y contexto de ejecución. Como ya hemos comentado otras veces al hablar de planes de ejecución, estadísticas o parameter sniffing, el texto T-SQL es solo una parte del problema. Importa, claro. Pero el comportamiento real nace de la combinación entre consulta, datos, estado del servidor y decisiones del optimizador.

Cuando entendemos ese recorrido dejamos de hablar de “consultas lentas” como si fueran criaturas con voluntad propia. Empezamos a hablar de ejecuciones concretas, planes concretos y cuellos de botella concretos. Y a partir de ahí ya se puede trabajar de verdad, que siempre resulta menos cómodo que opinar, pero da mejores resultados.

Conclusión

Cuando ejecutamos una consulta en SQL Server no ocurre magia. Ocurre una cadena compleja de análisis, resolución, optimización, acceso a datos, control de concurrencia y devolución de resultados. El motor no sigue el orden visual con el que leemos la sentencia, ni toma decisiones por estética, ni premia nuestras buenas intenciones. Hace estimaciones, aplica reglas, reutiliza lo que puede y ejecuta bajo las condiciones reales del sistema.

Entender ese proceso cambia por completo la forma de diagnosticar rendimiento. Dejamos de mirar solo la consulta y empezamos a mirar el plan, las estadísticas, la memoria, el log, los bloqueos y las esperas. Ahí es donde se separa la teoría útil de una infografía bonita. Y también donde empieza el trabajo del DBA de verdad.

Si tenéis alguna duda o sugerencia, podéis dejarla en Twitter, por mail o dejarnos un mensaje en los comentarios. Y recuerda que también tenemos un grupo de LinkedIn y un canal de YouTube a los que te puede unir. ¡Hasta la próxima!

Publicado por Roberto Carrancio en Cloud, Rendimiento, SQL Server, 0 comentarios
DiskANN: búsqueda vectorial en SQL Server 2025

DiskANN: búsqueda vectorial en SQL Server 2025

SQL Server 2025 ya no mira los vectores desde la barrera. Tenemos tipo vector, búsqueda exacta con VECTOR_DISTANCE y búsqueda aproximada con CREATE VECTOR INDEX y VECTOR_SEARCH, todo ello sobre una implementación que Microsoft vincula de forma explícita con DiskANN. No es una curiosidad de laboratorio ni una feature ornamental para demos con palabras como “semántico” y “copilot” repetidas ocho veces por minuto. Es una apuesta técnica real, todavía con partes en preview, pero real.

Ahora bien, una cosa es que el motor ya hable vectores y otra muy distinta que haya dejado de ser SQL Server. Cuando mezclamos embeddings con filtros relacionales, claves, joins y diseño físico serio, aparecen las costuras. Y eso es precisamente lo interesante. Porque el problema no está en que exista DiskANN, sino en cómo aterriza dentro de un motor que no nació para resolver búsquedas de proximidad semántica sobre datos de negocio.

Antes de entrar en el índice, conviene situar el problema. No para repetir una introducción de manual sobre IA, sino para dejar claro por qué esta funcionalidad no se comporta como un índice relacional clásico con mejor prensa.

Vectores, embeddings y kNN: no buscamos igualdad, buscamos cercanía

Un vector es una secuencia ordenada de números. En SQL Server se almacena con el tipo vector, que internamente usa un formato binario optimizado, aunque se presente como array JSON por comodidad. Cada dimensión ocupa 4 bytes en float32 y SQL Server 2025 añade soporte preview para float16, lo que abre una vía interesante para reducir espacio cuando la precisión lo permita.

Un embedding es un caso particular de vector: una representación numérica que intenta capturar características relevantes de un texto, una imagen o cualquier otro objeto. La consecuencia práctica es que dejamos de preguntar “¿es igual?” o “¿está entre A y B?” y pasamos a preguntar “¿qué está cerca de esto?”. Ese cambio parece pequeño hasta que toca ejecutar consultas. Entonces deja de ser filosofía y pasa a ser coste.

Cuando resolvemos el problema de forma exacta, hacemos un kNN exacto: calculamos distancia contra todos los candidatos y nos quedamos con los más próximos. Microsoft lo describe así en su documentación y, además, deja una recomendación bastante útil: la búsqueda exacta sigue teniendo sentido cuando el conjunto efectivo a revisar es pequeño, con una orientación general de unas 50.000 filas o menos después de aplicar predicados. Ese matiz importa mucho, porque evita uno de los errores más comunes: asumir que lo aproximado siempre es mejor solo porque tiene nombre moderno.

Hasta aquí el problema. La siguiente pregunta es evidente: cuando el volumen crece, ¿cómo evitamos que cada consulta termine pareciendo una inspección completa del universo conocido?

DiskANN: el “cómo” de Microsoft para escalar búsqueda vectorial

La respuesta de Microsoft para búsqueda aproximada es DiskANN. Conviene decirlo bien para no arrastrar una imagen mental equivocada: DiskANN no ordena vectores como si fuera un B-tree extraño. Es una familia de algoritmos de ANNS basada en grafos, pensada para combinar precisión razonable, baja latencia y uso eficiente de memoria y SSD. Microsoft Research describe Project Akupara precisamente como una línea de trabajo orientada a escalar ANNS para búsqueda y recomendación a gran escala, y la publicación original de 2019 presenta DiskANN como un sistema capaz de indexar y buscar mil millones de puntos en una sola máquina usando 64 GB de RAM y SSD.

La gracia de ese enfoque no es solo académica. Microsoft ha ido consolidando DiskANN como base tecnológica en su ecosistema, y SQL Server 2025 lo expone directamente en CREATE VECTOR INDEX con TYPE = ‘DISKANN’. En otras palabras, no estamos ante una abstracción vaporosa; el motor te deja tocarla. Y eso siempre es más útil que una promesa de roadmap adornada con iconos de nube.

Pero aquí aparece el primer matiz importante para cualquiera que haya peleado con diseño físico de tablas. Que SQL Server pueda almacenar vectores no significa que debamos meterlos sin pensar en la tabla principal, como si una fila de negocio de 100 bytes y un embedding de 1.536 dimensiones fuesen vecinos naturales. No lo son.

La decisión de modelado que más sentido tiene

Mi recomendación sigue siendo separar los embeddings en una tabla satélite 1:1 respecto a la tabla relacional principal. No por postureo arquitectónico, sino porque el coste físico de mezclar ambas cosas en la misma fila es demasiado alto para fingir que no existe. Microsoft lo explica con un ejemplo muy claro en su FAQ: una página de datos admite hasta 8.060 bytes (8 Kb) y un vector de 1.024 dimensiones en float32 ocupa 4.104 bytes contando cabecera, lo que ya limita a un solo vector por página. Si subimos dimensiones, el efecto no mejora precisamente.

Eso no significa solo “más fragmentación”, que es la forma rápida de contarlo. Significa también menos densidad de página, más I/O, peor aprovechamiento de caché y más penalización para accesos relacionales que no necesitan tocar el embedding. La guía de diseño de índices de SQL Server insiste en mantener los índices, en especial el clustered, lo más estrechos posible. Y aquí conviene recordar algo bastante básico, si la tabla principal sirve para OLTP o para consultas relacionales normales, meter una columna vectorial enorme ahí dentro porque “ya la usamos en algunas búsquedas” es una forma muy creativa de castigar todo lo demás.

Hay además un segundo argumento, todavía más relevante porque los índices vectoriales aproximados y VECTOR_SEARCH siguen en preview dentro de SQL Server 2025. La tabla que tiene un vector index debe tener una PK clustered entera de una sola columna y, mientras el índice exista, esa tabla queda de solo lectura. En SQL Server 2025 no está disponible ALLOW_STALE_VECTOR_INDEX, así que si cambian los datos toca recrear el índice. Separar embeddings en una tabla 1:1 aísla esa rigidez donde corresponde y evita congelar la tabla relacional principal.

De hecho, el propio material de Microsoft ya deja entrever ese patrón. En los ejemplos de VECTOR_SEARCH aparece una tabla llamada wikipedia_articles_embeddings, lo que sugiere un diseño separado para los vectores frente al contenido relacional o documental. No es una norma escrita en piedra, pero tampoco parece casualidad.

Una vez aceptado ese modelo, la consulta cambia. Y ahí es donde aparece la parte realmente delicada: el JOIN mejora el diseño físico, pero no convierte mágicamente la búsqueda vectorial en algo filter-aware.

¿Qué cambia en la consulta cuando el embedding vive fuera?

Con un diseño 1:1, lo lógico es que VECTOR_SEARCH se ejecute sobre la tabla de embeddings y luego se haga JOIN con la tabla relacional para recuperar atributos de negocio y aplicar filtros. Ese patrón encaja perfectamente con el estado actual del producto, porque VECTOR_SEARCH solo admite tablas base; no puede apuntar a una vista ni a una tabla temporal intermedia ya filtrada. Además, si encuentra un índice ANN compatible en esa columna y con la misma métrica, lo usa; si no, vuelve a kNN.

La consulta natural sería algo así:

Esto está bien desde el punto de vista del modelo físico. La tabla principal sigue estrecha, el índice vectorial vive donde debe y el JOIN 1:1, sobre una clave entera, normalmente será barato. Lo que no cambia es el orden lógico de la operación ANN respecto a los predicados de negocio. Y ahí vuelve a entrar el jarro de agua fría.

El JOIN no arregla el post-filter: solo mueve la frontera

La documentación de VECTOR_SEARCH es bastante clara, la búsqueda vectorial ocurre antes de aplicar cualquier predicado, y los filtros adicionales se evalúan después de devolver los vecinos más similares. Microsoft lo llama post-filter only. Si pides TOP_N = 10 y luego filtras por TenantId o IsActive, puedes acabar con menos de 10 filas o incluso con ninguna. Eso vale igual si el filtro cae sobre la misma tabla que contiene el embedding o si llega desde una tabla relacionada mediante JOIN. El JOIN no cambia esa semántica.

Eso significa que separar embeddings sí mejora el diseño físico, pero no resuelve la principal limitación actual del ANN en SQL Server 2025. El trabajo más caro sigue siendo la navegación del índice DiskANN sobre el conjunto indexado. Si después un filtro de negocio es muy selectivo, la forma de compensarlo suele ser sobremuestrear, subiendo TOP_N para tener suficientes candidatos válidos tras el JOIN y el WHERE. Y cuanto más selectivo sea el filtro, más se erosiona la ventaja del índice aproximado.

Aquí conviene evitar una confusión habitual. El problema no es que el JOIN sea especialmente caro. En una relación 1:1 bien indexada, no suele serlo. El problema es que el filtro relacional llega tarde para ayudar a la fase ANN. Y eso no lo arregla un buen modelo lógico, porque es una consecuencia de cómo está implementado el acceso vectorial hoy en el motor.

La pregunta entonces ya no es si separar la tabla, que para mí sí, sino cuándo conviene dejar de insistir con ANN y volver, sin complejos, a la búsqueda exacta.

¿Por qué no puedes filtrar antes sin romper algo?

La base técnica está bien documentada en la investigación de Microsoft sobre Filtered-DiskANN. El problema de los filtros en ANNS no se resuelve bien tocando solo la fase de búsqueda. El paper critica precisamente el enfoque de postprocesado, porque para filtros de baja especificidad puede obligar a recuperar muchísimos candidatos antes de encontrar uno que cumpla el predicado. Pero también deja claro que la solución trivial de “un índice por filtro” no escala.

La razón de fondo es que un índice tipo DiskANN es un grafo global construido sobre todos los vectores. Durante la búsqueda, algunos nodos no son resultados finales interesantes, pero sí son cruciales como puentes para navegar hacia la zona correcta del espacio vectorial. Si yo aplico un filtro arbitrario antes de buscar y elimino parte de esos nodos, puedo romper la navegabilidad efectiva del grafo. Dicho de otra manera, el subconjunto filtrado no conserva necesariamente las propiedades del índice original. Y eso afecta al recall, al coste o a ambas cosas.

Por eso filtered ANN de verdad no consiste en “empujar el WHERE hacia abajo” y quedarse tan ancho. Requiere índices diseñados con consciencia de filtro, estrategias de construcción distintas o, al menos, estructuras auxiliares que permitan no destrozar el recorrido. Ahí es donde la investigación va por delante de la implementación actual del motor. Y sinceramente, mejor admitirlo que fingir que un predicado sobre TenantId va a integrarse solo por buena voluntad. También podríamos confiar en MERGE en producción, pero cada uno gestiona sus traumas como puede.

A partir de aquí, la pregunta útil deja de ser “siempre ANN o siempre exacto” y pasa a ser mucho más adulta: “¿cuándo me compensa cada enfoque en SQL Server 2025?”.

¿Cuándo DiskANN tiene sentido y cuándo es mejor volver a VECTOR_DISTANCE?

Si el corpus es grande y los filtros relacionales son débiles, VECTOR_SEARCH con DiskANN es la opción natural. Para eso existe, para reducir latencia y coste frente a un barrido completo. Pero si el patrón real de consulta siempre filtra fuerte por cliente, estado, región o cualquier dimensión de negocio que reduzca mucho el conjunto efectivo, entonces la recomendación oficial de Microsoft cobra todo el sentido del mundo: cuando acabas buscando sobre 50.000 vectores o menos, la búsqueda exacta con VECTOR_DISTANCE vuelve a ser una alternativa muy seria.

Y aquí, precisamente, la separación 1:1 juega a favor. Puedes filtrar primero en la tabla relacional, quedarte con el subconjunto de negocio que realmente importa, hacer el JOIN hacia la tabla de embeddings y calcular distancias solo sobre esos candidatos. Es una estrategia mucho más alineada con el optimizador relacional clásico y, además, evita pedirle a un índice ANN global que resuelva con elegancia algo para lo que no ha sido diseñado. Sí, podemos pedirle heroicidades a cualquier índice. También podemos confiar en que un SELECT * no acabará mal. Pero a cierta altura de la película conviene distinguir entre esperanza y diseño.

Conclusión

SQL Server 2025 ha dado un paso importante con búsqueda vectorial nativa y DiskANN como base técnica para ANN. Eso merece atención, pruebas serias y bastante menos folklore del habitual. Pero también conviene poner cada pieza en su sitio. El mejor diseño, en la práctica, pasa por separar embeddings en una tabla 1:1, mantener la tabla relacional principal estrecha y aislar ahí las restricciones operativas del vector index. Eso es buena ingeniería de datos, no una preferencia estética.

Ahora bien, ese diseño no convierte VECTOR_SEARCH en filtered ANN. Hoy seguimos teniendo ANN global más post-filter relacional, aunque el filtro llegue por JOIN. Esa diferencia condiciona rendimiento, recall y criterio de uso. Entenderla no es un detalle menor. Es la diferencia entre usar la novedad con cabeza o descubrir, demasiado tarde, que el motor no estaba haciendo exactamente lo que tú dabas por supuesto. Y en bases de datos, como sabemos todos, dar cosas por supuestas suele salir carísimo.

Si tenéis alguna duda o sugerencia, podéis dejarla en Twitter, por mail o dejarnos un mensaje en los comentarios. Y recuerda que también tenemos un grupo de LinkedIn y un canal de YouTube a los que te puede unir. ¡Hasta la próxima!

Publicado por Roberto Carrancio en Cloud, Índices, Rendimiento, SQL Server, 0 comentarios

SQL Server sobre Hyper-V: Buenas prácticas

Durante años, mencionar Hyper-V en una conversación sobre infraestructura provocaba cierto escepticismo técnico. Los más veteranos recordamos entornos de laboratorio donde el rendimiento era, siendo amables, discutible. Pero como casi todo en tecnología, lo que ayer parecía limitado, hoy puede ser perfectamente válido… si se configura bien.

Con la adquisición de VMware por Broadcom y los movimientos corporativos que han seguido (licencias perpetuas eliminadas, precios en alza y suscripciones forzadas), muchas organizaciones están redescubriendo Hyper-V como una alternativa seria. Y no hablamos solo de pequeñas empresas. Desde entornos de desarrollo hasta producción crítica, Hyper-V vuelve a estar sobre la mesa.

Eso sí, SQL Server no se virtualiza bien «de fábrica», da igual el hipervisor. Hay que saber lo que se está haciendo. Ya vimos en el pasado artículo cómo hacerlo correctamente con VMware vSphere, y ahora toca hablar de Hyper-V.

Aquí va la guía técnica para desplegar SQL Server en máquinas virtuales Hyper-V con garantías de rendimiento, estabilidad y escalabilidad. 

Configuración de CPU y topología NUMA

Hyper-V, como vSphere, expone a la VM una topología virtual de sockets y núcleos que debe estar alineada con la arquitectura física del host. SQL Server es sensible a NUMA, así que ignorar este detalle suele traducirse en latencias adicionales, penalizaciones en la caché de CPU y acceso a memoria remota.

Más aún si usas la edición Standard de SQL Server, con su famoso límite de 4 sockets o 24 núcleos. No es raro encontrar máquinas configuradas con 8 sockets y 1 core por socket: SQL Server solo podrá usar 4 de esos sockets. Has pagado por núcleos que están ahí, pero no puedes tocarlos.

Recomendación: configura tus VMs con pocos sockets y muchos núcleos por socket, alineado con tu licenciamiento y el hardware del host. Consulta sys.dm_os_sys_info y sys.dm_os_memory_nodes para verificar desde dentro de SQL Server si la segmentación NUMA es coherente.

Memoria: estática, dedicada y bajo control

Si hay algo que no debe activarse nunca en una VM de SQL Server en Hyper-V, es la memoria dinámica. Repite conmigo: no, nunca. Realmente no se debe activar en ningúna máquina virtual sea el hipervisor que sea. SQL Server gestiona su propia memoria, y necesita tener garantizada la cantidad asignada. La memoria dinámica provoca contracciones del buffer pool, intercambios de páginas y, en general, un rendimiento lamentable.

Recomendación: desactiva la memoria dinámica. Establece un tamaño fijo adecuado y deja que SQL Server se encargue de gestionarla internamente. Si activas Lock Pages in Memory, más razón aún, asegúrate de que esa memoria está siempre disponible. Y, por favor, no sobrecomprometas memoria física del host.

Almacenamiento: discos VHDX fijos, separados y bien asignados

La tentación de usar discos dinámicos (VHDs que crecen según se escriben datos) es comprensible… pero letal. Cada crecimiento de un disco dinámico introduce latencia, fragmentación, y pausas difíciles de diagnosticar. SQL Server no espera a nadie, y menos a que el disco “se amplíe”.

Recomendación: utiliza VHDX de tamaño fijo para todos los volúmenes que gestionen datos, logs o TempDB. Preasignar el espacio elimina imprevisibilidades y evita sorpresas desagradables durante picos de carga. Una vez dentro de la VM, formatea todos los volúmenes con unidad de asignación de 64 KB, como es debido.

Además, separa cada tipo de carga I/O: el volumen del sistema operativo no debe compartir canal con los datos, ni los logs con TempDB. En Hyper-V puedes tener hasta 4 controladoras SCSI virtuales por VM. Úsalas, asigna cada grupo de discos (datos, logs, TempDB, backups) a una controladora distinta. Esto reduce la serialización del tráfico de I/O y mejora el throughput.

Rendimiento de CPU: evitar la sobreasignación

Virtualizar no significa regalar recursos. Asignar más vCPUs de las necesarias (o más de las que el host físico puede manejar de forma eficiente) no mejora el rendimiento. Al contrario, genera colas, ready time, y tiempos de espera innecesarios en ejecución.

Recomendación: empieza con pocas vCPUs y escala si la carga lo justifica. Monitoriza el uso real con sys.dm_exec_requests, sys.dm_os_wait_stats, y contadores del sistema. Si detectas latencia por CPU o colas de scheduler, ajusta. Pero no asignes 100 vCPUs “por si acaso”.

Y sí, esto aplica también si vienes de un entorno con VMware y estás migrando, el redimensionamiento es una oportunidad para quitar lastre.

Plan de energía: alto rendimiento en host y VM

Sí, sigo viendo entornos donde el host Hyper-V está en modo Balanced. En 2025. Como si SQL Server tuviera que ahorrar batería. La realidad, el plan de energía tiene un impacto directo en la frecuencia de CPU, en los P-states y, por tanto, en la latencia.

Recomendación: configura el modo High Performance en la BIOS del host, en el sistema operativo del host Hyper-V y en el sistema operativo de la VM. Sin excepciones.

Separación lógica: cada tipo de carga, su volumen

Este punto es tan importante que lo repito, SQL Server necesita separar sus cargas. La unidad C: para el sistema. Otra para MDF. Otra para LDF. Otra para TempDB. Otra para backups. Cada uno tiene un patrón de acceso distinto: los logs son secuenciales, TempDB es salvaje y los datos tienen mezcla de lecturas aleatorias y escrituras diferidas. Mezclarlo todo en un único VHD es invitar al desastre.

Recomendación: usa discos separados para cada componente, idealmente en diferentes controladoras. Y si puedes aislar físicamente esos volúmenes en distintos LUNs o pools de almacenamiento, mejor aún.

Noisy neighbors: identificarlos y silenciarlos

En entornos compartidos, tu SQL Server puede funcionar perfectamente… hasta que otra VM empieza a hacer pruebas de estrés, un backup de 5 TB, o cualquier tarea con hambre de CPU o disco. Y entonces llegan los síntomas: consultas lentas, latencia intermitente, rendimiento errático.

Recomendación: monitoriza a nivel de host. Si no tienes visibilidad desde SQL Server, estás ciego. Herramientas como SQL Sentry o el propio Performance Monitor del host pueden ayudarte a detectar estas interferencias. Considera usar Hyper-V Resource Controls para establecer límites o reservas en CPU y memoria. Y si es una carga crítica, valora asignar un host dedicado.

Otras buenas prácticas esenciales

  • Integration Services y Guest Services: mantén las integraciones Hyper-V activadas y actualizadas. Mejoran la gestión, aceleran apagados y ayudan en procesos de backup y restauración.
  • Snapshots: úsalos como lo que son, herramientas de prueba. No son backups, y dejarlos activos durante días afecta al rendimiento. Elimina los checkpoints en cuanto ya no sean necesarios.
  • Backup consistente: asegúrate de que cualquier solución de backup en caliente (VSS-aware) esté bien integrada. Si no, el backup será tan fiable como un BACKUP LOG con el disco lleno.

Conclusión

Hyper-V ya no es el pariente pobre de la virtualización. Bien configurado, es perfectamente capaz de soportar cargas exigentes de SQL Server en producción. Pero hay que saber lo que se está haciendo: desactivar memoria dinámica, evitar discos thin, configurar adecuadamente NUMA, y vigilar el entorno como un halcón.

Para quienes vienen de VMware y están considerando el cambio (por licencias, por costes o por estrategia), muchos de los principios que explicamos en el artículo anterior siguen aplicando. Pero Hyper-V tiene su propio conjunto de ajustes críticos que no se deben pasar por alto.

SQL Server necesita recursos estables, configuraciones coherentes y visibilidad total. Y eso, en Hyper-V, es perfectamente posible.

Publicado por Roberto Carrancio en Rendimiento, SQL Server, 0 comentarios

SQL Server sobre VMware vSphere: Buenas prácticas

La virtualización de SQL Server ha sido durante más de una década una de las estrategias más consolidadas en entornos corporativos. VMware vSphere ha jugado un papel central en ese éxito, ofreciendo una plataforma madura, estable y altamente optimizada para ejecutar cargas de trabajo críticas como bases de datos relacionales. Sin embargo, en 2025 la situación es diferente.

Desde que Broadcom cerró la adquisición de VMware a finales de 2023, el panorama ha cambiado. La transición forzada al modelo de suscripción, la retirada de licencias perpetuas y el aumento generalizado de precios han generado un malestar evidente en muchos clientes. Algunas organizaciones han iniciado migraciones hacia Hyper-V, otras han apostado por plataformas cloud y unas pocas han optado incluso por volver al metal físico para sus cargas más exigentes.

Pero en medio de esa reconfiguración estratégica, una cosa sigue siendo cierta: vSphere sigue siendo una de las plataformas más fiables para ejecutar entornos virtualizados críticos como servidores SQL Server, siempre que se configure correctamente. Y eso, precisamente, es lo que nos ocupa en esta guía.

A continuación, abordamos en profundidad las consideraciones técnicas clave para desplegar SQL Server sobre VMware vSphere en 2025, actualizando las recomendaciones oficiales a la realidad operativa actual. Porque más allá de las decisiones de licenciamiento o la política comercial de turno, lo que importa es garantizar que nuestras bases de datos funcionen con previsibilidad, rendimiento y resiliencia.

Antes de empezar, conviene señalar que la mayoría de los fundamentos que vamos a repasar están basados en la guía oficial de VMware publicada en 2019 (Architecting Microsoft SQL Server on vSphere). No obstante, han pasado seis años desde entonces, por lo que además de recuperar los puntos más relevantes, matizaremos aquellos aspectos donde la tecnología, el hardware o las prácticas de operación han evolucionado.

Diseño correcto de la máquina virtual

Uno de los errores más comunes en despliegues SQL Server sobre vSphere es asumir que la virtualización, cómo se puede redimensionar fácilmente, elimina la necesidad de una planificación detallada. Nada más lejos de la realidad. El rendimiento y la estabilidad de SQL Server dependen en gran medida de cómo se diseñe la máquina virtual así que si, podrás cambiarlo luego pero eso no quiere decir que no lo diseñes bien de primeras.

Dimensionamiento inicial (right-sizing)

A diferencia de entornos físicos, en VMware es preferible asignar sólo los recursos que la carga realmente necesita. Asignar más CPU o RAM de la necesaria puede generar efectos negativos: contención, mayor coste de scheduling, uso ineficiente de licencias, o incluso, aunque pueda ser contraintuitivo, peor rendimiento. Una VM con más vCPU de las que necesita puede sufrir más Ready Time, más latencia y menos rendimiento.

Para dimensionar correctamente, conviene recopilar datos de uso real de CPU, memoria y disco mediante DMVs, Performance Monitor o herramientas de observabilidad (SQL Sentry, vRealize Operations). A partir de ahí, se puede asignar el número adecuado de vCPU y memoria, ajustando posteriormente según carga.

Configuración de CPU y NUMA

Núcleos por socket: impacto técnico y de licenciamiento

La edición Standard de SQL Server impone límites que no siempre se respetan al definir una VM: máximo 4 sockets o 24 núcleos. Por tanto, es frecuente encontrar configuraciones ineficientes: por ejemplo, una VM con 8 sockets y 1 core por socket, que solo permite el uso de 4 sockets con un núcleo cada uno, aunque se hayan asignado 8 núcleos.

Lo recomendable es agrupar los núcleos virtuales en menos sockets: por ejemplo, 1 socket con 8 cores, o 2 sockets con 12. Esto permite que SQL Server aproveche todos los recursos asignados sin restricciones de licenciamiento.

Consideraciones NUMA

Cuando una VM supera los recursos de un nodo NUMA físico (CPU o memoria), se producen accesos a memoria remota, lo que introduce latencia física. SQL Server es muy sensible a este comportamiento así que tenlo en cuenta.

VMware detecta la topología NUMA del host y crea configuraciones vNUMA equivalentes, pero esto solo es posible si la VM tiene más de 8 vCPU (configurable). En cualquier caso, es necesario verificar que la asignación real de nodos NUMA es coherente, tanto desde el visor de recursos del host como desde dentro de SQL Server (por ejemplo, con sys.dm_os_memory_nodes).

Asignación y gestión de memoria

Evitar el overcommit de memoria

En entornos VMware es posible sobreasignar memoria física, confiando en mecanismos como ballooning o swapping para equilibrar el uso. Pero esto es completamente desaconsejado para máquinas que ejecutan SQL Server, ya que su gestión interna de memoria (buffer pool, cache) depende de una disponibilidad constante.

La práctica recomendada es reservar memoria para la VM (memory reservation = memory size) y evitar que el host pueda utilizar parte de esa RAM para otros fines. En ESXi modernos, la opción de Transparent Page Sharing (TPS) está deshabilitada por defecto, lo que refuerza la necesidad de no sobrecomprometer recursos.

Configuración dentro de SQL Server

Dentro de la instancia, es imprescindible limitar la memoria máxima (max server memory) para evitar que el sistema operativo se quede sin recursos. Hay que tener un especial cuidado al habilitar lock pages in memory, JAMÁS se debe activar si el balloning está activado para esa VM o puede producir errores. 

Siguiendo con estas configuraciones, el uso de large pages (Trace Flag 834) puede mejorar el rendimiento en sistemas con mucha RAM, pero es muy delicado y requiere pruebas específicas. De todas formas no seré yo quien os recomiende una configuración que ni Microsoft recomienda…

Almacenamiento y controladoras

Cuando se crean discos virtuales (VMDKs) para una VM, VMware permite elegir entre discos de tamaño fijo (thick provisioned) o dinámico (thin provisioned). Aunque el aprovisionamiento dinámico puede ahorrar espacio a corto plazo, introduce fragmentación y penalizaciones de rendimiento a medida que el disco crece.

En entornos SQL Server, donde la carga de I/O es constante y predecible, el uso de discos thin puede generar latencia adicional, sobre todo en sistemas de almacenamiento que no gestionan bien el crecimiento dinámico.

La recomendación es clara, utilizar discos de tamaño preasignado (thick eager zeroed) para todos los VMDKs que manejen datos, logs o TempDB. Este tipo de disco se crea completamente al aprovisionar la VM, evitando operaciones de escritura adicionales durante el uso. Esta práctica garantiza un mejor rendimiento sostenido, especialmente en entornos con IOPS elevados, y reduce el riesgo de fragmentación interna del datastore

Separación de discos y uso de PVSCSI

Una práctica consolidada es separar los distintos tipos de archivos de SQL Server en VMDKs distintos: sistema operativo, base de datos, logs, TempDB. Esto no solo mejora el rendimiento, también facilita la gestión de snapshots, backups y análisis de uso.

En cuanto a la controladora, VMware recomienda usar PVSCSI en lugar de LSI Logic SAS para los discos de datos. PVSCSI ofrece mayor profundidad de cola (hasta 256) y menor uso de CPU. La configuración ideal combina:

  1. LSI Logic SAS solo para la unidad C:\
  2. Varias controladoras PVSCSI adicionales para datos, logs y TempDB, separadas si es posible.

Compatibilidad con NVMe

Aunque los discos NVMe han ganado presencia en datacenters en los últimos años, las recomendaciones no cambian: PVSCSI sigue siendo preferible por compatibilidad, madurez y soporte. Solo si se trabaja directamente con hardware o almacenamiento NVMe puede valorarse otra configuración, siempre tras validación en laboratorio.

Instantáneas (snapshots): limitaciones y riesgos

Los snapshots de vSphere son herramientas útiles para pruebas y cambios puntuales, pero no deben utilizarse como método de backup. En SQL Server, los discos delta que crean los snapshots generan penalizaciones de rendimiento progresivas, especialmente en entornos con alto volumen de escritura.

Es frecuente que un snapshot olvidado de semanas cause degradaciones difíciles de diagnosticar. Si hay que usarlos, deben eliminarse en horas, no días. Y siempre debe preferirse una estrategia de backup basada en herramientas nativas o soluciones de backup empresarial compatibles con SQL Server.

Red y conectividad

SQL Server puede requerir un uso intensivo de red, especialmente en escenarios con alta concurrencia, réplicas, Always On o transacciones distribuidas.

Las interfaces de red deben ser siempre VMXNET3, que ofrecen mejor rendimiento y menor latencia que E1000. Además, se recomienda activar Receive Side Scaling (RSS) tanto en el sistema operativo como en las VMware Tools, para distribuir la carga de red entre múltiples núcleos de CPU.

Si se trabaja con grandes volúmenes de datos entre nodos (como en grupos de disponibilidad o replicaciones), debe garantizarse que la infraestructura física y virtual está optimizada para latencia baja y alto throughput. 

Por ejemplo, es una buena práctica usar una VLAN e interfaz de red (física y virtual) dedicada para la comunicación entre servidores SQL, o para la conexión con los servidores de copias de seguridad.

Configuración de energía

Cuando hablamos de servidores críticos debemos primar el rendimiento a la eficiencia energética y esto implica establecer un plan High Performance en todos los niveles

El plan de energía es una de las configuraciones más olvidadas (si aun en 2025), pero con mayor impacto. Por defecto, ESXi y Windows Server pueden utilizar políticas de ahorro de energía (Balanced), que reducen la frecuencia de CPU o introducen cambios de estado (P-states) perjudiciales para cargas sensibles.

La recomendación es clara: habilitar el modo High Performance en tres capas:

  1. BIOS del host físico.
  2. Política de energía de ESXi.
  3. Plan de energía dentro del sistema operativo invitado.

Esta configuración reduce la latencia de respuesta de la CPU y evita throttling no deseado. En pruebas reales, he notado mejoras de entre el 10 y el 15% en rendimiento solo con una buena configuración de energía.

Disponibilidad y recuperación

Garantizar la disponibilidad de una instancia de SQL Server virtualizada no se limita a activar vSphere HA o a desplegar un grupo de disponibilidad. Exige entender cómo interactúan las funcionalidades de VMware con las tecnologías de alta disponibilidad del propio SQL Server, y decidir cuál debe ser el punto de recuperación (RPO) y el tiempo de recuperación objetivo (RTO) para cada tipo de fallo.

Alta disponibilidad en la capa VMware

VMware proporciona mecanismos propios de alta disponibilidad y movilidad de cargas que pueden complementar (o incluso entrar en conflicto con) las estrategias nativas de SQL Server:

  • vSphere HA permite el reinicio automático de una VM en otro host del clúster cuando se detecta un fallo físico. No ofrece continuidad de servicio inmediata, pero sí reduce el downtime sin intervención manual. Es transparente para el sistema operativo y para SQL Server.
  • vSphere FT (Fault Tolerance) proporciona replicación síncrona a nivel de VM y failover instantáneo sin pérdida de estado. No está soportado para máquinas con más de 8 vCPU (en la mayoría de versiones actuales) y no es compatible con algunos escenarios de licenciamiento de SQL Server. Es útil solo en casos muy concretos y no se recomienda como estrategia principal de HA para SQL.
  • vMotion permite migrar en caliente una VM entre hosts sin apagado, útil para mantenimiento planificado o balanceo. Sin embargo, en cargas intensivas (como operaciones de backup, reindexación o grandes transacciones), la migración puede introducir latencias temporales. Aunque no genera caída del servicio, puede afectar al rendimiento en momentos críticos.
  • DRS (Distributed Resource Scheduler) balancea dinámicamente la carga entre hosts según consumo de recursos. Esto puede provocar movimientos frecuentes de la VM si no se definen reglas adecuadas. 

En entornos con SQL Server, es necesario evaluar cuidadosamente el impacto de vMotion y DRS en instancias críticas. Mover una VM durante una operación intensiva puede degradar el rendimiento o generar latencia adicional. Se recomienda definir reglas de afinidad o exclusión para evitar migraciones no deseadas. 

Consideraciones de diseño

A la hora de planificar una solución de alta disponibilidad para SQL Server sobre VMware, es fundamental definir el tipo de fallo que se quiere cubrir:

  • Fallo físico del host: vSphere HA es suficiente si se acepta un reinicio de VM.
  • Fallo de software en el sistema operativo o SQL Server: requiere HA a nivel de SQL Server (AG, FCI).
  • Fallo del datastore: debe cubrirse mediante almacenamiento redundante (vSAN, replicación SAN/NAS).
  • Fallo completo del datacenter: implica solución de recuperación ante desastres (DR), normalmente con réplicas geográficas y procedimientos definidos de failover manual o automático.

Es importante destacar que las soluciones de VMware y SQL Server no son excluyentes, pero deben coordinarse cuidadosamente para evitar conflictos: por ejemplo, no tendría sentido proteger una FCI solo con vSphere HA, o dejar que DRS migre réplicas sincronizadas entre hosts indiscriminadamente. En casos en los que se implementan soluciones HA de SQL Server se recomienda definir reglas de afinidad y exclusión para que las máquinas implicadas en el HA no compartan host físico.

También hay que considerar el licenciamiento: algunas ediciones de SQL Server requieren licencias activas en todas las réplicas del clúster, incluso si solo una está activa (según el uso que se haga). Además, ciertos entornos requieren asegurar que las réplicas pasivas no procesan cargas activas para beneficiarse de ventajas en licencias de Software Assurance.

Vecinos ruidosos

Uno de los retos más infravalorados en la virtualización de cargas críticas como SQL Server es el efecto de otras VMs compartiendo el mismo host físico, conocidas comúnmente como “noisy neighbors” (vecinos ruidosos).

Una máquina virtual que consume picos de CPU, I/O o memoria puede afectar negativamente a otras VMs en el mismo host, especialmente si no hay control de calidad de servicio (QoS) o reservas definidas. Los síntomas suelen incluir:

  • Latencia intermitente en consultas.
  • Variabilidad en tiempos de respuesta bajo carga constante.
  • Aumento de CPU Ready Time o Co-Stop sin incremento aparente de carga en SQL Server.

Para detectar estos problemas, se requiere visibilidad más allá del sistema operativo invitado. Herramientas como vRealize Operations, SQL Sentry o VMware Aria Operations permiten identificar VMs que saturan recursos compartidos, y correlacionar ese uso con degradaciones de rendimiento en las instancias afectadas.

En este caso las recomendaciones son:

  • Establecer reservas mínimas de recursos para VMs críticas.
  • Aplicar affinity/anti-affinity rules para separar cargas pesadas o sensibles.
  • Evitar que SQL Server comparta host con VMs que realizan tareas de backup, ETL o pruebas automatizadas en horarios críticos.
  • Monitorizar periódicamente la actividad del host para detectar comportamientos anómalos.

En entornos donde se requiere máxima previsibilidad (banca, OLTP de alto rendimiento, entornos de pricing), puede incluso justificarse la dedicación exclusiva de host para una o varias VMs SQL Server.

Versiones de hardware y VMware Tools

Mantener actualizadas tanto la versión de hardware virtual como las VMware Tools es esencial para acceder a nuevas funcionalidades, mejorar el rendimiento y garantizar el soporte oficial.

En 2025, la versión mínima recomendada es hardware version 19 (ESXi 7.0), aunque lo ideal sería estar en versión 20 u 21 si se usa ESXi 8.x. Las VMware Tools deben actualizarse de forma coordinada tras cambios de versión o parches del host.

Monitorización y observabilidad

Las métricas estándar de vSphere no siempre ofrecen suficiente visibilidad sobre los cuellos de botella que afectan a SQL Server. Por ello, se recomienda utilizar herramientas especializadas que permitan analizar desde la capa de hipervisor hasta el motor de base de datos:

  • SQL Sentry (SolarWinds): ofrece no solo métricas de SQL Server sino también propias del entorno virtual.
  • vRealize Operations: para el análisis de capacidad, planificación y rendimiento.
  • Perfmon + DMVs: exclusivo para el análisis desde dentro de la VM.

Indicadores como CPU Ready Time, Co-Stop, latencia de I/O, uso de memoria o crecimiento de snapshots deben estar controlados de forma continua.

Conclusión

La virtualización de SQL Server sobre VMware sigue siendo, en 2025, una estrategia plenamente válida para cargas de misión crítica. Pero su éxito depende de un diseño cuidadoso, de un conocimiento profundo del comportamiento del motor SQL y de una integración adecuada con las funcionalidades del hipervisor.

Los errores más comunes (malas configuraciones NUMA, exceso de vCPU, uso de snapshots como backup, planes de energía inadecuados) pueden evitarse si se siguen las prácticas que hemos repasado. Esta guía pretende servir como base técnica de referencia para garantizar que cada instancia virtualizada de SQL Server funcione con la previsibilidad, rendimiento y fiabilidad que un entorno de producción exige.

Si tenéis alguna duda o sugerencia, podéis dejarla en Twitter, por mail o dejarnos un mensaje en los comentarios. Y recuerda que también tenemos un grupo de Telegram y un canal de YouTube a los que te puede unir. ¡Hasta la próxima!

Publicado por Roberto Carrancio en Alta Disponibilidad, Rendimiento, SQL Server, 1 comentario

Nueva caché de seguridad en SQL Server 2025

Llevamos ya tiempo con la preview de SQL Server 2025 probando todas las novedades. La lista es larga y, a veces, cuesta distinguir entre lo realmente útil y lo puramente decorativo. Entre tanto brillo marketiniano, hay una mejora de la que no se ha hablado, que muchos pasarán por alto sin comprender las implicaciones, pero que a mi me ha hecho sonreír y decir: “Ya era hora”. Os hablo de la nueva capacidad de invalidar cachés de seguridad de forma específica por inicio de sesión en SQL Server 2025. Y sí, es tan buena como suena.

¿Qué es esto de la caché de seguridad?

Antes de meternos en harina, conviene recordar qué es exactamente esa “caché de seguridad” que ahora Microsoft ha afinado. Cuando un usuario se conecta a SQL Server, el motor evalúa qué permisos tiene para acceder a objetos, ejecutar procedimientos, ver datos… ya sabéis, lo básico para evitar que todo sea un buffet libre de SELECT * FROM SensitiveTable.

Como no somos tontos (y SQL Server tampoco), esta validación no se repite cada vez que alguien lanza una consulta. Sería un suicidio en términos de rendimiento. En su lugar, SQL Server guarda esa información en memoria (en la famosa security cache) y la reutiliza mientras no haya cambios que invaliden su contenido.

¿El problema? Hasta ahora era muy sensible. Si cambiabas cualquier permiso de cualquier usuario (cambio de permisos, modificación de roles, o revocar un acceso), se eliminaban todas las entradas DE TODOS LOS USUARIOS. Esto, por supuesto, tiene efectos inmediatos en el rendimiento de cualquier sesión abierta, aunque no tuviera nada que ver con el cambio.

Vamos, que tirábamos la caché como quien reinicia un servidor por si acaso: por pura desesperación.

SQL Server 2025 trae cordura: invalidación específica por login

Con la llegada de SQL Server 2025, se acabó el “todo o nada”. Ahora, cuando se invalidan entradas de la caché de seguridad debido a un cambio en los permisos, el sistema es lo bastante listo como para borrar solo las entradas correspondientes al login afectado. El resto permanece intacto.

Esto supone una mejora directa en varios frentes. Para empezar, reduce el impacto de cualquier cambio de seguridad. No hay invalidaciones masivas innecesarias, no se resienten las sesiones de otros usuarios, y la CPU no se dispara validando de nuevo cientos o miles de entradas de permisos para conexiones que no han cambiado ni una coma de sus privilegios.

¿Por qué esto importa? Rendimiento, escalabilidad y menos sustos

Puede parecer que un cambio de estas características no tiene mucha importancia, y así es entornos con pocos usuarios y permisos bastante genericos. Pero cuando el volumen de usuarios y la granularidad de los permisos son considerables, la diferencia entre invalidar toda la caché de seguridad y hacerlo de forma selectiva puede marcar la diferencia. En entornos con cientos o miles de sesiones concurrentes, la diferencia es tan evidente como entre ejecutar SELECT con un índice… o sin él.

Antes, un cambio puntual en los permisos podía convertirse en una mini tormenta de validaciones. La CPU subía, la latencia aumentaba, y tú te encontrabas explicando por qué un GRANT a las 9:30 había dejado la aplicación más lenta que un Access con macros durante unos minutos. Todo eso queda en el pasado con esta nueva implementación.

Ahora, si revocas un permiso a un usuario que ya estaba conectado, SQL Server se limita a eliminar sus entradas específicas de la caché. El resto de usuarios ni se enteran. Y tú tampoco tendrás que revisar gráficos de rendimiento preguntándote qué demonios ha pasado.

Cambios de roles, miembros de grupos y lo que siempre duele

Uno de los escenarios donde esto brilla especialmente es cuando se hace una modificación en roles o membresías de grupos. Añadir o quitar a alguien de un db_datareader, por ejemplo, solía ser sinónimo de purgar toda la caché de seguridad.

Con SQL Server 2025, eso se acabó. La limpieza de la caché se ciñe a ese login concreto, incluso si el cambio afecta a una membresía en roles de servidor o roles personalizados. Menos interrupciones, menos recompilaciones internas, y más estabilidad.

Que sí, que aún quedan muchos elementos que provocan recompilaciones y limpiezas de caché que no siempre tienen sentido, pero al menos aquí han puesto orden en una de las áreas más olvidadas por el motor en versiones anteriores.

¿Cómo saber si está funcionando?

No hay un nuevo DMV mágico que diga “estás disfrutando de la nueva caché de seguridad personalizada”, pero si monitorizas el uso de CPU y el acceso concurrente durante cambios de permisos, deberías notar una mejora clara. Sobre todo en sistemas con mucha actividad concurrente y una gestión de seguridad activa.

Y si eso no te vale y quieres verlo con tus propios ojos siempre nos quedarán los eventos extendidos, esa magnífica herramienta que lo captura todo. Puedes crear una sesión que capture los nuevos eventos “login_token_cache_hit” que se producen cuando el token de la caché de seguridad sigue activo y el evento “login_token_cache_miss” que es cuando el token de la caché no es válido y hay que generar una nueva entrada.

Demo caché de seguridad 

Vamos a comprobar que esto realmente funciona. Para ello tenemos nuestros logins con ID 267 y 268 y vamos a crear una sesión de xEvents que capture los eventos que hemos comentado antes:

Lo primero que vamos a hacer es borrar la caché de seguridad:

Como hemos borrado la caché, estas dos primeras entradas generan un evento “login_token_cache_miss”, no existe registro en la caché. 

Pero, si volvemos a conectarnos veremos ya los hit, es decir ha utilizado el token existente en caché

Ahora vamos a cambiar los permisos de uno de los logins

Y al volver a conectar veremos solo uno de los dos logins tiene el miss y el otro mantiene su token válido (evento hit). Además veís en la captura que yo he añadido tambien los eventos “security_cache_login_timestamp_increment” a mi captura de eventos, que me indica cuando se ha producido un cambio de permisos.

¿Qué implicaciones tiene para nosotros como DBAs?

Básicamente, nos devuelve un poco de control. Ahora podemos aplicar cambios de seguridad sin tener miedo a provocar un alud de recompilaciones y penalizaciones de rendimiento. Se acabó el tener que programar revocaciones de permisos fuera del horario laboral o acompañarlas de una explicación de daños colaterales.

También nos da más seguridad en entornos de desarrollo y pruebas. Podemos simular escenarios de cambios de permisos sin el temor a que medio entorno colapse por culpa de una caché global destruida innecesariamente.

Eso sí, no todo es perfecto. La documentación oficial todavía no entra en muchos detalles sobre cómo se comporta esta nueva caché con escenarios complejos de impersonación (EXECUTE AS), certificados o permisos a nivel de esquema. Habrá que investigar (y ya lo haré en otro artículo si me lo pedís) si este comportamiento selectivo se extiende también a esos casos.

Conclusión

La invalidación selectiva de la caché de seguridad en SQL Server 2025 es una mejora tan lógica que sorprende que no estuviera ya implementada hace años. Pero aquí está, y funciona como debe. Nos da más precisión, menos impacto colateral y permite cambios de seguridad sin temor a convertir el servidor en una verbena de validaciones.

Como siempre, no es magia. Si haces cambios absurdos o con prisas, te seguirás metiendo en líos. Pero al menos, ahora, el motor te lo pone un poco menos difícil. Y eso, en este mundo de permisos, roles y DBA que no duermen, ya es bastante.

Si tenéis alguna duda o sugerencia, podéis dejarla en Twitter, por mail o dejarnos un mensaje en los comentarios. Y recuerda que también tenemos un grupo de Telegram y un canal de YouTube a los que te puede unir. ¡Hasta la próxima! 

Publicado por Roberto Carrancio en Rendimiento, SQL Server, 0 comentarios

OPTIMIZED SP_EXECUTESQL: Novedad de SQL Server 2025

Con la llegada de SQL Server 2025, Microsoft introducirá una serie de mejoras orientadas a optimizar el rendimiento del motor. Una de ellas es la nueva configuración OPTIMIZED_SP_EXECUTESQL, pensada específicamente para entornos que hacen uso intensivo de SQL dinámico a través de sp_executesql. En este artículo profundizo en su funcionamiento, cómo activarla, qué impacto tiene en la caché de planes y cómo se comporta bajo carga concurrente.

Contexto: el problema con sp_executesql y el plan cache

Cuando ejecutamos consultas dinámicas con sp_executesql, especialmente en entornos con alta concurrencia, SQL Server puede compilar múltiples planes para lo que, conceptualmente, es la misma consulta. Aunque el texto del lote (batch text) sea idéntico y se usen parámetros, el compilador no siempre consigue identificarlo como la misma ejecución, lo que termina generando entradas repetidas en la plan cache.

Esto da lugar a varios problemas conocidos:

  • Bloat en la plan cache, con decenas o cientos de planes similares almacenados.
  • Consumo innecesario de CPU por compilaciones redundantes.
  • Contención en la compilación, cuando múltiples sesiones intentan compilar la misma consulta al mismo tiempo.

Con OPTIMIZED_SP_EXECUTESQL, SQL Server introduce un nuevo mecanismo para mitigar estos efectos.

¿Qué es OPTIMIZED SP_EXECUTESQL?

Se trata de una configuración a nivel de base de datos que se encuentra desactivada por defecto y que puede activarse mediante ALTER DATABASE SCOPED CONFIGURATION. Su objetivo principal es optimizar la forma en que SQL Server gestiona la compilación y almacenamiento en caché de planes asociados a ejecuciones de sp_executesql.

Cuando se activa esta opción, el comportamiento cambia significativamente:

  • La primera ejecución de un lote dinámico mediante sp_executesql compila el plan normalmente.
  • Otras sesiones concurrentes que ejecutan el mismo lote esperan a que finalice la compilación.
  • Una vez compilado, todas las sesiones reutilizan el mismo plan, del mismo modo que lo harían si fuera un procedimiento almacenado.

Requisitos y dependencias

Lo primero, obviamente, es tener SQL Server 2025 y tu base de datos en un nivel de compatibilidad 170. Además, si en tu base de datos tienes activada la opción de actualización automática de estadísticas, Microsoft recomienda habilitar también ASYNC_STATS_UPDATE_WAIT_AT_LOW_PRIORITY (más bien es un requisito pero, ¿has visto qué bien me ha quedado lo de “Microsoft recomienda”?). Esto asegura que el nuevo comportamiento no introduzca contención innecesaria durante las actualizaciones estadísticas, en escenarios de alta concurrencia.

SP_EXECUTESQL

Cómo activarlo

Para habilitar la funcionalidad, puedes ejecutar el siguiente bloque:

Recuerda, necesitas estar en nivel de compatibilidad 170 (SQL Server 2025) para que la opción sea válida.

Puedes comprobar el estado actual de la configuración con:

Demostración práctica

Vamos a ejecutar una serie de pruebas en una base de datos StackOverflow (modo de compatibilidad 170) para observar el impacto real de esta configuración en la plan cache.

Paso 1: Asegurar que la opción está desactivada

Paso 2: Limpiar la caché de planes

Paso 3: Ejecutar carga concurrente

Usaremos SQLQueryStress para lanzar la siguiente consulta, que ejecuta sp_executesql de forma concurrente:

Parámetros en SQLQueryStress:

  • Iteraciones por hilo: 10
  • Número de hilos: 100

Esto generará 1000 ejecuciones concurrentes de la misma consulta dinámica.

Paso 4: Analizar la plan cache

Una vez completadas las ejecuciones, inspeccionamos el número de planes generados:

Resultado esperado (sin optimización): múltiples planes distintos (en pruebas reales, se observan entre 20 y 30 para este escenario), aunque la consulta sea la misma.

Esto evidencia el comportamiento ineficiente por defecto: múltiples planes para la misma carga.

Paso 5: Activar la optimización

Paso 6: Limpiar de nuevo la caché

Paso 7: Ejecutar la misma carga

Volvemos a lanzar los 100 hilos concurrentes con SQLQueryStress, exactamente como antes.

Paso 8: Analizar resultados

Ejecutamos de nuevo la consulta sobre la plan cache:

Resultado esperado (con optimización): una sola entrada en la caché de planes, con una execution_count acumulada de 1000. El compilador ha tratado la consulta dinámica como si fuese un procedimiento almacenado.

Este comportamiento elimina de raíz el bloat en la caché y evita la compilación paralela.

¿Qué hace SQL Server exactamente?

El mecanismo detrás de esta optimización replica el modelo clásico de los procedimientos almacenados:

  1. La primera ejecución de una consulta concreta compila el plan.
  2. El resto de ejecuciones esperan a que ese plan esté disponible.
  3. Una vez compilado, todas las ejecuciones reutilizan ese mismo plan.

Este comportamiento garantiza una mayor eficiencia tanto en uso de CPU como en gestión de memoria. Ya no veremos un plan por cada sesión, sino un único plan compartido y referenciado por todas.

Consideraciones finales

Este cambio solo aplica a sp_executesql. No afecta a EXEC() ni a otros mecanismos de ejecución dinámica.

Las consultas deben ser realmente idénticas (texto y parámetros) para que se detecte la coincidencia.

Si tu aplicación genera SQL dinámico con pequeñas variaciones en los literales o estructura, el beneficio será menor.

La mejora se aprecia especialmente en entornos con alta concurrencia o patrones repetitivos.

Conclusión

OPTIMIZED_SP_EXECUTESQL es una de las mejoras más importantes en la gestión del plan cache que hemos visto en años. Su activación puede suponer una diferencia notable en servidores que sufrían problemas de rendimiento por exceso de compilaciones o saturación de memoria con planes efímeros.

Es una de esas configuraciones que, bien aplicada, no solo mejora el rendimiento, sino que permite aprovechar mejor los recursos existentes. No es una solución mágica, pero sí una herramienta eficaz para un problema conocido.

Si trabajas con aplicaciones que generan SQL dinámico intensivo y tienes pensado actualizar SQL Server 2025 cuando sea estable, este cambio debería estar en tu lista de tareas prioritarias.

Si tenéis alguna duda o sugerencia, podéis dejarla en Twitter, por mail o dejarnos un mensaje en los comentarios. Y recuerda que también tenemos un grupo de Telegram y un canal de YouTube a los que te puede unir. ¡Hasta la próxima! 

Publicado por Roberto Carrancio en Cloud, Rendimiento, SQL Server, 0 comentarios

Funciones inline en SQL Server.

Si vienes del artículo anterior sobre procedimientos almacenados vs funciones, ya sabes que no todas las funciones son iguales ni todas son igual de bienvenidas en producción. Hoy vamos a entrar en el terreno de las que sí merecen un sitio en entornos serios, las funciones inline con valores de tabla, o inline TVF.

Porque sí, en SQL Server también hay funciones que se comportan bien, no matan el rendimiento y hasta pueden ser aliadas del optimizador. Eso sí, como siempre en SQL Server, hay letra pequeña. Y no es precisamente corta.

¿Qué es exactamente una función inline?

Una función inline con valores de tabla es un objeto de base de datos que devuelve una tabla como resultado, definida a partir de una única instrucción SELECT. La sintaxis es clara: no hay BEGIN…END, no hay tablas variables intermedias ni lógica procedural. Solo una expresión SELECT que define el resultado.

Un ejemplo básico:

La clave aquí está en el hecho de que la función no tiene cuerpo procedural. El RETURN devuelve directamente el SELECT, sin necesidad de declarar una variable tipo tabla ni usar bloques de control. Y eso le encanta al optimizador de consultas.

¿Por qué son tan diferentes del resto?

La principal ventaja de las funciones inline es que se expanden dentro del plan de ejecución de la consulta que las llama. Es decir, se comportan como si hubieras escrito el SELECT directamente en la consulta. Esto permite al optimizador generar un único plan coherente, hacer estimaciones de cardinalidad más precisas y aplicar filtros, joins o incluso reescrituras sin perder información.

No hay saltos de contexto, no hay opacidad. Todo se ve. Todo se puede optimizar. Todo fluye.

Esto contrasta brutalmente con las funciones escalares o las funciones con valores de tabla multisentencia, donde el optimizador pierde visibilidad, rompe el plan y acaba tirando de valores fijos o estimaciones aleatorias dignas de una quiniela.

En resumen, si necesitas encapsular lógica reutilizable que devuelve datos en forma de tabla y quieres mantener el rendimiento, la inline TVF es tu opción.

Buenas prácticas: lo que conviene hacer

Usar funciones inline no solo es una cuestión de sintaxis correcta. También hay que saber cómo diseñarlas para que sean eficientes, reutilizables y mantenibles. Aquí van algunas prácticas que realmente marcan la diferencia:

Primero, mantén la lógica centrada en una única operación clara. Si necesitas múltiples pasos, joins complejos, condiciones opcionales o lógica condicional, es probable que estés forzando demasiado el diseño y que deberías estar en un procedimiento o en una vista indexada (si tienes valor para ello).

Segundo, evita usar funciones inline como simples extractores de columnas. Si solo encapsulas un SELECT de una tabla sin añadir valor, estás generando una capa innecesaria que complica los planes y aporta poco. Úsalas cuando encapsules lógica real: filtrados, joins relevantes, condiciones específicas.

Tercero, documenta los parámetros con claridad. SQL Server no impone muchas restricciones, pero el que venga detrás (que probablemente seas tú dentro de seis meses) agradecerá saber qué espera exactamente cada parámetro y cómo afecta al resultado.

Por último, cuidado con los TOP y los ORDER BY. En funciones inline, el ORDER BY solo es válido dentro de un TOP… pero eso no significa que respete el orden en la consulta final. El optimizador puede decidir hacer lo que le venga en gana si no hay ORDER BY explícito en la consulta externa. Que no te sorprenda.

Casos de uso donde brillan

Hay escenarios donde una función inline brilla con luz propia. Uno de los más frecuentes: filtros complejos reutilizables. Supón que tienes una lógica de negocio que define qué pedidos se consideran “activos”, basada en múltiples condiciones, fechas, flags y columnas calculadas. Puedes encapsular esa lógica en una función inline y usarla como si fuera una subconsulta parametrizada.

Otro caso clásico: reportes o dashboards parametrizados. Si usas Power BI o cualquier herramienta de reporting que genere consultas dinámicas, puedes exponer funciones inline como “vistas parametrizadas” que reducen la complejidad del modelo y mejoran el mantenimiento.

Y por supuesto, también son útiles en procesos ETL que necesitan aplicar filtros reutilizables en múltiples pasos sin replicar lógica por todas partes.

Lo que NO debes hacer (aunque la tentación sea fuerte)

El hecho de que las funciones inline se comporten tan bien no significa que sean inmunes al mal diseño. De hecho, hay errores recurrentes que siguen apareciendo incluso entre DBAs con experiencia.

Uno muy común es usar funciones inline con un JOIN interno a una tabla de millones de filas sin tener en cuenta filtros exteriores. Como la función se expande, esa tabla puede aparecer en cada ejecución de forma inesperada y generar planes desastrosos.

Otro clásico, parámetros mal definidos o con valores opcionales que no se gestionan bien. SQL Server no permite parámetros opcionales en funciones, así que muchos intentan simularlo con NULL y condiciones tipo (@Param IS NULL OR Columna = @Param). Esto degrada los planes de ejecución, impide la indexación eficaz y rompe la estimación de cardinalidad. Una joya.

Y uno más, pensar que por ser una función inline, siempre va a ir rápido. Si la lógica interna es compleja, hace joins mal indexados o usa filtros poco selectivos, el plan puede seguir siendo un desastre. El que se vea no significa que sea bueno.

Comparación real: subconsulta vs función inline

Para los escépticos, vamos con un ejemplo sencillo.

Subconsulta tradicional

Esto se puede convertir en una función inline así:

Y luego usarla así:

¿La diferencia? En un entorno donde se reutiliza la lógica o se combina con otras condiciones, encapsularla en una función inline puede mejorar la legibilidad y el mantenimiento. Y con OUTER APPLY, se comporta como una subconsulta correlacionada, pero con visibilidad total en el plan.

Eso sí, no uses CROSS APPLY si no estás 100% seguro de que todas las combinaciones producirán resultado. Te lo recordarás a las 3 de la mañana.

Conclusión

Las funciones inline son una de esas herramientas que, bien usadas, te hacen la vida más fácil y el código más limpio. Mal usadas, solo complican las cosas sin aportar rendimiento ni claridad. No son la solución a todo, pero en el conjunto correcto de circunstancias, pueden sustituir a vistas, subconsultas o incluso procedimientos ligeros con mucha más flexibilidad.

Y sobre todo, te permiten encapsular lógica sin pagar el peaje que sí tienen otras funciones. Como siempre en SQL Server, no es solo cuestión de saber que existen, sino de saber cuándo y cómo usarlas. Porque sí, hay funciones buenas. Solo hay que reconocerlas.

Si tenéis alguna duda o sugerencia, podéis dejarla en Twitter, por mail o dejarnos un mensaje en los comentarios. Y recuerda que también tenemos un grupo de Telegram y un canal de YouTube a los que te puede unir. ¡Hasta la próxima! 

Publicado por Roberto Carrancio en Cloud, Rendimiento, SQL Server, 0 comentarios