web-dev-qa-db-fra.com

AWS Glue: Comment gérer un JSON imbriqué avec différents schémas

Objectif: Nous espérons utiliser AWS Glue Data Catalog pour créer une seule table pour les données JSON résidant dans un compartiment S3, que nous interrogerions et analyserions ensuite via Redshift Spectrum.

Background: Les données JSON proviennent de DynamoDB Streams et sont profondément imbriquées. Le premier niveau de JSON comporte un ensemble cohérent d'éléments: Keys, NewImage, OldImage, SequenceNumber, ApproximateCreationDateTime, SizeBytes et EventName. La seule variation est que certains enregistrements n'ont pas de NewImage et que certains n'ont pas de OldImage. En dessous de ce premier niveau, cependant, le schéma varie beaucoup.

Idéalement, nous aimerions utiliser Glue pour analyser uniquement ce premier niveau de JSON, et traiter les niveaux inférieurs comme des objets STRING volumineux (que nous analyserions ensuite au besoin avec Redshift Spectrum). Actuellement, nous chargeons l'intégralité de l'enregistrement dans une seule colonne VARCHAR dans Redshift, mais les enregistrements approchent de la taille maximale d'un type de données dans Redshift (la longueur maximale de VARCHAR est de 65535). En conséquence, nous aimerions effectuer ce premier niveau d’analyse avant que les enregistrements n’atteignent Redshift.

Ce que nous avons essayé/référencé jusqu'à présent:

  • Le fait de pointer AWS Glue Crawler sur le compartiment S3 génère des centaines de tables avec un schéma de niveau supérieur cohérent (les attributs répertoriés ci-dessus), mais en modifiant les schémas à des niveaux plus profonds dans les éléments STRUCT. Nous n'avons pas trouvé de moyen de créer un travail Glue ETL capable de lire toutes ces tables et de le charger dans une seule table.
  • Créer une table manuellement n'a pas été fructueux. Nous avons essayé de définir chaque colonne sur un type de données STRING, mais le travail n'a pas réussi à charger les données (probablement parce que cela impliquerait une certaine conversion de STRUCTs en STRING). Lorsque vous définissez des colonnes sur STRUCT, un schéma défini est nécessaire - mais c'est précisément ce qui varie d'un enregistrement à un autre. Nous ne sommes donc pas en mesure de fournir un schéma STRUCT générique qui fonctionne pour tous les enregistrements en question.
  • La transformation AWS Glue Relationalize est intriguante, mais n’est pas ce que nous recherchons dans ce scénario (car nous souhaitons conserver une partie du code JSON au lieu de l’aplatir entièrement). Redshift Spectrum prend en charge scalar JSON data il y a quelques semaines, mais cela ne fonctionne pas avec le JSON imbriqué auquel nous avons affaire. Aucune de ces solutions ne semble aider à gérer les centaines de tables créées par Glue Crawler.

Question: Comment pourrions-nous utiliser Glue (ou une autre méthode) pour nous permettre d’analyser uniquement le premier niveau de ces enregistrements, tout en ignorant les divers schémas situés au-dessous des éléments situés au niveau supérieur, afin de pouvoir y accéder depuis Spectrum ou le charger physiquement dans Redshift?

Je suis nouveau dans la colle. J'ai passé pas mal de temps dans la documentation de Glue et à parcourir les informations (plutôt rares) sur les forums. Il se peut que je manque quelque chose d'évident - ou peut-être s'agit-il d'une limitation de la colle dans sa forme actuelle. Toutes les recommandations sont les bienvenues.

Merci!

7
Airik55082

Je ne suis pas sûr que vous puissiez le faire avec une définition de table, mais vous pouvez le faire avec un travail ETL en utilisant une fonction de mappage pour convertir les valeurs de niveau supérieur en chaînes JSON. Documentation: [ link ]

import json

# Your mapping function
def flatten(rec):
    for key in rec:
        rec[key] = json.dumps(rec[key])
    return rec

old_df = glueContext.create_dynamic_frame.from_options(
    's3',
    {"paths": ['s3://...']},
    "json")

# Apply mapping function f to all DynamicRecords in DynamicFrame
new_df = Map.apply(frame=old_df, f=flatten)

À partir de là, vous avez la possibilité d’exporter vers S3 (peut-être dans Parquet ou un autre format de colonne à optimiser pour l’interrogation) ou directement vers Redshift, même si je n’ai pas essayé.

1
x1084

À compter du 20/12/2018, j'ai été en mesure de définir manuellement une table avec des champs JSON de premier niveau sous forme de colonnes de type STRING. Ensuite, dans le script de collage, le cadre dynamique contient la colonne sous forme de chaîne. À partir de là, vous pouvez effectuer une opération Unbox de type json sur les champs. Cela analysera les champs et en déduira le schéma réel. La combinaison de Unbox avec Filter vous permet de parcourir et de traiter des schémas json hétérogènes à partir de la même entrée si vous pouvez parcourir une liste de schémas. 

Cependant, un mot d'avertissement, c'est incroyablement lent. Je pense que la colle télécharge les fichiers sources de s3 à chaque itération de la boucle. J'ai essayé de trouver un moyen de conserver les données source initiales, mais il semble que .toDF dérive le schéma des champs de chaîne json, même si vous les spécifiez sous la forme de glue StringType. J'ajouterai un commentaire ici si je peux trouver une solution offrant de meilleures performances.

0
Zambonilli

vous devriez ajouter un classificateur de colle de préférence $ [*]

Lorsque vous analysez le fichier JSON dans S3, il lit la première ligne du fichier.

Vous pouvez créer un travail de collage afin de charger la table de catalogue de données de ce fichier json dans le redshift.

Mon seul problème ici est que Redshift Spectrum a des problèmes pour lire les tables JSON dans le catalogue de données.

laissez-moi savoir si vous avez trouvé une solution

0
beni

La procédure que j'ai trouvée utile pour json imbriqué peu profond:

  1. ApplyMapping pour le premier niveau en tant que datasource0;

  2. Eclater les objets struct ou array pour supprimer le niveau d'élément df1 = datasource0.toDF().select(id,col1,col2,...,explode(coln).alias(coln), où explode requiert from pyspark.sql.functions import explode;

  3. Sélectionnez les objets JSON que vous souhaitez conserver intacts via intact_json = df1.select(id, itct1, itct2,..., itctm);

  4. Transformez df1 en dynamicFrame et relationalisez le DynamicFrame et supprimez les colonnes intactes de dataframe.drop_fields(itct1, itct2,..., itctm);

  5. Joignez une table relationnelle à la table intacte en vous basant sur la colonne 'id'

0
LinZ

Ceci est une limitation de la colle à partir de maintenant. Avez-vous jeté un coup d'oeil aux classificateurs de colle? C'est le seul article que je n'ai pas encore utilisé, mais qui pourrait répondre à vos besoins. Vous pouvez définir un chemin JSON pour un champ ou quelque chose comme ça.

Autre que cela - Glue Jobs sont la voie à suivre. C'est Spark en arrière-plan, vous pouvez donc pratiquement tout faire. Configurez un noeud final de développement et jouez avec. Je me suis heurté à divers obstacles au cours des trois dernières semaines et j'ai décidé de renoncer complètement à toutes les fonctionnalités de Glue et à Spark. Il est ainsi portable et fonctionne réellement.

N'oubliez pas, lors de la configuration du noeud final de développement, que le rôle IAM doit avoir un chemin d'accès "/". Vous devrez donc probablement créer manuellement un rôle distinct comportant ce chemin. Celui créé automatiquement a un chemin "/ service-role /".

0
LauriK