Lorsqu'un ensemble de données réparties résilient (RDD) est créé à partir d'un fichier texte ou d'une collection (ou d'un autre RDD), devons-nous appeler explicitement "cache" ou "persister" pour stocker les données RDD en mémoire? Ou les données RDD sont-elles stockées de manière distribuée dans la mémoire par défaut?
val textFile = sc.textFile("/user/emp.txt")
Selon ma compréhension, après l’étape ci-dessus, textFile est un RDD et est disponible dans toute/une partie de la mémoire du nœud.
Si c'est le cas, pourquoi devons-nous appeler "cache" ou "persister" sur textFile RDD?
La plupart des opérations de RDD sont paresseuses. Pensez à un RDD comme une description d'une série d'opérations. Un RDD n'est pas une donnée. Donc cette ligne:
val textFile = sc.textFile("/user/emp.txt")
Ça ne fait rien Il crée un RDD qui dit "nous devrons charger ce fichier". Le fichier n'est pas chargé à ce stade.
Les opérations RDD qui nécessitent l'observation du contenu des données ne peuvent pas être paresseuses. (Celles-ci s'appellent actions.) Un exemple est RDD.count
- pour vous dire le nombre de lignes dans le fichier, le fichier doit être lu. Donc, si vous écrivez textFile.count
, à ce stade, le fichier sera lu, les lignes seront comptées et le nombre retourné.
Que faire si vous appelez textFile.count
à nouveau? La même chose: le fichier sera lu et compté à nouveau. Rien n'est stocké. Un RDD n'est pas une donnée.
Alors, que fait RDD.cache
? Si vous ajoutez textFile.cache
au code ci-dessus:
val textFile = sc.textFile("/user/emp.txt")
textFile.cache
Ça ne fait rien RDD.cache
est également une opération lazy. Le fichier n'est toujours pas lu. Mais maintenant, le RDD dit "lisez ce fichier puis mettez le contenu en cache". Si vous exécutez ensuite textFile.count
pour la première fois, le fichier sera chargé, mis en cache et compté. Si vous appelez textFile.count
une seconde fois, l'opération utilisera le cache. Il va juste prendre les données du cache et compter les lignes.
Le comportement du cache dépend de la mémoire disponible. Si le fichier ne rentre pas dans la mémoire, par exemple, alors textFile.count
reviendra au comportement habituel et relira le fichier.
Je pense que la question serait mieux formulée comme suit:
Les processus Spark sont paresseux, c'est-à-dire que rien ne se passera tant que ce n'est pas nécessaire. Pour répondre rapidement à la question, une fois que val textFile = sc.textFile("/user/emp.txt")
a été émis, rien ne se passe pour les données, seul un HadoopRDD
est construit, en utilisant le fichier comme source.
Disons que nous transformons un peu ces données:
val wordsRDD = textFile.flatMap(line => line.split("\\W"))
Encore une fois, rien n’arrive aux données. Maintenant, il y a un nouveau RDD wordsRDD
qui contient une référence à testFile
et une fonction à appliquer en cas de besoin.
Seulement lorsqu'une action est appelée sur un RDD, telle que wordsRDD.count
, la chaîne de RDD appelée lignage sera exécutée. C'est-à-dire que les données, décomposées en partitions, seront chargées par les exécuteurs du cluster Spark, la fonction flatMap
sera appliquée et le résultat sera calculé.
Sur une lignée linéaire, comme celle de cet exemple, cache()
n'est pas nécessaire. Les données seront chargées dans les exécuteurs, toutes les transformations seront appliquées et enfin le count
sera calculé, le tout en mémoire - si les données sont en mémoire.
cache
est utile lorsque la lignée du RDD se ramifie. Supposons que vous souhaitiez filtrer les mots de l'exemple précédent en un compte pour les mots positifs et négatifs. Vous pouvez faire ça comme ça:
val positiveWordsCount = wordsRDD.filter(Word => isPositive(Word)).count()
val negativeWordsCount = wordsRDD.filter(Word => isNegative(Word)).count()
Ici, chaque branche émet un rechargement des données. L'ajout d'une instruction explicite cache
garantira que le traitement effectué précédemment est conservé et réutilisé. Le travail ressemblera à ceci:
val textFile = sc.textFile("/user/emp.txt")
val wordsRDD = textFile.flatMap(line => line.split("\\W"))
wordsRDD.cache()
val positiveWordsCount = wordsRDD.filter(Word => isPositive(Word)).count()
val negativeWordsCount = wordsRDD.filter(Word => isNegative(Word)).count()
Pour cette raison, on dit que cache
'casse la lignée' car il crée un point de contrôle qui peut être réutilisé pour un traitement ultérieur.
Règle de base: Utilisez cache
lorsque le lignage de votre RDD branches ou lorsqu'un RDD est utilisé plusieurs fois, comme dans une boucle.
Faut-il appeler explicitement "cache" ou "persister" pour stocker les données RDD en mémoire?
Oui, seulement si nécessaire.
Les données RDD stockées de manière distribuée dans la mémoire par défaut?
Non!
Et voici les raisons pour lesquelles:
Spark prend en charge deux types de variables partagées: les variables de diffusion, qui peuvent être utilisées pour mettre en cache une valeur en mémoire sur tous les nœuds, et les accumulateurs, qui sont des variables qui ne sont "que" ajoutées, telles que des compteurs et des sommes.
Les RDD prennent en charge deux types d'opérations: les transformations, qui créent un nouvel ensemble de données à partir d'un existant, et les actions, qui renvoient une valeur au programme de pilotes après l'exécution d'un calcul sur l'ensemble de données. Par exemple, map est une transformation qui transmet chaque élément de jeu de données à une fonction et renvoie un nouveau RDD représentant les résultats. D'autre part, réduire est une action qui regroupe tous les éléments du RDD en utilisant une fonction et renvoie le résultat final au programme du pilote (bien qu'il existe également une réduction parallèle, qui retourne un jeu de données distribué).
Toutes les transformations dans Spark sont paresseuses, en ce sens qu'elles ne calculent pas leurs résultats immédiatement. Au lieu de cela, ils se souviennent simplement des transformations appliquées à un ensemble de données de base (par exemple, un fichier). Les transformations ne sont calculées que lorsqu'une action nécessite qu'un résultat soit renvoyé au programme de pilote. Cette conception permet à Spark de fonctionner plus efficacement - par exemple, nous pouvons nous rendre compte qu'un jeu de données créé par carte sera utilisé dans une réduction et ne renverra que le résultat de la réduction au pilote, plutôt que le plus grand mappé. base de données.
Par défaut, chaque RDD transformé peut être recalculé chaque fois que vous exécutez une action dessus. Cependant, vous pouvez également conserver un RDD en mémoire à l'aide de la méthode persist (ou cache), auquel cas Spark conservera les éléments autour sur la grappe pour un accès beaucoup plus rapide la prochaine fois que vous l'interrogerez. Il existe également une prise en charge des RDD persistants sur le disque, ou répliqués sur plusieurs nœuds.
Pour plus de détails, veuillez consulter le Guide de programmation Spark .
Vous trouverez ci-dessous les trois situations dans lesquelles vous devez mettre en cache vos RDD:
en utilisant un RDD plusieurs fois
effectuer plusieurs actions sur le même RDD
pour de longues chaînes de transformations (ou très coûteuses)
Ajouter un autre motif pour ajouter (ou ajouter temporairement) cache
appel de méthode.
avec la méthode cache
, spark donnera des informations de débogage concernant la taille du RDD. Ainsi, dans l'interface utilisateur intégrée spark, vous obtiendrez des informations sur la consommation de mémoire RDD. et cela s’est avéré très utile pour diagnostiquer les problèmes de mémoire.