web-dev-qa-db-fra.com

Comment forcer l'évaluation DataFrame dans Spark

Parfois, par exemple (pour tester et marquer), je veux forcer l’exécution des transformations définies sur un DataFrame. AFAIK appeler une action telle que count ne garantit pas que toutes les Columns sont réellement calculées, show ne peut calculer qu'un sous-ensemble de toutes les Rows (voir les exemples ci-dessous)

Ma solution est d'écrire la DataFrame sur HDFS à l'aide de df.write.saveAsTable, mais cela "encombre" mon système avec des tables que je ne veux plus conserver.

Alors, quel est le meilleur moyen de déclencher l’évaluation d’une DataFrame?

Modifier:

Notez qu’il existe également une discussion récente sur la liste des développeurs spark: http://Apache-spark-developers-list.1001551.n3.nabble.com/Will-count-always-trigger-an-evaluation-of- each-row-td21018.html

J'ai créé un petit exemple qui montre que count sur DataFrame n'évalue pas tout (testé avec Spark 1.6.3 et spark-master = local[2]):

val df = sc.parallelize(Seq(1)).toDF("id")
val myUDF = udf((i:Int) => {throw new RuntimeException;i})

df.withColumn("test",myUDF($"id")).count // runs fine
df.withColumn("test",myUDF($"id")).show() // gives Exception

En utilisant la même logique, voici un exemple où show n'évalue pas toutes les lignes:

val df = sc.parallelize(1 to 10).toDF("id")
val myUDF = udf((i:Int) => {if(i==10) throw new RuntimeException;i})

df.withColumn("test",myUDF($"id")).show(5) // runs fine
df.withColumn("test",myUDF($"id")).show(10) // gives Exception

Edit 2: Pour Eliasah: L'exception dit ceci:

org.Apache.spark.SparkException: Job aborted due to stage failure: Task 0 in stage 6.0 failed 1 times, most recent failure: Lost task 0.0 in stage 6.0 (TID 6, localhost): Java.lang.RuntimeException
    at $iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$anonfun$1.apply$mcII$sp(<console>:68)
    at $iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$anonfun$1.apply(<console>:68)
    at $iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$anonfun$1.apply(<console>:68)
    at org.Apache.spark.sql.catalyst.expressions.GeneratedClass$SpecificUnsafeProjection.apply(Unknown Source)
    at org.Apache.spark.sql.execution.Project$$anonfun$1$$anonfun$apply$1.apply(basicOperators.scala:51)
    at org.Apache.spark.sql.execution.Project$$anonfun$1$$anonfun$apply$1.apply(basicOperators.scala:49)
    at scala.collection.Iterator$$anon$11.next(Iterator.scala:328)
.
.
.
.

Driver stacktrace:
    at org.Apache.spark.scheduler.DAGScheduler.org$Apache$spark$scheduler$DAGScheduler$$failJobAndIndependentStages(DAGScheduler.scala:1431)
    at org.Apache.spark.scheduler.DAGScheduler$$anonfun$abortStage$1.apply(DAGScheduler.scala:1419)
    at org.Apache.spark.scheduler.DAGScheduler$$anonfun$abortStage$1.apply(DAGScheduler.scala:1418)
    at scala.collection.mutable.ResizableArray$class.foreach(ResizableArray.scala:59)
    at scala.collection.mutable.ArrayBuffer.foreach(ArrayBuffer.scala:47)
    at org.Apache.spark.scheduler.DAGScheduler.abortStage(DAGScheduler.scala:1418)
    at org.Apache.spark.scheduler.DAGScheduler$$anonfun$handleTaskSetFailed$1.apply(DAGScheduler.scala:799)
    at org.Apache.spark.scheduler.DAGScheduler$$anonfun$handleTaskSetFailed$1.apply(DAGScheduler.scala:799)
    at scala.Option.foreach(Option.scala:236)
    at org.Apache.spark.scheduler.DAGScheduler.handleTaskSetFailed(DAGScheduler.scala:799)
    at org.Apache.spark.scheduler.DAGSchedulerEventProcessLoop.doOnReceive(DAGScheduler.scala:1640)
    at org.Apache.spark.scheduler.DAGSchedulerEventProcessLoop.onReceive(DAGScheduler.scala:1599)
    at org.Apache.spark.scheduler.DAGSchedulerEventProcessLoop.onReceive(DAGScheduler.scala:1588)
    at org.Apache.spark.util.EventLoop$$anon$1.run(EventLoop.scala:48)
    at org.Apache.spark.scheduler.DAGScheduler.runJob(DAGScheduler.scala:620)
    at org.Apache.spark.SparkContext.runJob(SparkContext.scala:1832)
    at org.Apache.spark.SparkContext.runJob(SparkContext.scala:1845)
    at org.Apache.spark.SparkContext.runJob(SparkContext.scala:1858)
    at org.Apache.spark.sql.execution.SparkPlan.executeTake(SparkPlan.scala:212)
    at org.Apache.spark.sql.execution.Limit.executeCollect(basicOperators.scala:165)
    at org.Apache.spark.sql.execution.SparkPlan.executeCollectPublic(SparkPlan.scala:174)
    at org.Apache.spark.sql.DataFrame$$anonfun$org$Apache$spark$sql$DataFrame$$execute$1$1.apply(DataFrame.scala:1500)
    at org.Apache.spark.sql.DataFrame$$anonfun$org$Apache$spark$sql$DataFrame$$execute$1$1.apply(DataFrame.scala:1500)
    at org.Apache.spark.sql.execution.SQLExecution$.withNewExecutionId(SQLExecution.scala:56)
    at org.Apache.spark.sql.DataFrame.withNewExecutionId(DataFrame.scala:2087)
    at org.Apache.spark.sql.DataFrame.org$Apache$spark$sql$DataFrame$$execute$1(DataFrame.scala:1499)
    at org.Apache.spark.sql.DataFrame.org$Apache$spark$sql$DataFrame$$collect(DataFrame.scala:1506)
    at org.Apache.spark.sql.DataFrame$$anonfun$head$1.apply(DataFrame.scala:1376)
    at org.Apache.spark.sql.DataFrame$$anonfun$head$1.apply(DataFrame.scala:1375)
    at org.Apache.spark.sql.DataFrame.withCallback(DataFrame.scala:2100)
    at org.Apache.spark.sql.DataFrame.head(DataFrame.scala:1375)
    at org.Apache.spark.sql.DataFrame.take(DataFrame.scala:1457)
    at org.Apache.spark.sql.DataFrame.showString(DataFrame.scala:170)
    at org.Apache.spark.sql.DataFrame.show(DataFrame.scala:350)
    at org.Apache.spark.sql.DataFrame.show(DataFrame.scala:311)
    at org.Apache.spark.sql.DataFrame.show(DataFrame.scala:319)
    at $iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC.<init>(<console>:74)
.
.
.
.
13
Raphael Roth

Je suppose que le simple fait d'obtenir une rdd sous-jacente de DataFrame et de déclencher une action dessus devrait permettre d'obtenir ce que vous recherchez. 

df.withColumn("test",myUDF($"id")).rdd.count // this gives proper exceptions
8
Sachin Tyagi

C'est un peu tard, mais voici la raison fondamentale: count n'agit pas de la même manière sur RDD et DataFrame.

Dans DataFrames, il y a une optimisation, car dans certains cas, vous n'avez pas besoin de charger des données pour connaître le nombre d'éléments dont il dispose (en particulier dans le cas où vous ne devez pas mélanger les données). Par conséquent, la DataFrame matérialisée lorsque count est appelée ne chargera aucune donnée et ne passera pas dans votre levée d'exception. Vous pouvez facilement faire l'expérience en définissant vos propres DefaultSource et Relation et voir que l'appel de count sur une DataFrame aboutira toujours dans la méthode buildScan sans requiredColumns, quel que soit le nombre de colonnes que vous avez sélectionnées (cf. org.Apache.spark.sql.sources.interfaces pour en comprendre davantage). C'est en fait une optimisation très efficace ;-)

Cependant, dans RDDs, il n'y a pas d'optimisation (c'est pourquoi vous devriez toujours essayer d'utiliser DataFrames lorsque cela est possible). Par conséquent, count sur RDD exécute toute la lignée et renvoie la somme de toutes les tailles des itérateurs composant les partitions.

L'appel de dataframe.count entre dans la première explication, mais l'appel de dataframe.rdd.count entre dans la seconde, car vous avez créé une RDD à partir de votre DataFrame. Notez que l'appel de dataframe.cache().count force la dataframe à se matérialiser comme vous avez demandé à Spark de mettre en cache les résultats (par conséquent, il doit charger toutes les données et les transformer). Mais cela a pour effet secondaire de mettre vos données en cache ...

9
Vince.Bdn

Il semble que df.cache.count est la voie à suivre:

scala> val myUDF = udf((i:Int) => {if(i==1000) throw new RuntimeException;i})
myUDF: org.Apache.spark.sql.expressions.UserDefinedFunction = UserDefinedFunction(<function1>,IntegerType,Some(List(IntegerType)))

scala> val df = sc.parallelize(1 to 1000).toDF("id")
df: org.Apache.spark.sql.DataFrame = [id: int]

scala> df.withColumn("test",myUDF($"id")).show(10)
[rdd_51_0]
+---+----+
| id|test|
+---+----+
|  1|   1|
|  2|   2|
|  3|   3|
|  4|   4|
|  5|   5|
|  6|   6|
|  7|   7|
|  8|   8|
|  9|   9|
| 10|  10|
+---+----+
only showing top 10 rows

scala> df.withColumn("test",myUDF($"id")).count
res13: Long = 1000

scala> df.withColumn("test",myUDF($"id")).cache.count
org.Apache.spark.SparkException: Failed to execute user defined function($anonfun$1: (int) => int)
        at org.Apache.spark.sql.catalyst.expressions.GeneratedClass$GeneratedIterator.processNext(Unknown Source)
.
.
.
Caused by: Java.lang.RuntimeException

La source

1
evan.oman

Je préfère utiliser df.save.parquet(). Cela ajoute du temps d'E/S de disque que vous pouvez estimer et soustraire plus tard, mais vous êtes certain que spark a effectué chaque étape à laquelle vous vous attendiez et ne vous a pas trompé avec une évaluation paresseuse.

0