Indexación por rangos en Sphinx

Como vimos en el post anterior, es posible lanzar una query que lea todos los datos que deseamos indexar. Esto no es problema si los datos son pocos, por ejemplo en el caso de employeesdb estamos hablando de 300.024 registros, que son más bien pocos. Cuando ya hablamos de varios gigas de datos y millones de filas las cosas se complican. Los índices de Sphinx hay que recrearlos cada cierto tiempo para que las búsquedas de nuestra web nos den datos lo más actuales posibles. Por lo tanto, lanzar una query que en definitiva es un escaneo completo de tabla, cada 5 minutos, con millones de registros, será un problema de rendimiento importante. Existen varias soluciones para aliviar un poco esta carga. La primera y más obvia que a mi me viene a la cabeza es usar un mySQL slave que solo usaremos para este actualizar los índices de Sphinx. Podemos incluso, si hay RAM suficiente, crear tablas con engine Memory y aumentar aún más el rendimiento. Pero como esto no son tutoriales sobre MySQL, si no sobre Sphinx, hablaremos una segunda posibilidad. Hacer escaneos por rangos.

En resumen, lo que haremos es, en lugar de leer 20 millones de registros de golpe, lo haremos en tandas que nosotros mismos definiremos. Para ello, el primer valor a definir será sql_range_step. Esta directiva tendrá por defecto un valor de 1000, el cual, para nuestro hipotético caso es un valor que se nos queda corto. Otra directiva que nos ayudará a aliviar la carga de nuestra base de datos será sql_ranged_throttle que indicará cuantos milisegundos esperar entre cada escaneo por rango.

Con estos datos ya podemos definir como queremos que sea la obtención de los datos, por lo que nos ponemos manos a la obra.

1- En primer lugar Sphinx debe saber cual es el valor mínimo y máximo de filas que tendrá que obtener. Para esto, lo recomendable es hacer uso den la base de datos de una columna tipo auto-incremental. Hay que tener en cuenta que es posible que la tabla tenga pequeños vacíos o huecos dentro de dicho autoincremental, debido al borrado de filas. Por lo que, si nos devuelve 1 - 10.000 no tienen porque ser 10.000 registros.

sql_query_range = SELECT MIN(emp_no), MAX(emp_no) from employees

2- Sphinx leerá la query anterior y guardará en variables internas los dos valores devueltos por la query. Dentro de cada iteración de la búsqueda por rangos, Sphinx definirá las variables $start y $end. Dichas variables se actualizarán según vayamos avanzando por la tabla. De esta forma, la query que vimos en el post anterior debería quedar así:

sql_query               = \
        select employees.emp_no, employees.first_name, employees.last_name, 
UNIX_TIMESTAMP(employees.hire_date) as hire_ts, salaries.salary, departments.dept_name
 from employees LEFT JOIN salaries ON employees.emp_no = salaries.emp_no LEFT JOIN 
dept_emp ON salaries.emp_no = dept_emp.emp_no LEFT JOIN departments ON 
dept_emp.dept_no = departments.dept_no WHERE emp_no.employees BETWEEN $start AND $end
 GROUP BY employees.emp_no

Si nos fijamos en la clausula WHERE, Sphinx ejecutará la query las veces que sean necesarias hasta recorrer todas las filas de la tabla, cambiando en cada iteración los valores de inicio $start. Un procedimiento fácil y eficaz.

3- Como tenemos 300.000 registros configuraremos Sphinx para que lo haga en grupos de 10.000 registros y con una espera de 1 segundo entre cada iteración.

sql_range_step = 10000
sql_ranged_throttle = 1000

Por lo que, la sección "source" de nuestra configuración quedará así:

source perconaserver1 
{
    type                    = mysql
    sql_host                = 192.168.100.10 
    sql_user                = sphinx
    sql_pass                = sph1nX
    sql_db                  = employees
        sql_query_range                         = SELECT MIN(emp_no), MAX(emp_no) from employees
        sql_range_step                          = 10000
        sql_ranged_throttle                     = 1000
    sql_query               = \
        select employees.emp_no, employees.first_name, employees.last_name,
 UNIX_TIMESTAMP(employees.hire_date) as hire_ts, salaries.salary, departments.dept_name
 from employees LEFT JOIN salaries ON employees.emp_no = salaries.emp_no LEFT JOIN
 dept_emp ON salaries.emp_no = dept_emp.emp_no LEFT JOIN departments ON 
dept_emp.dept_no = departments.dept_no WHERE employees.emp_no BETWEEN $start AND $end 
GROUP BY employees.emp_no
    sql_attr_uint           = salary
    sql_attr_timestamp      = hire_ts
}
index employees 
{
    source          = perconaserver1
    path            = /var/lib/sphinxsearch/data/employees
    min_word_len        = 1
    charset_type        = sbcs
}

Lanzamos de nuevo el indexer para que relea los datos de las tablas y las agregamos a nuestro índice:

root@sphinx1:~# indexer --all --rotate
Sphinx 0.9.9-release (r2117)
Copyright (c) 2001-2009, Andrew Aksyonoff

using config file '/etc/sphinxsearch/sphinx.conf'...
indexing index 'employees'...
collected 300024 docs, 7.0 MB
sorted 0.9 Mhits, 100.0% done
total 300024 docs, 7036568 bytes
total 59.928 sec, 117416 bytes/sec, 5006.37 docs/sec
total 3 reads, 0.004 sec, 3202.0 kb/call avg, 1.3 msec/call avg
total 22 writes, 0.029 sec, 1000.2 kb/call avg, 1.3 msec/call avg
rotating indices: succesfully sent SIGHUP to searchd (pid=2728).

Como podemos comprobar, el proceso ha tardado un poco más que de la forma normal (o conocida como "a lo bruto"). Pero la carga de nuestra base de datos ha sido menor y por lo tanto el impacto contra la experiencia web de nuestros usuarios no se ha visto afectada.

1 Comment · Tags: ,

Comments

  1. En casos como este, donde main+delta no sirve ya que hay datos antiguos que también necesitas actualizar (datos cambiados de un empleado), no lo he probado por lo que no sé si su rendimiento es mucho peor pero en el siguiente enlace por ejemplo, recomiendan usar real time indexes:

    http://www.ivinco.com/blog/should-you-switch-to-sphinx-real-time-indexes/

    Obviamente, no hay truco, supongo que el coste de la insert/update se va degradando.

    "Currently there are some performance issues with real-time indexes on large data sets. But for smaller ones (say, up to 500.000 Wikipedia documents) they show comparable to regular indexes speed."

    Ivan on