Azure-sdk-for-java: [CONSULTA] ¿Cómo aliviar los tiempos de espera en la operación List Blobs?

Creado en 15 sept. 2020  ·  57Comentarios  ·  Fuente: Azure/azure-sdk-for-java

Consulta / Pregunta
¿Cómo aliviar los tiempos de espera en la operación List Blobs?

El tiempo de espera se establece en 30 segundos, que es el máximo permitido para Blob Service (según la documentación de Azure). El número máximo de claves para la lista ( maxResultsPerPage ) es el predeterminado 5000 . Los depósitos que se enumeran son depósitos grandes con más de 100.000 objetos.

Sé que agregar un reintento es otra posibilidad, pero preferiría que hubiera otra alternativa.

La excepción de tiempo de espera se da a continuación

Caused by: reactor.core.Exceptions$ReactiveException: java.util.concurrent.TimeoutException: Did not observe any item or terminal signal within 30000ms in 'flatMap' (and no fallback has been configured)
        at reactor.core.Exceptions.propagate(Exceptions.java:393) ~[observer-3.20.92.jar:na]
        at reactor.core.publisher.BlockingIterable$SubscriberIterator.hasNext(BlockingIterable.java:168) ~[observer-3.20.92.jar:na]
        at reactor.core.publisher.BlockingIterable$SubscriberIterator.next(BlockingIterable.java:198) ~[observer-3.20.92.jar:na]
        at kdc.cloudadapters.adapters.MicrosoftAzureAdapter$AzureListRequest.nextBatch(MicrosoftAzureAdapter.java:566) ~[observer-3.20.92.jar:na]
        ... 9 common frames omitted

Caused by: java.util.concurrent.TimeoutException: Did not observe any item or terminal signal within 30000ms in 'flatMap' (and no fallback has been configured)
        at reactor.core.publisher.FluxTimeout$TimeoutMainSubscriber.handleTimeout(FluxTimeout.java:289) ~[observer-3.20.92.jar:na]
        at reactor.core.publisher.FluxTimeout$TimeoutMainSubscriber.doTimeout(FluxTimeout.java:274) ~[observer-3.20.92.jar:na]
        at reactor.core.publisher.FluxTimeout$TimeoutTimeoutSubscriber.onNext(FluxTimeout.java:396) ~[observer-3.20.92.jar:na]
        at reactor.core.publisher.StrictSubscriber.onNext(StrictSubscriber.java:89) ~[observer-3.20.92.jar:na]
        at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onNext(FluxOnErrorResume.java:73) ~[observer-3.20.92.jar:na]
        at reactor.core.publisher.MonoDelay$MonoDelayRunnable.run(MonoDelay.java:117) ~[observer-3.20.92.jar:na]
        at reactor.core.scheduler.SchedulerTask.call(SchedulerTask.java:68) ~[observer-3.20.92.jar:na]
        at reactor.core.scheduler.SchedulerTask.call(SchedulerTask.java:28) ~[observer-3.20.92.jar:na]
        at java.util.concurrent.FutureTask.run(FutureTask.java:266) ~[na:1.8.0_252]
        at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180) ~[na:1.8.0_252]
        at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293) ~[na:1.8.0_252]
       ... 3 common frames omitted

Información Adicional

A continuación se ofrece una variación más breve del código utilizado.

class AzureList {

        BlobContainerClient container;
        Iterator<PagedResponse<BlobItem>> iterator;
        String continuationToken;

        public AzureList(String accountName, String accountKey, String bucketName) {
            StorageSharedKeyCredential credential = new StorageSharedKeyCredential(accountName, accountKey);
            String endpoint = String.format(Locale.ROOT, "https://%s.blob.core.windows.net", accountName);
            BlobServiceClient serviceClient = new BlobServiceClientBuilder().credential(credential)
                                                         .endpoint(endpoint)
                                                         .buildClient();
            container = serviceClient.getBlobContainerClient(bucketName);
            iterator = getIterator(/*prefix*/ "");  // Current use case is just "" as prefix but can be different in the future
            continuationToken = null;
        }

        private Iterator<PagedResponse<BlobItem>> getIterator(String prefix) {
            ListBlobsOptions options = new ListBlobsOptions().setPrefix(prefix);
            return container.listBlobs(options, continuationToken, Duration.ofSeconds(30L)).iterableByPage().iterator();
        }

        public void iterate() {
            List<BlobItem> blobs;
            do {
                blobs = listBlobs();
                // hand off blob list to different consumer class
            } while (continuationToken != null);
        }

        private List<BlobItem> listBlobs() {
            PagedResponse<BlobItem> pagedResponse = iterator.next();
            List<BlobItem> blobs = pagedResponse.getValue();
            continuationToken = pagedResponse.getContinuationToken();
            return blobs;
        }
    }

¿Por qué esto no es un error o una solicitud de función?
No estoy seguro de si es un error o un problema con mi env / mi código local.

Configuración (complete la siguiente información si corresponde):

  • SO: Ubuntu 18.04
  • IDE: IntelliJ 19.1.4
  • SDK: azure-storage-blob v12.7.0

Lista de verificación de información
Por favor, asegúrese de haber agregado toda la siguiente información arriba y marque los campos obligatorios; de lo contrario, trataremos al emisor como un informe incompleto.

  • [x] Consulta agregada
  • [x] Información de configuración agregada
Client Storage customer-reported question

Todos 57 comentarios

Hola, @somanshreddy. De hecho, ya estamos investigando el rendimiento de las listas de blobs. Un par de cosas:

  1. ¿Puede describir más sobre el comportamiento de su aplicación? ¿Tiene alguna cantidad de simultaneidad cuando ve este problema? ¿O la aplicación es de un solo hilo? ¿Sucede esto cada vez o de forma intermitente?
  2. ¿Puede intentar ejecutar la aplicación sin especificar un tiempo de espera? Esto realmente nos ayudará a obtener más información y a correlacionarla con otros problemas. Si puede cargar un enlace slf4j y activarlo para que podamos obtener algunos registros de la capa de red, sería muy útil.
  3. Hubo algunas mejoras de rendimiento en torno al análisis que aparecieron en azure-core 12.8.1. ¿Podrías intentar fijar eso en tu pom y ver si eso ayuda? Aún no hemos lanzado una versión de almacenamiento con esta dependencia central actualizada, pero lo haremos pronto.

Hola @ rickle-msft

  1. La aplicación tiene varios subprocesos. Pero cada hilo solo accedería a un contenedor. Por lo tanto, no tendríamos varios subprocesos que enumeren el mismo contenedor.

El problema no ocurre siempre, pero en el caso de contenedores grandes, es bastante frecuente. Si la lista completa de un contenedor con 1 millón de objetos se cuenta como una operación, diría que 1 de cada 3 operaciones falla con este tiempo de espera. Y si el LISTADO se 'pausa' y se reanuda utilizando el token de continuación, es más probable que encontremos un error.

  1. Pensé que el tiempo de espera estaba limitado internamente a 30 segundos. Por el propio Azure. https://docs.microsoft.com/en-us/rest/api/storageservices/setting-timeouts-for-blob-service-operations

Claro, puedo habilitar el registro. ¿Puede ayudarme siendo un poco más específico en cuanto a qué tipo de registro preferiría / cómo habilitar este registro en la capa de red?

  1. ¿Cuál es el 'análisis' que está sucediendo aquí? ¿Se trata de mejoras en la forma en que el SDK analiza internamente la respuesta LIST?

Gracias por esta información.

  1. En este momento, la preocupación es menos sobre la contención en el extremo del servicio y más sobre la contención en el extremo del cliente, por lo que no importa tanto que sea el mismo contenedor o diferente porque creemos que algunos recursos del cliente son el cuello de botella. ¿Cuántos hilos estimaría que tiene haciendo operaciones de listado a la vez? ¿Cuánto dura esta "pausa"? Es interesante que hace que sea más probable que se produzcan errores. No estoy seguro de que hacer con eso

  2. El servicio todavía tiene un tiempo de espera de 30 segundos, por lo que es probable que la operación aún falle, pero al eliminar el parámetro de tiempo de espera, permitimos que el servicio cierre la conexión, lo que propaga el error de una manera diferente que, con suerte, nos dará más información sobre el estado de la pila de redes. Puede seguir esta guía para habilitar el registro. Habilitar el registro de nivel de depuración debería ser suficiente. Eso debería capturar todos los registros de la capa netty del reactor (que es nuestro cliente http).

  3. Eso es correcto. La respuesta de la lista vuelve como XML y la analizamos en un iterable para devolver. Y el último núcleo tiene algunas mejoras de rendimiento en torno a este análisis que podrían ayudar con esto.

@ rickle-msft

  1. Desde que se descubrió este problema, solo se ha utilizado 1 hilo, es decir, solo se inició la lista de un contenedor. Se reinició cuando se produjo un error de tiempo de espera.

La pausa fue de aproximadamente 30 segundos, creo que 1 minuto. Haga una pausa aquí para 'detener' el hilo que se enumera y luego generar un nuevo hilo para continuar enumerando.

Con respecto a la pausa, no he hecho una comparación lado a lado ni tengo números concretos para justificar que es más probable que ocurra cuando se reanuda desde un token de continuación. Probablemente es solo que un único hilo ininterrumpido se siente como si hubiera fallado con menos frecuencia que un hilo que se detuvo y luego se generó un nuevo hilo con el token de continuación para retomar desde donde se había detenido por última vez.

Probablemente pueda volver con algunos números, pero me preocupa más abordar la lista única ininterrumpida, ya que la pausa + reanudar es una característica secundaria.

Nota al margen: la misma lógica de 'Listado' para AWS funciona bien. Por tanto, dudo que se deba al cuello de botella de los recursos del cliente. Con suerte, los registros revelan más.

  1. Seguro, intentaré esto y me pondré en contacto contigo.

  2. Fresco. Lo intentaré también.

Además, dado que todavía no estamos seguros de la causa exacta, ¿reducir el número máximo de claves que se enumeran a, digamos 2500 , aliviaría un poco el problema? Podría intentarlo también después de capturar los registros para la lista actual de 5000 objetos.

  1. Veo. Gracias por esa información. Es de esperar que eso se preste bien a nuestra investigación continua. Disculpas si no estaba claro aquí, pero por recursos del cliente, me refiero específicamente a cómo el SDK está administrando el acceso al analizador XML, nada que ver con sus propias máquinas :)

He visto algunos casos en los que reducir el tamaño de la lista ha ayudado a resolver este problema, por lo que vale la pena intentarlo. No he explorado esto lo suficiente como para saber qué tamaño actúa como una especie de umbral para el éxito. Por lo general, también he obtenido solo la primera página, ya que eso ha sido suficiente para reproducir el problema, por lo que no estoy seguro de si reducir el tamaño de las páginas seguirá siendo útil cuando revise toda la lista. Pero, de nuevo, definitivamente es bueno intentarlo.

@ rickle-msft Vaya, perdón por el malentendido.

He agregado registros de nivel DEBUG cuando se llama a la operación LIST con y sin tiempo de espera. Se adjuntan a continuación

_1.

a) Error a través de SDK

Exception in thread "main" reactor.core.Exceptions$ReactiveException: java.util.concurrent.TimeoutException: Did not observe any item or terminal signal within 30000ms in 'flatMap' (and no fallback has been configured)
    at reactor.core.Exceptions.propagate(Exceptions.java:393)
    at reactor.core.publisher.BlockingIterable$SubscriberIterator.hasNext(BlockingIterable.java:168)
    at reactor.core.publisher.BlockingIterable$SubscriberIterator.next(BlockingIterable.java:198)
    at AzureCloudSystem.listBlobs(AzureCloudSystem.java:91)
    at AzureCloudUtil.main(AzureCloudUtil.java:53)
Caused by: java.util.concurrent.TimeoutException: Did not observe any item or terminal signal within 30000ms in 'flatMap' (and no fallback has been configured)
    at reactor.core.publisher.FluxTimeout$TimeoutMainSubscriber.handleTimeout(FluxTimeout.java:289)
    at reactor.core.publisher.FluxTimeout$TimeoutMainSubscriber.doTimeout(FluxTimeout.java:274)
    at reactor.core.publisher.FluxTimeout$TimeoutTimeoutSubscriber.onNext(FluxTimeout.java:396)
    at reactor.core.publisher.StrictSubscriber.onNext(StrictSubscriber.java:89)
    at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onNext(FluxOnErrorResume.java:73)
    at reactor.core.publisher.MonoDelay$MonoDelayRunnable.run(MonoDelay.java:117)
    at reactor.core.scheduler.SchedulerTask.call(SchedulerTask.java:68)
    at reactor.core.scheduler.SchedulerTask.call(SchedulerTask.java:28)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180)
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at java.lang.Thread.run(Thread.java:748)

b) Registros de depuración de com.azure.core -> https://pastebin.com/YRmmq1uh


_2.

a) Error a través de SDK

Exception in thread "main" reactor.core.Exceptions$ReactiveException: io.netty.channel.unix.Errors$NativeIoException: readAddress(..) failed: Connection reset by peer
    at reactor.core.Exceptions.propagate(Exceptions.java:393)
    at reactor.core.publisher.BlockingIterable$SubscriberIterator.hasNext(BlockingIterable.java:168)
    at reactor.core.publisher.BlockingIterable$SubscriberIterator.next(BlockingIterable.java:198)
    at AzureCloudSystem.listBlobs(AzureCloudSystem.java:91)
    at AzureCloudUtil.main(AzureCloudUtil.java:53)
Caused by: io.netty.channel.unix.Errors$NativeIoException: readAddress(..) failed: Connection reset by peer

b) Registros de depuración de com.azure.core -> https://pastebin.com/PrEN8Bzv


Todavía no he examinado los archivos de registro. El error sin un tiempo de espera parece que brinda más información, pero aún tengo que investigarlo. Acabo de pegar el error tan pronto como pude reproducirlo, para mantenerlo actualizado @ rickle-msft

Probaré el núcleo azul 12.8.1 y veré cómo funcionan las cosas. Y también el reducido número de llaves.

Avísame si se requiere algo más mientras tanto.

Interesante. La única excepción que veo en ambos casos es un UnsatisfiedLinkError. ¿Está seguro de que ambos registros capturan el problema en cuestión? ¿También podría ser que no pueda ver todo el contenido que compartiste? ¿Parece que ambos se cortan sospechosamente de forma bastante abrupta en 348 líneas?

Perdón por la respuesta tardía, estaba atrapado en otro trabajo.

Los archivos de registro anteriores capturaron el caso en el que falló la primera llamada LIST . De ahí el abrupto final, supongo. Permítanme intentar reproducir el caso cuando hay algunas llamadas LIST exitosas seguidas de una que falla (esta cadena usa el token de continuación como se menciona en el fragmento de código). Entonces, tal vez haya algún otro error que surja en este caso de prueba.

Mientras tanto, ¿hay algún problema para obtener un UnsatisfiedLinkError ?

¡Sin preocupaciones!

Basado en los javadocs , sospecho que el

@ rickle-msft
Con tiempo de espera explícito (30 segundos) -> https://pastebin.com/APnJ7tdj

Se realizó una llamada a las 15: 00: 33.248. Esto expiró 30 segundos después a las 15: 01: 03.265. Después de esto, mi aplicación reinició la operación de LISTADO.

@ rickle-msft En la excepción lanzada por el servicio cuando el cliente no ha pasado un tiempo de espera explícito en la llamada de la API LIST

Caused by: io.netty.channel.unix.Errors$NativeIoException: readAddress(..) failed: Connection reset by peer

¿No es esto una indicación clara de que el servidor en sí no responde y, por lo tanto, se agotó el tiempo de espera?

@ rickle-msft Cuando no hay un tiempo de espera explícito, a veces el servidor no responde con una falla. Se queda atascado en una espera infinita. Adjuntando el código y los registros a continuación

Código a continuación

       public void foo() {
            // setup .. 
            PagedResponse<BlobItem> pagedResponse = getNextPage();
            List<T> objectList = getObjectList(pagedResponse);
            // process the list 
        }

        private PagedResponse<BlobItem> getNextPage() {
            for (int trialNumber = 1; trialNumber <= MAX_SERVER_ERROR_RETRIES; trialNumber++) {
                log.debug("Try #{} for listObjects for bucket={} with current marker={}", trialNumber, container.getBlobContainerName(), continuationToken);
                try {
                    return iterator.next();  // iterator is set by this ' container.listBlobs(options, continuationToken, /*timeout*/ null).iterableByPage().iterator() '
                } catch (Exception e) {
                    log.error("Try #{} failed to list objects for bucket={} with current marker={}", trialNumber, container.getBlobContainerName(), continuationToken, e);
                    // sleep
                }
            }
            // throw exception
        }

        private List<BlobItem> getObjectList(PagedResponse<BlobItem> pagedResponse) {
            List<BlobItem> objects = pagedResponse.getValue();
            log.debug("Listed {} objects for bucket={} with current marker={}", objects.size(), container.getBlobContainerName(), continuationToken);
            return objects;
        }

Registros a continuación. La última línea fue la última operación registrada. El hilo estaba atascado en iterator.next () esperando que el servidor saliera por error

2020-09-30 12:28:35.053 DEBUG fileoperations [bufferedListProvider-pool-63-thread-1] - Try #1 for listObjects for bucket=10m-dataset with current marker=2!108!MDAwMDM2IUZvbGRlcjYvanVseTIyL2Q0L2QzOC9kMTMvZDI0L2YzLnR4dCEwMDAwMjghOTk5OS0xMi0zMVQyMzo1OTo1OS45OTk5OTk5WiE- 
2020-09-30 12:28:37.116 DEBUG fileoperations [bufferedListProvider-pool-63-thread-1] - Listed 5000 objects for bucket=10m-dataset with current marker=2!108!MDAwMDM2IUZvbGRlcjYvanVseTIyL2Q0L2QzOC9kMTMvZDI0L2YzLnR4dCEwMDAwMjghOTk5OS0xMi0zMVQyMzo1OTo1OS45OTk5OTk5WiE-

2020-09-30 12:28:37.118 DEBUG fileoperations [bufferedListProvider-pool-63-thread-1] - Try #1 for listObjects for bucket=10m-dataset with current marker=2!108!MDAwMDM1IUZvbGRlcjYvanVseTIyL2Q0L2Q0L2Q1L2QxMy9mNDUuanBnITAwMDAyOCE5OTk5LTEyLTMxVDIzOjU5OjU5Ljk5OTk5OTlaIQ--
2020-09-30 12:28:39.225 DEBUG fileoperations [bufferedListProvider-pool-63-thread-1] - Listed 5000 objects for bucket=10m-dataset with current marker=2!108!MDAwMDM1IUZvbGRlcjYvanVseTIyL2Q0L2Q0L2Q1L2QxMy9mNDUuanBnITAwMDAyOCE5OTk5LTEyLTMxVDIzOjU5OjU5Ljk5OTk5OTlaIQ--

2020-09-30 12:28:39.226 DEBUG fileoperations [bufferedListProvider-pool-63-thread-1] - Try #1 for listObjects for bucket=10m-dataset with current marker=2!108!MDAwMDM2IUZvbGRlcjYvanVseTIyL2Q0L2Q0Mi9kMTcvZDUyL2Y2LnRhciEwMDAwMjghOTk5OS0xMi0zMVQyMzo1OTo1OS45OTk5OTk5WiE-

@somanshreddy Gracias por esta información. El restablecimiento de la conexión es una indicación de que el servidor ha cerrado la conexión, generalmente como resultado de que el cliente estuvo inactivo durante demasiado tiempo. Sin embargo, no me queda claro que sea lo mismo que el tiempo de espera. Creo que en el caso de que se exceda este tiempo de espera para una operación determinada, el servidor debería devolver un error de tiempo de espera en lugar de simplemente cerrar la conexión. Por otro lado, si el servidor está esperando demasiado tiempo para obtener información de un socket y no obtiene nada, creo que cerrará el socket abruptamente y veremos que esta conexión se restablece.

El restablecimiento de la conexión es coherente con otro caso de listado problemático. Una cosa que estamos investigando es que puede haber algunos problemas específicamente con la lista de sincronización. ¿Sería posible para usted intentar usar el cliente asíncrono y obtener un iterable de eso en lugar de usar el PagedIterable devuelto por el cliente de sincronización? El tipo PagedFlux devuelto por el cliente asíncrono debería tener métodos .byPage().toIterable() que deberían darle algo que sea funcionalmente más o menos igual, pero con suerte elude este tipo iterable que parece tener algunos problemas.

Pensándolo bien. Creo que el método toIterable realidad podría ser lo que causa el problema. Puede que sea mejor esperar a que [este] RP se fusione y envíe y vuelva a intentarlo en ese momento.

He probado un par de opciones.

1) Duerme después de un tiempo de espera. Y luego vuelva a intentarlo. Este es básicamente el reintento de la línea iterator.next() . No ayuda.

2) Duerme después de un tiempo de espera. Pero descarte el iterador de lista e inicie un nuevo iterador en caso de falla usando container.listBlobs(options, continuationToken, /*timeout*/ null).iterableByPage().iterator() . Esto ha disminuido la tasa de errores, pero todavía está presente.

3) Duerme después de un tiempo de espera. Pero descarte el BlobServiceClient completo. Y cree uno nuevo para reanudar la lista. No vi ninguna diferencia en la tasa de errores del método 2. Ambos tienen un número menor de fallas en comparación con el anterior en contenedores con 10 millones de blobs pero, no obstante, hay un error.

¿Puede compartir el enlace al RP que ha mencionado?

@ rickle-msft He intentado usar el cliente asíncrono para realizar la solicitud de API ListBlobs. Como en el caso de un cliente de sincronización sin tiempo de espera explícito configurado, eventualmente una de las solicitudes de la Lista se bloquea indefinidamente.

Veo la siguiente línea en el registro de almacenamiento de Azure (contenedor de registros de $)

1.0;2020-10-04T05:12:24.1093529Z;ListBlobs;NetworkError;200;25314;576;authenticated;kompriseqa;kompriseqa;blob;"https://kompriseqa.blob.core.windows.net:443/1m-dataset?prefix=&amp;marker=2%2192%21MDAwMDI1IWF1ZzUvZDMvZDUvZDEvZDE2L2Y0MC5tcDQhMDAwMDI4ITk5OTktMTItMzFUMjM6NTk6NTkuOTk5OTk5OVoh&amp;restype=container&amp;comp=list";"/kompriseqa/1m-dataset";3be05f24-c01e-0094-250c-9a10bf000000;0;49.206.55.100:33489;2019-07-07;581;0;210;1916690;0;;;;;;"Test/1.0 Test/3.4.0 azsdk-java-azure-storage-blob/12.7.0 (1.8.0_181; Linux 4.18.16-1.el7.elrepo.x86_64)";;"d5360de4-e69d-4adc-b58f-101395999201"

El token de interés aquí es NetworkError seguido de un código de estado de respuesta Http igualmente interesante de 200

La descripción de NetworkError según https://docs.microsoft.com/en-us/rest/api/storageservices/storage-analytics-logged-operations-and-status-messages es
Most commonly occurs when a client prematurely closes a connection before timeout expiration.

La solicitud asincrónica no tiene una sobrecarga de tiempo de espera, por lo que no hay una excepción de tiempo de espera. ¿Alguna suposición de por qué el cliente está cerrando la conexión prematuramente?

Nota al margen: dos subprocesos realizaban las llamadas de la lista en dos contenedores separados que pertenecían a dos cuentas de almacenamiento diferentes (por lo tanto, estaban usando dos objetos 'BlobServiceAsyncClient' diferentes)

El HttpClient del cliente de servicio se configuró como se muestra a continuación y se agregó al constructor como builder.httpClient(getHttpClient())

private com.azure.core.http.HttpClient getHttpClient() {
    return new NettyAsyncHttpClientBuilder(HttpClient.create())
                    .connectionProvider(ConnectionProvider.create(/*name*/ "http", /*maxConnections*/ 1000))
                    .build();
}

@somanshreddy ¿Puedes probar una cosa más? Sé que tal vez sentimos que hemos estado dando vueltas a este problema por un tiempo porque he estado tratando de experimentar con algunas soluciones mientras solucionamos esta otra solución de lista. Me disculpo por eso. ¿Podría intentar actualizar a blobs 12.9.0-beta.1 y ver si eso ayuda? La solución que estaba buscando se publicó allí y espero que se resuelva el problema.

Lo siento, pude haber hablado mal. Creo que el núcleo se envió después que nosotros, por lo que es posible que deba probar azure-core v1.9.0 para obtener la solución.

@ rickle-msft Hola, lo probé con el SDK central recomendado pero aún encontré el mismo problema. Actualicé el SDK de almacenamiento a la versión beta sugerida por usted, pero tampoco tuve suerte.

Sin embargo, noté algo en los registros antes de actualizar el SDK. No se recibió algo llamado 'Último paquete HTTP'. Supongo que es por eso que el cliente estaba agotado. Intentaré reproducir esto y le daré las líneas de registro relevantes. Mientras tanto, ¿alguna pista de por qué no se recibió el último paquete HTTP?

Gracias, @somanshreddy por intentarlo. Estaré feliz de echar un vistazo a algunos registros.

@alzimmermsft, ¿alguna vez ha visto algún registro sobre el último paquete http que no se recibió? No creo que haya visto este error antes.

@ rickle-msft Vaya, lo siento. Debería haber sido más explícito. Estaba observando los registros y comparé los registros para una solicitud exitosa con la que se agotó el tiempo de espera. Y el de ese tiempo de espera no tenía la línea de registro que dice 'Recibido último paquete HTTP' que tenían las solicitudes exitosas. Así que supongo que el tiempo de espera se debe a que no se recibió este paquete en particular.

Correcto, LastHttpContent es un tipo especial de HttpContent en Netty que indica que la entrada debe recibirse por completo. Si nunca recibe esta señal, la conexión permanecerá abierta a la espera de más datos de la conexión remota.

@alzimmermsft, por lo que parece que si el servicio deja de enviar datos, no podemos hacer mucho al respecto más que volver a intentarlo. Pero, en teoría, deberíamos volver a intentar estas excepciones de tiempo de espera, ¿verdad?

@ rickle-msft eso es correcto. En una versión reciente de azure-core , agregamos un manejo de tiempo de espera granular para enviar solicitudes, recibir una respuesta y recibir el cuerpo de una respuesta a nuestras bibliotecas de implementación HttpClient . Estos fallarán dentro del flujo de trabajo de la solicitud, a diferencia del tiempo de espera de bloqueo establecido en toda la solicitud, lo que activará su reintento.

@alzimmermsft ¿Puedes guiar a @somanshreddy sobre cómo configurar estas opciones de tiempo de espera para que podamos

El siguiente sería un ejemplo de configuración de tiempos de espera de envío, respuesta y lectura en 30 segundos cada uno.

HttpClient httpClient = new NettyAsyncHttpClientBuilder()
    .writeTimeout(Duration.ofSeconds(30))
    .responseTimeout(Duration.ofSeconds(30))
    .readTimeout(Duration.ofSeconds(30))
    .build();

BlobServiceClient serviceClient = new BlobServiceClientBuilder()
    .connectionString(<connection-string>)
    .httpClient(httpClient)
    .buildClient();

Más detalles sobre qué hace exactamente esto.

El tiempo de espera de escritura es entre los paquetes que se escriben en el socket, por lo que si está cargando un flujo que está descargando si los paquetes se leen en un intervalo de 15 segundos entre paquetes, el tiempo de espera nunca se activará. Si en algún momento hay más de 30 segundos entre los paquetes escritos, la operación se considerará agotada.

Una vez que se haya completado la operación de escritura, comenzará el tiempo de espera de respuesta. Una respuesta se considera recibida una vez que se devuelven los encabezados. El tiempo de espera de respuesta es sencillo.

El tiempo de espera de lectura es como el tiempo de espera de escritura, pero para recibir paquetes desde el socket.

Gracias @alzimmermsft , intentaré esto

@alzimmermsft Ahora que estamos estableciendo tiempos de espera detallados en el cliente, supongo que ya no deberíamos pasar un tiempo de espera en la solicitud en sí.

container.listBlobs(options, continuationToken, TIMEOUT).iterableByPage().iterator();

@somanshreddy con el uso de tiempos de espera de grano fino, puede que no sea necesario aplicar un tiempo de espera a la propia llamada de la API. El tiempo de espera de la llamada a la API todavía tiene un propósito si desea limitar la cantidad de tiempo que una llamada a la API puede demorar en general, por ejemplo, si desea detener el procesamiento si la llamada (con reintentos incluidos) demora más de 10 minutos. Mirando su caso de uso, basado en este problema, no creo que sea necesario.

Bueno. Y probé RequestRetryOptions que tiene ServiceClient, anteriormente en el SDK 12.7.0, pero eso no pareció ayudar. Tengo una lógica de bloque de captura de prueba simple en su lugar que está haciendo un reintento actualmente. ¿Recomiendas que vuelva al mecanismo de reintento incorporado?

Además, dado que el problema es con LastHttpContent, ¿eso significa que un eventual reintento simplemente funcionaría? Porque a veces 3-4 reintentos fallaron consecutivamente. Así que supongo que hubo algo mal en el lado del cliente, ya que el servidor no debería fallar tantas veces seguidas. Entonces, si mantenemos una lógica de reintento persistente, ¿podemos esperar que eventualmente tenga éxito?

@alzimmermsft Este es el error con el tiempo de espera detallado en el cliente y sin tiempo de espera explícito establecido en la solicitud en sí. El reintento (no integrado, pero el reintento de captura de prueba personalizado) funcionó. Este también fue el caso antes. Pero a veces incluso el reintento terminó fallando.

2020-10-20 20:43:50.490 ERROR fileoperations [bufferedListProvider-pool-28-thread-1] - Try #1 failed to list objects for bucket=10m-dataset
reactor.core.Exceptions$ReactiveException: java.util.concurrent.TimeoutException: Channel read timed out after 30000 milliseconds.
    at reactor.core.Exceptions.propagate(Exceptions.java:393) ~[observer-3.4.0.jar:na]
    at reactor.core.publisher.BlockingSingleSubscriber.blockingGet(BlockingSingleSubscriber.java:97) ~[observer-3.4.0.jar:na]
    at reactor.core.publisher.Flux.blockLast(Flux.java:2497) ~[observer-3.4.0.jar:na]
    at com.azure.core.util.paging.ContinuablePagedByIteratorBase.requestPage(ContinuablePagedByIteratorBase.java:94) ~[observer-3.4.0.jar:na]
    at com.azure.core.util.paging.ContinuablePagedByIteratorBase.hasNext(ContinuablePagedByIteratorBase.java:53) ~[observer-3.4.0.jar:na]
    at com.azure.core.util.paging.ContinuablePagedByIteratorBase.next(ContinuablePagedByIteratorBase.java:42) ~[observer-3.4.0.jar:na]
    at kdc.cloudadapters.adapters.MicrosoftAzureAdapter$AzureListRequest.getNextPage(MicrosoftAzureAdapter.java:800) [observer-3.4.0.jar:na]
    Suppressed: java.lang.Exception: #block terminated with an error
        at reactor.core.publisher.BlockingSingleSubscriber.blockingGet(BlockingSingleSubscriber.java:99) ~[observer-3.4.0.jar:na]
        ... 15 common frames omitted
Caused by: java.util.concurrent.TimeoutException: Channel read timed out after 30000 milliseconds.
    at com.azure.core.http.netty.implementation.ReadTimeoutHandler.readTimeoutRunnable(ReadTimeoutHandler.java:68) ~[observer-3.4.0.jar:na]
    at com.azure.core.http.netty.implementation.ReadTimeoutHandler.lambda$handlerAdded$0(ReadTimeoutHandler.java:48) ~[observer-3.4.0.jar:na]
    at io.netty.util.concurrent.PromiseTask.runTask(PromiseTask.java:98) ~[observer-3.4.0.jar:na]
    at io.netty.util.concurrent.ScheduledFutureTask.run(ScheduledFutureTask.java:176) ~[observer-3.4.0.jar:na]
    at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:164) ~[observer-3.4.0.jar:na]
    at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:472) ~[observer-3.4.0.jar:na]
    at io.netty.channel.epoll.EpollEventLoop.run(EpollEventLoop.java:384) ~[observer-3.4.0.jar:na]
    at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989) ~[observer-3.4.0.jar:na]
    at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) ~[observer-3.4.0.jar:na]
    at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) ~[observer-3.4.0.jar:na]
    ... 1 common frames omitted

¿Recomiendas que vuelva al mecanismo de reintento incorporado?

El objetivo sería minimizar el número de declaraciones try / catch externas fuera del SDK. Puede haber situaciones en las que las operaciones devuelvan respuestas que no se pueden recuperar y que aparecerán como un error o casos en los que la operación agote la cantidad de reintentos permitidos y finalice.

Además, dado que el problema es con LastHttpContent, ¿eso significa que un eventual reintento simplemente funcionaría? Porque a veces 3-4 reintentos fallaron consecutivamente. Así que supongo que hubo algo mal en el lado del cliente, ya que el servidor no debería fallar tantas veces seguidas. Entonces, si mantenemos una lógica de reintento persistente, ¿podemos esperar que eventualmente tenga éxito?

Sí, si la operación es idempotente, un reintento debería funcionar eventualmente.

Este es el error con el tiempo de espera detallado en el cliente y sin tiempo de espera explícito establecido en la solicitud en sí. El reintento (no integrado, pero el reintento de captura de prueba personalizado) funcionó. Este también fue el caso antes. Pero a veces incluso el reintento terminó fallando.

Echaré un vistazo a esto. Puede darse el caso de que la excepción que devuelve HttpClient se incluya en el tipo de excepción de Reactor y su causa no se inspeccione para que se pueda volver a ejecutar.

@alzimmermsft ¿Los tiempos de espera del cliente http que hemos establecido (30 s) incluyen también el tiempo de reintento? Como mencionaste que el tiempo de espera de la API incluye el tiempo de reintento.

@somanshreddy Los tiempos de espera del cliente http son por solicitud, por lo que no incluyen reintentos. Los tiempos de espera de la API son por operación, por lo que incluyen reintentos.

@somanshreddy , he echado un vistazo a la excepción que se devuelve y no se vuelve a intentar. Los tiempos de espera de escritura y respuesta se volverán a intentar cuando se produzcan debido a que suceden al enviar la solicitud y en espera de la respuesta; los tiempos de espera de lectura se pueden reintentar cuando se produzcan.

Los tiempos de espera de lectura no tienen una garantía explícita de volver a intentarlo, ya que el consumo del cuerpo de respuesta puede comenzar en una ubicación diferente. Generalmente, no comenzamos a leer el cuerpo hasta que alcanzamos la lógica de deserialización, esto sucede fuera del contexto de nuestro HttpPipeline , por lo tanto, queda fuera del alcance de RequestRetryPolicy / RetryPolicy que intentaría volver a procesar la solicitud.

Teniendo esto en cuenta por el momento, sería mejor retener su bloque externo try / catch. Los escenarios en los que una solicitud que se envía o los encabezados de respuesta que se reciben tardan más de lo esperado serían manejados por el SDK. La última lectura que se atasque debería detectarse externamente.

Investigaré soluciones a este problema para que el SDK pueda manejar los tres escenarios de tiempo de espera de forma segura.

Gracias @alzimmermsft @ rickle-msft

Además, ¿cuál es la estimación para que 12.9.0-beta.1 se publique oficialmente, es decir, la versión de disponibilidad general?

@somanshreddy Esperamos lanzarlo como parte de nuestro lanzamiento de noviembre en un par de una o dos semanas.

@ rickle-msft @alzimmermsft He vuelto a 10 millones de objetos. A veces, el intento 1 falló, pero el intento 2 siempre tuvo éxito. Pero para contenedores más grandes con alrededor de 30 millones de objetos, la lista fracasó en alrededor de la marca de 24 millones.

Intenté esto con los tiempos de espera personalizados en el cliente Http y un tiempo de espera null en la API. Las versiones de SDK se dan a continuación

        <dependency>
            <groupId>com.azure</groupId>
            <artifactId>azure-storage-blob</artifactId>
            <version>12.9.0-beta.1</version>
        </dependency>
        <dependency>
            <groupId>com.azure</groupId>
            <artifactId>azure-core</artifactId>
            <version>1.9.0</version>
        </dependency>

Seguimiento de pila

2020-11-10 22:42:16.738 ERROR fileoperations [bufferedListProvider-pool-336-thread-1] - Try #3 failed to list objects for bucket=large-dataset with current marker=2!108!MDAwMDM3IUZvbGRlcjEvRm9sZGVyMS9hdWc1L2QzL2QwL2Q4L2Y1LnRpZmYhMDAwMDI4ITk5OTktMTItMzFUMjM6NTk6NTkuOTk5OTk5OVoh
reactor.core.Exceptions$ReactiveException: io.netty.channel.unix.Errors$NativeIoException: readAddress(..) failed: Connection reset by peer
        at reactor.core.Exceptions.propagate(Exceptions.java:393) ~[observer-3.20.111.jar:na]
        at reactor.core.publisher.BlockingSingleSubscriber.blockingGet(BlockingSingleSubscriber.java:97) ~[observer-3.20.111.jar:na]
        at reactor.core.publisher.Flux.blockLast(Flux.java:2497) ~[observer-3.20.111.jar:na]
        at com.azure.core.util.paging.ContinuablePagedByIteratorBase.requestPage(ContinuablePagedByIteratorBase.java:94) ~[observer-3.20.111.jar:na]
        at com.azure.core.util.paging.ContinuablePagedByIteratorBase.hasNext(ContinuablePagedByIteratorBase.java:53) ~[observer-3.20.111.jar:na]
        at com.azure.core.util.paging.ContinuablePagedByIteratorBase.next(ContinuablePagedByIteratorBase.java:42) ~[observer-3.20.111.jar:na]
        at kdc.cloudadapters.adapters.MicrosoftAzureAdapter$AzureListRequest.getNextPage(MicrosoftAzureAdapter.java:1144) [observer-3.20.111.jar:na]

'

Hola @somanshreddy ,

Actualmente estoy investigando el problema, pero dado el tipo de error, la conexión restablecida por el par, se necesita información adicional para acercarme a la causa raíz. ¿Existe alguna posibilidad de que pueda habilitar un registro más detallado en el propio Netty? Esto ayudará a brindar una mejor vista de la aplicación en el momento de la falla. Por ejemplo, cuántas conexiones abiertas hay en el grupo de conexiones de Netty, más detalles sobre la causa de la falla, etc.

He podido replicar un restablecimiento de conexión por parte de un par utilizando un servidor de prueba personalizado. A partir de esto, pude validar que se reintentará el restablecimiento de la conexión mediante errores de pares, pero en su caso, alcanzó un límite de reintentos cuando ocurrió el error. Una posible causa de este problema es que varias conexiones en el grupo de conexiones de Netty quedaron obsoletas y el servicio les indicó que se cerraran debido a la inactividad. Sin embargo, sin registros que indiquen que el grupo de conexiones tenía conexiones de larga duración sin usar, no puedo decir explícitamente si esa es la causa raíz.

Seguro @alzimmermsft. Intentaré reproducir esto y responderle. ¿El nivel DEBUG en io.netty sería suficiente?

Seguro @alzimmermsft. Intentaré reproducir esto y responderle. ¿El nivel DEBUG en io.netty sería suficiente?

Sí, DEBUG debe incluir información sobre la cantidad de conexiones activas e inactivas dentro del grupo de conexiones y contener otra información relacionada con las solicitudes y respuestas.

@alzimmermsft Dado que todavía estamos usando el bloque try catch, estaríamos reintentando manualmente cualquier error. Aquí hay uno de esos fallos que ocurre después del primer intento, pero un reintento manual (segundo intento) tiene éxito.

Seguimiento de la pila de excepciones del SDK -> https://pastebin.com/raw/5Cf5y7EK

Registros de DEBUG de Netty -> https://pastebin.com/raw/gQyMSneb

Los registros de Netty cubren un área más grande que solo el error de listado porque pensé que sería mejor en caso de que se necesitara un contexto más amplio para comprender los registros. La marca de tiempo del momento de la solicitud y la excepción lanzadas está presente en el fragmento anterior que debería ayudar a ver los registros de Netty correspondientes

Hola @somanshreddy , acabo de fusionar este PR (https://github.com/Azure/azure-sdk-for-java/pull/17699) que debería hacer que el cliente HTTP lea con entusiasmo el cuerpo de la respuesta cuando sepamos que se deserializará. Esto debería reducir la cantidad de ocurrencias cuando se lanza una TimeoutException o PrematureCloseException desde el SDK al completar más del consumo de respuesta HTTP dentro del alcance de nuestra lógica de reintento. Estos cambios deberían estar disponibles en Maven después de nuestro próximo lanzamiento de SDK.

@alzimmermsft Ahora los 3 intentos han fallado. Y los los se capturan abajo. Por favor avíseme si necesita más información.

Seguimiento de la pila de excepciones del SDK -> https://pastebin.com/raw/f4q5gf0u

Registros de DEBUG de Netty -> https://pastebin.com/raw/Hq5UZ3Lv

Hola @somanshreddy , ¿estos registros son de usar el código fuente integrado o versiones publicadas? Al mirar los registros, noto un caso en el que se activan excepciones después de que se ha cerrado la canalización, lo que debería solucionarse en un cambio de código fuente. Además, habría esperado que los registros de Netty también contengan el restablecimiento de la conexión por registros, similar a esto:

128162 [reactor-http-epoll-4] WARN reactor.netty.http.client.HttpClientConnect - [id: 0x982a505e, L:/127.0.0.1:47488 - R:localhost/127.0.0.1:7777] The connection observed an error
io.netty.channel.unix.Errors$NativeIoException: readAddress(..) failed: Connection reset by peer

@alzimmermsft Estos son registros de 12.9.0 azure-storage-blob. ¿Cuándo se solucionó la excepción?

Parece que esto fue solucionado por azure-core 1.11.0 y azure-core-http-netty 1.7.0 , azure-storage-blob 12.9.0 tiene dependencias en las versiones 1.10.0 y 1.6.3, por lo que no tendrá la solución . Si incluye las versiones más recientes de azure-core y azure-core-http-netty en su proyecto directamente, se usarán en lugar de las versiones de las que depende azure-storage-blob, y esto será seguro ya que las versiones más nuevas lo son compatible con versiones anteriores. Una vez que esté disponible una versión más reciente de azure-storage-blob, según las versiones de corrección, o más reciente, debería poder eliminar las dependencias directas de azure-core y azure-core-http-netty.

@alzimmermsft @ rickle-msft ¿Tiene una estimación de tiempo para la próxima versión del SDK de almacenamiento? ¡Gracias!

Esto debería estar listo en febrero

@somanshreddy Hemos lanzado una nueva versión GA del SDK. ¿Podría intentarlo y ver si soluciona su problema? Si es así, ¿podría cerrar el problema también?

Gracias @ rickle-msft. Probablemente necesite algo de tiempo para probar esto, pero mantendrá este hilo actualizado con los resultados.

@somanshreddy ¿Cómo van las cosas? ¿Podemos cerrar este problema o necesita más ayuda?

@ rickle-msft Lo sentimos, en realidad estamos esperando que # 17648 sea lanzado en una versión GA para que podamos probar ambos juntos. Cada actualización del SDK requiere una prueba de regresión completa, por lo que preferiríamos hacerlo solo una vez cuando ambas correcciones estén fuera :)

Veo. Tiene sentido. Dado que febrero fue una versión beta, espero que marzo sea una versión de GA

@ rickle-msft Gracias por esa información :)

Hola @somanshreddy

Desafortunadamente, hemos agregado algunas características adicionales a la última versión beta que nos han impedido lanzar una versión estable de la biblioteca 12.11.0. Actualizaremos este hilo cuando podamos GA la biblioteca.

Gracias @ gapra-msft

Sigo revisando el registro de cambios cada dos días esperando una actualización :)
Aprecia la información. ¡Gracias!

¿Fue útil esta página
0 / 5 - 0 calificaciones