La interpretación más fuerte del diseño y arquitectura de Apache Hudi

Gracias al colaborador de Apache Hudi: Wang Xianghu por su traducción y contribución.

Bienvenido a prestar atención a la cuenta pública de WeChat: ApacheHudi

Este artículo presentará los conceptos básicos, el diseño y la infraestructura general de Apache Hudi.

1. Introducción

Apache Hudi (abreviatura: Hudi) le permite almacenar grandes cantidades de datos en la parte superior del almacenamiento compatible con Hadoop. También proporciona dos primitivas, que permiten el procesamiento de flujo en el lago de datos además del procesamiento por lotes clásico. Las dos primitivas son:

  • Actualización / eliminación de registros : Hudi utiliza índices de nivel de archivo / registro específicos para admitir la actualización / eliminación de registros, al tiempo que proporciona garantías de transacciones para operaciones de escritura. La consulta procesa la última instantánea enviada y se basa en esta salida.

  • Cambio de flujo : Hudi proporciona soporte de primera clase para obtener cambios de datos: puede obtener el flujo incremental de todos los registros en la tabla dada que se han actualizado / insertado / eliminado desde un punto de tiempo dado, y desbloquear nuevas poses de consulta (categorías).

Estas primitivas se combinan estrechamente para desbloquear las capacidades de transmisión / procesamiento incremental basadas en la abstracción DFS. Si está familiarizado con el procesamiento de flujo, esto es similar a consumir eventos de temas kafka y luego usar el almacenamiento de estado para acumular gradualmente resultados intermedios. Esto tendrá las siguientes ventajas en arquitectura: 1) Mejora de la eficiencia: la ingesta de datos generalmente necesita lidiar con actualizaciones, eliminaciones y aplicar restricciones de clave únicas. Sin embargo, debido a la falta de un sistema como Hudi que pueda proporcionar soporte estándar para estas funciones, los ingenieros de datos a menudo usan grandes lotes de trabajos para reprocesar todo un día de eventos, o recargar toda la base de datos ascendente cada vez que se ejecutan, lo que resulta en una gran cantidad de Los recursos informáticos se desperdician. Debido a que Hudi admite actualizaciones a nivel de registro, solo proporciona una mejora de rendimiento de orden de magnitud para estas operaciones al procesar solo registros modificados y reescribir solo las partes actualizadas / eliminadas de la tabla, en lugar de reescribir toda la partición de la tabla o incluso la tabla completa. 2) ETL más rápido / Tuberías derivadas: después de ingerir datos de sistemas externos, el siguiente paso es usar Apache Spark / Apache Hive o cualquier otro marco de procesamiento de datos para ETL estos datos para el almacenamiento de datos, aprendizaje automático o simplemente análisis de datos Espere algunos escenarios de aplicación. Por lo general, estos procesos vuelven a depender de trabajos por lotes expresados ​​en código o SQL, que procesarán por lotes todos los datos de entrada y volverán a calcular todos los resultados de salida. Mediante el uso de consultas incrementales en lugar de consultas de instantáneas para consultar una o más tablas de entrada, puede acelerar en gran medida dichas canalizaciones de datos, nuevamente procesando solo los cambios incrementales de las tablas ascendentes como se indicó anteriormente, y luego insertar o eliminar la tabla derivada de destino. 3) Adquisición de datos nuevos: no es común reducir recursos y obtener mejoras de rendimiento. Después de todo, generalmente usamos más recursos (como la memoria) para mejorar el rendimiento (como la latencia de consulta). Hudi ha eliminado radicalmente el método de gestión tradicional de los conjuntos de datos y ha aportado un beneficio adicional al procesamiento incremental de lotes: en comparación con el lago de datos anterior, la tubería se ejecutará en tiempos más cortos y la entrega de datos será más rápida. 4) Almacenamiento unificado: en base a las tres ventajas anteriores, un procesamiento más rápido y ligero en el lago de datos existente significa que ya no se necesitan almacenes especiales o marts de datos con el fin de acceder a datos casi en tiempo real.

2. Principios de diseño.

Lectura / escritura de transmisión : Hudi se basa en los principios del diseño de bases de datos y diseños desde cero para aplicar a la entrada y salida de grandes conjuntos de datos para grabar secuencias. Con este fin, Hudi proporciona una implementación de índice que puede asignar rápidamente las claves grabadas a la ubicación del archivo donde se encuentra. Del mismo modo, para la transmisión de datos de salida, Hudi agrega y rastrea metadatos a nivel de registro a través de sus columnas especiales, que pueden proporcionar una secuencia incremental precisa de todos los cambios. Autogestión : Hudi ha notado que los usuarios pueden tener diferentes expectativas para la actualización de datos (fácil de escribir) y el rendimiento de la consulta (fácil de leer / consultar). Admite tres tipos de consulta, que proporcionan instantáneas en tiempo real, transmisión incremental y versiones anteriores. Datos de columna pura. En cada paso, Hudi se esfuerza por autogestionarse (como optimizar automáticamente el paralelismo de los programas de escritura y mantener el tamaño del archivo) y autorrepararse (como revertir automáticamente los envíos fallidos), incluso si hacerlo aumentará ligeramente los costos de tiempo de ejecución (como : Almacena en caché los datos de entrada en la memoria para analizar la carga de trabajo). Sin estas palancas operativas incorporadas / funciones de autogestión, los costos operativos de estas tuberías a gran escala generalmente se duplicarán. Todo es un registro : Hudi también tiene un diseño de datos de nube que solo se puede agregar que implementa el principio de un sistema de almacenamiento estructurado de registros y puede administrar sin problemas los datos de todos los proveedores de la nube.
Modelo de datos clave-valor : en términos de escritura, la tabla Hudi se modela como un conjunto de datos de pares clave-valor, donde cada registro tiene una clave de registro única. Además, una clave de registro también puede incluir una ruta de partición bajo la cual los registros se pueden particionar y almacenar. Esto generalmente ayuda a reducir el espacio de búsqueda para las consultas de índice.

3. Diseño de mesa

Después de comprender la motivación técnica clave del proyecto Hudi, profundicemos en el diseño del sistema Hudi. En un nivel superior, el componente utilizado para escribir la tabla Hudi está incrustado en un trabajo Apache Spark de forma compatible, y genera un conjunto de archivos que representan la tabla Hudi en el almacenamiento habilitado para DFS. Luego, con ciertas garantías, los motores de consulta como Apache Spark, Presto y Apache Hive pueden consultar la tabla. Los tres componentes principales de la tabla Hudi: 1) Metadatos de línea de tiempo ordenados. Similar al registro de transacciones de la base de datos. 2) Archivo de datos con diseño jerárquico: los datos realmente escritos en la tabla. 3) Índice (implementaciones múltiples): asigna el conjunto de datos que contiene el registro especificado.

Hudi proporciona las siguientes funciones para escribir y consultar datos básicos, lo que lo convierte en un módulo importante para grandes lagos de datos: 1) admite una inserción de índice rápida y conectable (); 2) eficiente, solo escanea en busca de nuevos datos Consulta de volumen; 3) liberación y reversión de datos atómicos, soporte para Savepoint guardado; 4) aislamiento de instantánea de lectura y escritura diseñado usando el estilo mvcc (control de concurrencia de múltiples versiones); 5) usa estadísticas para administrar el tamaño del archivo; 6) tiene Compresión autogestionada con actualización de registro / delta; 7) Metadatos de la línea de tiempo para la modificación de datos; 8) Cumplir con GDPR (Reglamento General de Protección de Datos) y funciones de eliminación de datos.

3.1 Cronología

En esencia, Hudi mantiene una línea de tiempo que contiene todas las operaciones instantáneas realizadas en el conjunto de datos en diferentes momentos instantáneos para proporcionar una vista instantánea de la tabla, al tiempo que admite la recuperación de datos en orden de llegada. La línea de tiempo es similar al registro de rehacer / transacciones de la base de datos y consta de un conjunto de instancias de línea de tiempo. Hudi garantiza la atomicidad de las operaciones realizadas en la línea de tiempo y la consistencia de la línea de tiempo basada en el tiempo instantáneo. La línea de tiempo se implementa como un conjunto de archivos en la carpeta de metadatos .hoodie debajo de la ruta base de la tabla. Específicamente, el último instante se guarda como un solo archivo, y el instante anterior se archiva en la carpeta de archivo de la línea de tiempo para limitar la cantidad de archivos enumerados por escritores y consultas. Un instante de línea de tiempo de Hudi consta de los siguientes componentes: 1) Tipo de operación: el tipo de operación realizada en el conjunto de datos; 2) Tiempo instantáneo: el tiempo instantáneo suele ser una marca de tiempo (por ejemplo: 20190117010349), que se basa en el tiempo de inicio de la operación El orden es monótono; 3) Estado instantáneo: el estado actual del instante; cada instante tiene información de metadatos en formato avro o json, que detalla el estado de la operación y el estado del instante. Los tipos de operación instantánea clave son: 1) COMPROMISO: un compromiso significa escribir un conjunto de átomos de registro en el conjunto de datos; 2) LIMPIAR: eliminar la actividad de fondo de la versión anterior del archivo que ya no es necesaria en el conjunto de datos; 3) DELTA_COMMIT: se procesará por lotes Registre las escrituras atómicas en el conjunto de datos de tipo de almacenamiento MergeOnRead, algunas de las cuales solo se pueden escribir en el registro incremental; 4) COMPACTACIÓN: Coordine las actividades en segundo plano de la estructura de datos diferenciales en Hudi, por ejemplo: actualización desde el registro basado en filas El archivo se convierte en un formato de columna. Internamente, la compresión aparece como un commit especial en la línea de tiempo; 5) ROLLBACK: indica que el commit / commit incremental no fue exitoso y se ha revertido, eliminando todos los archivos parciales generados durante el proceso de escritura; 6) SAVEPOINT: ciertos archivos El grupo está marcado como "guardado" para que el programa de limpieza no lo elimine. En caso de desastre / recuperación de datos, ayuda a restaurar el conjunto de datos a un cierto punto en la línea de tiempo; 

3.2 Archivos de datos

Hudi organiza la tabla en una estructura de carpetas bajo la ruta básica en DFS. Si la tabla está particionada, habrá otras particiones debajo de la ruta básica, que son las carpetas que contienen los datos de la partición, que es muy similar a la tabla Hive. Cada partición se identifica de manera única por la ruta de partición relativa a la ruta base. Dentro de cada partición, los archivos se organizan en grupos de archivos, identificados de forma única por ID de archivo. Cada segmento contiene el archivo de columna básico (* .parquet) y un conjunto de archivos de registro (* .log *) generados en un determinado momento de confirmación / compresión instantánea, que contiene la inserción / actualización del archivo básico desde que se generó el archivo básico . Hudi utiliza el diseño MVCC. La operación de compresión combina el registro y los archivos básicos para generar nuevas piezas de archivo, mientras que la operación de limpieza elimina las piezas de archivo no utilizadas / antiguas para recuperar espacio en el DFS.

 

3.3 Índice

Hudi proporciona operaciones de inserción eficaces a través del mecanismo de indexación, que asignará una combinación de clave de registro + ruta de partición a una ID de archivo. La asignación entre esta clave de registro y la identificación de grupo / archivo de archivo se escribe en el grupo de archivos Ya no cambiará desde el principio. En resumen, este grupo de archivos de asignación contiene todas las versiones de un grupo de archivos. Hudi actualmente proporciona tres implementaciones de índice (HBaseIndex, HoodieBloomIndex (HoodieGlobalBloomIndex), InMemoryHashIndex) para asignar una clave de registro al ID de archivo que contiene el registro. Esto nos permitirá aumentar significativamente la velocidad de inserción sin tener que escanear todos los registros de la tabla. El índice Hudi se puede clasificar según su capacidad para consultar registros de partición: 1) Índice global: se puede consultar el ID de archivo del mapa de claves de registro sin información de partición. Por ejemplo, el escritor puede pasar nulo o cualquier cadena como la ruta de la partición, pero el índice aún encontrará la ubicación del registro. El índice global es muy útil cuando se garantiza que la clave de registro sea única en toda la tabla, pero el consumo de la consulta aumenta funcionalmente con el tamaño de la tabla. 2) Índice no global: a diferencia del índice global, el índice no global depende de la ruta de la partición (divisionPath), para una clave de registro dada, solo encontrará el registro debajo de la ruta de la partición dada. Esto es más adecuado para el escenario donde la ruta de la partición y la clave de registro siempre se generan al mismo tiempo, y al mismo tiempo, puede disfrutar de una mejor escalabilidad, ya que el consumo del índice de consulta solo está relacionado con el tamaño del conjunto de datos escrito en la partición.

4. Tipo de tabla

4.1 Copia en escritura 表

Cuando se escribe la tabla COW, los datos se escriben directamente en el archivo base, (parquet) no escribe el archivo de registro. Por lo tanto, el segmento de archivo de la tabla COW contiene solo un archivo base (un archivo de parquet constituye un segmento de archivo). Spark DAG con este método de almacenamiento es relativamente simple. El objetivo clave es utilizar el particionador para dividir el registro RDD de Hudi etiquetado (el llamado etiquetado se refiere a la consulta de índice, marcando la posición de cada registro de entrada en la tabla) en una serie de actualizaciones e inserciones. Para mantener el tamaño del archivo, primero ingresamos Muestreo para obtener un perfil de carga de trabajo, este perfil registra la inserción y actualización del registro de entrada, y la distribución en la partición y otra información. Empaquete los datos del nuevo de esta manera: 1) Para las actualizaciones, la última versión de la ID del archivo se reescribirá una vez, y use el nuevo valor para todos los registros modificados 2) Para las inserciones. Los registros se empaquetan primero en cada ruta de partición En el archivo más pequeño, hasta alcanzar el tamaño máximo configurado. Todos los registros restantes después de eso se empaquetarán nuevamente en un nuevo grupo de archivos, y el nuevo grupo de archivos también cumplirá con los requisitos de tamaño máximo de archivo. 

 

4.2 Fusionar en lectura 表

Al escribir datos en la tabla MOR, el registro primero se escribirá rápidamente en el archivo de registro y luego se fusionará con el archivo base mediante la operación de compresión en la línea de tiempo. Dependiendo de si la consulta lee la secuencia de instantánea fusionada o cambia la secuencia en el registro, o solo el archivo base no fusionado, la tabla MOR admite múltiples tipos de consulta. En un nivel alto, el escritor MOR pasará por las mismas etapas que el escritor COW cuando lea datos. Estas actualizaciones se agregarán al último archivo de registro en el último archivo y no se fusionarán. Para insertar, Hudi admite dos modos: 1) insertar en el archivo de registro: la tabla con el archivo de registro indexable realizará esta operación (índice HBase); 2) insertar el archivo de parquet: tabla sin archivo de índice (como el índice de Bloom) y Al igual que copy-on-write (COW), los registros de entrada en la ubicación marcada se dividen de manera que todos los upserts enviados al mismo ID de archivo se agrupan. Este lote de upserts se escribirá en el archivo de registro como uno o más bloques de registro. Hudi le permite al cliente controlar el tamaño del archivo de registro. WriteClient de Hudi es lo mismo para los escritores copy-on-write (COW) y merge-on-read (MOR). Varias rondas de escritura de datos acumularán uno o más archivos de registro. Estos archivos de registro junto con los archivos básicos de parquet (si los hay) forman un segmento de archivo, y este segmento de archivo representa una versión completa del archivo. Este tipo de mesa es la mesa más versátil y más avanzada. Proporciona una gran flexibilidad para las escrituras (puede especificar diferentes estrategias de compresión para absorber el tráfico de escritura en ráfaga) y consultas (como sopesar la frescura de los datos y el rendimiento de las consultas). Al mismo tiempo, contiene una curva de aprendizaje para controlarlo en funcionamiento. 

 

5. Escribir diseño

5.1 Operación de escritura

Puede ser útil comprender las tres operaciones de escritura diferentes proporcionadas por la fuente de datos Hudi o la herramienta deltastreamer y cómo utilizarlas mejor. Estas operaciones se pueden seleccionar / cambiar en cada commit / delta commit emitido para el conjunto de datos. 1) Operación Upsert: esta es la operación predeterminada. En esta operación, los registros de datos se marcan primero para su inserción o actualización consultando el índice, y luego se ejecutan las heurísticas para determinar la mejor forma de empaquetarlos en el almacenamiento para optimizar el tamaño del archivo Y finalmente escribe el registro. Para casos de uso como la captura de cambios en la base de datos, se recomienda utilizar esta acción cuando la entrada casi seguramente contiene actualizaciones. 2) Operación de inserción: en comparación con upsert, la operación de inserción también ejecutará heurística para determinar el método de empaquetado y optimizar el tamaño del archivo, pero omitirá por completo la consulta de índice. Entonces, para casos de uso como la deduplicación de registros (combinada con la opción de duplicar filtro mencionada a continuación), es mucho más rápido que upsert. Esto también se aplica a los casos de uso en los que el conjunto de datos puede tolerar duplicados, pero solo requiere que Hudi tenga capacidades de gestión transaccional de escritura / almacenamiento incremental / extracción. 3) operación de inserción masiva: las operaciones de inserción y inserción mantendrán los registros de entrada en la memoria para acelerar la velocidad de cálculo heurístico de almacenamiento, por lo que puede ser engorroso para el caso de uso inicial de cargar / iniciar el conjunto de datos Hudi. La inserción masiva proporciona la misma semántica que la inserción, y también implementa un algoritmo de escritura de datos basado en la clasificación, que puede expandir bien la carga inicial de cientos de terabytes. Pero este es solo el mejor esfuerzo para ajustar el tamaño del archivo, no para garantizar el tamaño del archivo como insertar / actualizar.

5.2 Compresión

La compresión es una operación instantánea, que toma un conjunto de segmentos de archivo como entrada, combina todos los archivos de registro en cada segmento de archivo con su archivo base (archivo de parquet) para generar un nuevo segmento de archivo comprimido, y lo escribe como uno en la línea de tiempo comprometerse La compresión solo se aplica al tipo de tabla MOR al leer, y la estrategia de compresión (de forma predeterminada, selecciona el segmento de archivo con el registro sin comprimir más grande) para seleccionar el segmento de archivo que se va a comprimir. Esta estrategia de compresión se evaluará después de cada operación de escritura. En un nivel alto, hay dos formas de compresión: 1) Compresión sincrónica: la compresión aquí se realiza sincrónicamente por el proceso del escritor en sí después de cada escritura, es decir, la siguiente operación de escritura no puede iniciarse hasta que se complete la compresión. En términos de operación, este es el más simple, porque no hay necesidad de organizar un proceso de compresión separado, pero la frescura garantizada de los datos es la más baja. Sin embargo, este método sigue siendo muy útil si puede comprimir la última partición de tabla en cada operación de escritura mientras retrasa la compresión de particiones tardías / antiguas. 2) Compresión asincrónica: de esta manera, el proceso de compresión puede ejecutarse de forma asincrónica con la operación de escritura de tabla. Esto tiene la ventaja obvia de que la compresión no bloquea el siguiente lote de escrituras de datos, lo que resulta en una actualización de datos casi en tiempo real. Herramientas como Hudi DeltaStreamer admiten el modo continuo de límites, donde las operaciones de compresión y escritura se realizan en un solo clúster de tiempo de ejecución de Spark de esta manera.

5.3 Limpieza

La limpieza es una operación instantánea básica, su propósito es eliminar archivos antiguos y limitar el espacio de almacenamiento ocupado por la tabla. La limpieza se realizará automáticamente después de cada operación de escritura y utilizará los metadatos de la línea de tiempo almacenados en caché en el servidor de la línea de tiempo para evitar escanear toda la tabla para evaluar la oportunidad de limpieza. Hudi admite dos métodos de limpieza: 1) limpieza mediante commits / deltacommits: este es el más común y debe usarse en consultas incrementales. De esta forma, Cleaner retendrá todos los segmentos de archivo escritos en los últimos N commit / delta commits, proporcionando así la capacidad de realizar consultas incrementales dentro de cualquier rango inmediato. Aunque esto es muy útil para consultas incrementales, ya que todas las versiones de los fragmentos de archivo dentro del rango de configuración se retienen, es posible que se requiera más espacio de almacenamiento en ciertos escenarios de alta carga de escritura. 2) Limpieza por segmentos de archivo reservados: este es un método de limpieza más simple, aquí solo guardamos los últimos N segmentos de archivo en cada grupo de archivos. Algunos motores de consulta, como Apache Hive, procesan consultas muy grandes. Estas consultas pueden tardar varias horas en completarse. En este caso, configure N para que sea lo suficientemente grande como para no eliminar archivos a los que la consulta aún puede acceder. La película es muy útil. Además, la operación de limpieza garantizará que siempre haya solo un segmento de archivo (el último) debajo de cada grupo de archivos.

5.4 Optimización del acceso DFS

Hudi también realiza varias funciones clave de gestión de almacenamiento en los datos almacenados en la tabla. La clave para almacenar datos en DFS es administrar el tamaño del archivo y contar y reclamar espacio de almacenamiento. Por ejemplo, HDFS es conocido por tratar con archivos pequeños que ponen memoria / presión RPC en NameNode puede destruir la estabilidad de todo el clúster. En general, los motores de consulta pueden proporcionar un mejor rendimiento en archivos de columna de tamaño apropiado porque pueden amortizar efectivamente el costo de obtener estadísticas de columna, etc. Incluso en algunos almacenes de datos en la nube, incluir un directorio que contenga una gran cantidad de archivos pequeños generará costos. Aquí hay algunos métodos eficientes de escritura, gestión de almacenamiento de datos de Hudi: 1) La función de procesamiento de archivos pequeños analizará la carga de trabajo de entrada y distribuirá el contenido al grupo de archivos existente en lugar de crear un nuevo grupo de archivos (esto dará como resultado la generación de archivos pequeños ) 2) Use una memoria caché de línea de tiempo en el escritor, de modo que mientras el clúster Spark no se reinicie cada vez, las operaciones de escritura posteriores no necesitan enumerar el directorio DFS para obtener la lista de segmentos de archivo en la ruta de partición especificada. 3) El usuario también puede ajustar el coeficiente de relación entre el tamaño del archivo básico y el archivo de registro y la relación de compresión deseada, para dividir un número suficiente de inserciones en un grupo de archivos unificado, generando así un archivo básico de un tamaño adecuado. 4) Ajuste de manera inteligente el grado paralelo de inserción masiva, y puede ajustar el grupo de archivos inicial con el tamaño apropiado nuevamente. De hecho, es fundamental realizar esta operación correctamente, ya que el grupo de archivos no se puede eliminar una vez que se ha creado, y solo se puede expandir como se describió anteriormente.

6. Consulta

En vista de este diseño de datos flexible y completo y de una rica línea de tiempo, Hudi puede admitir tres formas diferentes de consultar tablas, según el tipo de tabla.

Tipo de consulta VACA MOR
Consulta de instantánea La consulta se ejecuta en el último archivo básico en todos los segmentos de archivo en una tabla o partición de tabla determinada, y se verá el último registro enviado. Realice la consulta combinando los últimos archivos básicos y los archivos de registro en todos los segmentos de archivo en una tabla o partición de tabla determinada, y verá los registros escritos por la última operación delta-commit.
Consulta incremental En los intervalos de tiempo instantáneos de inicio y finalización dados, la consulta se realiza en el último archivo básico (llamado ventana de consulta incremental), y solo las columnas especificadas por Hudi se utilizan para extraer los registros escritos en esta ventana. La consulta se realiza en el último segmento de archivo en la ventana de consulta incremental, dependiendo de la ventana en sí, la combinación de registros de lectura en el bloque básico o bloque de registro.
Leer consulta optimizada Igual que la consulta de instantánea Acceda solo a archivos básicos, proporcionando datos para un segmento de archivo dado desde la última operación de compresión. Por lo general, la garantía del último grado de datos de consulta depende de la estrategia de compresión

 

 

6.1 Consulta de instantánea

Puede ver la última instantánea de la tabla después de una operación de confirmación o confirmación delta determinada. En el caso de una tabla de fusión en tiempo de lectura (MOR), proporciona una tabla casi en tiempo real (unos minutos) al fusionar instantáneamente los archivos básicos y los archivos incrementales del último segmento de archivo. Para copiar en escritura (COW), puede reemplazar las tablas de parquet existentes (o tablas del mismo tipo de archivo básico), al tiempo que proporciona upsert / delete y otras funciones de escritura.

6.2 Consulta incremental

Puede ver datos recién escritos desde una operación de confirmación / confirmación delta determinada. Proporcione de manera efectiva flujos de cambios para habilitar canalizaciones de datos incrementales.

6.3 Leer consulta optimizada

Puede ver la última instantánea de la tabla para una operación de confirmación / compacta inmediata. Solo los archivos básicos / de columna de los últimos documentos están expuestos a la consulta, y se garantiza el mismo rendimiento de consulta de columna que la tabla no Hudi.

Indice Leer consulta optimizada Consulta de instantánea
Retraso de datos Alta Bajo
Retraso de consulta Bajo Alta

  

Supongo que te gusta

Origin www.cnblogs.com/leesf456/p/12710118.html
Recomendado
Clasificación