web-dev-qa-db-fra.com

Lecture de plusieurs fichiers depuis S3 en Spark par période de date

La description

J'ai une application qui envoie des données à AWS Kinesis Firehose et cela écrit les données dans mon compartiment S3. Firehose utilise le format "aaaa/MM/jj/HH" pour écrire les fichiers.

Comme dans cet exemple de chemin S3:

s3://mybucket/2016/07/29/12

J'ai maintenant une application Spark écrite en Scala, où je dois lire les données d'une période spécifique. J'ai des dates de début et de fin. Les données sont au format JSON et c'est pourquoi j'utilise sqlContext.read.json() pas sc.textFile().

Comment puis-je lire les données rapidement et efficacement?

Qu'est-ce que j'ai essayé?

  1. Caractères génériques - Je peux sélectionner les données de toutes les heures d'une date spécifique ou de toutes les dates d'un mois spécifique, par exemple:

    val df = sqlContext.read.json("s3://mybucket/2016/07/29/*")
    val df = sqlContext.read.json("s3://mybucket/2016/07/*/*")
    

    Mais si je dois lire des données de la période de quelques jours, par exemple 2016-07-29 - 2016-07-30, je ne peux pas utiliser l'approche générique de la même manière.

    Ce qui m'amène à mon prochain point...

  2. Utilisation de plusieurs chemins ou d'un CSV de répertoires présenté par samthebest dans - cette solution. Il semble que la séparation des répertoires par des virgules ne fonctionne qu'avec sc.textFile() et non sqlContext.read.json().
  3. Union - Une deuxième solution du lien précédent par cloud suggère de lire chaque répertoire séparément, puis les réunir. Bien qu'il suggère l'union des RDD, il existe également une option pour l'union des DataFrames. Si je génère manuellement les chaînes de date à partir d'une période donnée, je peux créer un chemin d'accès qui n'existe pas et au lieu de l'ignorer, toute la lecture échoue. Au lieu de cela, je pourrais utiliser AWS SDK et utiliser la fonction listObjects d'AmazonS3Client pour obtenir toutes les clés comme dans la solution d'iMKanchwala de la précédente lien.

    Le seul problème est que mes données changent constamment. Si la fonction read.json() obtient toutes les données en tant que paramètre unique, elle lit toutes les données nécessaires et est suffisamment intelligente pour déduire le schéma json des données. Si je lis 2 répertoires séparément et que leurs schémas ne correspondent pas, je pense que l'union de ces deux cadres de données devient un problème.

  4. Syntaxe Glob (?) - This solution by nhahtdh est un peu mieux que les options 1 et 2 car elles fournir la possibilité de spécifier les dates et les répertoires plus en détail et comme un "chemin" unique afin qu'il fonctionne également avec read.json().

    Mais encore une fois, un problème familier se produit concernant les répertoires manquants. Disons que je veux toutes les données du 20.07 au 30.07, je peux le déclarer comme ceci:

    val df = sqlContext.read.json("s3://mybucket/2016/07/[20-30]/*")
    

    Mais si je manque des données de disons le 25 juillet, alors le chemin ..16/07/25/ N'existe pas et la fonction entière échoue.

Et évidemment, cela devient plus difficile lorsque la période demandée est par exemple le 25.11.2015-12.02.2016, alors je devrais programmer (dans mon script Scala) créer un chemin de chaîne quelque chose comme ceci:

"s3://mybucket/{2015/11/[25-30],2015/12/*,2016/01/*,2016/02/[01-12]}/*"

Et en le créant, je ne serais pas en quelque sorte sûr que ces intervalles 25-30 et 01-12 ont tous des chemins correspondants, s'il en manque un, il échoue à nouveau. (Asterisk traite heureusement les répertoires manquants, car il lit tout ce qui existe)

Comment puis-je lire toutes les données nécessaires à partir d'un seul chemin de répertoire en une seule fois sans possibilité d'échec en raison d'un répertoire manquant entre un certain intervalle de date?

21
V. Samma

Il existe une solution beaucoup plus simple. Si vous regardez la DataFrameReader API , vous remarquerez qu'il existe une méthode .json(paths: String*). Construisez simplement une collection des chemins que vous voulez, avec des globs non, comme vous préférez, puis appelez la méthode, par exemple,

val paths: Seq[String] = ...
val df = sqlContext.read.json(paths: _*)
12
Sim