Vitess: Escalado Horizontal de MySQL

Vitess es una plataforma de clustering para MySQL desarrollada por YouTube que permite escalar horizontalmente la capa de base de datos mediante sharding, gestión de conexiones y migración de esquemas sin tiempo de inactividad. Hoy en día es la solución de referencia para organizaciones que necesitan escalar MySQL más allá de lo que una sola instancia puede ofrecer, y es la base de datos utilizada por PlanetScale.

Requisitos Previos

  • Ubuntu 22.04 o CentOS/Rocky Linux 8+
  • MySQL 8.0+ o Percona Server en cada nodo de tablet
  • etcd o ZooKeeper para la topología del clúster
  • Go 1.21+ si se compila desde código fuente
  • Al menos 4 GB de RAM y 4 vCPUs por servidor

Arquitectura de Vitess

Vitess se compone de varios componentes clave:

  • VTGate: proxy de entrada que recibe las consultas SQL de las aplicaciones y las enruta a los shards correctos
  • VTTablet: proceso que corre junto a cada instancia MySQL, gestionando las conexiones y el pool
  • VTorc: controlador de orquestación de MySQL para failover automático
  • vtctld: daemon de control para gestión y administración
  • Topology service: etcd o ZooKeeper para almacenar la topología del clúster

Instalación en Linux

# Descargar los binarios de Vitess
VITESS_VERSION="v19.0.0"
wget "https://github.com/vitessio/vitess/releases/download/${VITESS_VERSION}/vitess-${VITESS_VERSION}-$(uname -s | tr '[:upper:]' '[:lower:]')-amd64.tar.gz"

tar -xzf vitess-${VITESS_VERSION}-linux-amd64.tar.gz
mv vitess-${VITESS_VERSION}-linux-amd64/bin/* /usr/local/bin/

# Verificar la instalación
vtgate --version
vttablet --version
vtctldclient --version

# Instalar etcd para la topología del clúster
apt update && apt install -y etcd

# Configurar etcd como cluster de 3 nodos (recomendado para producción)
cat > /etc/etcd/etcd.conf.yml << 'EOF'
name: etcd-1
data-dir: /var/lib/etcd
listen-client-urls: http://0.0.0.0:2379
advertise-client-urls: http://etcd-1.miempresa.local:2379
listen-peer-urls: http://0.0.0.0:2380
initial-advertise-peer-urls: http://etcd-1.miempresa.local:2380
initial-cluster: etcd-1=http://etcd-1.miempresa.local:2380,etcd-2=http://etcd-2.miempresa.local:2380,etcd-3=http://etcd-3.miempresa.local:2380
initial-cluster-state: new
EOF

systemctl enable etcd && systemctl start etcd

Configuración de VTGate y VTTablet

# Variables de entorno comunes
export VTDATAROOT=/var/lib/vitess
export VTCTLD_ADDR=vtctld.miempresa.local:15999
export TOPO_FLAGS="--topo_implementation etcd2 --topo_global_server_address etcd-1.miempresa.local:2379,etcd-2.miempresa.local:2379 --topo_global_root /vitess/global"

# Crear la estructura de directorios
mkdir -p $VTDATAROOT/{tabletdata,vtgate,vtctld}

# Inicializar la topología global
vtctl $TOPO_FLAGS InitShardMaster

# Iniciar el daemon de control vtctld
vtctld \
  $TOPO_FLAGS \
  --cell=us-east \
  --web_dir=/usr/local/share/vitess/web/vtctld2/app \
  --port=15000 \
  --grpc_port=15999 \
  --service_map='grpc-vtctl,grpc-vtctld' \
  --log_dir=$VTDATAROOT/vtctld \
  &
# Configurar un VTTablet para una instancia MySQL específica

# Primero, crear el keyspace y shard en la topología
vtctldclient \
  --server=localhost:15999 \
  CreateKeyspace \
  --durability-policy=semi_sync \
  miapp

# Agregar el tablet
TABLET_ALIAS="us-east-0000000101"

vttablet \
  $TOPO_FLAGS \
  --cell=us-east \
  --tablet-path="us-east-0000000101" \
  --tablet_hostname=mysql-01.miempresa.local \
  --db_host=localhost \
  --db_port=3306 \
  --db_charset=utf8mb4 \
  --db_app_user=vitess_app \
  --db_app_password=contraseña-app \
  --db_dba_user=vitess_dba \
  --db_dba_password=contraseña-dba \
  --keyspace=miapp \
  --shard='-' \
  --port=15101 \
  --grpc_port=16101 \
  --service_map='grpc-queryservice,grpc-tabletmanager' \
  --init_tablet_type=replica \
  --mycnf_server_id=101 \
  &

# Iniciar VTGate para recibir las consultas de la aplicación
vtgate \
  $TOPO_FLAGS \
  --cell=us-east \
  --cells_to_watch=us-east \
  --tablet_types_to_wait=PRIMARY,REPLICA \
  --service_map='grpc-vtgating' \
  --port=15001 \
  --grpc_port=15991 \
  --mysql_server_port=3306 \
  --mysql_auth_server_impl=none \
  &

# Las aplicaciones se conectan a VTGate en el puerto 3306 como si fuera MySQL normal
mysql -h vtgate.miempresa.local -P 3306 -u root -e "SHOW KEYSPACES;"

Estrategias de Sharding

# Definir un VSchema para el keyspace que describe cómo hacer sharding
cat > vschema.json << 'EOF'
{
  "sharded": true,
  "vindexes": {
    "hash": {
      "type": "hash"
    },
    "lookup_unique": {
      "type": "consistent_lookup_unique",
      "params": {
        "table": "usuario_lookup",
        "from": "email",
        "to": "usuario_id"
      },
      "owner": "usuarios"
    }
  },
  "tables": {
    "usuarios": {
      "column_vindexes": [
        {
          "column": "usuario_id",
          "name": "hash"
        },
        {
          "column": "email",
          "name": "lookup_unique"
        }
      ]
    },
    "pedidos": {
      "column_vindexes": [
        {
          "column": "usuario_id",
          "name": "hash"
        }
      ]
    },
    "productos": {
      "type": "reference"
    }
  }
}
EOF

# Aplicar el VSchema al keyspace
vtctldclient ApplyVSchema \
  --server=localhost:15999 \
  --keyspace=miapp \
  --vschema-file=vschema.json

# Verificar el VSchema aplicado
vtctldclient GetVSchema --server=localhost:15999 miapp

# Crear los shards para dividir el keyspace en dos partes (-80 y 80-)
vtctldclient CreateShard --server=localhost:15999 miapp/-80
vtctldclient CreateShard --server=localhost:15999 miapp/80-

Gestión de Esquemas

# Aplicar un cambio de esquema (DDL) a todos los shards simultáneamente
# Vitess usa Online DDL para evitar bloqueos

# Agregar una columna con Online DDL (sin bloqueo de tabla)
vtctldclient ApplySchema \
  --server=localhost:15999 \
  --keyspace=miapp \
  --ddl-strategy="online" \
  --sql="ALTER TABLE usuarios ADD COLUMN telefono VARCHAR(20) NULL"

# Ver el estado de las migraciones de esquema
vtctldclient GetSchemaMigrations \
  --server=localhost:15999 \
  miapp

# Ver los detalles de una migración específica
vtctldclient GetSchemaMigrations \
  --server=localhost:15999 \
  --migration-context=<migration-uuid> \
  miapp

# Revertir una migración Online DDL en curso
vtctldclient RevertSchemaMigration \
  --server=localhost:15999 \
  --migration-context=<migration-uuid> \
  miapp

# Verificar que el esquema es consistente en todos los shards
vtctldclient ValidateSchemaKeyspace --server=localhost:15999 miapp

Operaciones de Resharding

El resharding permite dividir un shard en dos o más shards cuando crece demasiado.

# Escenario: dividir el shard '-' (sin sharding) en '-80' y '80-'

# Paso 1: Crear los nuevos shards destino
vtctldclient CreateShard --server=localhost:15999 miapp/-80
vtctldclient CreateShard --server=localhost:15999 miapp/80-

# Paso 2: Arrancar tablets para los nuevos shards
# (repetir el proceso de iniciar vttablet para los nuevos servidores MySQL)

# Paso 3: Iniciar la copia de datos (reshard workflow)
vtctldclient Reshard \
  --server=localhost:15999 \
  --workflow=reshard_workflow \
  create \
  --source-shards='-' \
  --target-shards='-80,80-' \
  miapp

# Paso 4: Monitorear el progreso de la copia
vtctldclient Reshard \
  --server=localhost:15999 \
  --workflow=reshard_workflow \
  status \
  miapp

# Paso 5: Verificar que los datos son consistentes
vtctldclient Reshard \
  --server=localhost:15999 \
  --workflow=reshard_workflow \
  verifydata \
  miapp

# Paso 6: Cambiar el tráfico de lectura a los nuevos shards (sin downtime)
vtctldclient Reshard \
  --server=localhost:15999 \
  --workflow=reshard_workflow \
  switchtraffic --tablet-types=RDONLY,REPLICA \
  miapp

# Paso 7: Cambiar el tráfico de escritura a los nuevos shards
vtctldclient Reshard \
  --server=localhost:15999 \
  --workflow=reshard_workflow \
  switchtraffic --tablet-types=PRIMARY \
  miapp

# Paso 8: Limpiar los shards antiguos
vtctldclient Reshard \
  --server=localhost:15999 \
  --workflow=reshard_workflow \
  complete \
  miapp

Despliegue en Kubernetes

# Instalar el operador de Vitess con Helm
helm repo add vitess https://vitessio.github.io/vitess-helm-charts
helm repo update

# Crear el namespace
kubectl create namespace vitess

# Archivo de valores personalizado
cat > vitess-values.yaml << 'EOF'
etcd:
  replicaCount: 3

vtctld:
  replicaCount: 1
  
vtgate:
  replicaCount: 2
  mysqlPort: 3306
  webPort: 15001

keyspaces:
  - name: miapp
    durability: semi_sync
    shards:
      - name: "-80"
        tablets:
          - type: primary
            replicas: 1
          - type: replica
            replicas: 2
      - name: "80-"
        tablets:
          - type: primary
            replicas: 1
          - type: replica
            replicas: 2
EOF

helm install vitess vitess/vitess \
  --namespace vitess \
  --values vitess-values.yaml

# Verificar el despliegue
kubectl get pods -n vitess
kubectl get services -n vitess

# Conectarse a Vitess desde dentro del clúster
kubectl exec -n vitess -it <vtgate-pod> -- \
  mysql -h localhost -P 3306 -u root -e "SHOW KEYSPACES;"

Solución de Problemas

VTTablet no se conecta a MySQL:

# Verificar la conexión a MySQL desde el servidor
mysql -u vitess_dba -p -h localhost -e "SELECT 1;"

# Verificar los grants del usuario Vitess en MySQL
mysql -u root -p -e "SHOW GRANTS FOR 'vitess_dba'@'localhost';"

# Ver los logs del tablet
vttablet ... --log_dir=/var/log/vitess 2>&1 | grep -i error

Consultas lentas o errores de routing:

# Ver el estado del keyspace
vtctldclient GetKeyspace --server=localhost:15999 miapp

# Verificar que el VSchema es correcto
vtctldclient GetVSchema --server=localhost:15999 miapp

# Ver las estadísticas de VTGate
curl http://vtgate.miempresa.local:15001/debug/vars | jq .

Error durante el resharding:

# Ver el estado detallado del workflow
vtctldclient Workflow --server=localhost:15999 miapp show

# Si hay un error, pausar el workflow
vtctldclient Reshard \
  --server=localhost:15999 \
  --workflow=reshard_workflow \
  stop miapp

# Resolver el problema y reanudar
vtctldclient Reshard \
  --server=localhost:15999 \
  --workflow=reshard_workflow \
  start miapp

Conclusión

Vitess resuelve el problema del escalado horizontal de MySQL de una forma que es transparente para las aplicaciones: estas se conectan a VTGate como si fuera MySQL normal, mientras Vitess gestiona el sharding, la distribución de consultas y la gestión del esquema por debajo. El proceso de resharding sin tiempo de inactividad es especialmente valioso en producción, ya que permite redistribuir los datos cuando un shard crece demasiado sin interrumpir el servicio. Para organizaciones que han llegado al límite vertical de MySQL, Vitess es la ruta natural hacia el escalado horizontal.