Procedimientos recomendados para mejorar el rendimiento de Apache Phoenix
El aspecto más importante del rendimiento de Apache Phoenix es optimizar la instancia de Apache HBase subyacente. Phoenix crea un modelo de datos relacionales sobre HBase que convierte las consultas SQL en operaciones de HBase, como recorridos. Tanto el diseño del esquema de tabla como la selección y el orden de los campos en la clave principal y el uso que realiza de los índices afectan el rendimiento de Phoenix.
Diseño de esquemas de tabla
Cuando crea una tabla en Phoenix, se almacena en una tabla HBase. La tabla HBase contiene grupos de columnas (familias de columnas) a las que se accede en conjunto. Una fila de la tabla Phoenix es una fila de la tabla HBase, donde cada fila consta de celdas con versión asociadas con una o varias columnas. Lógicamente, una fila HBase única es una colección de pares clave-valor, donde cada uno tiene el mismo valor de clave de fila. Es decir, cada par clave-valor tiene un atributo de clave de fila y el valor de ese atributo es igual para una fila determinada.
El diseño de esquemas de una tabla Phoenix incluye el diseño de la clave principal, el diseño de la familia de columnas, el diseño de las columnas individuales y la manera de particionar los datos.
Diseño de la clave principal
La clave principal que se define en una tabla en Phoenix determina cómo se almacenan los datos dentro de la clave de fila de la tabla HBase subyacente. En HBase, la única manera de acceder a una fila determinada es con la clave de fila. Además, los datos almacenados en una tabla HBase se ordenan según la clave de fila. Phoenix construye el valor de clave de fila al concatenar los valores de cada una de las columnas de la fila, en el orden en que están definidos en la clave principal.
Por ejemplo, una tabla para los contactos tiene el nombre, el apellido, el número de teléfono y la dirección, todo en la misma familia de columnas. Puede definir una clave principal en función de un número de secuencia creciente:
clave de fila | address | phone | firstName | lastName |
---|---|---|---|---|
1000 | 1111 San Gabriel Dr. | 1-425-000-0002 | John | Dole |
8396 | 5415 San Gabriel Dr. | 1-230-555-0191 | Calvin | Raji |
Sin embargo, si habitualmente realiza consultas por lastName, es posible que esta clave principal no funcione correctamente, porque cada consulta requiere un recorrido de tabla completo para leer el valor de cada columnas lastName. En su lugar, puede definir una clave principal en las columnas lastName, firstName y número del seguro social. La última columna se usa para eliminar la ambigüedad entre dos residentes de la misma dirección que tengan el mismo nombre como, por ejemplo, padre e hijo.
clave de fila | address | phone | firstName | lastName | socialSecurityNum |
---|---|---|---|---|---|
1000 | 1111 San Gabriel Dr. | 1-425-000-0002 | John | Dole | 111 |
8396 | 5415 San Gabriel Dr. | 1-230-555-0191 | Calvin | Raji | 222 |
Con esta clave principal nueva, las claves de fila que Phoenix genera serían:
clave de fila | address | phone | firstName | lastName | socialSecurityNum |
---|---|---|---|---|---|
Dole-John-111 | 1111 San Gabriel Dr. | 1-425-000-0002 | John | Dole | 111 |
Raji-Calvin-222 | 5415 San Gabriel Dr. | 1-230-555-0191 | Calvin | Raji | 222 |
En la primera fila de la tabla dada, los datos de la clave rowkey se representan como se muestra:
clave de fila | key | value |
---|---|---|
Dole-John-111 | address | 1111 San Gabriel Dr. |
Dole-John-111 | phone | 1-425-000-0002 |
Dole-John-111 | firstName | John |
Dole-John-111 | lastName | Dole |
Dole-John-111 | socialSecurityNum | 111 |
Esta clave de fila ahora almacena una copia duplicada de los datos. Tenga en cuenta el tamaño y el número de las columnas que incluye en la clave principal, porque este valor se incluye con cada celda en la tabla HBase subyacente.
Además, si la clave principal tiene valores que aumentan de manera continuada, debe crear la tabla con cubos de sal para evitar crear zonas activas de escritura. Consulte las particiones de datos.
Diseño de familia de columnas
Si se accede a algunas columnas con más frecuencia que a otras, debe crear varias familias de columnas para separar las columnas a las que se accede con frecuencia de las columnas a las que se accede con menos frecuencia.
Además, si se tiende a acceder a ciertas columnas en conjunto, colóquelas en la misma familia de columnas.
Diseño de columnas
- Mantenga las columnas VARCHAR aproximadamente por debajo de 1 MB por los costos de E/S de las columnas de gran tamaño. Cuando se procesan las consultas, HBase materializa en su totalidad las celdas antes de enviarlas al cliente y el cliente las recibe completas antes de entregarlas al código de la aplicación.
- Almacene los valores de columna con un formato compacto como protobuf, Avro, msgpack o BSON. No se recomienda usar JSON, porque es más grande.
- Considere la posibilidad de comprimir los datos antes del almacenamiento para reducir la latencia y los costos de E/S.
Partición de datos
Phoenix le permite controlar el número de regiones en las que se distribuyen los datos, lo que puede aumentar considerablemente el rendimiento de lectura/escritura. Cuando se crea una tabla Phoenix, puede cifrar con sal los datos o dividirlos previamente.
Para cifrar con sal una tabla durante la creación, especifique el número de cubos de sal:
CREATE TABLE CONTACTS (...) SALT_BUCKETS = 16
Este cifrado con sal divide la tabla según los valores de las claves principales, mediante la elección automática de los valores.
Para controlar dónde se producen las divisiones de la tabla, puede dividirla previamente proporcionando los valores de intervalo según los cuales se produce la división. Por ejemplo, para crear una división de tabla en tres regiones:
CREATE TABLE CONTACTS (...) SPLIT ON ('CS','EU','NA')
Diseño del índice
Un índice de Phoenix es una tabla HBase que almacena una copia de algunos o de todos los datos de la tabla indexada. Un índice mejora el rendimiento de tipos específicos de consultas.
Cuando tiene varios índices definidos y, a continuación, se consulta una tabla, Phoenix selecciona automáticamente el mejor índice para la consulta. El índice principal se crea automáticamente en función de las claves principales que selecciona.
En el caso de las consultas anticipadas, también puede crear índices secundarios si especifica sus columnas.
Cuando diseñe los índices:
- Cree solo los índices que necesita.
- Limite el número de índices en las tablas que se actualizan con frecuencia. Las actualizaciones de una tabla se traducen en escrituras tanto en la tabla principal como en las tablas de índice.
Creación de índices secundarios
Los índices secundarios pueden mejorar el rendimiento de lectura al convertir lo que sería un recorrido de tabla completo en una búsqueda puntual, a costa del espacio de almacenamiento y de la velocidad de escritura. Los índices secundarios se pueden agregar o quitar después de crear la tabla y no requieren cambios en las consultas existentes: simplemente, las consultas se ejecutan más rápido. En función de cuáles sean sus necesidades, considere la posibilidad de crear índices cubiertos, índices funcionales o ambos.
Uso de índices cubiertos
Los índices cubiertos son los índices que incluyen datos de la fila además de los valores que se indexan. Después de encontrar la entrada de índice deseada, no es necesario acceder a la tabla principal.
Por ejemplo, en la tabla de contactos de ejemplo, podría crear un índice secundario solo en la columna socialSecurityNum. Este índice secundario aceleraría las consultas que filtran por valores socialSecurityNum, pero recuperar otros valores de campo requieren otra lectura en la tabla principal.
clave de fila | address | phone | firstName | lastName | socialSecurityNum |
---|---|---|---|---|---|
Dole-John-111 | 1111 San Gabriel Dr. | 1-425-000-0002 | John | Dole | 111 |
Raji-Calvin-222 | 5415 San Gabriel Dr. | 1-230-555-0191 | Calvin | Raji | 222 |
Sin embargo, si habitualmente desea buscar el valor firstName y lastName si se proporciona el valor socialSecurityNum, puede crear un índice cubierto que incluya firstName y lastName como datos reales en la tabla de índice:
CREATE INDEX ssn_idx ON CONTACTS (socialSecurityNum) INCLUDE(firstName, lastName);
Este índice cubierto permite que la consulta siguiente adquiera todos los datos simplemente al leer de la tabla que contiene el índice secundario:
SELECT socialSecurityNum, firstName, lastName FROM CONTACTS WHERE socialSecurityNum > 100;
Uso de índices funcionales
Los índices funcionales permiten crear un índice en una expresión arbitraria que espera que se use en las consultas. Una vez que existe un índice funcional y una consulta usa esa expresión, el índice se puede usar para recuperar los resultados en lugar de la tabla de datos.
Por ejemplo, podría crear un índice que le permita realizar búsquedas sin distinción de mayúsculas y minúsculas en la combinación de nombre y apellido de una persona:
CREATE INDEX FULLNAME_UPPER_IDX ON "Contacts" (UPPER("firstName"||' '||"lastName"));
Diseño de consultas
Las principales consideraciones en el diseño de consultas son:
- Comprender el plan de consulta y comprobar que se comporta según lo esperado.
- Combinar de manera eficaz.
Comprensión del plan de consulta
En SQLLine, use EXPLAIN seguido de la consulta SQL para ver el plan de operaciones que realiza Phoenix. Compruebe que el plan:
- Usa la clave principal cuando corresponde.
- Usa los índices secundarios adecuados, en lugar de la tabla de datos.
- Usa RANGE SCAN o SKIP SCAN siempre que sea posible, en lugar de TABLE SCAN.
Ejemplos de plan
Como ejemplo, supongamos que tiene una tabla denominada FLIGHTS que almacena información sobre los retrasos de los vuelos.
Para seleccionar todos los vuelos con un airlineid
de 19805
, donde airlineid
es un campo que no está en la clave principal o en ningún índice:
select * from "FLIGHTS" where airlineid = '19805';
Ejecute el comando explicado de la siguiente manera:
explain select * from "FLIGHTS" where airlineid = '19805';
El plan de consulta tiene este aspecto:
CLIENT 1-CHUNK PARALLEL 1-WAY ROUND ROBIN FULL SCAN OVER FLIGHTS
SERVER FILTER BY AIRLINEID = '19805'
En este plan, observe la frase FULL SCAN OVER FLIGHTS. Esta frase indica que la ejecución hace una operación TABLE SCAN de todas las filas de la tabla en lugar de usar la opción más eficaz RANGE SCAN o SKIP SCAN.
Ahora, supongamos que desea consultar los vuelos del 2 de enero de 2014 con el operador AA
con valor flightnum mayor que 1. Supongamos que las columnas year, month, dayofmonth, carrier y flightnum existen en la tabla de ejemplo y que todas forman parte de la clave principal compuesta. La consulta tendría el aspecto siguiente:
select * from "FLIGHTS" where year = 2014 and month = 1 and dayofmonth = 2 and carrier = 'AA' and flightnum > 1;
Examinemos el plan para esta consulta con:
explain select * from "FLIGHTS" where year = 2014 and month = 1 and dayofmonth = 2 and carrier = 'AA' and flightnum > 1;
El plan resultante es:
CLIENT 1-CHUNK PARALLEL 1-WAY ROUND ROBIN RANGE SCAN OVER FLIGHTS [2014,1,2,'AA',2] - [2014,1,2,'AA',*]
Los valores entre corchetes son el intervalo de los valores para las claves principales. En este caso, los valores del intervalo se fijan en relación con year 2014, month 1 y dayofmonth 2, pero permiten valores para flightnum que parten en 2 y van hasta (*
). Este plan de consulta confirma que la clave principal se usa según lo previsto.
A continuación, cree un índice en la tabla FLIGHTS denominada carrier2_idx
que solo está en el campo carrier. Este índice también incluye flightdate
, tailnum
, origin
y flightnum
como columnas cubiertas cuyos datos también se almacenan en el índice.
CREATE INDEX carrier2_idx ON FLIGHTS (carrier) INCLUDE(FLIGHTDATE,TAILNUM,ORIGIN,FLIGHTNUM);
Supongamos que desea obtener el operador junto con y flightdate
tailnum
, como en la consulta siguiente:
explain select carrier,flightdate,tailnum from "FLIGHTS" where carrier = 'AA';
Debe ver que se usa este índice:
CLIENT 1-CHUNK PARALLEL 1-WAY ROUND ROBIN RANGE SCAN OVER CARRIER2_IDX ['AA']
Para una lista completa de los elementos que pueden aparecen en los resultados del plan explain, consulte la sección sobre los planes Explain en Apache Phoenix Tuning Guide (Guía de ajuste de Apache Phoenix).
Combinación eficaz
Por lo general, desea evitar las combinaciones a menos que un lado sea pequeño, en particular en las consultas frecuentes.
Si es necesario, puede realizar combinaciones de gran tamaño con la sugerencia /*+ USE_SORT_MERGE_JOIN */
, pero una combinación de gran tamaño es una operación costosa que abarca enormes cantidades de filas. Si el tamaño total de todas las tablas del lado derecho superara la memoria disponible, use la sugerencia /*+ NO_STAR_JOIN */
.
Escenarios
Las instrucciones siguientes describen algunos patrones comunes.
Cargas de trabajo con mucha actividad de lectura
En casos de uso con mucha actividad de lectura, asegúrese de usar índices. Además, para ahorrar la sobrecarga en el tiempo de lectura, considere la posibilidad de crear índices cubiertos.
Cargas de trabajo con mucha actividad de escritura
En el caso de las cargas de trabajo con mucha actividad de escritura en las que la clave principal aumenta de manera uniforme, cree cubos de sal para ayudar a evitar las zonas activas de escritura, a costa del rendimiento de lectura general debido a los recorridos adicionales que se requieren. Además, cuando use UPSERT para escribir una gran cantidad de registros, desactive autoCommit y procese por lote los registros.
Eliminaciones masivas
Cuando elimine un conjunto de datos de gran tamaño, active autoCommit antes de emitir la consulta DELETE; de este modo, el cliente no necesita recordar las claves de fila de todas las filas eliminadas. AutoCommit evita que el cliente almacene en búfer las filas afectadas por la consulta DELETE, de manera que Phoenix puede eliminarlas directamente en los servidores de región sin el gasto que implica devolverlas al cliente.
Inalterable y de solo anexar
Si el escenario favorece la velocidad de escritura por sobre la integrar de datos, considere deshabilitar el registro de escritura previa cuando cree las tablas:
CREATE TABLE CONTACTS (...) DISABLE_WAL=true;
Para conocer detalles sobre esta y otras opciones, consulte Apache Phoenix Grammar (Gramática de Apache Phoenix).
Pasos siguientes
- Apache Phoenix Tuning Guide (Guía de ajuste de Apache Phoenix)
- Secondary Indexes (Índices secundarios)