Estamos desarrollando la interfaz más conveniente del mundo* para ver registros

Estamos desarrollando la interfaz más conveniente del mundo* para ver registros Si alguna vez ha utilizado interfaces web para ver registros, probablemente haya notado que, por regla general, estas interfaces son engorrosas y (a menudo) no muy convenientes y receptivas. Puedes acostumbrarte a algunos, otros son absolutamente terribles, pero me parece que la razón de todos los problemas es que abordamos incorrectamente la tarea de ver registros: intentamos crear una interfaz web donde se encuentra la CLI (interfaz de línea de comando). Funciona mejor. Personalmente me siento muy cómodo trabajando con tail, grep, awk y otros y, por lo tanto, para mí la interfaz ideal para trabajar con registros sería algo similar a tail y grep, pero que también podría usarse para leer registros que provienen de muchos servidores. Eso sí, ¡léelos desde ClickHouse!

*según la opinión personal del usuario habra tú Molas

Conoce logscli

No se me ocurrió un nombre para mi interfaz y, para ser honesto, más bien existe en forma de prototipo, pero si desea ver inmediatamente el código fuente, de nada: https://github.com/YuriyNasretdinov/logscli (350 líneas de código Go seleccionado).

oportunidades

Mi objetivo era crear una interfaz que resultara familiar para aquellos que están acostumbrados a tail/grep, es decir, que admitiera las siguientes cosas:

  1. Ver todos los registros, sin filtrar.
  2. Deje líneas que contengan una subcadena fija (bandera -F у grep).
  3. Deje líneas que coincidan con la expresión regular (bandera -E у grep).
  4. De forma predeterminada, la visualización se realiza en orden cronológico inverso, ya que los registros más recientes suelen ser los primeros en interesarse.
  5. Mostrar contexto al lado de cada línea (opciones -A, -B и -C у grep, imprimiendo N líneas antes, después y alrededor de cada línea coincidente, respectivamente).
  6. Ver registros entrantes en tiempo real, con o sin filtrado (esencialmente tail -f | grep).
  7. La interfaz debe ser compatible con less, head, tail y otros: de forma predeterminada, los resultados deben devolverse sin restricciones en cuanto a su número; las líneas se imprimen como una secuencia siempre que el usuario esté interesado en recibirlas; señal SIGPIPE debería interrumpir silenciosamente la transmisión de registros, tal como lo hacen tail, grep y otras utilidades UNIX.

implementación

Asumiré que de alguna manera ya sabes cómo entregar registros a ClickHouse. Si no, recomiendo probarlo. LSD и casa de gatitosy este artículo sobre la entrega de registros.

Primero debes decidir el esquema básico. Dado que normalmente desea recibir registros ordenados por tiempo, parece lógico almacenarlos de esa manera. Si hay muchas categorías de registro y todas son del mismo tipo, entonces puede crear una categoría de registro como la primera columna de la clave principal; esto le permitirá tener una tabla en lugar de varias, lo que será una gran ventaja cuando insertar en ClickHouse (en servidores con discos duros, se recomienda insertar datos no más de ~1 veces por segundo para todo el servidor).

Es decir, necesitamos aproximadamente el siguiente esquema de tabla:

CREATE TABLE logs(
    category LowCardinality(String), -- категория логов (опционально)
    time DateTime, -- время события
    millis UInt16, -- миллисекунды (могут быть и микросекунды, и т.д.): рекомендуется хранить, если событий много, чтобы было легче различать события между собой
    ..., -- ваши собственные поля, например имя сервера, уровень логирования, и так далее
    message String -- текст сообщения
) ENGINE=MergeTree()
ORDER BY (category, time, millis)

Desafortunadamente, no pude encontrar de inmediato ninguna fuente abierta con registros realistas que pudiera capturar y descargar, así que tomé esto como ejemplo. reseñas de productos de Amazon antes de 2015. Por supuesto, su estructura no es exactamente la misma que la de los registros de texto, pero a efectos ilustrativos esto no es importante.

instrucciones para cargar reseñas de Amazon en ClickHouse

Creemos una tabla:

CREATE TABLE amazon(
   review_date Date,
   time DateTime DEFAULT toDateTime(toUInt32(review_date) * 86400 + rand() % 86400),
   millis UInt16 DEFAULT rand() % 1000,
   marketplace LowCardinality(String),
   customer_id Int64,
   review_id String,
   product_id LowCardinality(String),
   product_parent Int64,
   product_title String,
   product_category LowCardinality(String),
   star_rating UInt8,
   helpful_votes UInt32,
   total_votes UInt32,
   vine FixedString(1),
   verified_purchase FixedString(1),
   review_headline String,
   review_body String
)
ENGINE=MergeTree()
ORDER BY (time, millis)
SETTINGS index_granularity=8192

En el conjunto de datos de Amazon solo hay una fecha para una revisión, pero no hay una hora exacta, así que completemos estos datos con un aleatorio.

No es necesario descargar todos los archivos tsv y limitarse a los primeros ~10-20 para obtener un conjunto de datos bastante grande que no cabe en 16 GB de RAM. Para cargar archivos TSV utilicé el siguiente comando:

for i in *.tsv; do
    echo $i;
    tail -n +2 $i | pv |
    clickhouse-client --input_format_allow_errors_ratio 0.5 --query='INSERT INTO amazon(marketplace,customer_id,review_id,product_id,product_parent,product_title,product_category,star_rating,helpful_votes,total_votes,vine,verified_purchase,review_headline,review_body,review_date) FORMAT TabSeparated'
done

En un disco persistente estándar (que es un HDD) en Google Cloud con un tamaño de 1000 GB (tomé este tamaño principalmente para que la velocidad fuera un poco mayor, aunque quizás un SSD del tamaño requerido hubiera sido más barato) la carga La velocidad fue de aproximadamente ~ 75 MB/seg en 4 núcleos.

  • Debo hacer una reserva de que trabajo en Google, pero usé una cuenta personal y este artículo no tiene nada que ver con mi trabajo en la empresa.

Produciré todas las ilustraciones con este conjunto de datos en particular, ya que esto es todo lo que tenía a mano.

Mostrar el progreso del escaneo de datos

Dado que en ClickHouse usaremos un escaneo completo en una tabla con registros, y esta operación puede llevar una cantidad significativa de tiempo y puede no producir ningún resultado durante mucho tiempo si se encuentran pocas coincidencias, es recomendable poder mostrar el progreso de la consulta hasta recibir las primeras filas con el resultado. Para hacer esto, existe un parámetro en la interfaz HTTP que le permite enviar el progreso en los encabezados HTTP: send_progress_in_http_headers=1. Desafortunadamente, la biblioteca Go estándar no puede leer los encabezados a medida que se reciben, pero ClickHouse admite la interfaz HTTP 1.0 (¡no debe confundirse con 1.1!), por lo que puede abrir una conexión TCP sin formato a ClickHouse y enviarla allí. GET /?query=... HTTP/1.0nn y recibir los encabezados y el cuerpo de la respuesta sin ningún tipo de escape ni cifrado, por lo que en este caso ni siquiera necesitamos usar la biblioteca estándar.

Transmisión de registros desde ClickHouse

ClickHouse ha tenido optimización para consultas con ORDER BY durante un tiempo relativamente largo (¿desde 2019?), por lo que una consulta como

SELECT time, millis, message
FROM logs
WHERE message LIKE '%something%'
ORDER BY time DESC, millis DESC

Inmediatamente comenzará a devolver líneas que tengan la subcadena "algo" en su mensaje, sin esperar a que finalice el análisis.

Además, sería muy conveniente que ClickHouse cancelara la solicitud cuando se cerrara la conexión, pero este no es el comportamiento predeterminado. La cancelación automática de solicitudes se puede habilitar usando la opción cancel_http_readonly_queries_on_client_close=1.

Manejo correcto de SIGPIPE en Go

Cuando ejecuta, digamos, el comando some_cmd | head -n 10, exactamente como el comando some_cmd detiene la ejecución cuando head restó 10 líneas? La respuesta es simple: cuando head termina, la tubería se cierra y la salida estándar del comando some_cmd comienza a apuntar, condicionalmente, "a ninguna parte". Cuando some_cmd intenta escribir en una tubería cerrada, recibe una señal SIGPIPE, que finaliza silenciosamente el programa de forma predeterminada.

En Go esto también sucede de forma predeterminada, pero el controlador de señales SIGPIPE también imprime "señal: SIGPIPE" o un mensaje similar al final, y para borrar este mensaje solo necesitamos manejar SIGPIPE nosotros mismos de la manera que queramos, es decir, simplemente en silencio. salida:

ch := make(chan os.Signal)
signal.Notify(ch, syscall.SIGPIPE)
go func() {
    <-ch
    os.Exit(0)
}()

Mostrar contexto del mensaje

A menudo desea ver el contexto en el que ocurrió algún error (por ejemplo, qué solicitud causó pánico o qué problemas relacionados eran visibles antes del bloqueo) y en grep Esto se hace usando las opciones -A, -B y -C, que muestran el número especificado de líneas después, antes y alrededor del mensaje, respectivamente.

Desafortunadamente, no he encontrado una manera fácil de hacer lo mismo en ClickHouse, por lo que para mostrar el contexto, se envía una solicitud adicional como esta a cada línea del resultado (los detalles dependen de la clasificación y de si el contexto se muestra antes). o después):

SELECT time,millis,review_body FROM amazon
WHERE (time = 'ВРЕМЯ_СОБЫТИЯ' AND millis < МИЛЛИСЕКУНДЫ_СОБЫТИЯ) OR (time < 'ВРЕМЯ_СОБЫТИЯ')
ORDER BY time DESC, millis DESC
LIMIT КОЛИЧЕСТВО_СТРОК_КОНТЕКСТА
SETTINGS max_threads=1

Dado que la solicitud se envía casi inmediatamente después de que ClickHouse devuelve la línea correspondiente, termina en el caché y, en general, la solicitud se ejecuta con bastante rapidez y consume un poco de CPU (normalmente la solicitud tarda unos ~6 ms en mi máquina virtual).

Mostrar nuevos mensajes en tiempo real

Para mostrar los mensajes entrantes en (casi) tiempo real, simplemente ejecutamos la solicitud una vez cada pocos segundos, recordando la última marca de tiempo que encontramos antes.

Ejemplos de comandos

¿Cómo se ven los comandos típicos de logscli en la práctica?

Si descargó el conjunto de datos de Amazon que mencioné al principio del artículo, puede ejecutar los siguientes comandos:

# Показать строки, где встречается слово walmart
$ logscli -F 'walmart' | less

# Показать самые свежие 10 строк, где встречается "terrible"
$ logscli -F terrible -limit 10

# То же самое без -limit:
$ logscli -F terrible | head -n 10

# Показать все строки, подходящие под /times [0-9]/, написанные для vine и у которых высокий рейтинг
$ logscli -E 'times [0-9]' -where="vine='Y' AND star_rating>4" | less

# Показать все строки со словом "panic" и 3 строки контекста вокруг
$ logscli -F 'panic' -C 3 | less

# Непрерывно показывать новые строки со словом "5-star"
$ logscli -F '5-star' -tailf

referencias

El código de la utilidad (sin documentación) está disponible en github en https://github.com/YuriyNasretdinov/logscli. Me encantaría conocer su opinión sobre mi idea de una interfaz de consola para ver registros basada en ClickHouse.

Fuente: habr.com

Añadir un comentario