Reconstruction de Hive dans HDP : patch, test et build
6 oct. 2020
- Catégories
- Big Data
- Infrastructure
- Tags
- Maven
- GitHub
- Java
- Hive
- Git
- Versions et évolutions
- TDP
- Tests unitaires [plus][moins]
Ne ratez pas nos articles sur l'open source, le big data et les systèmes distribués, fréquence faible d’un email tous les deux mois.
La distribution HDP d’Hortonworks va bientôt être dépreciée a profit de la distribution CDP proposée par Cloudera. Un client nous a demandé d’intégrer d’une nouvelle feature de Apache Hive sur son installation reposant sur HDP 2.6.0. Il s’agit d’une bonne opportunité pour essayer de compiler manuellement la version de Hive incluse dans HDP 2.6.0. Dans cet article, nous allons tenter de backporter une liste de patchs fournie par Hortonworks sur le project open source Hive de manière automatisée.
Re-build de Hive dans HDP 2.6.0
La première étape fut d’essayer de compiler la même version de Apache Hive que dans HDP 2.6.0.
Pour chaque release d’HDP, Hortonworks (désormais Cloudera) publie dans ses release notes la liste des patchs appliqués sur chaque composants. Nous allons d’abord récupérer cette liste appliqués sur Apache Hive 2.1.0 et la mettre dans un fichier texte :
cat hdp_2_6_0_0_patches.txt
HIVE-9941
HIVE-12492
HIVE-14214
...
...
HIVE-16319
HIVE-16323
HIVE-16325
Il y en a 120 :
wc -l hdp_2_6_0_0_patches.txt
120 hdp_2_6_0_0_patches.txt
Comment appliquer ces patchs ?
Application des patchs dans l’ordre d’Hortonworks
Avant d’appliquer les patchs, nous pouvons cloner le repository d’Apache Hive et faire un checkout de la version incluse dans HDP 2.6.0 qui est la 2.1.0
:
git clone https://github.com/apache/hive.git
git checkout rel/release-2.1.0
Nous pensions au début que les patchs étaient listés par Hortonworks dans l’ordre chronologique de merge dans la branche master de Hive. Nous avons donc essayé de télécharger les patchs et de les appliquer dans cet ordre.
Pour chaque patch dans la liste, il faut récupérer le fichier patch dans la JIRA associée et lancer la commande git apply
sur ce patch. Cela peut s’avérer fastidieux pour des dizaines de patchs. Nous verrons plus loin dans l’article comment automatiser ce processus.
Commençons par appliquer le premier patch de notre liste : HIVE-9941
. Nous pouvons constater dans l’issue JIRA qu’il y a plusieurs pièces jointes.
Téléchargeons la dernière et essayons de l’appliquer :
wget https://issues.apache.org/jira/secure/attachment/12833864/HIVE-9941.3.patch
git apply HIVE-9941.3.patch
HIVE-9941.3.patch:17: new blank line at EOF.
+
HIVE-9941.3.patch:42: new blank line at EOF.
+
HIVE-9941.3.patch:71: new blank line at EOF.
+
HIVE-9941.3.patch:88: new blank line at EOF.
+
warning: 4 lines add whitespace errors.
La commande git apply
renvoie quelques avertissements mais le patch a bien été appliqué comme nous l’indique la commande git status
:
git status
HEAD detached at rel/release-2.1.0
Untracked files:
(use "git add <file>..." to include in what will be committed)
HIVE-9941.3.patch
ql/src/test/queries/clientnegative/authorization_alter_drop_ptn.q
ql/src/test/queries/clientnegative/authorization_export_ptn.q
ql/src/test/queries/clientnegative/authorization_import_ptn.q
ql/src/test/queries/clientnegative/authorization_truncate_2.q
ql/src/test/results/clientnegative/authorization_alter_drop_ptn.q.out
ql/src/test/results/clientnegative/authorization_export_ptn.q.out
ql/src/test/results/clientnegative/authorization_import_ptn.q.out
ql/src/test/results/clientnegative/authorization_truncate_2.q.out
nothing added to commit but untracked files present (use "git add" to track)
Ce patch a créer des nouveaux fichiers mais n’en a modifié aucun.
Continuons avec le prochain patch de la liste, HIVE-12492
:
wget https://issues.apache.org/jira/secure/attachment/12854164/HIVE-12492.02.patch
git apply HIVE-12492.02.patch
HIVE-12492.02.patch:362: trailing whitespace.
Map 1
[...]
error: src/java/org/apache/hadoop/hive/conf/HiveConf.java: No such file or directory
error: src/test/resources/testconfiguration.properties: No such file or directory
error: src/java/org/apache/hadoop/hive/ql/optimizer/ConvertJoinMapJoin.java: No such file or directory
error: src/java/org/apache/hadoop/hive/ql/optimizer/stats/annotation/StatsRulesProcFactory.java: No such file or directory
error: src/java/org/apache/hadoop/hive/ql/stats/StatsUtils.java: No such file or directory
Cette fois-ci, nous rencontrons plusieurs erreurs du type No such file or directory
. Sur la première ligne du fichier patch, nous pouvons voir que le fichier ciblé est common/src/java/org/apache/hadoop/hive/conf/HiveConf.java
:
diff --git common/src/java/org/apache/hadoop/hive/conf/HiveConf.java common/src/java/org/apache/hadoop/hive/conf/HiveConf.java
index 3777fa9..08422d5 100644
--- common/src/java/org/apache/hadoop/hive/conf/HiveConf.java
+++ common/src/java/org/apache/hadoop/hive/conf/HiveConf.java
@@ -1422,6 +1422,11 @@ private static void populateLlapDaemonVarsSet(Set<String> llapDaemonVarsSetLocal
"This controls how many partitions can be scanned for each partitioned table.\n" +
[...]
L’erreur nous indique que le fichier src/java/org/apache/hadoop/hive/conf/HiveConf.java
n’existe pas. C’est parce que la commande git apply
dispose d’une propiété -p
(décrite ici) qui enlève la première partie des chemins des fichiers (la valeur par défaut est 1) :
Nous n’avons pas rencontré cette erreur avec le précédent patch car ce-dernier utilisait un préfixe dans le chemin des fichiers, par exemple :
diff --git a/ql/src/test/queries/clientnegative/authorization_alter_drop_ptn.q b/ql/src/test/queries/clientnegative/authorization_alter_drop_ptn.q
Réessayons avec -p0
:
git apply -p0 HIVE-12492.02.patch
HIVE-12492.02.patch:362: trailing whitespace.
Map 1
[...]
error: patch failed: common/src/java/org/apache/hadoop/hive/conf/HiveConf.java:1422
error: common/src/java/org/apache/hadoop/hive/conf/HiveConf.java: patch does not apply
error: patch failed: itests/src/test/resources/testconfiguration.properties:501
error: itests/src/test/resources/testconfiguration.properties: patch does not apply
error: patch failed: ql/src/java/org/apache/hadoop/hive/ql/optimizer/ConvertJoinMapJoin.java:53
error: ql/src/java/org/apache/hadoop/hive/ql/optimizer/ConvertJoinMapJoin.java: patch does not apply
error: patch failed: ql/src/java/org/apache/hadoop/hive/ql/optimizer/stats/annotation/StatsRulesProcFactory.java:51
error: ql/src/java/org/apache/hadoop/hive/ql/optimizer/stats/annotation/StatsRulesProcFactory.java: patch does not apply
C’est mieux mais le patch ne s’applique toujours pas. Cette fois-ci nous rencontrons l’erreur patch does not apply
qui est assez parlante.
En reprenant la liste des JIRA fournie par Hortonworks, nous nous sommes rendu compte que les patchs étaient donnés par ordre numérique. Prenons le cas des deux issues suivantes : HIVE-14405
and HIVE-14432
. La première est plus ancienne que la deuxième mais cette dernière a bénéficiée d’un patch avant l’autre. Si ces deux patchs étaient ammenés à modifier le même fichier, nous pourrions rentrontrer l’erreur ci-dessus.
Appliquer les patchs dans cet ordre ne semble pas être la bonne solution. Dans la prochaine partie, nous allons essayer d’appliquer les patchs dans l’ordre dans lequel ils ont été push sur la branche master.
Application des patchs par ordre d’upload sur JIRA
Essayons d’itérer sur la liste patch pour voir combien nous pouvons en appliquer avec cette nouvelle stratégie.
Récupérer et appliquer tous les patchs depuis JIRA peut être fastidieux pour les raisons suivantes :
- Il y en a 120
- Nous venons de constater que certains patchs sont appliqués au niveau d’arborescence
-p0
et d’autres au niveau-p1
- Certains JIRA indiqués par Hortonworks n’ont aucune pièce jointe (ex : HIVE-16238)
- Certains JIRA ont des pièces jointes qui ne sont pas des patchs (ex : HIVE-16323 dans laquelle il y a une capture d’écran)
- Certains patchs sont au format
.txt
au lieu de.patch
(ex : HIVE-15991)
Voici un extrait (en CoffeeScript) du script que nous avons écrit pour récupérer le dernier patch de chaque JIRA :
generate_patch_manifest = ->
# For each patch in the given list, return a manifest
patches_manifest = hdp_2_6_0_0_patches
# Generate a manifest with patch id, description, creation date and url
.map (patch) ->
# Parse the JIRA id
jira_id = patch.split(/:(.+)/)[0]
# Parse the commit description
commit_description = patch.split(/:(.+)/)[1]
# Get all the attachments
response = await axios.get "https://issues.apache.org/jira/rest/api/2/search?jql=key=#{jira_id}&fields=attachment"
# Filter the attachments that are actual patches
attachments = response.data.issues[0].fields.attachment.filter (o) -> o.filename.includes jira_id
# Get only the latest patch
most_recent_created_time = Math.max.apply Math, attachments.map (o) -> Date.parse o.created
most_recent_created_attachment = attachments.filter (o) -> Date.parse(o.created) is most_recent_created_time
# Create the patch manifest (implicit return in CoffeeScript)
id: jira_id
description: commit_description
created: Date.parse most_recent_created_attachment[0].created
url: most_recent_created_attachment[0].content
# Order the patches by date
.sort (a,b) ->
a.created - b.created
# Write tbe object to a YAML file
fs.writeFile './patch_manifest.yml', yaml.safeDump(patches_manifest), (err) ->
if err
then console.error err
else console.info 'done'
Cette fonction génère un fichier patch_manifest.yaml
avec la syntaxe suivante :
- id: $JIRA_NUMBER
description: $COMMIT_MESSAGE
created: $TIMESTAMP
url: $LATEST_PATCH_URL
Créer ce manifeste va nous aider à appliquer les patchs dans l’ordre.
Nous pouvons désormais itérer sur cette liste, télécharger et essayer d’appliquer le plus de patch possible. Voici un autre extrait de code réalise cela :
# For each patch in the manifest
for patch, index in patches_manifest
try
await nikita.execute """
# Download the patch file
wget -q -N #{patch.url}
# If the patch as the "a/" leading part in the files, apply with -p1
if grep "git \\\\ba/" patches/#{patch.url.split('/').pop()}; then
git apply patches/#{patch.url.split('/').pop()}
# If not, then apply with -p0
else
git apply -p0 patches/#{patch.url.split('/').pop()}
fi
# Commit changes
git add -A
git commit -m "Applied #{patch.url.split('/').pop()}"
"""
console.info "Patch #{patch.url.split('/').pop()} applied successfully"
catch err
console.error "Patch #{patch.url.split('/').pop()} failed to apply"
Cela ne s’est pas très bien passé. Seulement 19 des 120 patchs dans la liste se sont appliqués correctement. Toutes les erreurs patch does not apply
peuvent avoir plusieurs causes :
- Nous n’appliquons toujours pas les patchs dans le bon ordre
- Certains pré-requis au niveau du code sont manquants (la liste des JIRA serait incomplète ?)
- Le code doit être adapté pour être backporté
- Toutes les propositions précédentes
Dans la partie suivante, nous allons essayer une autre stratégie : réaliser un cherry-picking des commits liés aux JIRAs HIVE-XXXXX.
Application des patch dans l’ordre chronologique de push sur la branche master
Chaque message de commit du repository Apache Hive comprends le numéro de l’issue JIRA liée dans sa description :
Pour ce nouvel essai, nous allons récupérer la liste des commits de la branch master Apache Hive pour générer une liste de commits contenants les patchs que nous souhaitons appliquer dans l’ordre chronologique.
git checkout master
git log --pretty=format:'%at;%H;%s' > apache_hive_master_commits.csv
La commande git log
avec les bons paramètres permet de générer un fichier CSV avec le format timestamp;commit_hash;commit_message
.
Par exemple :
head apache_hive_master_commits.csv
1597212601;a430ac31441ad2d6a03dd24e3141f42c79e022f4;HIVE-23995:Don't set location for managed tables in case of replication (Aasha Medhi, reviewed by Pravin Kumar Sinha)
1597206125;ef47b9eb288dec6e6a014dd5e52accdf0ac3771f;HIVE-24030: Upgrade ORC to 1.5.10 (#1393)
1597144690;d4af3840f89408edea886a28ee4ae7d79a6f16f8;HIVE-24014: Need to delete DumpDirectoryCleanerTask (Arko Sharma, reviewed by Aasha Medhi)
1597135126;71c9af7b8ead470520a6c3a4848be9c67eb80f10;HIVE-24001: Don't cache MapWork in tez/ObjectCache during query-based compaction (Karen Coppage, reviewed by Marta Kuczora)
1597108110;af911c9fe0ff3bb49d80f6c0109d30f5e1046849;HIVE-23996: Remove unused line in UDFArgumentException (Guo Philipse reviewed by Peter Vary, Jesus Camacho Rodriguez)
1597107811;17ee33f4f4d8dc9437d1d3a47663a635d2e47b58;HIVE-23997: Some logs in ConstantPropagateProcFactory are not straightforward (Zhihua Deng, reviewed by Jesus Camacho Rodriguez)
1596833809;5d9a5cf5a36c1d704d2671eb57547ea50249f28b;HIVE-24011: Flaky test AsyncResponseHandlerTest ( Mustafa Iman via Ashutosh Chauhan)
1596762366;9fc8da6f32b68006c222ccfd038719fc89ff8550;HIVE-24004: Improve performance for filter hook for superuser path(Sam An, reviewed by Naveen Gangam)
1595466890;7293956fc2f13ccbe72825b67ce1b53dce536359;HIVE-23901: Overhead of Logger in ColumnStatsMerger damage the performance
1593631609;4457c3ec9360650be021ea84ed1d5d0f007d8308;HIVE-22934 Hive server interactive log counters to error stream ( Ramesh Kumar via Ashutosh Chauhan)
Les commandes suivantes permettent de récupérer uniquement les commits qui nous intéressent : la liste de JIRAs fournie par Hortonworks. Finalement, nous pouvons trier cette liste par ordre chronologique :
cat hdp_2_6_0_0_patches.txt | while read in; do grep $in apache_hive_master_commits.csv; done > commits_to_apply.csv
sort commits_to_apply.csv > sorted_commits_to_apply.csv
Maintenant que nous disposons d’une liste ordonnée de commits que nous voulons appliquer, nous pouvons revenir à la version 2.1.0
et cherry-pick ces commits.
Le cherry-pick semble être une solution plus adaptée que l’application de fichiers patchs. En effet, nous venons de constater que certains patchs n’étaient pas applicables à cause du manque de code dépendant.
Essayons de faire du cherry pick sur le premier JIRA. Le commit hash du HIVE-14214 est b28ec7fdd8317b47973c6c8f7cdfe805dc20a806
.
head -1 sorted_commits_to_apply.csv
1469348583;b28ec7fdd8317b47973c6c8f7cdfe805dc20a806;HIVE-14214: ORC Schema Evolution and Predicate Push Down do not work together (no rows returned) (Matt McCline, reviewed by Prasanth Jayachandran/Owen O'Malley)
git cherry-pick -x 15dd7f19bc49ee7017fa5bdd65f9b4ec4dd019d2 --strategy-option theirs
CONFLICT (modify/delete): ql/src/test/results/clientpositive/tez/explainanalyze_5.q.out deleted in HEAD and modified in 15dd7f19bc... HIVE-15084: Flaky test: TestMiniTezCliDriver:explainanalyze_1, 2, 3, 4, 5 (Pengcheng Xiong). Version 15dd7f19bc... HIVE-15084: Flaky test: TestMiniTezCliDriver:explainanalyze_1, 2, 3, 4, 5 (Pengcheng Xiong) of ql/src/test/results/clientpositive/tez/explainanalyze_5.q.out left in tree.
[...]
CONFLICT (modify/delete): ql/src/test/queries/clientpositive/explainanalyze_1.q deleted in HEAD and modified in 15dd7f19bc... HIVE-15084: Flaky test: TestMiniTezCliDriver:explainanalyze_1, 2, 3, 4, 5 (Pengcheng Xiong). Version 15dd7f19bc... HIVE-15084: Flaky test: TestMiniTezCliDriver:explainanalyze_1, 2, 3, 4, 5 (Pengcheng Xiong) of ql/src/test/queries/clientpositive/explainanalyze_1.q left in tree.
error: could not apply 15dd7f19bc... HIVE-15084: Flaky test: TestMiniTezCliDriver:explainanalyze_1, 2, 3, 4, 5 (Pengcheng Xiong)
L’output ci-dessus a été raccourci mais le point important à retenir est que nous rencontrons des conflits. La commande git cherry-pick
dans cette situation essaye de faire un git merge
du commit que nous ciblons vers notre branche actuelle. L’intérêt du cherry-pick dans cette situation est que nous pouvons forcer le processus pour faire en sorte que notre fichier local tel qu’il est dans le commit que nous ciblons.
Pour cela, nous pouvons ajouter le paramètre --strategy-option theirs
à la commande de cherry-pick. Celle-ci va corriger la plupart des confits mais nous pouvons encore en rencontrer du type :
Unmerged paths:
(use "git add/rm ..." as appropriate to mark resolution)
deleted by us: llap-server/src/java/org/apache/hadoop/hive/llap/io/encoded/SerDeEncodedDataReader.java
added by them: llap-server/src/java/org/apache/hadoop/hive/llap/io/encoded/VectorDeserializeOrcWriter.java
Ces-derniers devront être gérés manuelement avec git add
ou git rm
selon la situation.
Avec la méthode du cherry-pick et la résolution manuelle des conflits pour certains patchs, nous avons été en mesure d’importer les 120 patchs JIRA que nous avons ciblé en début d’article.
Remarque : Nous avons essayé d’automatiser le processus au maximum mais il est important de noter qu’il est fortement recommandé d’exécuter les tests unitaires avant de compiler et de déployer les patchs sur un cluster de production. Nous allons aborder ce sujet dans la prochaine partie.
git checkout rel/release-2.1.0
while read line; do
commit=$(echo $line | awk -F "\"*;\"*" '{print $2}')
if git cherry-pick -x $commit --strategy-option theirs > patches/logs/cherry_pick_$commit.out 2>&1; then
echo "Cherry picking $commit SUCCEEDED"
else
git status > patches/logs/cherry_pick_$commit.out 2>&1
git status | sed -n 's/.* by .*://p' | xargs git add
git -c core.editor=true cherry-pick --continue
echo "Cherry picking $commit SUCEEDED with complications"
fi
done < sorted_commits_to_apply.csv
Avec le script ci-dessus, nous pouvons appliquer 100% des patchs. Dans la section suivante, nous allons voir comment compiler et tester notre distribution de Hive.
Compilation
Avant de lancer les tests unitaires, essayons déjà de faire un build de notre release Hive avec la commande suivante :
mvn clean package -Pdist -DskipTests
Après quelques minutes, le build s’interrompt avec l’erreur suivante :
[ERROR] /home/leo/Apache/hive/storage-api/src/test/org/apache/hadoop/hive/ql/exec/vector/TestStructColumnVector.java:[106,5] cannot find symbol
symbol: class Timestamp
location: class org.apache.hadoop.hive.ql.exec.vector.TestStructColumnVector
Si on regarde notre version du fichier ./storage-api/src/test/org/apache/hadoop/hive/ql/exec/vector/TestStructColumnVector.java
, la classe java.sql.Timestamp
n’est en effet pas importée.
Ce fichier a été modifié par le cherry-pick du commit de HIVE-16245.
Ce fix modifie 3 fichiers :
ql/src/java/org/apache/hadoop/hive/ql/optimizer/physical/Vectorizer.java
storage-api/src/java/org/apache/hadoop/hive/ql/exec/vector/VectorizedRowBatch.java
storage-api/src/test/org/apache/hadoop/hive/ql/exec/vector/TestStructColumnVector.java
Comment Hortonworks s’y est pris pour appliquer ce patch tout en étant capable de compiler sans erreurs ? Dans la prochaine section, nous allons explorer le repository Hive public d’Hortonworks pour comprendre comment les patchs listés pour HDP 2.6.0.0 ont été appliqués.
Exploration du repo hive2-release de Hortonworks
Sur Github, Hortonworks (maintenant Cloudera) maintient un repository public pour chaque composant de la stack HDP. Celui qui nous intéresse est hive2-release :
git clone http://web.archive.org/web/20200906030301/https://github.com/hortonworks/hive2-release/.git
cd hive2-release && ls
README.md
De prime abord, le repository semble vide mais les fichiers peuvent être trouvés en faisant le checkout d’un tag. Il y a un tag par release d’HDP :
git checkout HDP-2.6.0.3-8-tag
Essayons de voir comment Cloudera a appliqué le patch qui a provoqué notre erreur de build précédente :
git log --pretty=format:'%at;%H;%s' | grep HIVE-16245
1489790314;2a9150604edfbc2f44eb57104b4be6ac3ed084c7;BUG-77383 backport HIVE-16245: Vectorization: Does not handle non-column key expressions in MERGEPARTIAL mode
Maintenant, regardons quels fichiers ont été modifiés par ce commit :
git diff 2a9150604edfbc2f44eb57104b4be6ac3ed084c7^ | grep diff
diff --git a/ql/src/java/org/apache/hadoop/hive/ql/optimizer/physical/Vectorizer.java b/ql/src/java/org/apache/hadoop/hive/ql/optimizer/physical/Vectorizer.java
diff --git a/storage-api/src/java/org/apache/hadoop/hive/ql/exec/vector/VectorizedRowBatch.java b/storage-api/src/java/org/apache/hadoop/hive/ql/exec/vector/VectorizedRowBatch.java
Contrairement à ce qui est dans le patch (et ce qui a été push sur la branche master d’Apache Hive), le backport effectué par Cloudera modifie seulement 2 fichiers sur 3. Le procédé semble donc être manuel et non documenté. Les fichiers ayant menés à l’erreur lors du build n’ont pas été modifiés ici.
Conclusion
Le backporting d’une liste de patchs sur un projet open source tel que Apache Hive est difficile à automatiser. Avec la distribution HDP et selon le type de patch, Hortonworks a appliqué des modifications manuelles sur le code source ou bien a travaillé sur une version “forkée” de hive. Une bonne compréhension de la structure globale du project (modules, test unitaires, etc.) et de son écosystème (dépendances) est également nécéssaire pour pouvoir réaliser un build testé et fonctionnel.