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)
.
.
.
.
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
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 DataFrame
s, 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 RDD
s, il n'y a pas d'optimisation (c'est pourquoi vous devriez toujours essayer d'utiliser DataFrame
s 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 ...
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
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.