Backups y restores “al vuelo” sin almacenamiento intermedio

Backups y restores “al vuelo” sin almacenamiento intermedio

Seguramente los más “senior” recordarán la posibilidad que existía en versiones SQL Server antiguas de realizar backups utilizando named pipes. Cuando hablo de versiones antiguas, me refiero a “antiguas de verdad”, ya que esta funcionalidad fue marcada como obsoleta en SQL Server 7, se mantuvo en SQL 2000 pero ya se eliminó de SQL Server 2005 y posteriores. (más…)

Azure Files Premium + SQL Server Failover Cluster instance= OnPremise to Cloud simplificado

Azure Files Premium + SQL Server Failover Cluster instance= OnPremise to Cloud simplificado

Uno de los problemas que muchos clientes se encuentran al intentar migrar instancias OnPremise a Cloud es la ausencia de un “storage compartido” sencillo. Existen alternativas, apoyadas en software de terceros o bien soluciones SDS, que nos permiten configurar una instancia Failover Cluster en Azure pero no están exentas de complejidad y añaden bastante coste al TCO de la solución. (más…)

Demos la bienvenida a los grafos en SQL Server 2017

Demos la bienvenida a los grafos en SQL Server 2017

El aumento de las necesidades de análisis basado en relaciones hace que el uso de grafos esté aumentando. La existencia de bases de datos orientadas a grafos no es nueva ya que en los años 90 existían alternativas maduras pero que han tenido un uso relativamente marginal hasta ahora. Estas bases de datos se caracterizan por su flexibilidad de esquema en lo que a las relaciones entre entidades se refiere. El concepto de triples compuestos por un sujeto, un predicado o verbo y un objeto es la base de almacenamiento de estas bases de datos. El lenguaje de consulta para este tipo de bases de datos también es propio (SPARQL) y tiene ciertas similitudes con el SQL. Podéis obtener más información https://www.w3.org/RDF/ y en https://www.w3.org/TR/sparql11-query/.

(más…)

Estadísticas supersíncronas

Estadísticas supersíncronas

En nuestro día a día cuando hablamos de estadísticas en SQL Server casi siempre nos centramos en la necesidad de su mantenimiento periódico, a la recomendación de mantener activas las actualizaciones automáticas de estadísticas y, salvo en ciertos casos, las ventajas de la actualización asíncrona de estadísticas. (más…)

Combinando NGrams y FullText Search

Combinando NGrams y FullText Search

Con bastante frecuencia nos encontramos que nuestros clientes sufren problemas de rendimiento debido a filtros sobre campos de texto. Es bastante habitual que se quiera permitir filtrados “libres” sobre textos, descripciones, documentos, etc. pero no se tenga en cuenta que, en función del patrón de búsqueda, esto puede ser bastante pesado para SQL Server. (más…)

Cazando vampiros de memoria en SQL Server

Cazando vampiros de memoria en SQL Server

Un recurso muy valioso para cualquier motor de base de datos es la memoria. Esta memoria se utilizará con muchos fines, destacando especialmente el cacheo de páginas/segmentos de datos, así como planes de ejecución. También se necesitará memoria para la propia ejecución de las consultas, para mantener temporalmente los buffers necesarios para el procesamiento de los datos, para su ordenación, para almacenar tablas hash, etc. Desgraciadamente no existe un punto único desde donde podamos determinar y diagnosticar todos los consumos de memoria independientemente de su naturaleza por lo que al final tendremos que ir analizando desde un punto de vista más alto hasta uno más bajo para poder poner el dedo exactamente en la llaga que origina el dolor al servidor. (más…)

Integridad referencial en SQL Server 2017 Graphs

Integridad referencial en SQL Server 2017 Graphs

En SQL Server 2017 se incorpora por primera vez en el motor de SQL Server el soporte nativo de grafos. En el SolidQ Summit 2018 impartí una sesión dedicada precisamente a mostrar en detalle esta nueva funcionalidad. En este post vamos a tratar cómo podemos añadir un workaround a una limitación de integridad que no se incluye por defecto y nos parece de bastante importancia.

(más…)

La importancia del connection pooling cuando usamos SSL/TLS sobre TDS

La importancia del connection pooling cuando usamos SSL/TLS sobre TDS

El uso de connection pooling es una técnica importante desde el punto de vista de rendimiento de SQL Server. Si cada petición que lanzáramos a la base de datos tuviera que pasar necesariamente por el proceso completo de conexión al servidor, con su autenticación, etc. el rendimiento sería muy pobre.

Desgraciadamente aún nos encontramos con aplicaciones, casi siempre heredadas, que no utilizan connection pooling y por tanto abren y cierran físicamente la conexión cada vez con la base de datos. Una buena forma de detectar si nuestras aplicaciones están utilizando o no connection pooling es analizando los contadores de rendimiento Login/sec y Logout/sec. Cuando estos valores sean elevados respecto al número de conexiones existentes entonces con una alta probabilidad estaremos teniendo este problema al menos en parte de las aplicaciones que conectan contra nuestro servidor.

En algunos casos extremos podemos tener valores elevados de estos valores si tenemos muchas aplicaciones que, aun utilizando pooling, generen picos de conexiones concurrentes elevadas. Esto hará que el tamaño del pool crezca para poder servir este número de conexiones que se necesitan de forma puntual, generando un pico de Login/sec. Pasado un tiempo de inactividad (por defecto suele ser 60 segundos) las conexiones se cerrarán por falta de uso generando un pico de Logout/sec. Si tenemos muchos clientes con este mismo comportamiento podemos llegar a tener, en todo momento, un número elevado de Login/sec y de Logout/sec causados por estas oscilaciones en los tamaños de los pools sin que realmente tengamos aplicaciones que no utilicen connection pooling. Este timeout de desconexión puede indicarse al proveedor vía cadena de conexión o también mediante configuración directa del proveedor.

En el caso de ODBC por ejemplo podemos controlar el pool timeout (CPTimeout) desde el administrador de Data Sources:

También tenemos casos en los que el mal uso del connection pooling es el problema de los problemas de rendimiento sufridos. Por ejemplo, imaginemos que para evitar el delay que ocurre cuando nos conectamos a un servidor a través de un pool y tenemos que abrir la conexión físicamente se decide configurar un mínimo de 100 conexiones abiertas en una aplicación cliente-servidor. Puede parecer excesiva esta medida, pero cuando la autenticación del usuario recae en Active Directory, tenemos muchos usuarios distintos y el AD se encuentra saturado o lejano geográficamente (y no tenemos AD secundarios locales) el proceso de login puede durar hasta varios segundos. Seguramente alguien se ha topado con este problema especialmente al intentar conectar con SQL Database desde el Microsoft SQL Management Studio. Por defecto el Microsoft SQL Management Studio utiliza 15 segundos como login timeout lo cual resulta insuficiente en ocasiones para completar el proceso de enrutado y login.

El problema de mantener un pool con un mínimo tan elevado de conexiones es que éstas no se cerrarán y si el número de usuarios de dicha aplicación cliente-servidor aumenta, el número de conexiones aumentará también de forma lineal. El límite de conexiones que una instancia de SQL Server puede aceptar es de 32767 por lo que con menos de 330 usuarios de la aplicación el servidor quedará saturado no aceptando más usuarios ni conexiones. Por tanto, la clave estará en configurar de forma equilibrada los valores mínimos para el pool de conexiones, el valor máximo y el tiempo que dichas conexiones permanecerán abiertas.

Una vez introducida la importancia del buen uso del connection pooling queremos mostrar otro escenario que puede amplificar más aún el impacto de no utilizar connection pooling. Concretamente un cliente nos contactó con problemas de rendimiento derivados de la activación de la encriptación SSL de las conexiones con SQL Server. No es habitual encontrarnos con este tipo de problemas ya que, aunque la encriptación SSL tiene un coste extra, en general es asumible y no supone más que un pequeño incremento en el consumo de CPU total del servidor. Sin embargo, en este caso el consumo de CPU no era excesivo y los tiempos de ejecución de las consultas similares, pero sin embargo lo que sí teníamos eran ralentizaciones durante el proceso de conexión. Por tanto, el problema de rendimiento era debido a esperas durante el proceso de conexión a la base de datos una vez se habilitaba y forzaba el uso de SSL.

Para verificar que efectivamente existe un impacto medible realizamos una prueba de concepto donde estresaríamos una máquina virtual para comprobar el impacto de forzar la encriptación SSL. El primer paso es configurar SQL Server con un certificado SSL apropiado para ello. Es importante que el certificado cumpla ciertas condiciones ya que en caso contrario no será aceptado. En la sección de troubleshooting de este artículo podemos encontrar más información: (https://support.microsoft.com/en-us/help/316898/how-to-enable-ssl-encryption-for-an-instance-of-sql-server-by-using-mi)

Aunque nos salgamos un poco de contexto es conveniente indicar que este certificado no tiene nada que ver con el que necesitaríamos por ejemplo para una encriptación TDE. Para poder encriptar con TDE necesitaríamos un certificado obtenido siguiendo el siguiente proceso: https://blogs.msdn.microsoft.com/sql_pfe_blog/2014/02/04/generating-a-trusted-tde-certificate-in-the-proper-format-from-a-certificate-authority/ Si tuviéramos que reutilizar un pfx existente habría que hacer una conversión con esta herramienta para adaptarlo al formato requerido: https://support.microsoft.com/en-ph/help/2914662/how-to-use-pfx-formatted-certificates-in-sql-server

Para la prueba de concepto crearemos un certificado autofirmado con este comando powershell:

Con este comando el certificado creado se cargará en el store “personal”:

 

Para que la validez del certificado pueda ser validada tendremos que exportar dicho certificado e importarlo dentro la carpeta con los certificados raíz de confianza. Normalmente este proceso no será necesario ya que el certificado que utilicemos vendrá de una fuente de certificación de confianza:

Antes de forzar el uso de encriptación realizaremos una prueba donde lanzaremos una consulta extremadamente sencilla con 50 threads y 100 iteraciones. Antes de lanzar la carga configuraremos la conexión a la base de datos sin connection pooling:

Podemos ver que sin encriptación SSL tenemos un tiempo total de ejecución de 14 segundos y un tiempo de respuesta medio de 9 milisegundos que nos servirá como línea base cuando comparemos con el entorno con SSL. Si monitorizamos el número de logins por segundo que alcanzamos podemos ver que llegamos a un pico de 450 por segundo:

Para configurar la encriptación SSL abriremos el Configuration Manager de SQL Server y en la sección “protocols” editaremos sus propiedades y activaremos el flag para forzar la encriptación de las conexiones:

En la pestaña “certificate” seleccionaremos el certificado que hemos creado previamente y reiniciaremos el servicio de SQL Server:

Antes de lanzar la carga comprobaremos que efectivamente las conexiones que realicemos contra el servidor están efectivamente encriptadas consultando la DMV sys.dm_exec_connections:

El siguiente paso será volver a lanzar la carga anterior contra el servidor:

Podemos ver que el tiempo total ha subido a casi 19 segundos (un 35% más) y el tiempo medio por ejecución de 9ms a 46ms. En lo que respecta al número de conexiones por segundo que podemos procesar vemos que se estancan en menos de 300 (vs las 450 que alcanzábamos sin SSL):

Queda por tanto claro que el número de conexiones por segundo que podemos abrir depende de forma importante de si habilitamos el SSL o no. En casos donde no tengamos paralelismo o éste sea más moderado las latencias por conexión pueden tener un impacto mucho mayor ya que el aumento de la latencia será más significativo. En el siguiente gráfico mostramos un caso real donde podemos ver que pasamos de tiempos de 10 segundos para un proceso serial sin SSL a más de 30 segundos con el SSL activado para el mismo proceso:

En conclusión, en condiciones normales, donde el connection pooling es utilizado de forma correcta, el uso de SSL no supone una sobrecarga relevante ni genera problemas. Sin embargo, donde ya teníamos una situación problemática previa (no uso de connection pooling y un proceso serial)  la activación de SSL sí puede empeorar la situación de forma notable. Recomendamos por tanto antes de habilitar a ciegas el SSL realizar una revisión previa del servidor para asegurarnos que no tenemos ya un problema previo que pudiera verse amplificado con su activación.

Azure SQL Data Sync

Azure SQL Data Sync

Una pregunta que suele surgirnos con cierta frecuencia cuando planteamos escenarios híbridos utilizando SQL Database es cómo se puede mantener la información sincronizada con un entorno onpremise.

Cuando la sincronización necesaria es únicamente unidireccional y en el sentido “ascendiente”, desde el entorno onpremise hacia SQL Database la opción que suele dar mejor resultado es el uso de replicación transaccional. En cierta forma hablamos de una versión “descafeinada” de replicación transaccional ya que tendremos bastantes limitaciones adicionales, pero pese a ello en muchos casos es suficiente para tener una sincronización básica. Para poder utilizarla tendremos que cumplir ciertos requisitos en nuestro entorno: https://docs.microsoft.com/en-us/sql/relational-databases/replication/replication-to-sql-database

En el caso que se requiera una sincronización bidireccional nos encontramos que no disponemos de soporte nativo por parte de la replicación tradicional. Es decir, ni la replicación de mezcla, ni la transaccional actualizable, ni la transaccional P2P van a funcionar contra SQL Database. Por tanto, quedamos en manos de “componentes externos” para realizar esta sincronización. La lista de software de terceros que nos permite sincronizar bases de datos es muy extensa y simplemente nombraremos algunos como referencia: SymmetricDS (Open source), Attunity Replicate, Quest Shareplex, HVR Database Replication, etc. Otra opción que siendo un software externo es un producto 100% Microsoft es Azure SQL Data Sync y nos permitirá sincronizar datos entre bases de datos SQL Server onpremise y cloud de forma bidireccional.

En este post vamos a mostrar el funcionamiento de esta solución que, aun siendo bastante sencilla, puede ser una opción válida para nuestras necesidades. En ocasiones en este post aludiré entre paréntesis a la terminología habitual en entornos con replicación de SQL Server para facilitar a los conocedores de ésta cómo encaja una solución con otra.

El modelo de sincronización de Azure SQL Data Sync es el de un modelo Hub&Spoke donde tendremos un Hub (publicador), una SyncDB con su Sync App (base de datos distribution y agente de distribución) y varios Members/Spokes (subscriptores). El Hub deberá estar en una base de datos SQL Database necesariamente, mientras que los Members podrán ser bases de datos SQL Database o bases de datos onpremise asociadas a un servicio de sincronización (agente de distribución). La dirección de replicación podrá ser Hub to Member (download), Member to Hub (upload) o ambas simultáneamente. La detección de cambios se realiza mediante triggers y se almacenan los cambios en unas tablas de cambios (como en la réplica de Mezcla).

Para la identificación unívoca de las filas durante la sincronización se utiliza la clave primaria (el equivalente al rowguid en réplica de Mezcla) por lo que se convierte el tener clave primaria en una condición obligatoria para sincronizar tablas (como en la réplica transaccional). Es importante no modificar el valor de la clave primaria mediante un update, en caso necesario se debe implementar como un delete+insert.

Como las cosas se entienden mejor con un ejemplo vamos a crear un grupo de sincronización (publicación) involucrando a una base de datos Hub en SQL Database (publicador), una base de datos Sync en SQL Database (distribution), una base de datos Member1 en SQL Database (suscriptor) y una base de datos Member2 en SQL Server onpremise (suscriptor). El primer paso es crear cada una de las bases de datos en los dos entornos. Todas las bases de datos excepto la Hub (publicador) las crearé vacías:

Para comenzar seleccionaremos la base de datos Hub en Azure, abriremos la pantalla de sincronización y crearemos un nuevo grupo:

Pondremos un nombre al grupo de sincronización (publicación) y seleccionaremos la base de datos que queremos utilizar como base de datos Sync (distribution). También configuraremos la frecuencia de refresco (5 minutos es lo mínimo por ahora) así como la política de resolución de conflictos (Hub win o Member win):

Una vez creado (puede tardar unos segundos) procederemos a añadir los miembros del grupo (suscriptores). Comenzamos añadiendo el Member1 indicando los credenciales necesarios así como que queremos una sincronización bidireccional:

El siguiente paso es añadir el Member2 (suscriptor) onpremise. Para ello deberemos crear e instalar el agente de sincronización, indicando un nuevo nombre para identificarlo y generando una clave de sincronización:

Una vez tenemos la instalación completada, ejecutaremos el Microsoft SQL Data Sync para configurarlo. Comenzaremos introduciendo la clave de sincronización, así como las credenciales a utilizar para conectar a la base de datos Sync (distributor):

Es posible que obtengamos un error debido a que no hemos creado una regla específica en el firewall para la IP del servidor onpremise. En dicho caso crearemos la regla correspondiente:

Una vez conectemos se nos activará la posibilidad de registrar bases de datos:

Registraremos nuestra base de datos Member2 onpremise utilizando, en nuestro caso, un login dedicado llamado “sync” y que tiene permisos dbo_owner sobre la base de datos Member2:

Una vez registrada la base de datos nos aparecerá en el listado como “reachable” por lo que podremos seguir la configuración desde el portal de Azure:

Desde el portal seleccionaremos un nombre para el miembro así como el sentido de sincronización, bidireccional en este caso:

Una vez tenemos los dos miembros ya configurados podemos seguir con la configuración del grupo de sincronización:

Cuando configuramos las tablas a incluir en la replicación veremos que es bastante probable que algunas columnas no puedan ser replicadas. Esto es debido a que tipos de datos como sysname o tipos definidos por el usuario no están soportadas. Es importante revisar estas limitaciones ya que es bastante probable que alguna de las tablas que deseamos replicar las incumplan.

También existen otras limitaciones, como la longitud de los nombres de las bases de datos y tablas a replicar que debe ser de máximo 50 caracteres, lo cual puede en algunos casos no ser suficiente:

Una vez confirmemos que ya hemos seleccionado todo lo que deseamos replicar el sistema comenzará a intentar crear el esquema de las tablas en los suscriptores. En nuestro caso obtuvimos un error debido a que hemos seleccionado columnas calculadas para replicar (no nos lo ha impedido el GUI) pero actualmente no están soportadas:

La solución en este caso es tan sencilla como eliminar dichas columnas calculadas de la replicación para que funcione. En otros casos tenemos que crear manualmente algunos objetos que no se replican, como es el caso de las secuencias. Si no creamos las secuencias cuando se intenten desplegar objetos que dependan de dichas secuencias, obtendremos un error:

Una vez la secuencia esté creada en los miembros (suscriptores) el proceso de sincronización funcionará:

El siguiente paso es realizar algunas pruebas para comprobar que la sincronización funciona correctamente de forma bidireccional. Antes de eso analizaremos un poco qué artefactos se añaden a la base de datos para la sincronización:

Básicamente tenemos una tabla de cambios por cada tabla replicada (artículo) y tres triggers. Además, tenemos una tabla llamada provision_marker_dss que mantiene las versiones de cada una de las tablas de forma que al sincronizar podemos saber si tenemos la última versión o no a la hora de enumerar los cambios a aplicar.

Para testear la sincronización comenzaremos con 1000 inserciones en un suscriptor onpremise:

INSERT INTO [dbo].[ErrorLog]

([ErrorTime]

,[ErrorNumber]

,[ErrorSeverity]

,[ErrorState]

,[ErrorProcedure]

,[ErrorLine]

,[ErrorMessage])

VALUES

(getdate()

,1

,null

,null

,null

,null

,'Mensaje de error de prueba. Cualquier texto nos vale.')

GO 1000

Vemos que, al intentar sincronizar, ocurre un error:

Desgraciadamente no se nos dice en qué consiste el error exactamente ni tenemos más información para diagnosticar el problema, pero si intentamos simular manualmente el insert rápidamente veremos cuál es el problema:

SET XACT_ABORT ON

BEGIN tran

SET IDENTITY_INSERT errorlog ON

INSERT INTO errorlog ( ErrorLogID,     ErrorTime,    ErrorNumber,       ErrorSeverity,       ErrorState,   ErrorProcedure,       ErrorLine,    ErrorMessage)

VALUES (1,    '2018-03-08 15:08:15.287',  1,     NULL,  NULL,       NULL,  NULL,  'Mensaje de error de prueba. Cualquier texto nos vale.')

SET IDENTITY_INSERT errorlog OFF

Rollback

Es decir, que el hecho de haber excluido una columna para sincronización no es suficiente ya que, concretamente en este caso, la columna no sincronizada no admite valores nulos y por ello falla al realizar las inserciones sin dicho valor. Sería realmente útil tener más información sobre el error y no un mensaje genérico en el log de SQL Data Sync. Dicho esto, permitiremos valores nulos en dicha columna para que podamos sincronizarla:

ALTER TABLE errorlog ALTER COLUMN username sysname null

Cuando la siguiente sincronización ocurre veremos cómo los 1000 registros “suben” al hub y, en la siguiente sincronización, bajan también al Member1:

Vamos a probar a realizar algún cambio con algo más de volumen, en vez de 1000 registros vamos a insertar 1 millón de registros en onpremise en unos 15 segundos:

INSERT INTO [dbo].[ErrorLog]

([ErrorTime]

,[ErrorNumber]

,[ErrorSeverity]

,[ErrorState]

,[ErrorProcedure]

,[ErrorLine]

,[ErrorMessage])

SELECT TOP (1000000)

getdate()

,1

,null

,null

,null

,null

,'Mensaje de error de prueba. Cualquier texto nos vale.'

FROM sys.objects s1, sys.objects s2, sys.objects s3, sys.objects s4, sys.objects s5

Un efecto que vemos rápidamente es que nuestra base de datos HUB (una S3) sube mucho su consumo de DTUs:

Algo parecido nos pasa con Member1 (S2) y podemos ver también que en ambos casos es la IO del log de transacciones la que nos hace de cuello de botella:

Si analizamos los tiempos totales necesarios para el upload, vemos que hemos necesitado unos 20 minutos para subir el millón de registros que se generaron en 15 segundos en el entorno onpremise (esta es una problemática habitual de los sistemas de replicación):

Para sincronizar la base de datos member2 se requirieron 4 sincronizaciones, de aproximadamente 150K, 300K, 500K y 50K filas cada una. El tiempo total para tener el millón de cambios sincronizados fue de unos 34 minutos. Debemos tener en cuenta el dimensionamiento de los miembros-suscriptores (subirlos a Premium) si queremos tener un buen rendimiento de sincronización con volúmenes altos de cambios.

A continuación, vamos a probar el funcionamiento de la resolución de conflictos. Para ello de forma simultánea aplicaremos un cambio en un registro tanto en los dos members como en el hub:

UPDATE errorlog

SET errormessage=REPLACE(errormessage,'texto', DB_NAME(DB_ID()))

WHERE errorlogid=10000

Podemos ver que tenemos dos iteraciones, una primera en la que se realiza un upload y un download en cada member, y una segunda iteración donde únicamente descargamos del hub:

El resultado tras la sincronización es el esperado en base a la configuración, que prevalece la versión del hub:

En el caso que únicamente realicemos los cambios de forma simultánea en el los dos members veremos algo parecido, primero los dos subirán el cambio al hub y en la siguiente iteración obtendrán un cambio a aplicar:

La política de resolución de conflictos parece ser que gane el cambio realizado más tardíamente. En el caso que, por ejemplo, en cada member se realice una inserción de un registro con el mismo valor para una clave primaria tendríamos el mismo comportamiento solo que en vez de ganar el “último” lo haría el primero que hizo la inserción

En la base de datos Sync encontramos tablas con información útil respecto a la configuración de la sincronización. Por ejemplo, con fines de testing, si el volumen de datos a sincronizar en cada iteración no es elevado podermos forzar frecuencias de refresco inferiores, en vez de 300 segundos como mínimo podemos bajarlo a 60 segundos:

Si volvemos atrás en el tiempo todo apunta a que lo que tenemos en Azure es una implementación del Microsoft Sync Framework que lleva ya desarrollándose y evolucionando desde hace 10 años. La conectividad con SQL Database de este framework y el cliente data sync 1.0 apareció por 2010 aproximadamente. Cierto es que de las primeras versiones recuerdo haberme quedado con un mal sabor de boca pero parece que con los años y con esta versión sobre Azure la solución parece más estable y más utilizable. Aún quedan algunos flecos relacionados con los tipos de datos y parece que el despliegue de cambios de esquema puede ser algo complejo por ahora. Por ello no lo recomendaría para escenarios donde una réplica de mezcla tradicional sea aplicable, por ejemplo, en un entorno onpremise o bien entre instancias SQL Server virtuales corriendo en Azure. En un futuro quizás Managed Instances incluyan soporte para replicación nativa de SQL Server, pero a día de hoy no está soportada Managed Instances ni con Azure SQL Data Sync ni con replicación tradicional de SQL Server (probablemente en breve lo esté).

También debemos tener en cuenta que esta solución de Azure SQL Data Sync podemos aumentar su escalabilidad con una arquitectura multinivel similar a la que configuraríamos mediante republicaciones en una arquitectura de replicación de SQL Server tradicional. Podéis encontrar más información al respecto aquí: https://azure.microsoft.com/es-es/blog/sync-sql-data-in-large-scale-using-azure-sql-data-sync/ También es recomendable que si os planteáis utilizar esta herramienta reviséis las buenas prácticas publicadas aquí: https://docs.microsoft.com/en-us/azure/sql-database/sql-database-best-practices-data-sync