web-dev-qa-db-fra.com

Erreur de mémoire insuffisante lors de la collecte de données hors du cluster Spark

Je sais qu'il y a beaucoup de questions sur SO sur les erreurs de mémoire insuffisante sur Spark mais je n'ai pas trouvé de solution pour la mienne.

J'ai un workflow simple:

  1. lire des fichiers ORC d'Amazon S3
  2. filter jusqu'à un petit sous-ensemble de lignes
  3. select un petit sous-ensemble de colonnes
  4. collect dans le nœud du pilote (donc je peux faire des opérations supplémentaires dans R)

Lorsque j'exécute ce qui précède, puis cache la table à spark mémoire, il prend <2 Go - minuscule par rapport à la mémoire disponible pour mon cluster - alors j'obtiens une erreur OOM lorsque J'essaie de collect les données sur mon nœud de pilote.

J'ai essayé de courir sur les configurations suivantes:

  • mode local sur un ordinateur avec 32 cœurs et 244 Go de RAM
  • mode autonome avec 10 exécuteurs de 6,2 Go et un nœud de pilote de 61 Go

Pour chacun d'eux, j'ai joué avec de nombreuses configurations de executor.memory, driver.memory Et driver.maxResultSize Pour couvrir la gamme complète des valeurs possibles dans ma mémoire disponible, mais je finis toujours avec une erreur de mémoire insuffisante à l'étape collect; soit Java.lang.OutOfMemoryError: Java heap space,
Java.lang.OutOfMemoryError : GC overhead limit exceeded Ou Error in invoke_method.spark_Shell_connection(spark_connection(jobj), : No status is returned. (une erreur sparklyr indiquant des problèmes de mémoire).

Sur la base de ma compréhension [limitée] de Spark, la mise en cache d'une table avant la collecte devrait forcer tous les calculs - c'est-à-dire que si la table se trouve joyeusement en mémoire après la mise en cache à <2 Go, alors je ne devrais pas avoir besoin de beaucoup plus de 2 Go de mémoire pour collecter dans le nœud du pilote.

Notez que les réponses à cette question ont quelques suggestions que je n'ai pas encore essayées, mais celles-ci sont susceptibles d'avoir un impact sur les performances (par exemple la sérialisation du RDD) donc nous aimerions éviter d'utiliser si possible.

Mes questions:

  1. comment se peut-il qu'une trame de données qui occupe si peu d'espace après avoir été mise en cache puisse causer des problèmes de mémoire?
  2. y a-t-il quelque chose d'évident à vérifier/modifier/dépanner pour aider à résoudre le problème, avant de passer à des options supplémentaires qui peuvent compromettre les performances?

Je vous remercie

Edit: note en réponse au commentaire de @ Shaido ci-dessous, appeler cache via Sparklyr "force le chargement des données en mémoire en exécutant un count(*) over the table "[de la documentation Sparklyr] - c'est-à-dire que la table doit être en mémoire et que tous les calculs doivent être exécutés (je crois) avant d'appeler collect.

Modifier: quelques observations supplémentaires depuis avoir suivi les suggestions ci-dessous:

  • Selon les commentaires ci-dessous, j'ai maintenant essayé d'écrire les données sur csv au lieu de les collecter pour avoir une idée de la taille probable du fichier. Cette opération crée un ensemble de fichiers csv équivalant à environ 3 Go et ne prend que 2 secondes lorsqu'elle est exécutée après la mise en cache.
  • Si je définis driver.maxResultSize Sur <1G, j'obtiens une erreur indiquant que la taille du RDD sérialisé est de 1030 Mo, supérieure à driver.maxResultSize.
  • Si je regarde l'utilisation de la mémoire dans le Gestionnaire des tâches après avoir appelé collect, je constate que l'utilisation continue d'augmenter jusqu'à ce qu'elle atteigne ~ 90 Go, moment auquel l'erreur OOM se produit. Donc, pour une raison quelconque, la quantité de RAM utilisée pour effectuer l'opération collect est ~ 100x supérieure à la taille de le RDD que j'essaye de collecter.

Modifier: code ajouté ci-dessous, comme demandé dans les commentaires.

#__________________________________________________________________________________________________________________________________

# Set parameters used for filtering rows
#__________________________________________________________________________________________________________________________________

firstDate <- '2017-07-01'
maxDate <- '2017-08-31'
advertiserID <- '4529611'
advertiserID2 <- '4601141'
advertiserID3 <- '4601141'

library(dplyr)
library(stringr)
library(sparklyr)

#__________________________________________________________________________________________________________________________________

# Configure & connect to spark
#__________________________________________________________________________________________________________________________________

Sys.setenv("SPARK_MEM"="100g")
Sys.setenv(HADOOP_HOME="C:/Users/Jay.Ruffell/AppData/Local/rstudio/spark/Cache/spark-2.0.1-bin-hadoop2.7/tmp/hadoop") 

config <- spark_config()
config$sparklyr.defaultPackages <- "org.Apache.hadoop:hadoop-aws:2.7.3" # used to connect to S3
Sys.setenv(AWS_ACCESS_KEY_ID="")
Sys.setenv(AWS_SECRET_ACCESS_KEY="") # setting these blank ensures that AWS uses the IAM roles associated with the cluster to define S3 permissions

# Specify memory parameters - have tried lots of different values here!
config$`sparklyr.Shell.driver-memory` <- '50g' 
config$`sparklyr.Shell.executor-memory` <- '50g'
config$spark.driver.maxResultSize <- '50g'
sc <- spark_connect(master='local', config=config, version='2.0.1')

#__________________________________________________________________________________________________________________________________

# load data into spark from S3 ----
#__________________________________________________________________________________________________________________________________

#+++++++++++++++++++
# create spark table (not in memory yet) of all logfiles within logfiles path
#+++++++++++++++++++

spark_session(sc) %>%
  invoke("read") %>% 
  invoke("format", "orc") %>%
  invoke("load", 's3a://nz-omg-ann-aipl-data-lake/aip-connect-256537/orc-files/dcm-log-files/dt2-facts') %>% 
  invoke("createOrReplaceTempView", "alldatadf") 
alldftbl <- tbl(sc, 'alldatadf') # create a reference to the sparkdf without loading into memory

#+++++++++++++++++++
# define variables used to filter table down to daterange
#+++++++++++++++++++

# Calculate firstDate & maxDate as unix timestamps
unixTime_firstDate <- as.numeric(as.POSIXct(firstDate))+1
unixTime_maxDate <- as.numeric(as.POSIXct(maxDate)) + 3600*24-1

# Convert daterange params into date_year, date_month & date_day values to pass to filter statement
dateRange <- as.character(seq(as.Date(firstDate), as.Date(maxDate), by=1))
years <- unique(substring(dateRange, first=1, last=4))
if(length(years)==1) years <- c(years, years)
year_y1 <- years[1]; year_y2 <- years[2]
months_y1 <- substring(dateRange[grepl(years[1], dateRange)], first=6, last=7)
minMonth_y1 <- min(months_y1)
maxMonth_y1 <- max(months_y1)
months_y2 <- substring(dateRange[grepl(years[2], dateRange)], first=6, last=7)
minMonth_y2 <- min(months_y2)
maxMonth_y2 <- max(months_y2) 

# Repeat for 1 day prior to first date & one day after maxdate (because of the way logfile orc partitions are created, sometimes touchpoints can end up in the wrong folder by 1 day. So read in extra days, then filter by event time)
firstDateMinusOne <- as.Date(firstDate)-1
firstDateMinusOne_year <- substring(firstDateMinusOne, first=1, last=4)
firstDateMinusOne_month <- substring(firstDateMinusOne, first=6, last=7) 
firstDateMinusOne_day <- substring(firstDateMinusOne, first=9, last=10)
maxDatePlusOne <- as.Date(maxDate)+1
maxDatePlusOne_year <- substring(maxDatePlusOne, first=1, last=4)
maxDatePlusOne_month <- substring(maxDatePlusOne, first=6, last=7)
maxDatePlusOne_day <- substring(maxDatePlusOne, first=9, last=10)

#+++++++++++++++++++
# Read in data, filter & select
#+++++++++++++++++++

# startTime <- proc.time()[3]
dftbl <- alldftbl %>% # create a reference to the sparkdf without loading into memory

  # filter by month and year, using ORC partitions for extra speed
  filter(((date_year==year_y1  & date_month>=minMonth_y1 & date_month<=maxMonth_y1) |
            (date_year==year_y2 & date_month>=minMonth_y2 & date_month<=maxMonth_y2) |
            (date_year==firstDateMinusOne_year & date_month==firstDateMinusOne_month & date_day==firstDateMinusOne_day) |
            (date_year==maxDatePlusOne_year & date_month==maxDatePlusOne_month & date_day==maxDatePlusOne_day))) %>%

  # filter to be within firstdate & maxdate. Note that event_time_char will be in UTC, so 12hrs behind.
  filter(event_time>=(unixTime_firstDate*1000000) & event_time<(unixTime_maxDate*1000000)) %>%

  # filter by advertiser ID
  filter(((advertiser_id==advertiserID | advertiser_id==advertiserID2 | advertiser_id==advertiserID3) & 
            !is.na(advertiser_id)) |
           ((floodlight_configuration==advertiserID | floodlight_configuration==advertiserID2 | 
               floodlight_configuration==advertiserID3) & !is.na(floodlight_configuration)) & user_id!="0") %>%

  # Define cols to keep
  transmute(time=as.numeric(event_time/1000000),
            user_id=as.character(user_id),
            action_type=as.character(if(fact_type=='click') 'C' else if(fact_type=='impression') 'I' else if(fact_type=='activity') 'A' else NA),
            lookup=concat_ws("_", campaign_id, ad_id, site_id_dcm, placement_id),
            activity_lookup=as.character(activity_id),
            sv1=as.character(segment_value_1),
            other_data=as.character(other_data))  %>%
  mutate(time_char=as.character(from_unixtime(time)))

# cache to memory
dftbl <- sdf_register(dftbl, "filtereddf")
tbl_cache(sc, "filtereddf")

#__________________________________________________________________________________________________________________________________

# Collect out of spark
#__________________________________________________________________________________________________________________________________

myDF <- collect(dftbl)
12
jay

Lorsque vous dites collecter sur la trame de données, il se passe 2 choses,

  1. Tout d'abord, toutes les données doivent être écrites sur la sortie du pilote.
  2. Le pilote doit collecter les données de tous les nœuds et les conserver dans sa mémoire.

Répondre:

Si vous cherchez à simplement charger les données dans la mémoire des exceutors, count () est également une action qui chargera les données dans la mémoire de l'exécuteur qui peut être utilisée par d'autres processus.

Si vous souhaitez extraire les données, essayez cela avec d'autres propriétés lors de la compression des données "--conf spark.driver.maxResultSize = 10g".

5
BalaramRaju

Comme mentionné ci-dessus, "cache" n'est pas une action, vérifiez RDD Persistence :

You can mark an RDD to be persisted using the persist() or cache() methods on it. The first time it is computed in an action, it will be kept in memory on the nodes. 

Mais "collect" est une action, et tous les calculs (y compris "cache") seront lancés lorsque "collect" sera appelé.

Vous exécutez l'application en mode autonome, cela signifie que le chargement initial des données et tous les calculs seront effectués dans la même mémoire.

Le téléchargement de données et d'autres calculs sont utilisés dans la plupart des mémoires, pas pour "collecter".

Vous pouvez le vérifier en remplaçant "collect" par "count".

2
pasha701