Instalación de Sonic: Motor de Búsqueda Ligero

Sonic es un motor de búsqueda de texto completo minimalista y extremadamente eficiente escrito en Rust, diseñado para ser la pieza de búsqueda en aplicaciones donde los recursos son limitados. A diferencia de Elasticsearch o Solr, Sonic no almacena los documentos originales sino únicamente el índice invertido, lo que lo hace ideal como backend de búsqueda complementario a tu base de datos principal. Esta guía cubre la instalación en Linux, los canales de comunicación, la ingesta de datos y la integración con aplicaciones web.

Requisitos Previos

  • Ubuntu 20.04/22.04, Debian 11+ o CentOS 8+/Rocky Linux 8+
  • 128 MB de RAM mínimo (Sonic es muy eficiente en memoria)
  • Rust y Cargo (para compilar desde fuente) o binario precompilado
  • Puerto 1491 disponible

Instalación de Sonic

Instalación del binario precompilado

# Descargar el binario precompilado para Linux x86_64
SONIC_VERSION=1.4.9
wget "https://github.com/valeriansaliou/sonic/releases/download/v${SONIC_VERSION}/v${SONIC_VERSION}.tar.gz" -P /tmp
cd /tmp && tar xzf v${SONIC_VERSION}.tar.gz

# Mover el binario al PATH del sistema
sudo mv sonic /usr/local/bin/
sudo chmod +x /usr/local/bin/sonic

# Verificar la instalación
sonic --version

Compilación desde fuente con Cargo

# Instalar Rust si no está disponible
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source ~/.cargo/env

# Instalar dependencias de compilación
sudo apt install -y build-essential pkg-config libssl-dev  # Ubuntu/Debian
sudo dnf install -y gcc openssl-devel                       # CentOS/Rocky

# Clonar y compilar Sonic
git clone https://github.com/valeriansaliou/sonic.git /tmp/sonic-src
cd /tmp/sonic-src
cargo build --release

# Mover el binario compilado
sudo cp target/release/sonic /usr/local/bin/

Configuración

Crea el archivo de configuración:

sudo mkdir -p /etc/sonic /var/lib/sonic
sudo useradd -r -s /bin/false sonic
sudo chown -R sonic:sonic /var/lib/sonic
# /etc/sonic/config.cfg

[server]
log_level = "error"

[channel]
# Puerto TCP donde Sonic escucha
inet = "127.0.0.1:1491"
# Contraseña de autenticación del canal
auth_password = "SecretPassword"
# Número máximo de clientes simultáneos
tcp_timeout = 300

[store]
[store.kv]
# Directorio donde se almacena el índice
path = "/var/lib/sonic/store/kv/"
# Retener el índice entre reinicios
retain_word_objects = 1000

[store.fst]
# Directorio del índice FST (para autocompletado)
path = "/var/lib/sonic/store/fst/"
# Intervalo de consolidación del índice FST (segundos)
consolidate_after = 180
# Crear el servicio systemd
sudo tee /etc/systemd/system/sonic.service > /dev/null <<EOF
[Unit]
Description=Sonic Search Backend
After=network.target

[Service]
Type=simple
User=sonic
Group=sonic
ExecStart=/usr/local/bin/sonic -c /etc/sonic/config.cfg
Restart=on-failure
RestartSec=5
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable sonic --now

# Verificar que está corriendo
sudo systemctl status sonic

# Verificar conectividad al puerto
nc -zv 127.0.0.1 1491

Protocolo de Canal (Channel Protocol)

Sonic usa un protocolo TCP basado en texto plano similar a Redis. La comunicación se organiza en tres canales, cada uno con sus comandos específicos:

# Conectarse manualmente con netcat para depuración
nc 127.0.0.1 1491
# Respuesta: CONNECTED <sonic-server v1.4.9>

# Iniciar sesión en el canal de búsqueda
START search SecretPassword
# Respuesta: STARTED search protocol(1) buffer(20000)

Los tres canales son:

  • search: para realizar búsquedas (solo lectura)
  • ingest: para añadir, eliminar y vaciar el índice (escritura)
  • control: para operaciones de administración del servidor

Canal Ingest: Indexar Datos

# Conectar al canal ingest via netcat
nc 127.0.0.1 1491

# Iniciar sesión
START ingest SecretPassword
# Respuesta: STARTED ingest protocol(1) buffer(20000)

# Formato de los comandos:
# PUSH <coleccion> <bucket> <id_objeto> "<texto_a_indexar>"

# Indexar productos
PUSH productos tienda prod-001 "Camiseta básica algodón colores varios"
# Respuesta: OK

PUSH productos tienda prod-002 "Zapatillas deportivas running ligeras amortiguacion"
# Respuesta: OK

PUSH productos tienda prod-003 "Pantalon vaquero slim ajustado corte moderno azul"
# Respuesta: OK

# Eliminar un objeto del índice
POP productos tienda prod-001
# Respuesta: RESULT 1

# Vaciar todos los datos de una colección/bucket
FLUSHB productos tienda
# Respuesta: RESULT 3

# Vaciar una colección completa
FLUSHC productos
# Respuesta: OK

# Cerrar la sesión
QUIT

La estructura de Sonic es jerárquica:

  • Colección (collection): agrupación principal (ej: "productos", "artículos", "usuarios")
  • Bucket: partición dentro de la colección (ej: "tienda-madrid", "tienda-barcelona", o simplemente "default")
  • ID de objeto: identificador único del documento en tu base de datos
  • Texto: contenido textual a indexar

Canal Search: Buscar

# Conectar al canal de búsqueda
nc 127.0.0.1 1491

START search SecretPassword
# STARTED search protocol(1) buffer(20000)

# Búsqueda básica: QUERY <coleccion> <bucket> "<terminos>"
QUERY productos tienda "zapatillas running"
# Respuesta: PENDING Bt2m9h41
# EVENT QUERY Bt2m9h41 prod-002
# (devuelve los IDs de los objetos que coinciden)

# Búsqueda con límite y offset
QUERY productos tienda "zapatillas" LIMIT(5) OFFSET(0)

# Búsqueda en todos los buckets de una colección
QUERY productos * "camiseta"

# Sugerencias (autocompletado): SUGGEST <coleccion> <bucket> "<prefijo>"
SUGGEST productos tienda "zapati"
# Respuesta: PENDING Xt9k2j88
# EVENT SUGGEST Xt9k2j88 zapatilla zapatillas zapatillas_running

QUIT

Canal Control

nc 127.0.0.1 1491
START control SecretPassword

# Consolidar el índice FST (mejora el rendimiento del autocompletado)
TRIGGER consolidate
# Respuesta: PONG

# Ver información del servidor
INFO
# Respuesta: RESULT uptime(3600) clients_connected(2) commands_total(1500) ...

# Reiniciar los canales sin reiniciar el servicio
TRIGGER reindex

QUIT

Integración con Aplicaciones

Sonic tiene clientes oficiales y de la comunidad para múltiples lenguajes:

Python

pip install sonic-client
from sonic import SearchClient, IngestClient

# Indexar datos desde tu base de datos
with IngestClient("127.0.0.1", 1491, "SecretPassword") as ingest:
    # Indexar productos desde la base de datos
    for producto in obtener_productos_de_db():
        texto = f"{producto['nombre']} {producto['descripcion']} {' '.join(producto['etiquetas'])}"
        ingest.push("productos", "tienda", str(producto['id']), texto)

# Buscar
with SearchClient("127.0.0.1", 1491, "SecretPassword") as search:
    # Obtener IDs de objetos coincidentes
    ids = search.query("productos", "tienda", "zapatillas running", limit=10)
    print(f"IDs encontrados: {ids}")

    # Obtener sugerencias para autocompletado
    sugerencias = search.suggest("productos", "tienda", "zapati", limit=5)
    print(f"Sugerencias: {sugerencias}")

# Luego, con los IDs devueltos por Sonic, buscar los datos completos en tu BD

Node.js

npm install sonic-channel
const { Search, Ingest } = require("sonic-channel");

// Indexar datos
const ingestChannel = new Ingest({
  host: "127.0.0.1",
  port: 1491,
  auth: "SecretPassword",
});

ingestChannel.connect({
  connected: () => {
    // Indexar un producto
    ingestChannel.push(
      "productos",
      "tienda",
      "prod-001",
      "Camiseta básica algodón varios colores"
    );
  },
});

// Buscar
const searchChannel = new Search({
  host: "127.0.0.1",
  port: 1491,
  auth: "SecretPassword",
});

searchChannel.connect({
  connected: () => {
    searchChannel.query("productos", "tienda", "camiseta", {
      limit: 10,
      offset: 0,
    }).then(ids => {
      console.log("IDs encontrados:", ids);
      // Buscar los documentos completos en tu base de datos usando estos IDs
    });
  },
});

Patrón de uso típico con base de datos

import psycopg2
from sonic import SearchClient, IngestClient

def indexar_todos_los_productos():
    """Reindexar todos los productos desde PostgreSQL en Sonic."""
    conn = psycopg2.connect("postgresql://usuario:pass@localhost/midb")
    cursor = conn.cursor()
    cursor.execute("SELECT id, nombre, descripcion, categoria FROM productos WHERE activo = true")

    with IngestClient("127.0.0.1", 1491, "SecretPassword") as ingest:
        # Vaciar el índice existente
        ingest.flush_collection("productos")

        # Reindexar todos los productos
        for fila in cursor.fetchall():
            id_prod, nombre, descripcion, categoria = fila
            texto = f"{nombre} {descripcion} {categoria}"
            ingest.push("productos", "general", str(id_prod), texto)

    cursor.close()
    conn.close()

def buscar_productos(query, pagina=1, por_pagina=20):
    """Buscar productos y devolver los datos completos desde PostgreSQL."""
    offset = (pagina - 1) * por_pagina

    with SearchClient("127.0.0.1", 1491, "SecretPassword") as search:
        ids = search.query("productos", "general", query, limit=por_pagina, offset=offset)

    if not ids:
        return []

    # Obtener los datos completos de los productos encontrados
    conn = psycopg2.connect("postgresql://usuario:pass@localhost/midb")
    cursor = conn.cursor()
    cursor.execute(
        "SELECT id, nombre, precio, categoria FROM productos WHERE id = ANY(%s)",
        (ids,)
    )
    resultados = cursor.fetchall()
    cursor.close()
    conn.close()

    return resultados

Solución de Problemas

Sonic no arranca:

# Verificar logs del servicio
sudo journalctl -u sonic -n 50 --no-pager

# Verificar permisos del directorio de datos
ls -la /var/lib/sonic/
sudo chown -R sonic:sonic /var/lib/sonic/

# Probar la configuración manualmente
sudo -u sonic sonic -c /etc/sonic/config.cfg

Error "Connection refused" al conectar:

# Verificar que Sonic está escuchando
sudo ss -tlnp | grep 1491

# Verificar la dirección de escucha en la configuración
grep "inet" /etc/sonic/config.cfg
# Si es 127.0.0.1, solo acepta conexiones locales
# Cambiar a 0.0.0.0 para aceptar conexiones remotas

El autocompletado no funciona:

# El autocompletado usa el índice FST que se consolida periódicamente
# Forzar la consolidación manualmente
nc 127.0.0.1 1491
START control SecretPassword
TRIGGER consolidate

# O reducir el intervalo de consolidación en config.cfg:
# consolidate_after = 30  # 30 segundos

Búsquedas que no devuelven resultados esperados:

# Sonic solo indexa texto plano; verifica que estás indexando el texto correcto
# El texto indexado debe incluir los términos que quieres buscar

# Verificar que los datos se han indexado correctamente
nc 127.0.0.1 1491
START ingest SecretPassword
COUNT productos tienda
# Devuelve el número de objetos indexados

Conclusión

Sonic es la solución perfecta cuando necesitas añadir búsqueda de texto completo a una aplicación sin la complejidad operativa de Elasticsearch o Solr. Su enfoque minimalista, limitado al índice invertido sin almacenamiento de documentos, lo hace extraordinariamente eficiente en recursos y fácil de mantener. El patrón de uso ideal es como complemento a tu base de datos principal: Sonic gestiona el índice de búsqueda y tú recuperas los datos completos desde tu base de datos con los IDs devueltos, obteniendo lo mejor de ambos mundos.