Donc, après avoir passé de nombreuses années dans un monde orienté objet avec la réutilisation du code, les modèles de conception et les meilleures pratiques toujours pris en compte, je me retrouve quelque peu en difficulté avec l'organisation et la réutilisation du code dans World of Spark.
Si j'essaie d'écrire du code de manière réutilisable, il a presque toujours un coût de performance et je finis par le réécrire dans ce qui est optimal pour mon cas d'utilisation particulier. Cette constante "écrire ce qui est optimal pour ce cas d'utilisation particulier" affecte également l'organisation du code, car la division du code en différents objets ou modules est difficile lorsque "tout appartient vraiment ensemble" et je me retrouve donc avec très peu d'objets "Dieu" contenant de longues chaînes de transformations complexes. En fait, je pense souvent que si j'avais jeté un coup d'œil à la plupart des codes Spark que j'écris maintenant lorsque je travaillais dans le monde orienté objet, j'aurais grimacé et rejeté comme "code spaghetti".
J'ai navigué sur Internet en essayant de trouver une sorte d'équivalent aux meilleures pratiques du monde orienté objet, mais sans beaucoup de chance. Je peux trouver quelques "meilleures pratiques" pour la programmation fonctionnelle mais Spark ajoute juste une couche supplémentaire, parce que les performances sont un facteur majeur ici.
Donc, ma question est la suivante: certains d'entre vous ont-ils Spark gourous trouvé quelques bonnes pratiques pour écrire Spark code que vous pouvez recommander?)
MODIFIER
Comme écrit dans un commentaire, je ne m'attendais pas vraiment à ce que quelqu'un poste une réponse sur la façon de résoudre ce problème, mais j'espérais plutôt que quelqu'un dans ce La communauté avait rencontré un type de Martin Fowler, qui avait écrit des articles ou des articles de blog quelque part sur la façon de résoudre les problèmes d'organisation du code dans le monde de Spark.
@DanielDarabos a suggéré que je pourrais mettre dans un exemple d'une situation où l'organisation du code et les performances sont en conflit. Bien que je trouve que j'ai souvent des problèmes avec cela dans mon travail quotidien, je trouve un peu difficile de le résumer à un bon exemple minimal;) mais je vais essayer.
Dans le monde orienté objet, je suis un grand fan du principe de responsabilité unique, donc je m'assurerais que mes méthodes n'étaient responsables que d'une chose. Il les rend réutilisables et facilement testables. Donc, si je devais, par exemple, calculer la somme de certains nombres dans une liste (correspondant à certains critères) et que je devais calculer la moyenne du même nombre, je créerais très certainement deux méthodes - une qui calculait la somme et une qui calculé la moyenne. Comme ça:
def main(implicit args: Array[String]): Unit = {
val list = List(("DK", 1.2), ("DK", 1.4), ("SE", 1.5))
println("Summed weights for DK = " + summedWeights(list, "DK")
println("Averaged weights for DK = " + averagedWeights(list, "DK")
}
def summedWeights(list: List, country: String): Double = {
list.filter(_._1 == country).map(_._2).sum
}
def averagedWeights(list: List, country: String): Double = {
val filteredByCountry = list.filter(_._1 == country)
filteredByCountry.map(_._2).sum/ filteredByCountry.length
}
Je peux bien sûr continuer à honorer SRP dans Spark:
def main(implicit args: Array[String]): Unit = {
val df = List(("DK", 1.2), ("DK", 1.4), ("SE", 1.5)).toDF("country", "weight")
println("Summed weights for DK = " + summedWeights(df, "DK")
println("Averaged weights for DK = " + averagedWeights(df, "DK")
}
def avgWeights(df: DataFrame, country: String, sqlContext: SQLContext): Double = {
import org.Apache.spark.sql.functions._
import sqlContext.implicits._
val countrySpecific = df.filter('country === country)
val summedWeight = countrySpecific.agg(avg('weight))
summedWeight.first().getDouble(0)
}
def summedWeights(df: DataFrame, country: String, sqlContext: SQLContext): Double = {
import org.Apache.spark.sql.functions._
import sqlContext.implicits._
val countrySpecific = df.filter('country === country)
val summedWeight = countrySpecific.agg(sum('weight))
summedWeight.first().getDouble(0)
}
Mais parce que mon df
peut contenir des milliards de lignes, je préfère ne pas avoir à effectuer le filter
deux fois. En fait, les performances sont directement couplées au coût du DME, donc je ne veux vraiment pas cela. Pour le surmonter, je décide donc de violer SRP et simplement de mettre les deux fonctions en une et de m'assurer d'appeler persist sur le DataFrame
filtré par pays, comme ceci:
def summedAndAveragedWeights(df: DataFrame, country: String, sqlContext: SQLContext): (Double, Double) = {
import org.Apache.spark.sql.functions._
import sqlContext.implicits._
val countrySpecific = df.filter('country === country).persist(StorageLevel.MEMORY_AND_DISK_SER)
val summedWeights = countrySpecific.agg(sum('weight)).first().getDouble(0)
val averagedWeights = summedWeights / countrySpecific.count()
(summedWeights, averagedWeights)
}
Maintenant, cet exemple est bien sûr une énorme simplification de ce qui se rencontre dans la vie réelle. Ici, je pourrais simplement le résoudre en filtrant et en persistant df
avant de le remettre aux fonctions sum et avg (qui seraient également plus SRP ), mais dans la vie réelle, il peut y avoir un certain nombre de calculs intermédiaires en cours qui sont nécessaires encore et encore. En d'autres termes, la fonction filter
ici est simplement une tentative de faire un exemple simple de quelque chose qui bénéficiera de la persistance. En fait, je pense que les appels à persist
sont un mot clé ici. Appeler persist
accélérera considérablement mon travail, mais le coût est que je dois coupler étroitement tout le code qui dépend du DataFrame
persistant - même s'ils sont logiquement séparés.
Je pense que vous pouvez vous abonner Apache Spark
, databricks
channel sur youtube, écoutez et en savoir plus, en particulier pour les expériences et les leçons des autres.
voici quelques vidéos recommandées:
slide
Spark in Production: Lessons from 100+ Production Users
slide
Construction, débogage et optimisation Spark Pipelines d'apprentissage machine
slide
Les 5 principales erreurs lors de l'écriture Spark applications
ne compréhension plus approfondie de Spark Internals - Aaron Davidson (Databricks)
slide
Une compréhension plus approfondie de Spark Internals - Aaron Davidson (Databricks)et je l'ai posté et toujours mis à jour sur mon github et mon blog:
j'espère que cela peut vous aider ~