Thursday, March 13, 2014

Parquet "java.lang.NoClassDefFoundError: org/apache/thrift/TEnum"

If you encounter this problem using Cloudera parcels, here is the solution according to this page
org.apache.hadoop.mapred.YarnChild: Error running child : java.lang.NoClassDefFoundError: org/apache/thrift/TEnum
 at java.lang.ClassLoader.defineClass1(Native Method)
 at java.lang.ClassLoader.defineClass(ClassLoader.java:792)
 at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
 at java.net.URLClassLoader.defineClass(URLClassLoader.java:449)
 at java.net.URLClassLoader.access$100(URLClassLoader.java:71)
 at java.net.URLClassLoader$1.run(URLClassLoader.java:361)
 at java.net.URLClassLoader$1.run(URLClassLoader.java:355)
 at java.security.AccessController.doPrivileged(Native Method)
 at java.net.URLClassLoader.findClass(URLClassLoader.java:354)
 at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
 at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308)
 at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
 at parquet.hadoop.metadata.CompressionCodecName.(CompressionCodecName.java:21)
 at parquet.hadoop.ParquetOutputFormat.getCodec(ParquetOutputFormat.java:217)
 at parquet.hadoop.ParquetOutputFormat.getRecordWriter(ParquetOutputFormat.java:254)
 at org.apache.pig.backend.hadoop.executionengine.mapReduceLayer.PigOutputFormat.getRecordWriter(PigOutputFormat.java:84)
 at org.apache.hadoop.mapred.ReduceTask$NewTrackingRecordWriter.(ReduceTask.java:562)
 at org.apache.hadoop.mapred.ReduceTask.runNewReducer(ReduceTask.java:636)
 at org.apache.hadoop.mapred.ReduceTask.run(ReduceTask.java:404)
 at org.apache.hadoop.mapred.YarnChild$2.run(YarnChild.java:160)
 at java.security.AccessController.doPrivileged(Native Method)
 at javax.security.auth.Subject.doAs(Subject.java:415)
 at org.apache.hadoop.security.UserGroupInformation.doAs(UserGroupInformation.java:1408)
 at org.apache.hadoop.mapred.YarnChild.main(YarnChild.java:155)
Caused by: java.lang.ClassNotFoundException: org.apache.thrift.TEnum
 at java.net.URLClassLoader$1.run(URLClassLoader.java:366)
 at java.net.URLClassLoader$1.run(URLClassLoader.java:355)
 at java.security.AccessController.doPrivileged(Native Method)
 at java.net.URLClassLoader.findClass(URLClassLoader.java:354)
 at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
 at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308)
 at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
 ... 24 more
$ cd /opt/cloudera/parcels/CDH/lib/parquet
$ ls original-parquet*.jar
# delete the jars
$ rm original-parquet*.jar
$ cd /opt/cloudera/parcels/CDH/lib/hadoop
# delete those symlinks
$ rm original-parquet*.jar

Thursday, February 27, 2014

Yarn MapReduce Log Level

When you write a Pig or Hive UDF, a debug log may be very useful. You don't have to ask your administrator for help. Setting property 'mapreduce.map.log.level' or 'mapreduce.reduce.log.level' to 'DEBUG', that is it.
set mapreduce.map.log.level 'DEBUG'

register '/home/bwang/workspace/pig-scratch/target/pig-scratch-0.0.1-SNAPSHOT.jar';

define asByteArray com.mycompany.pig.SaveObjectAsByteArray();

d_val = load '/tmp/double.txt' as (id: chararray, val: double);

dba_val = foreach d_val generate flatten(asByteArray(*)), 0 as pos;

g = group dba_val all;

dump g;

But this is not perfect in that you will see a lot of Hadoop DEBUG log information too when you check the syslog of a map task.

Can I just output the DEBUG log for my own class? log4j definitely supports that, but it is not straight forward in Yarn MapReduce job as you think.

If you read MRApps.addLog4jSystemProperties, you will find that the log4j.configuration is actually hard coded to 'container-log4j.properties', which is packed in hadoop-yarn-server-nodemanager-2.0.0-cdh4.5.0.jar.

I found a way to fool NodeManager to achieve setting the log level for my UDF class:

  • Find container-log4j.properties in maven dependencies.
  • Copy the content to a property file, e.g., as_byte_array.log4j.properties.
  • Add 'log4j.logger.com.mycompany.pig=${com.mycompany.pig.logger}' into as_byte_array.log4j.properties.
  • Build a package and make sure as_byte_array.log4j.properties in your udf jar.
  • Change the pig script like this:
    set mapreduce.map.log.level 'INFO,CLA -Dlog4j.configuration=as_byte_array.log4j.properties -Dcom.mycompany.pig.logger=DEBUG'
    
    This is totally a HACK. If you check 'ps -ef | grep mycompany' when the map task is running, you will see something like this:
    /usr/java/jdk1.7.0_25/bin/java -Djava.net.preferIPv4Stack=true -Dhadoop.metrics.log.level=WARN -Xmx825955249 -Djava.io.tmpdir=/yarn/nm/usercache/bwang/appcache/application_1393480846083_0011/container_1393480846083_0011_01_000002/tmp -Dlog4j.configuration=container-log4j.properties -Dyarn.app.mapreduce.container.log.dir=/var/log/hadoop-yarn/container/application_1393480846083_0011/container_1393480846083_0011_01_000002 -Dyarn.app.mapreduce.container.log.filesize=0 -Dhadoop.root.logger=INFO,CLA -Dlog4j.configuration=as_byte_array.log4j.properties -Dcom.mycompany.pig.logger=DEBUG,CLA org.apache.hadoop.mapred.YarnChild 127.0.0.1 60261 attempt_1393480846083_0011_m_000000_0 2
    
    Basically I inject a new log4j.configuration to point to my own log4j.properties, which overwrites container-log4j.properties because it appears behind it. And "-Dcom.mycompany.pig.logger=DEBUG" let me control the log level for my UDF.

Wednesday, January 22, 2014

start screen with different windows with different working directories

Create ~/.screenrc with the following lines

chdir $HOME/workspace/core-project
screen -t core
chdir $HOME/workspace/hadoop-project
screen -t hdp
When you run screen, there are two windows named "core" and "hdp", the working directory is "$HOME/workspace/core-project" in "core" window, and "$HOME/workspace/hadoop-project" in "hdp" window.

Using .screenrc means every screen session will have the same windows. If this is not the case, using alias and a customized screenrc file like this:

$ alias xxxscr='screen -D -R -S xxx -c ~/.xxx.screenrc'
$ xxxscr

You can quit and kill all windows using this key CTRL+A+|

Sunday, January 19, 2014

Maven Test

  • Don't run unit tests, run integration tests directly
    $ mvn test-compile failsafe:integration-test
    
  • Run a single integration test in multiple modules projects.
    $ mvn -am -pl :sub-module -Dit.test=MyIntegrationTest -DfailIfNoTests=false test-compile failsafe:integration-test
    
    • -am allows resolving dependencies using the working directory.
    • -pl :sub-module specifies the module
    • -DfailIfNoTests=false allows not failing in dependent modules.

Friday, December 20, 2013

Convert a bag of key-value pairs to map in Pig

Here is my input file: data.txt.
id_1    key_1  v_i1_a1
id_1    key_2  v_i1_a2
id_2    key_1  v_i2_a1
id_1    key_3  v_i1_a3
id_2    key_3  v_i2_a3
id_1    key_4  v_i1_a4
I want to get a map of key-value pairs for an ID like this:
(id_1, [key_1#v_i1_a1, key_2#v_i1_a2, key_3#v_i1_a3, key_4#v_i1_a4])
(id_2, [key_1#v_i2_a1, key_3#v_i2_a3])
Does this pig script work? TOMAP and BagToTuple are built-in UDFs.
data = load 'data.txt' as (id: chararray, key: chararray, value: chararray);

-- Group data by id
g_data = group data by id;
describe g_data;

-- Convert key-value pair bag to tuple, then convert to map
to_tuple = foreach g_data generate group, TOMAP(BagToTuple(data.(key, value)));
dump to_tuple;
describe to_tuple;
Unfortunately, the above script doesn't work. The output looks like this:
g_data: {group: chararray,data: {(id: chararray,key: chararray,value: chararray)
}}
(id_1,)
(id_2,)
to_tuple: {group: chararray,map[]}
What's wrong? Let us remove TOMAP and run it again
...
to_tuple = foreach g_data generate group, BagToTuple(data.(key, value)) as kv;
...
The results looks like this:
g_data: {group: chararray,data: {(id: chararray,key: chararray,value: chararray)
}}
(id_1,(key_4,v_i1_a4,key_3,v_i1_a3,key_2,v_i1_a2,key_1,v_i1_a1))
(id_2,(key_3,v_i2_a3,key_1,v_i2_a1))
to_tuple: {group: chararray,kv: (key: chararray,value: chararray)}
Did you find anything wrong? BagToTuple converts the bag to tuples correctly, but the schema of the tuple kv has only two fields. What about flatten the tuple, and use project-range? Unfortunately it still not working. Only one key-value pair in the map.
to_tuple = foreach g_data generate group, FLATTEN(BagToTuple(data.(key, value)));
...

to_map = foreach to_tuple generate group, TOMAP($1 ..);
dump to_map;
describe to_map;

g_data: {group: chararray,data: {(id: chararray,key: chararray,value: chararray)}}
(id_1,key_4,v_i1_a4,key_3,v_i1_a3,key_2,v_i1_a2,key_1,v_i1_a1)
(id_2,key_3,v_i2_a3,key_1,v_i2_a1)
to_tuple: {group: chararray,org.apache.pig.builtin.bagtotuple_8::key: chararray,
org.apache.pig.builtin.bagtotuple_8::value: chararray}
(id_1,[key_4#v_i1_a4])
(id_2,[key_3#v_i2_a3])
to_map: {group: chararray,map[]}

The problem is caused by BagToTuple, who sets the output schema to the tuple schema in the bag. This is not correct because the generated tuple's length is unknown. BagToTuple should let Pig to figure out the schema. The fix would be very simple: just find the file BagToTuple.java, and totally remove output schema method.

And you also need to resolve another issue related to TOMAP. Using TOMAP(BagToTuple(..)) won't work because the tuple will be passed as the first field. You have to FLATTEN the tuple and use position reference like this:

define MyBagToTuple my_package.BagToTuple();

data = load 'data.txt' as (id: chararray, key: chararray, value: chararray);

-- Group data by id
g_data = group data by id;
describe g_data;

-- Convert key-value pair bag to tuple, then convert to map
to_tuple = foreach g_data generate group, FLATTEN(MyBagToTuple(data.(key, value)));
dump to_tuple;
describe to_tuple;

to_map = foreach to_tuple generate $0 as id, TOMAP($1 ..) as kv;
dump to_map;
describe to_map;

g_data: {group: chararray,data: {(id: chararray,key: chararray,value: chararray)
}}
(id_1,key_4,v_i1_a4,key_3,v_i1_a3,key_2,v_i1_a2,key_1,v_i1_a1)
(id_2,key_3,v_i2_a3,key_1,v_i2_a1)
Schema for to_tuple unknown.
(id_1,[key_3#v_i1_a3,key_2#v_i1_a2,key_1#v_i1_a1,key_4#v_i1_a4])
(id_2,[key_3#v_i2_a3,key_1#v_i2_a1])
to_map: {id: bytearray,kv: map[]}

Wednesday, September 11, 2013

Deploy LZO for YARN in CDH4

This page Using the LZO Parcel is only for MRv1, not for YARN. It took me a while to figure out how to set up LZO in YARN correctly.

You may experience different error messages if you do not configure YARN correctly:

  • Class com.hadoop.compression.lzo.LzoCodec not found.
  • Class com.hadoop.mapred.DeprecatedLzoTextInputFormat not found.
  • No LZO codec found, cannot run.
  • native-lzo library not available

Here are the steps to setup LZO correctly:
  • You can follow the instruction in "Using the LZO Parcel" to install and activate the parcel
  • Add LzoCodec and LzopCodec. In cloudera manager, find the field for core-site.xml: hdfs1->Configuration -> Service-Wide -> Advanced -> Cluster-wide Configuration Safety Valve for core-site.xml. and add this property:
    
        io.compression.codecs
     org.apache.hadoop.io.compress.GzipCodec,org.apache.hadoop.io.compress.DefaultCodec,org.apache.hadoop.io.compress.BZip2Codec,com.hadoop.compression.lzo.LzoCodec,com.hadoop.compression.lzo.LzopCodec
    
    
  • Add classpath and native library. In Cloudera manager, find this field in mapred-site.xml: yarn1->Configuration->Service-Wide->Advanced->YARN Service MapReduce Configuration Safety Valve, then add the following two properties:
      
        mapreduce.application.classpath
        $HADOOP_MAPRED_HOME/share/hadoop/mapreduce/*,$HADOOP_MAPRED_HOME/share/hadoop/mapreduce/lib/*,/opt/cloudera/parcels/HADOOP_LZO/lib/hadoop/lib/*
      
      
        mapreduce.admin.user.env
        LD_LIBRARY_PATH=$HADOOP_COMMON_HOME/lib/native:/opt/cloudera/parcels/HADOOP_LZO/lib/hadoop/lib/native
      
    
  • Restart YARN and put the configuration files to gateways.
  • Don't forget to run "Deploy Client Configuration"

Sunday, September 1, 2013

Make Eclipse Font Smaller in Gnome/Xfce

There are a lot of posts on the Internet. Here are the one working for me:

  • To make the menu font smaller
    • create ~/.gtkrc-eclipse
      style "eclipse" {
        font_name = "Sans 9"
      }
      
      class "GtkWidget" style "eclipse"
    • If you want the buttons are more compact, please follow this page. I use like this, but have a problem the color of menu items on the menu bar is same as the background.
      style "eclipse" {
        font_name = "Sans 9"
      }
      
      class "GtkWidget" style "eclipse"
      
      style "compact-toolbar" {
          GtkToolbar::internal-padding = 0
          xthickness = 1
          ythickness = 1
      }
      
      style "compact-button" {
          xthickness = 0
          ythickness = 0
      }
      
      class "GtkToolbar" style "compact-toolbar"
      widget_class "*<GtkToolbar>*<GtkButton>" style "compact-button"
      
    • add this line into ~/.bashrc
      export GTK2_RC_FILES=/usr/share/themes/Clearlooks/gtk-2.0/gtkrc:$HOME/.gtkrc-eclipse 
      
  • To make the tab font smaller, such as "Package Explorer", you need to modify the css file (version # could be different).
    $ vi $ECLIPSE_HOME/plugins/org.eclipse.platform_4.2.2.v201302041200/css/e4_default_gtk.css
    
    .MPartStack {
            font-size: 9;
            swt-simple: false;
            swt-mru-visible: false;
    }
    
    You may try to make the height of tabs smaller as suggested in this page, but I found it didn't work well because the buttons like 'minimum' or 'maximum' become too small and large when the mouse is hovering.
    .MPartStack {
      font-size: 9;
      font-family: Liberation Sans;
      swt-tab-renderer: null;
      swt-tab-height: 22px;
      swt-selected-tabs-background: #FFFFFF #ECE9D8 100%;
      swt-simple: false;
      swt-mru-visible: false;
    }