Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

5.5 Datos a escala: leer desde almacenamiento de objetos en la nube

Todos los conjuntos de datos de este libro hasta ahora cabían en memoria. Este cuaderno trabaja contra uno que no cabe: el análisis MUR SST — temperatura superficial del mar global diaria a 0.01° (~1 km) de resolución, 2002–2020, publicado como un almacén Zarr en el registro de datos abiertos de AWS (s3://mur-sst/zarr-v1, región us-west-2, acceso anónimo). Decodificado, el almacén describe unos 106 TiB de arreglos. Vamos a abrirlo, tomar un subconjunto y calcular sobre él desde una laptop, con una pregunta siempre al frente: ¿cuántos bytes se mueven en realidad?

Esta es la versión ejecutable del patrón de «leer en el lugar» de la sección 5.4.

Crédito de los datos: JPL MUR MEaSUREs Project (2015), GHRSST Level 4 MUR Global Foundation Sea Surface Temperature Analysis v4.1, NASA PO.DAAC, NASA/JPL (2015); Chin, Vazquez-Cuervo & Armstrong (2017), Remote Sensing of Environment 200.

🖥️ Diapositivas — Sesión 27 (vie 4 dic)

Alcanzar el almacén — y fallar rápido si no se puede

Un objeto pequeño, .zmetadata, contiene los metadatos consolidados del almacén: la forma, el tipo de dato, el fragmentado y la compresión de cada arreglo, en una sola lectura de ~10 KiB. Lo revisamos primero con tiempos de espera cortos, para que una red bloqueada produzca un error claro en segundos en vez de un cuelgue silencioso de varios minutos.

store reachable; consolidated metadata: 10.3 KiB -- one GET describes everything below

Abrir 106 TiB de forma perezosa

xr.open_dataset(..., engine="zarr", chunks={}) lee únicamente ese objeto de metadatos. chunks={} significa «dame un arreglo dask cuyos fragmentos coincidan con los fragmentos en disco» — ningún valor de datos cruza la red hasta que se lo pidamos.

opened in 12.9 s: 106.3 TiB of arrays described, ~10.3 KiB actually read
Loading...

Los fragmentos: la unidad de todo

Un fragmento (chunk) es la unidad atómica de E/S y de compresión del almacén: cada lectura trae y descomprime fragmentos completos, nunca valores sueltos, así que su patrón de acceso paga en monedas del tamaño del fragmento. Zarr v3 agrega los shards (agrupaciones de almacenamiento) — muchos fragmentos pequeños empacados en un solo objeto de almacenamiento, para que un almacén de objetos no se ahogue en millones de archivos diminutos; este almacén es anterior a ellos (disposición de Zarr v2), así que cada fragmento es un objeto de S3 y la unidad de transferencia por red.

analysed_sst se almacena como int16 (un par escala/desplazamiento reconstruye los kelvin) en fragmentos de 5 días × 1799 lat × 3600 lon. Pesemos uno.

shape: (6443, 17999, 36000) -> storage chunks: (5, 1799, 3600)
stored dtype: int16 | decoded dtype: float64
chunk grid: (1289, 11, 10) = 141,790 chunk objects for this variable
one chunk: 20.0 MiB compressed on S3 -> 247.1 MiB decoded in RAM
variable:  ~2.7 TiB compressed (est. from that chunk) -> 30.4 TiB decoded
dask chunks after rechunk-on-read: (10, 1799, 3600)

Tomar el subconjunto de forma perezosa y luego calcular

Diez días sobre la plataforma del Pacífico Noroeste: 46–49°N, 126–122°O, junio de 2019. Rebanar un arreglo perezoso edita el grafo de tareas — sigue moviendo cero bytes.

subset (10, 301, 401): 9.2 MiB across 3 chunks -- 0 bytes moved so far

.compute() es donde llega la cuenta de la red. Importan dos conteos de bytes distintos, y medimos ambos:

  • bytes en RAM — el nbytes de dask sobre el resultado calculado: lo que su respuesta cuesta en memoria;
  • bytes movidos — la red transfiere objetos de fragmento comprimidos completos, así que enumeramos exactamente qué objetos de fragmento toca el subconjunto y sumamos sus tamaños en S3.
chunk objects fetched : 3 (= 3 graph partitions)
bytes moved           : 60.6 MiB compressed, in 20.4 s
bytes in RAM (result) : 9.2 MiB
bytes stored (decoded): 30.4 TiB
moved : stored        = 1 : 525,483
mean SST in the box   : 285.94 K

Esa razón es el argumento completo a favor de los formatos optimizados para la nube. El almacén contiene 30.4 TiB (decodificados) de analysed_sst; responder nuestra pregunta movió 60.6 MiB — una parte en ~525 000 — porque el fragmentado abarata las lecturas parciales y la pereza implica que solo pedimos los fragmentos que toca nuestra rebanada. Los 60.6 MiB son además ~6× más grandes que los 9.2 MiB que aterrizaron en RAM: la granularidad del fragmento es el impuesto que se paga por el almacenamiento de objetos, y la razón por la que la forma del fragmento debería parecerse a su patrón de acceso.

El momento en que muere el trabajo en memoria

Haga la aritmética antes de llamar .compute() sobre algo grande. Un solo paso temporal global de analysed_sst se decodifica a 17 999 × 36 000 × 8 bytes ≈ 4.8 GiB. Tres días agotan una laptop de 16 GiB. La variable completa son 30.4 TiB — unas dos mil laptops de RAM — y sst.values (o .compute() sobre el arreglo sin subconjuntar) intentaría alegremente materializarla. No vamos a correr eso, y usted tampoco debería: la solución nunca es un .compute() más grande, es un algoritmo que retenga solo unos pocos fragmentos a la vez.

Transmisión por flujo: la agregación que sí funciona

Una media mensual sobre la misma caja necesita 30 días globales — ~145 GiB decodificados si cargara pasos temporales enteros, pero aun así solo 7 objetos de fragmento para nuestra rebanada. mean("time") sobre el arreglo perezoso construye un grafo de medias parciales por fragmento que dask combina: cada trabajador trae un objeto de fragmento de 20 MiB, lo descomprime (62 MiB de int16, 247 MiB una vez decodificado a float64), toma su media parcial sobre la rebanada y lo descarta. La memoria máxima se queda en un puñado de fragmentos por más largo que sea el eje temporal.

graph input : 27.6 MiB across 7 chunks
graph output: 943.0 KiB
streamed in 40.8 s; peak memory ~ a few chunks, not 30.4 TiB
<Figure size 700x400 with 2 Axes>

Laptop → HPC → nube: la decisión en números

Este cuaderno respondió preguntas sobre una variable de 30.4 TiB moviendo ~200 MiB en total — holgadamente un trabajo de laptop, porque los fragmentos que tocamos caben en nuestro ancho de banda y nuestra RAM. Esa es la regla general:

  • Los fragmentos que toca ≪ su RAM y su paciencia → laptop, exactamente como aquí. Apertura perezosa, subconjunto, flujo.
  • Los fragmentos que toca ≈ el almacén completo (una climatología global de todo el registro; entrenamiento de aprendizaje automático sobre cada fragmento) → lleve el cómputo a los datos, no los datos al cómputo: una instancia en us-west-2 lee este bucket a varios GB/s sin cargo de salida, y un clúster de HPC funciona cuando los datos (o un espejo) ya están en su sistema de archivos.
  • En cualquier caso, la consulta viaja hacia los datos y solo las respuestas viajan de vuelta — el patrón de «leer en el lugar» de la sección 5.4, que además cubre la escalera de cómputo en sí: clúster departamental, asignaciones nacionales (NSF ACCESS en EE. UU.; LANCAD, SNCAD, NLHPC o la RES en el ámbito iberoamericano), nube comercial, y la disciplina de costos que hace sobrevivible a esta última.
References
  1. NASA/JPL. (2015). GHRSST Level 4 MUR Global Foundation Sea Surface Temperature Analysis (v4.1). NASA Physical Oceanography Distributed Active Archive Center. 10.5067/GHGMR-4FJ04