Problemas con los destinos dinámicos de flujo de datos

Scicrazed:

Tengo un trabajo de flujo de datos que lee datos de pubsub y se basa en el tiempo y el nombre del archivo escribe el contenido a GCS donde la ruta de la carpeta se basa en el formato AAAA / MM / DD. Esto permite que los archivos que se generan en carpetas en función de la fecha y utiliza Apache haz de FileIOy Dynamic Destinations.

Hace aproximadamente dos semanas, me di cuenta de una acumulación inusual de mensajes no confirmados. Al reiniciar el trabajo df los errores desaparecieron y los nuevos archivos se estaban escribiendo en GCS.

Después de un par de días, la escritura se detuvo de nuevo, pero esta vez, hubo errores que afirman que el procesamiento era atascado. Después de algunas investigaciones SO de confianza, descubrí que esto era probablemente causado por un problema de bloqueo en la pre 2.90 Beam, ya que utiliza la biblioteca Conscrypt como el proveedor de seguridad predeterminado. Por lo tanto, he actualizado a 2.11 Viga Viga de 2,8.

Una vez más, funcionó, hasta que no lo hizo. Miré más de cerca el error y se dio cuenta de que tenía un problema con un objeto SimpleDateFormat, que no es seguro para subprocesos. Por lo tanto, me cambié a utilizar Java.time y DateTimeFormatter, lo que es seguro para subprocesos. Funcionó hasta que no lo hizo. Sin embargo, esta vez, el error fue ligeramente diferente y no señaló nada en mi código: Se proporciona el siguiente error.

Processing stuck in step FileIO.Write/WriteFiles/WriteShardedBundlesToTempFiles/WriteShardsIntoTempFiles for at least 05m00s without outputting or completing in state process
  at sun.misc.Unsafe.park(Native Method)
  at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
  at org.apache.beam.vendor.guava.v20_0.com.google.common.util.concurrent.AbstractFuture.get(AbstractFuture.java:469)
  at org.apache.beam.vendor.guava.v20_0.com.google.common.util.concurrent.AbstractFuture$TrustedFuture.get(AbstractFuture.java:76)
  at org.apache.beam.runners.dataflow.worker.MetricTrackingWindmillServerStub.getStateData(MetricTrackingWindmillServerStub.java:202)
  at org.apache.beam.runners.dataflow.worker.WindmillStateReader.startBatchAndBlock(WindmillStateReader.java:409)
  at org.apache.beam.runners.dataflow.worker.WindmillStateReader$WrappedFuture.get(WindmillStateReader.java:311)
  at org.apache.beam.runners.dataflow.worker.WindmillStateReader$BagPagingIterable$1.computeNext(WindmillStateReader.java:700)
  at org.apache.beam.vendor.guava.v20_0.com.google.common.collect.AbstractIterator.tryToComputeNext(AbstractIterator.java:145)
  at org.apache.beam.vendor.guava.v20_0.com.google.common.collect.AbstractIterator.hasNext(AbstractIterator.java:140)
  at org.apache.beam.vendor.guava.v20_0.com.google.common.collect.MultitransformedIterator.hasNext(MultitransformedIterator.java:47)
  at org.apache.beam.sdk.io.WriteFiles$WriteShardsIntoTempFilesFn.processElement(WriteFiles.java:701)
  at org.apache.beam.sdk.io.WriteFiles$WriteShardsIntoTempFilesFn$DoFnInvoker.invokeProcessElement(Unknown Source)

Este error comenzó a ocurrir aproximadamente 5 horas después de la implementación del trabajo y a un ritmo creciente en el tiempo. La escritura se redujo de manera significativa dentro de las 24 horas. Tengo 60 trabajadores y sospecho que un trabajador no cada vez que hay un error, que finalmente mata al trabajo.

En mi escritor, yo analizo las líneas para ciertas palabras clave (puede no ser la mejor manera) con el fin de determinar la carpeta que pertenece en. Continuación procedo a insertar el archivo de GCS con el nombre de archivo determinado. Aquí está el código que utilizo para mi escritor:

La función de partición se proporciona como la siguiente:

@SuppressWarnings("serial")
public static class datePartition implements SerializableFunction<String, String> {     
    private String filename;

    public datePartition(String filename) {
        this.filename = filename;
    }

    @Override
    public String apply(String input) {

        String folder_name = "NaN";             
        String date_dtf    = "NaN";     
        String date_literal = "NaN";
        try {
            Matcher foldernames = Pattern.compile("\"foldername\":\"(.*?)\"").matcher(input);
            if(foldernames.find()) {
                folder_name = foldernames.group(1);
            }
            else {
                Matcher folderid = Pattern.compile("\"folderid\":\"(.*?)\"").matcher(input);
                if(folderid.find()) {
                    folder_name = folderid.group(1);
                }   
            }

            Matcher date_long = Pattern.compile("\"timestamp\":\"(.*?)\"").matcher(input);
            if(date_long.find()) {
                date_literal = date_long.group(1);
                if(Utilities.isNumeric(date_literal)) {
                    LocalDateTime date = LocalDateTime.ofInstant(Instant.ofEpochMilli(Long.valueOf(date_literal)), ZoneId.systemDefault());
                    date_dtf = date.format(dtf);                        
                }
                else {
                    date_dtf = date_literal.split(":")[0].replace("-", "/").replace("T", "/");
                }
            }
            return folder_name + "/" + date_dtf + "h/" + filename;
        }

        catch(Exception e) {
            LOG.error("ERROR with either foldername or date");
            LOG.error("Line : " + input);
            LOG.error("folder : " + folder_name);
            LOG.error("Date : " + date_dtf);

            return folder_name + "/" + date_dtf + "h/" + filename;
        }           
    }
}

Y el lugar real donde se implementa y ejecuta la tubería se puede encontrar a continuación:

public void streamData() {

    Pipeline pipeline = Pipeline.create(options);
    pipeline.apply("Read PubSub Events", PubsubIO.readMessagesWithAttributes().fromSubscription(options.getInputSubscription()))
            .apply(options.getWindowDuration() + " Window",
                        Window.<PubsubMessage>into(FixedWindows.of(parseDuration(options.getWindowDuration())))
                                  .triggering(AfterWatermark.pastEndOfWindow()) 
                                  .discardingFiredPanes()
                                  .withAllowedLateness(parseDuration("24h")))
                .apply(new GenericFunctions.extractMsg())
                .apply(FileIO.<String, String>writeDynamic()
                                 .by(new datePartition(options.getOutputFilenamePrefix()))
                                 .via(TextIO.sink())
                                 .withNumShards(options.getNumShards())
                                 .to(options.getOutputDirectory())
                                 .withNaming(type -> FileIO.Write.defaultNaming(type, ".txt"))
                                 .withDestinationCoder(StringUtf8Coder.of()));

    pipeline.run();
}
Scicrazed:

Desde la publicación de esta pregunta, he optimizado el trabajo de flujo de datos para esquivar los cuellos de botella y aumentar la paralelización. Al igual que rsantiago explicó, el procesamiento de pegado no es un error, pero se comunica de flujo de datos simplemente una manera de que un paso está tomando mucho más tiempo que otros pasos, que es esencialmente un cuello de botella que no pueda solucionar con los recursos disponibles. Los cambios que hice parecen tener dirigió a ellos. El nuevo código es el siguiente:

public void streamData() {

        try {
            Pipeline pipeline = Pipeline.create(options);

            pipeline.apply("Read PubSub Events", PubsubIO.readMessagesWithAttributes().fromSubscription(options.getInputSubscription()))
            .apply(options.getWindowDuration() + " Window",
                    Window.<PubsubMessage>into(FixedWindows.of(parseDuration(options.getWindowDuration())))
                          .triggering(AfterWatermark.pastEndOfWindow()) 
                          .discardingFiredPanes()
                          .withAllowedLateness(parseDuration("24h")))
            .apply(FileIO.<String,PubsubMessage>writeDynamic()
                    .by(new datePartition(options.getOutputFilenamePrefix()))
                    .via(Contextful.fn(
                            (SerializableFunction<PubsubMessage, String>) inputMsg -> new String(inputMsg.getPayload(), StandardCharsets.UTF_8)),
                            TextIO.sink())
                    .withDestinationCoder(StringUtf8Coder.of())
                    .to(options.getOutputDirectory())
                    .withNaming(type -> new CrowdStrikeFileNaming(type))
                    .withNumShards(options.getNumShards())
                    .withTempDirectory(options.getTempLocation()));

            pipeline.run();
        }

        catch(Exception e) {

            LOG.error("Unable to deploy pipeline");
            LOG.error(e.toString(), e);
        }

    }

El cambio más grande implicado la eliminación de la función extractMsg () y el cambio de partición únicamente al uso de metadatos. Ambos pasos obligados deserialización / reserialization de mensajes y el rendimiento fuertemente impactado.

Además, ya que mi conjunto de datos no tuvo límites, tenía que establecer un número distinto de cero de fragmentos. Quería simplificar mi política de nombramiento de ficheros, así que me puse a 1 sin saber cuánto le duele el rendimiento. Desde entonces, he encontrado un buen equilibrio de los trabajadores / fragmentos / tipo de máquina para mi trabajo (en su mayoría sobre la base de conjetura y la verificación, por desgracia).

Aunque todavía es posible que un cuello de botella puede ser observado con una carga de datos lo suficientemente grande, el gasoducto ha tenido un buen desempeño a pesar de carga pesada (3-5tb por día). Los cambios también mejoraron significativamente autoscaling, pero no estoy seguro de por qué. La tarea de flujo de datos ahora reacciona a los picos y valles mucho más rápido.

Supongo que te gusta

Origin http://43.154.161.224:23101/article/api/json?id=176543&siteId=1
Recomendado
Clasificación