Ajuste del rendimiento de Amazon EMR Hudi: agrupación

Con el creciente volumen de datos, la gente ha planteado más requisitos sobre el rendimiento de consultas de Hudi. Además de las ventajas de rendimiento originales del formato de almacenamiento Parquet, también se espera que Hudi pueda proporcionar enfoques más técnicos para la optimización del rendimiento, especialmente para Hudi: Después de escribir la tabla en alta concurrencia y generar una gran cantidad de archivos pequeños, es necesario usar Presto/Trino para realizar consultas ad-hoc de alto rendimiento en la tabla Hudi. Cómo manejar estos archivos pequeños, es decir, hacer que la tabla Hudi originalmente optimizada para escritura admita la optimización de lectura, se ha convertido en un problema que los usuarios que usan Hudi deben resolver.

La comunidad de desarrolladores de tecnología en la nube de Amazon proporciona a los desarrolladores recursos tecnológicos de desarrollo global. Hay documentos técnicos, casos de desarrollo, columnas técnicas, vídeos de formación, actividades y concursos, etc. Ayude a los desarrolladores chinos a conectarse con las tecnologías, ideas y proyectos más avanzados del mundo, y recomiende desarrolladores o tecnologías chinos destacados a la comunidad global de la nube. Si aún no ha prestado atención/favorito, no se apresure cuando vea esto, ¡ haga clic aquí para convertirlo en su tesoro técnico!

 

Este artículo utilizará un ejemplo práctico para reorganizar y reescribir los archivos de datos de la tabla Hudi utilizando la tecnología de agrupación en clústeres, a fin de mejorar el rendimiento de las consultas SQL de la tabla Hudi.

1. severo

Hudi lleva la funcionalidad principal de los almacenes de datos y las bases de datos directamente a los lagos de datos. Hudi proporciona tablas, transacciones, actualización/eliminación eficiente, indexación avanzada, servicios de ingesta de streaming, agrupación de datos (Clustering), optimización de la compresión y concurrencia, al tiempo que mantiene los datos en un formato de archivo de código abierto, es decir, los datos de las tablas de Hudi se pueden guardar. En HDFS, Amazon S3 y otros sistemas de archivos.

La razón por la que Hudi se ha vuelto rápidamente popular y aceptado por la mayoría de los usuarios de desarrollo es que se puede usar fácilmente en cualquier plataforma en la nube y se puede acceder a él a través de cualquier motor de consulta popular (incluidos Apache Spark, Flink, Presto, Trino, Hive, etc.). Los datos de Hudi son aún más encomiables porque los diseñadores de Hudi han considerado tantos escenarios comerciales y necesidades reales como sea posible.

A partir del escenario empresarial real, los requisitos de la plataforma del lago de datos se pueden dividir en dos categorías: preferencia de lectura y preferencia de escritura, por lo que Apache Hudi proporciona dos tipos de tablas:

  • Copiar en tabla de escritura: COW para abreviar, este tipo de tabla Hudi utiliza un formato de archivo de columnas (como Parquet) para almacenar datos; si hay datos escritos, copiará todo el archivo Parquet, adecuado para operaciones preferidas de lectura.
  • Combinar al leer tabla: MOR para abreviar, este tipo de tabla Hudi utiliza un formato de archivo de columnas (como Parquet) y un formato de archivo de filas (como Avro) para almacenar datos juntos. Cuando se actualizan los datos, se escriben en archivos de filas y luego se comprimen para generar archivos de columnas de forma sincrónica o asincrónica, lo cual es adecuado para operaciones preferidas de escritura.

Subdividido aún más, Hudi proporciona diferentes tipos de consultas para dos tipos de tablas:

  • Consultas de instantáneas: consulta de instantáneas, consulta la última instantánea de datos, es decir, todos los datos
  • Consultas incrementales: consultas incrementales, que pueden consultar datos nuevos o modificados dentro de un rango de tiempo específico.
  • Leer consultas optimizadas: leer consultas optimizadas, para la tabla MOR, consultar solo los datos en el archivo Parquet

Para los tres tipos de consultas anteriores, las consultas optimizadas para lectura solo se pueden usar para tablas MOR (en realidad, no tiene sentido usarlas para COW, COW solo tiene archivos Parquet para almacenar datos), y los otros dos modos de consulta pueden Se puede utilizar para tablas COW y tablas MOR.

No solo eso, Hudi también utiliza muchos conceptos y tecnologías avanzados en indexación, gestión de transacciones, concurrencia, compresión, etc., lo que también proporciona un amplio espacio y más para los usuarios que desean ajustar el rendimiento de las tablas de Hudi. Los medios, como Index , Tabla de metadatos, agrupación en clústeres, etc., este artículo presentará la tecnología de agrupación en clústeres.

2. Agrupación de Hudi

En un lago de datos/almacén de datos, una de las compensaciones clave es entre la velocidad de escritura y el rendimiento de las consultas. Las escrituras de datos generalmente prefieren archivos pequeños para aumentar el paralelismo y hacer que los datos estén disponibles para las consultas lo más rápido posible. Sin embargo, si hay muchos archivos pequeños, el rendimiento de la consulta será deficiente. Además, durante la escritura, los datos generalmente se escriben en el archivo en la misma ubicación según la hora de llegada. Sin embargo, el motor de consultas funciona mejor cuando los datos consultados con frecuencia se ubican en el mismo lugar.

Esto plantea requisitos para la reorganización de datos de Hudi, es decir, utilizar archivos pequeños al escribir datos y archivos grandes al consultar datos.

2.1 Establecer los parámetros de agrupación de la tabla Hudi

En el documento [RFC-19], el autor creó una tabla Hudi y configuró los parámetros de agrupación en clústeres, luego inició el trabajo de agrupación en clústeres asíncrono y comparó los resultados. Tenga en cuenta que al crear la tabla Hudi en este documento, llame al método getQuickstartWriteConfigs para establecer el parámetro sudadera con capucha.upsert.shuffle.parallelism en 2, lo que obviamente no es suficiente para una prueba con una gran cantidad de datos.

Veamos un ejemplo diferente. Primero, genere un conjunto de datos de prueba TPC-DS, que incluye específicamente 24 tablas y 99 declaraciones de consulta SQL para pruebas de rendimiento. Para conocer los pasos específicos de generación de datos, consulte:

Mejora del precio/rendimiento de EMR con Amazon Graviton2 | Blog oficial de AWS

Cree un clúster de Amazon EMR, versión 6.5.0, con la siguiente configuración de hardware:

imagen.png

Se necesitan unos 30 minutos para utilizar este clúster para generar un conjunto de datos TPC-DS de 100G.

Amazon EMR proporciona el componente Hudi y luego usa los datos TPC-DS generados para generar una tabla Hudi. Seleccionamos la tabla store_sales y el script es el siguiente:

spark-shell --master yarn \
--deploy-mode client \
--conf "spark.serializer=org.apache.spark.serializer.KryoSerializer" \
--conf "spark.sql.hive.convertMetastoreParquet=false" \
--packages org.apache.hudi:hudi-spark3-bundle_2.12:0.10.0


  import org.apache.hudi.QuickstartUtils._
  import org.apache.spark.sql.SaveMode._
  import org.apache.hudi.DataSourceReadOptions._
  import org.apache.hudi.DataSourceWriteOptions._
  import org.apache.hudi.config.HoodieWriteConfig._
  import java.util.Date

  val tableName = "store_sales"
  val basePath = "s3://dalei-demo/hudi/tpcds_hudi_cluster/store_sales"
  val partitionKey = "ss_sold_date_sk"

  val df = spark.read.format("parquet").
                load(s"s3://dalei-demo/tpcds/data10g/store_sales").
                filter("ss_sold_time_sk is not null and ss_item_sk is not null and ss_sold_date_sk is not null and ss_customer_sk is not null").
                withColumn("ts", lit((new Date()).getTime)).
                repartition(1000)

  df.write.format("org.apache.hudi").
        option(TABLE_NAME, tableName).
        option("hoodie.datasource.write.precombine.field", "ts").
        option("hoodie.datasource.write.recordkey.field", "ss_sold_time_sk, ss_item_sk").
        option("hoodie.datasource.write.partitionpath.field", partitionKey).
        option("hoodie.upsert.shuffle.parallelism", "1000").
        option("hoodie.datasource.write.table.type", "MERGE_ON_READ").
        option("hoodie.datasource.write.operation", "upsert").
        option("hoodie.parquet.max.file.size", "10485760").
        option("hoodie.datasource.write.hive_style_partitioning", "true").
        option("hoodie.datasource.write.keygenerator.class", "org.apache.hudi.keygen.ComplexKeyGenerator").
        option("hoodie.datasource.hive_sync.enable", "true").
        option("hoodie.datasource.hive_sync.mode", "hms").
        option("hoodie.datasource.hive_sync.database", "tpcds_hudi_cluster").
        option("hoodie.datasource.hive_sync.table", tableName).
        option("hoodie.datasource.hive_sync.partition_fields", partitionKey).
        option("hoodie.parquet.small.file.limit", "0").
        option("hoodie.clustering.inline", "true").
        option("hoodie.clustering.inline.max.commits", "2").
        option("hoodie.clustering.plan.strategy.max.num.groups", "10000").
        option("hoodie.clustering.plan.strategy.target.file.max.bytes", "1073741824").
        option("hoodie.clustering.plan.strategy.small.file.limit", "629145600").
        option("hoodie.clustering.plan.strategy.sort.columns", "ss_customer_sk").
        mode(Append).
        save(basePath);

Explique los principales parámetros utilizados en el código anterior:

  • sudadera con capucha.upsert.shuffle.parallelism: el número de paralelismo de reproducción aleatoria durante la inserción
  • sudadera con capucha.parquet.max.file.size: especifica el tamaño del archivo Parquet. Para comparar el efecto antes y después de la agrupación, esperamos generar una gran cantidad de archivos pequeños, por lo que el tamaño del archivo es limitado aquí.
  • sudadera con capucha.datasource.write.keygenerator.class: si es una clave primaria compuesta, debe especificar el valor de este parámetro como org.apache.hudi.keygen.ComplexKeyGenerator
  • sudadera con capucha.datasource.hive_sync.*: Estos parámetros son para sincronizar la información del esquema de la tabla Hudi con Hive MetaStore
  • sudadera con capucha.parquet.small.file.limit: si es más pequeño que el valor especificado, se considerará un archivo pequeño. Cuando se realiza Upsert, el archivo pequeño se reemplazará con un archivo grande (la llamada "extensión") en lugar de generar un nuevo archivo, el valor se establece en 0, es decir, el límite de archivos pequeños está desactivado, de modo que se generará un nuevo archivo cada vez que se escriban datos.
  • sudadera con capucha.clustering.inline: habilite la agrupación en clústeres sincrónica, es decir, una vez que se alcance el número de confirmaciones, la agrupación en clústeres se ejecutará inmediatamente
  • sudadera con capucha.clustering.inline.max.commits: después de cuántas confirmaciones, comience a ejecutar la agrupación
  • sudadera con capucha.clustering.plan.strategy.max.num.groups: cuántos grupos de archivos generará la agrupación, por defecto 30
  • sudadera con capucha.clustering.plan.strategy.target.file.max.bytes: límite de tamaño de archivo después de la agrupación
  • sudadera con capucha.clustering.plan.strategy.small.file.limit: Los archivos más pequeños que este valor se agruparán
  • sudadera con capucha.clustering.plan.strategy.sort.columns: al agrupar, utilice este campo para ordenar

Los parámetros pueden usar constantes definidas en org.apache.hudi.DataSourceWriteOptions (como TABLE_NAME), o usar cadenas directamente (como "hoodie.datasource.write.table.name"), el efecto es el mismo.

2.2 Agrupación de desencadenadores

La operación anterior simplemente creó la tabla Hudi y configuró la agrupación. Dado que el número de confirmaciones fue inferior a 2 (preste atención a los parámetros de configuración, la Upsert anterior fue 1 confirmación), la agrupación no se activó. Primero puede comprender la confirmación. como una operación Upsert.

Simulemos la operación Commit nuevamente, modifiquemos un determinado campo de una partición de la tabla store_sales y luego carguemoslo en la tabla, el código es el siguiente:

val df1 = spark.read.format("hudi").option("hoodie.datasource.query.type", "read_optimized").
                  load("s3://dalei-demo/hudi/tpcds_hudi_cluster/store_sales").
                  filter("ss_sold_date_sk=2450816").
                  drop(col("_hoodie_commit_seqno")).drop(col("_hoodie_commit_time")).
                  drop(col("_hoodie_record_key")).drop(col("_hoodie_partition_path")).
                  drop(col("_hoodie_file_name"))

  val df2 = df1.withColumn("ss_ext_tax", col("ss_ext_tax") + lit(1.0))


  df2.write.format("org.apache.hudi").
        option(TABLE_NAME, tableName).
        option("hoodie.datasource.write.precombine.field", "ts").
        option("hoodie.datasource.write.recordkey.field", "ss_sold_time_sk, ss_item_sk").
        option("hoodie.datasource.write.partitionpath.field", partitionKey).
        option("hoodie.upsert.shuffle.parallelism", "1000").
        option("hoodie.datasource.write.table.type", "MERGE_ON_READ").
        option("hoodie.datasource.write.operation", "upsert").
        option("hoodie.parquet.max.file.size", "10485760").
        option("hoodie.datasource.write.hive_style_partitioning", "true").
        option("hoodie.datasource.write.keygenerator.class", "org.apache.hudi.keygen.ComplexKeyGenerator").
        option("hoodie.datasource.hive_sync.enable", "true").
        option("hoodie.datasource.hive_sync.mode", "hms").
        option("hoodie.datasource.hive_sync.database", "tpcds_hudi_cluster").
        option("hoodie.datasource.hive_sync.table", tableName).
        option("hoodie.datasource.hive_sync.partition_fields", partitionKey).
        option("hoodie.parquet.small.file.limit", "0").
        option("hoodie.clustering.inline", "true").
        option("hoodie.clustering.inline.max.commits", "2").
        option("hoodie.clustering.plan.strategy.max.num.groups", "10000").
        option("hoodie.clustering.plan.strategy.target.file.max.bytes", "1073741824").
        option("hoodie.clustering.plan.strategy.small.file.limit", "629145600").
        option("hoodie.clustering.plan.strategy.sort.columns", "ss_customer_sk").
        mode(Append).
        save(basePath);

Una vez ejecutado el código, el número de confirmaciones llega a 2 veces y la agrupación en clústeres se ha ejecutado en segundo plano.

2.3 Explicar el proceso de operación de agrupación.

Antes de explicar la agrupación, primero introduzca la composición del archivo de operación de la tabla Hudi.

2.3.1 Archivo de operación de la tabla Hudi

Tomando como ejemplo la tabla store_sales generada anteriormente, los registros de operación de esta tabla están contenidos en el directorio .hoodie, como se muestra en la siguiente figura:

imagen.png

Figura 1: Archivo de operación para la tabla Hudi

El nombre del archivo de la operación Hudi normalmente consta de tres partes:

  • Hora instantánea: la hora de operación, una marca de tiempo de 17 dígitos (fecha de 8 dígitos + hora de 9 dígitos, con una precisión de milisegundos)
  • Acción instantánea: El tipo de operación. Cuando el front-end ejecuta Upsert, el tipo de operación que se generará es deltacommit; el tipo de operación que generará Clustering es replacecommit.
  • Estado instantáneo: el estado de la operación, solicitado significa la solicitud, en vuelo significa que está en progreso y el estado vacío significa que la ejecución se ha completado.

Puede descargar el archivo de solicitud de agrupación en clústeres 20220701161238291.replacecommit.requested porque está en formato Avro. Utilice avro-tools para ver su contenido:

[ec2-user@cm ~]$ aws s3 cp s3://dalei-demo/hudi/tpcds_hudi_cluster/store_sales/.hoodie/20220701161238291.replacecommit.requested ./

[ec2-user@cm ~]$ wget http://archive.apache.org/dist/avro/avro-1.9.2/java/avro-tools-1.9.2.jar

[ec2-user@cm ~]$ java -jar avro-tools-1.9.2.jar tojson 20220701161238291.replacecommit.requested >> 20220701161238291.replacecommit.requested.json

Puede utilizar un navegador para abrir el archivo, como se muestra a continuación:

imagen.png

Figura 2: Archivo de solicitud de agrupación

Los grupos de entrada en la figura anterior son grupos de archivos, los sectores son sectores de archivos y los ID de archivos. Estos tres conceptos se introducirán en 2.3.2. Este archivo es una solicitud para iniciar una operación de agrupación. Estos archivos se utilizan como entrada para generar archivos más grandes. archivos para reemplazarlos, los archivos generados también se introducirán en 2.3.2.

El tamaño del archivo 20220701161238291.replacecommit.inflight es 0, lo que indica que la agrupación se ha completado de inmediato. Veamos el archivo 20220701161238291.replacecommit. Es un archivo en formato json y se puede abrir directamente. El contenido es el siguiente:

{
  "partitionToWriteStats" : {
    "ss_sold_date_sk=2451080" : [ {
      "fileId" : "91377ca5-48a9-491a-9c82-56a1ba4ba2e3-0",
      "path" : "ss_sold_date_sk=2451080/91377ca5-48a9-491a-9c82-56a1ba4ba2e3-0_263-1967-116065_20220701161238291.parquet",
      "prevCommit" : "null",
      "numWrites" : 191119,
      "numDeletes" : 0,
      "numUpdateWrites" : 0,
      "numInserts" : 191119,
      "totalWriteBytes" : 11033199,
      "totalWriteErrors" : 0,
      "tempPath" : null,
      "partitionPath" : "ss_sold_date_sk=2451080",
      "totalLogRecords" : 0,
      "totalLogFilesCompacted" : 0,
      "totalLogSizeCompacted" : 0,
      "totalUpdatedRecordsCompacted" : 0,
      "totalLogBlocks" : 0,
      "totalCorruptLogBlock" : 0,
      "totalRollbackBlocks" : 0,
      "fileSizeInBytes" : 11033199,
      "minEventTime" : null,
      "maxEventTime" : null
    } ],
    ......
  },
  "compacted" : false,
  "extraMetadata" : {
    "schema" : "{\"type\":\"record\",\"name\":\"store_sales_record\",\"namespace\":\"hoodie.store_sales\",\"fields\":[{\"name\":\"ss_sold_time_sk\",\"type\":[\"null\",\"int\"],\"default\":null},{\"name\":\"ss_item_sk\",\"type\":[\"null\",\"int\"],\"default\":null},{\"name\":\"ss_customer_sk\",\"type\":[\"null\",\"int\"],\"default\":null},{\"name\":\"ss_cdemo_sk\",\"type\":[\"null\",\"int\"],\"default\":null},{\"name\":\"ss_hdemo_sk\",\"type\":[\"null\",\"int\"],\"default\":null},{\"name\":\"ss_addr_sk\",\"type\":[\"null\",\"int\"],\"default\":null},{\"name\":\"ss_store_sk\",\"type\":[\"null\",\"int\"],\"default\":null},{\"name\":\"ss_promo_sk\",\"type\":[\"null\",\"int\"],\"default\":null},{\"name\":\"ss_ticket_number\",\"type\":[\"null\",\"long\"],\"default\":null},{\"name\":\"ss_quantity\",\"type\":[\"null\",\"int\"],\"default\":null},{\"name\":\"ss_wholesale_cost\",\"type\":[\"null\",\"double\"],\"default\":null},{\"name\":\"ss_list_price\",\"type\":[\"null\",\"double\"],\"default\":null},{\"name\":\"ss_sales_price\",\"type\":[\"null\",\"double\"],\"default\":null},{\"name\":\"ss_ext_discount_amt\",\"type\":[\"null\",\"double\"],\"default\":null},{\"name\":\"ss_ext_sales_price\",\"type\":[\"null\",\"double\"],\"default\":null},{\"name\":\"ss_ext_wholesale_cost\",\"type\":[\"null\",\"double\"],\"default\":null},{\"name\":\"ss_ext_list_price\",\"type\":[\"null\",\"double\"],\"default\":null},{\"name\":\"ss_ext_tax\",\"type\":[\"null\",\"double\"],\"default\":null},{\"name\":\"ss_coupon_amt\",\"type\":[\"null\",\"double\"],\"default\":null},{\"name\":\"ss_net_paid\",\"type\":[\"null\",\"double\"],\"default\":null},{\"name\":\"ss_net_paid_inc_tax\",\"type\":[\"null\",\"double\"],\"default\":null},{\"name\":\"ss_net_profit\",\"type\":[\"null\",\"double\"],\"default\":null},{\"name\":\"ts\",\"type\":\"long\"},{\"name\":\"ss_sold_date_sk\",\"type\":[\"null\",\"int\"],\"default\":null}]}"
  },
  "operationType" : "CLUSTER",
  "partitionToReplaceFileIds" : {
    "ss_sold_date_sk=2451080" : [ "2e2bec06-78fb-4059-ad89-2914f63dd1c0-0", "63fc2a2d-73e6-4261-ab30-ff44912e1696-0", "fc5fd42e-0f3f-434c-aa56-ca43c36c659d-0", "41299b3d-0be9-4338-bbad-6feeb41d4975-0", "c23873a1-03a3-424a-aa9c-044b40f1659f-0", "8af23590-4b8c-4b44-946e-0fdd73747e19-0", "7d740b43-83ca-48ca-a9dc-6b8e19fce6f0-0", "bc90dfd5-7323-4786-832c-4a6516332adf-0", "67abd081-dfcc-45d9-8f29-50a4fb71108c-0", "80bffa2b-df05-4c9f-9766-84a700403a89-0", "cbba9f2a-32cd-4c73-a38b-570cbb5501e4-0", "ea59e1a4-1f97-40e8-baae-3bedc5752095-0", "55cffcb6-5410-4c2a-a61d-01300be50171-0", "601b74b3-663d-4ef8-bf5e-158f135f81ea-0", "c46e8539-418e-482d-936e-a79464d869ac-0", "3dbe1997-bfc2-41a7-ac12-f302d3013c87-0", "acf9be44-71a3-436f-b595-c0f322f34172-0", "d7bbe517-87c7-482c-b885-a16164062b81-0", "f1060ef7-ba7c-4b8e-abc3-c409cd6af7d4-0" ],
    ......
  },
  "writePartitionPaths" : [ "ss_sold_date_sk=2451080", ......],
  "fileIdAndRelativePaths" : {
    "742c6044-4f76-4d04-993c-d4255235d484-0" : "ss_sold_date_sk=2451329/742c6044-4f76-4d04-993c-d4255235d484-0_511-1967-116236_20220701161238291.parquet",
    "20dafb58-8ae7-41d6-a02d-2b529bcdcc83-0" : "ss_sold_date_sk=2452226/20dafb58-8ae7-41d6-a02d-2b529bcdcc83-0_1407-1967-116870_20220701161238291.parquet",
    ......
  },
  "totalRecordsDeleted" : 0,
  "totalLogRecordsCompacted" : 0,
  "totalLogFilesCompacted" : 0,
  "totalCompactedRecordsUpdated" : 0,
  "totalLogFilesSize" : 0,
  "totalScanTime" : 0,
  "totalCreateTime" : 151847,
  "totalUpsertTime" : 0,
  "minAndMaxEventTime" : {
    "Optional.empty" : {
      "val" : null,
      "present" : false
    }
  }
}

Arriba se omite una gran cantidad de contenido repetitivo, la información principal es la siguiente:

  • particiónToWriteStats: enumera las particiones que se agruparán y la información de los archivos que se agruparán
  • metadatos adicionales: esquema Hudi 表的
  • OperationType: Indica que el tipo de operación es Clustering.
  • particiónToReplaceFileIds: enumera las particiones y los ID de archivos que se agruparán
  • fileIdAndRelativePaths: nuevos archivos generados por Clustering, tenga en cuenta que la marca de tiempo del nombre del archivo
2.3.2 Archivo de datos de la tabla Hudi

A continuación, introduzca la composición del archivo de datos de la tabla Hudi, tomando como ejemplo la tabla de tipo MOR, como se muestra en la siguiente figura:

imagen.png

Figura 3: Estructura de archivos de la tabla MOR

Se puede ver que la jerarquía y la relación de inclusión de archivos es: Partición -> Grupo de archivos -> Sector de archivos -> Parquet + Registro, donde:

  • Partición: partición, todos la conocen, es posible que algunas tablas no tengan partición
  • Grupo de archivos: se utiliza para controlar la versión del archivo, hay una ID de archivo única en el mismo grupo de archivos
  • Segmento de archivo: se utiliza para organizar los datos del archivo. En el mismo segmento de archivo, no solo el ID del archivo sino también la hora instantánea deben ser los mismos.
  • El archivo Parquet es un archivo de formato de almacenamiento de columnas y el archivo de registro es un formato de archivo de almacenamiento de filas. El valor predeterminado es Apache Avro, que registra la modificación del archivo Parquet en el mismo segmento de archivo.

Veamos un ejemplo de un grupo de archivos:

imagen.png

Figura 4: Ejemplo de grupo de archivos

En la Figura 4, el ID de archivo del primer archivo y el segundo archivo son los mismos, lo que indica que son el mismo grupo de archivos, pero el tiempo instantáneo es diferente, lo que indica que no son el mismo segmento de archivo. método optimizado para lectura, leerá el archivo Parquet más grande de Take Instant Time.

Mire el archivo de datos de la tabla store_sales, como se muestra a continuación:

imagen.png

Figura 5: Archivo de datos para la tabla store_sales

En la Figura 5, el archivo de registro y el archivo Parquet marcados tienen el mismo ID de archivo y tiempo instantáneo, lo que indica que el archivo de registro se genera después de una inserción basada en el archivo Parquet y pertenecen al mismo segmento de archivo. consulta, es necesario leer el archivo de registro del mismo segmento de archivo y los datos de Parquet juntos.

En la Figura 5, el archivo "68c14d48-cba6-4f82-a4b5-48fadf1282f6-0_0-1967-115358_20220701161238291.parquet" es el archivo generado por la agrupación. Puede descargarlo y usar parquet-tool para ver sus datos, de la siguiente manera:

[ec2-user@cm ~]$ wget http://logservice-resource.oss-cn-shanghai.aliyuncs.com/tools/parquet-tools-1.6.0rc3-SNAPSHOT.jar

[ec2-user@cm ~]$ aws s3 cp s3://dalei-demo/hudi/tpcds_hudi_cluster/store_sales/ss_sold_date_sk=2450816/68c14d48-cba6-4f82-a4b5-48fadf1282f6-0_0-1967-115358_20220701161238291.parquet ./

[ec2-user@cm ~]$ java -jar ./parquet-tools-1.6.0rc3-SNAPSHOT.jar head -n 10 68c14d48-cba6-4f82-a4b5-48fadf1282f6-0_0-1967-115358_20220701161238291.parquet

El comando anterior muestra los datos de 10 archivos Parquet después de la agrupación. Preste atención al valor de Ordenar columna (ss_customer_sk), que ya está ordenado.

Al comparar los archivos antes y después de la agrupación, se puede ver que los datos originales guardados con 10 archivos Parquet de aproximadamente 1 M son solo un archivo Parquet de 5,1 M después de la agrupación. En cuanto a guardar la misma cantidad de datos y por qué la capacidad total del archivo disminuye tanto, consulte el conocimiento relevante de Parquet: Apache Parquet  .

2.3.3 Agrupación de tablas de múltiples particiones

De forma predeterminada, Hudi usa el parámetro sudadera con capucha.clustering.plan.strategy.max.num.groups (el valor predeterminado es 30) para consideraciones de carga de trabajo y especifica que la agrupación en clústeres solo creará 30 grupos de archivos (según la configuración del tamaño del documento, actualmente cada partición solo necesita crear 1 grupo de archivos)

Si hay muchas particiones, puede utilizar el parámetro hoodie.clustering.plan.partition.filter.mode para planificar el rango de particiones de la agrupación. Para obtener más información, consulte: [ Todas las configuraciones | Apache Hudi  .]( Todas las configuraciones | Apache Hudí  .)

3. Utilice Trino para consultar datos

3.1 Preparar otras tablas

Los datos de la tabla store_sales están listos. De manera similar, también podemos generar cuatro tablas customer_address, customer, date_dim y item, que son todas tablas utilizadas para consultas de prueba. Estas cuatro tablas son todas tablas de dimensiones y los cambios no serán muy frecuentes, por lo que todas generan tablas COW, y el código para generar tablas de dirección_cliente es el siguiente:

 val tableName = "customer_address"
  val basePath = "s3://dalei-demo/hudi/tpcds_hudi_cluster/customer_address"

  val df = spark.read.format("parquet").
                load(s"s3://dalei-demo/tpcds/data10g/customer_address").
                filter("ca_address_sk is not null")

  df.write.format("org.apache.hudi").
          option(TABLE_NAME, tableName).
          option("hoodie.datasource.write.precombine.field", "ca_address_id").
          option("hoodie.datasource.write.recordkey.field", "ca_address_sk").
          option("hoodie.upsert.shuffle.parallelism", "100").
          option("hoodie.datasource.write.table.type", "COPY_ON_WRITE").
          option("hoodie.datasource.write.operation", "upsert").
          option("hoodie.parquet.max.file.size", "10485760").
          option("hoodie.datasource.hive_sync.enable", "true").
          option("hoodie.datasource.hive_sync.mode", "hms").
          option("hoodie.datasource.hive_sync.database", "tpcds_hudi_cluster").
          option("hoodie.datasource.hive_sync.table", tableName).
          option("hoodie.parquet.small.file.limit", "0").
          option("hoodie.clustering.inline", "true").
          option("hoodie.clustering.inline.max.commits", "2").
          option("hoodie.clustering.plan.strategy.target.file.max.bytes", "1073741824").
          option("hoodie.clustering.plan.strategy.small.file.limit", "629145600").
          option("hoodie.clustering.plan.strategy.sort.columns", "").
          mode(Append).
          save(basePath);

El código que activa la agrupación es el siguiente:

val df1 = spark.read.format("hudi").option("hoodie.datasource.query.type", "read_optimized").
                load("s3://dalei-demo/hudi/tpcds_hudi_cluster/customer_address")
  val df2 = df1.withColumn("ca_gmt_offset", col("ca_gmt_offset") + lit(1.1))

  df2.write.format("org.apache.hudi").
          option(TABLE_NAME, tableName).
          option("hoodie.datasource.write.precombine.field", "ca_address_id").
          option("hoodie.datasource.write.recordkey.field", "ca_address_sk").
          option("hoodie.upsert.shuffle.parallelism", "100").
          option("hoodie.datasource.write.table.type", "COPY_ON_WRITE").
          option("hoodie.datasource.write.operation", "upsert").
          option("hoodie.parquet.max.file.size", "10485760").
          option("hoodie.datasource.hive_sync.enable", "true").
          option("hoodie.datasource.hive_sync.mode", "hms").
          option("hoodie.datasource.hive_sync.database", "tpcds_hudi_cluster").
          option("hoodie.datasource.hive_sync.table", tableName).
          option("hoodie.parquet.small.file.limit", "0").
          option("hoodie.clustering.inline", "true").
          option("hoodie.clustering.inline.max.commits", "2").
          option("hoodie.clustering.plan.strategy.target.file.max.bytes", "1073741824").
          option("hoodie.clustering.plan.strategy.small.file.limit", "629145600").
          option("hoodie.clustering.plan.strategy.sort.columns", "").
          mode(Append).
          save(basePath);

Las declaraciones de generación de las otras tres tablas son similares a la tabla dirección_cliente, puede intentar generarlas.

A modo de comparación, también necesitamos generar un conjunto de tablas con el mismo nombre que no utilicen Clustering. Estos dos conjuntos de tablas se pueden colocar en diferentes bases de datos de Hive, como tpcds_hudi_cluster y pcds_hudi_nocluster. Generar scripts sin tablas de Clustering es similar a generar Tablas de agrupación en clústeres El script de es similar, simplemente elimine los parámetros relacionados con la agrupación en clústeres.

3.2 Consulta

Trino360 se proporciona en Amazon EMR 6.5.0 y lo usamos para probar el rendimiento de las consultas SQL de la tabla Hudi. El comando de inicio es el siguiente: /usr/lib/trino/bin/trino-cli-360-executable –server localhost:8889 –catalog hive –schema tpcds_hudi_cluster

Si los datos de prueba de TPC-DS se generan de acuerdo con 2.1, verá la declaración de consulta generada en conjunto para la prueba. Usamos q6.sql para realizar la prueba. El script es el siguiente:

--q6.sql--

SELECT state, cnt FROM (
 SELECT a.ca_state state, count(*) cnt
 FROM
    customer_address a, customer c, store_sales_ro s, date_dim d, item i
 WHERE a.ca_address_sk = c.c_current_addr_sk
        AND c.c_customer_sk = s.ss_customer_sk
        AND s.ss_sold_date_sk = d.d_date_sk
        AND s.ss_item_sk = i.i_item_sk
        AND d.d_month_seq =
             (SELECT distinct (d_month_seq) FROM date_dim
        WHERE d_year = 2001 AND d_moy = 1)
        AND i.i_current_price > 1.2 *
             (SELECT avg(j.i_current_price) FROM item j
                    WHERE j.i_category = i.i_category)
 GROUP BY a.ca_state
) x
WHERE cnt >= 10
ORDER BY cnt LIMIT 100

La consulta de la tabla Hudi sin Clustering es la siguiente:

imagen.png

Figura 6: Consultas para tablas Hudi sin agrupación

La consulta de la tabla Hudi usando Clustering es la siguiente:

imagen.png

Figura 7: Consulta de tabla Hudi mediante agrupación en clústeres

Se puede ver que el rendimiento de las consultas de la tabla Hudi que usa Clustering mejora en un 35,4% en comparación con la tabla Hudi sin Clustering, la cantidad de registros leídos es la misma y la capacidad del archivo leído se reduce considerablemente.

4. Algunas sugerencias para usar Clustering

4.1 Impacto en Upsert

Al ejecutar el Clustering se implementa el nivel de aislamiento Snapshot Isolation para el File Group, por lo que no se permiten sus modificaciones, es decir, si hay operaciones Upsert y Compaction (tablas MOR), se debe esperar hasta el final del Clustering.

4.2 Consideraciones de carga

Si la cantidad de datos de tabla necesarios para la agrupación en clústeres es relativamente grande y si hay muchas particiones, realizar la agrupación en clústeres una vez también generará una gran cantidad de carga, por lo que Hudi proporciona una variedad de opciones para el alcance de la agrupación en clústeres. Para tablas que requieren escritura de alta concurrencia y lectura de alto rendimiento, la agrupación en clústeres se puede realizar durante el período mínimo de escritura de alta concurrencia, como por la noche.

4.3 Síncrono o asíncrono

udi proporciona dos métodos de agrupación en clústeres, sincrónico y asincrónico. No se recomienda utilizar agrupación sincrónica al escribir en tablas Hudi con alta concurrencia. Puede consultar el método en [RFC-19] y usar comandos para realizar una agrupación asincrónica.

4.4 Si se elige Ordenar columna

Si algunos campos se usan a menudo para unirse y se puede garantizar que el valor de este campo no esté vacío, se puede colocar en Ordenar columna. Si hay varios archivos después de la agrupación, Ordenar columna ayuda a confirmar cada uno El rango de este El campo en el archivo puede evitar la lectura excesiva del archivo y mejorar el rendimiento de la operación de unión. El principio es algo similar a Hive Clustering; consulte: Bucketing in Hive: Crear una tabla agrupada en Hive | blog de upGrad  .

Los amigos que estén interesados ​​pueden comparar la diferencia en el rendimiento de la consulta Unirse entre elegir Ordenar columna o no.

4.5 ¿La agrupación en clústeres es lo mismo que los archivos grandes?

Algunas personas dirán que la agrupación en clústeres consiste en fusionar archivos pequeños en archivos grandes. Al crear una tabla Hudi, ¿puedo seleccionar simplemente el archivo grande? Si solo considera el rendimiento de lectura, es posible hacerlo. Sin embargo, la agrupación en clústeres proporciona más opciones. La agrupación en clústeres es muy adecuada para tablas que a veces tienen escritura de alta concurrencia (adecuada para archivos pequeños) y, a veces, lectura de alto rendimiento (adecuada para archivos grandes).

4.6 Consulta incremental

En la versión actual de Hudi 0.10, la agrupación en clústeres no admite muy bien las consultas incrementales. Los datos posteriores a la agrupación en clústeres se considerarán datos "nuevos" y también aparecerán en los resultados de las consultas incrementales, lo cual no es lo que esperábamos. Porque no se han realizado cambios. Se ha realizado algún cambio en los datos, se consideran datos incrementales si simplemente se reescriben de un archivo pequeño a un archivo grande. Por lo tanto, no se recomienda utilizar la agrupación en clústeres para tablas que dependen de consultas incrementales.

4.7 ¿Cuándo especificar la agrupación?

Puede especificar la configuración relevante de Clustering siempre que se requiera Clustering, no solo al crear una tabla Hudi, es decir, para cualquier tabla Hudi, si se encuentra una gran cantidad de archivos pequeños, si se cumplen otras condiciones (sin alta concurrencia). escritura, sin dependencia de consultas incrementales, etc.), puede especificar la agrupación en clústeres en cualquier momento.

documentos de referencia

Mejora del precio/rendimiento de EMR con Amazon Graviton2 | Blog oficial de AWS

Agrupación | apache hudi

RFC - 19 Agrupación de datos para actualización y rendimiento de consultas - HUDI - Apache Software Foundation

Parquet apache

Badass - Amazon EMR

Presto y Trino - Amazon EMR

Almacenamiento en cubos en Hive: crear una tabla dividida en cubos en Hive | blog de actualización

El autor de este artículo.

imagen.png

Dalei Xu

Experto técnico en productos de análisis de datos de Amazon, responsable de la consultoría y diseño arquitectónico de las soluciones de análisis de datos de Amazon. Ha estado involucrado en el desarrollo de primera línea durante muchos años y ha acumulado una rica experiencia en desarrollo de datos, diseño de arquitectura, optimización del rendimiento y gestión de componentes. Espera promover los excelentes componentes de servicio de Amazon a más usuarios empresariales y lograr un crecimiento común y beneficioso para todos. con clientes.

Fuente del artículo: https://dev.amazoncloud.cn/column/article/6309c8e20c9a20404da79150?sc_medium=regulartraffic&sc_campaign=crossplatform&sc_channel=CSDN 

Supongo que te gusta

Origin blog.csdn.net/u012365585/article/details/132286257
Recomendado
Clasificación