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.
La alternativa a día de hoy pasa por utilizar la misma técnica que utilizan muchas aplicaciones de backup; el interfaz VDI. El problema es que no tenemos una forma sencilla desde SSMS para poder realizar este tipo de backups. Afortunadamente la comunidad ha creado y publicado una pequeña tool (https://github.com/adzm/mssqlPipe) que apoyándose en VDI nos permite simular esta funcionalidad perdida. Básicamente esta herramienta nos va a permitir utilizar los stdin y stdout del operativo para realizar backups y restaurarlos.
Vamos a comenzar con un caso sencillo donde vamos a utilizar esta herramienta para “clonar” una base de datos existente en una instancia mediante un backup/restore pero sin tener que utilizar ningún fichero .bak intermedio. Esto nos permite ahorrar espacio y además acelerar el tiempo necesario, ya que si hacemos un backup tradicional tendremos que esperar a que finalice antes de poder lanzar el restore. En la línea de comandos tendríamos que indicar que queremos hacer una operación de backup en una instancia y luego una de restore donde las enlazaremos mediante un pipe:
mssqlPipe sql2017 backup Backup_test | mssqlPipe sql2017 restore Backup_test2
La salida del comando nos va mostrando las operaciones que se van realizando durante su ejecución:
Para asegurarnos que todo funciona según lo previsto hemos añadido una monitorización mediante la herramienta Process Monitor de sysinternals para ver exactamente qué operaciones realiza SQL Server sobre el disco durante el proceso:
Podemos ver que se realizan lecturas y escrituras de forma concurrente e intercalada. A medida que se va leyendo del fichero de datos original se va realizando la escritura mientras que el “contenido” del backup no se materializa en ningún punto, únicamente vive dentro del pipe durante el tiempo de ejecución en forma de stream. También si vemos lo que se lanza contra SQL Server vemos que se están creando un par de virtual devices y se están usando para realizar el backup con copy_only y luego un restore con with move a la ruta por defecto:
Si lo que queremos hacer es una operación “remota”, donde el servidor destino se encuentra en otra máquina necesitaremos utilizar alguna herramienta que nos permita conectar por pipes dos procesos que ejecutan en distinta máquina. Dentro del mismo pack de herramientas de SysInternals tenemos una herramienta que nos permite realizar ejecución remota y además añade esta funcionalidad de redirigir la entrada/salida estándar entre los procesos remotos (PSExec). Sin embargo, he encontrado que en algunos casos esta herramienta está “corrompiendo” el stdout además requiere revisar firewalls, permisos, de ejecución remota, etc. desde una máquina a la otra. Podemos probar como funciona esta herramienta lanzando un ipconfig por ejemplo de forma remota:
psexec.exe \\DESKTOP-FLFONP9 ipconfig /all
Sin embargo, como he obtenido resultados variables (unos backups funciona, otros no, etc.) he decidido utilizar una herramienta mucho más básica y sencilla llamada Netcat. Pese a la mala fama que tiene (por su funcionalidad de port-scanning y por ser utilizada por malwares varios) es una utilidad muy útil cuando queremos configurar una transferencia entre puertos sencilla. En nuestro caso lo que necesitamos es transmitir la salida de nuestro backup y recibirla en otro servidor para realizar el restore.
Comenzamos testeando el funcionamiento en local, para ello utilizaremos abriremos para la escucha un puerto arbitrario 12345 en la dirección loopback local 127.0.0.1. Lo que leamos por dicho puerto lo enviaremos mediante el mssqlPipe a un comando de restore:
nc.exe -l 127.0.0.1 -p 12345 | mssqlPipe.exe sql2017 restore Backup_test2
Por otro lado lanzaremos el backup y lo enviaremos a dicho puerto mediante Netcat en modo “envío”:
mssqlPipe.exe sql2017 backup Backup_test | nc.exe 127.0.0.1 12345
El resultado es el esperado, un restore y backup por red sin materializarse el fichero intermedio:
El siguiente paso es realizar este mismo proceso pero entre dos equipos distintos. Verificaremos que tenemos abierto el puerto 12345 y procederemos utilizando una IP externa (10.40.200.85) en vez del loopback local. El comando para “escuchar” sería el siguiente:
nc.exe -l -p 12345 | mssqlPipe.exe restore backup_test2
El comando para enviar sería el siguiente:
mssqlPipe.exe sql2017 backup Backup_test | nc.exe 10.40.200.85 12345
A la izquierda veremos el progreso mientas vamos recibiendo el backup y a la derecha mientras lo vamos enviando:
Desgraciadamente el rendimiento del port de Netcat en Windows no parece ser muy bueno, dando un throughput relativamente bajo, solo 5 MB/s por lo que no sería muy buena opción para grandes transferencias. Además, tenemos el problema que muchos antivirus pueden darnos guerra con esta utilidad por los motivos ya comentados. Existen algunas alternativas, en powershell, pero que no me han funcionado correctamente. Los problemas fueron varios, como no realizar correctamente el “streaming” haciendo un bloqueo a nivel de pipe esperando a que finalice el backup (y guardándolo localmente en un temporal antes de enviarlo) o corromper los finales de los ficheros en ciertos casos. En resumen, que algo relativamente sencillo se puede volver más complicado de lo deseable.
Afortunadamente tenemos otras alternativas a Netcat, como Socat, que viene a proporcionar una funcionalidad similar aunque Socat es más potente si necesitamos aplicar procesamiento sobre los datos del stream. Para nuestro caso concreto solo necesitaremos la función de escuchar en un puerto y redirigir correctamente la salida y entrada standard. Hemos utilizado un port que se apoya en Cygwin.dll de la versión de Socat 1.7.3 (https://cygwin.com/cgi-bin2/package-grep.cgi?grep=socat&arch=x86_64). Para escuchar en el puerto 12345 utilizaríamos el siguiente comando:
socat TCP-LISTEN:12345 - | mssqlPipe.exe sql2017 restore Backup_test2
Y para el envío del backup utilizaremos el siguiente comando:
mssqlPipe.exe sql2017 backup Backup_test | socat - TCP:127.0.0.1:12345
Podemos ver que en este caso el rendimiento es entre 6 y 7 veces mayor que con el port de Netcat cuando utilizamos la dirección loopback. Si utilizamos una IP externa y traspasamos la red el rendimiento es también similar, aproximadamente 30 MB/s de velocidad de backup&restore. Los comandos serán los mismos solamente cambiando la IP:
socat TCP-LISTEN:12345 - | mssqlPipe.exe sql2017 restore Backup_test2
Y para el envío del backup utilizaremos el siguiente:
mssqlPipe.exe sql2017 backup Backup_test | socat - TCP:10.40.200.85:12345
Si utilizamos la funcionalidad de backup y restore que nos ofrece mssqPipe estamos un poco limitados respecto a las operaciones que podemos realizar. Sin embargo, es posible utilizar únicamente la funcionalidad de crear un dispositivo VDI y lanzar nuestros comandos de backup/restore. Por ejemplo, si quisiéramos hacer un restore “with no recovery” en el destino podríamos hacerlo modificando el comando en destino. El proceso sería, primero abrimos en destino con socat y mssqlPipe el “canal + dispositivo” por el que recibiremos el backup:
socat TCP-LISTEN:12346 - | mssqlPipe.exe pipe to MiPipe
A continuación, lanzaremos el backup, el cual quedará bloqueado al no tener nadie “leyendo” al otro lado y llenarse los buffers de backup. Este comando no cambia respecto al anterior escenario:
mssqlPipe.exe sql2017 backup Backup_test | socat - TCP:10.40.200.85:12346
En el destino deberemos lanzar un comando de restore que lea del device “MiPipe” y haga el restore correspondiente:
Cuando se pone en marcha el restore veremos cómo va pasando el backup por el pipe y se acaba restaurando la base de datos en estado “no recovery” tal y como indicamos:
Lo mismo podríamos hacer para el origen y lanzar nuestros comandos de backup personalizados contra el device, para, por ejemplo hacer backups del log y aplicarlos en destino. En este caso crearemos otro device pero que redirija lo que escribamos en el comando de backup y lo envíe “al otro lado” donde estará escuchando el comando de restore correspondiente pero que lanzaremos posteriormente. Primero abrimos el pipe en destino:
socat TCP-LISTEN:12346 - | mssqlPipe.exe pipe to MiPipe
Luego abrimos el pipe en origen:
mssqlPipe.exe sql2017 pipe from MiPipe | socat - TCP:10.40.200.85:12346
Ahora lanzamos el backup log contra el pipe y, en este caso al ser tan pequeño, no tiene ni que esperar a que lo consuman en destino ya que cabe en un simple buffer:
En destino lanzaríamos el restore correspondiente leyendo del pipe:
Con la extensión al mundo Linux de SQL Server estas operaciones serían aún más sencillas de implementar ya que en un sistema operativo POSIX “todo” es un fichero, por lo que sería mucho más sencillo enviar directamente el backup contra un dispositivo mapeado a un fichero que a su vez realizara este piping contra otro device en destino de donde leeríamos el backup como si fuese un fichero más.
En conclusión, aunque no tengamos esta funcionalidad de backups vía pipes de forma nativa en versiones modernas, es posible emularla mediante el uso de herramientas externas. No creemos que estas herramientas sean las ideales para ser utilizadas de forma habitual como herramientas de backup pero sí pueden ser muy útiles en circunstancias especiales donde no tenemos donde materializar el backup o queremos, por rendimiento, evitar tener que esperar a que finalice el backup para comenzar el restore. También nos permitirá “emular” la funcionalidad del automatic seeding que tenemos en los grupos de disponibilidad pero sin algunas de sus limitaciones, ya que los comandos de backup y restore pueden ser totalmente personalizados por nosotros. Podríamos utilizar esta técnica para inicializar log shippings, mirrors, grupos de disponibilidad, etc. para realizar compresiones/descompresiones “al vuelo” con 7zip, para aplicar algún tipo de encriptación/desencriptación personalizada sobre los backups, etc.