Monday, August 31, 2015

Multiple Output Format - Hadoop

Friends,

Hadoop have many output formats  such as :

1) TextOutput Format
2) Sequence File Output Format
3) MapFile Output Format
4) Multiple Output Format

Out of all the available formats, we will talk about Multiple Output formats. I will implement a simple example to demonstrate how it works.

In this example, we will use a sample data which is used for traffic accidents analysis.
We want to calculate following :
1)   Number of accidents per weather condition
2)   Number of accidents per  light condition.

The sample data looks like :

AccidentId, Weather_Condition_Id,Light_Condition_Id
1,3,5,
2,4,3
3,5,6

Explanation of sample data :
Here accident id =1 occurred due to Weather Condition = 3 and Light Condition =5 and so on.

How do we make sure that reducer creates 2 files , one for weather condition and another for Light condition.

I am splitting my csv file on "," and in my scenario 25th column in row is weather condition id and 24th column is Light condition id .

In map method , before writing intermediate key value pairs , I am making sure to attach characters "W" and "L" to distinguish between 2 different columns .

public static class AccidentWeatherMapper extends
            Mapper<LongWritable, Text, Text, IntWritable> {

        @Override
        protected void map(LongWritable key, Text value, Context context)
                throws java.io.IOException, InterruptedException {

            if (value.toString().contains(",")) {
                String[] array = value.toString().split(",");

                context.write(new Text("W" + array[25]), new IntWritable(1));
                context.write(new Text("L" + array[24]), new IntWritable(1));
            }

        };
    }

Now in reducer code, we will create  Multiple Output format in setup method. In reduce method , I used  the character that I attached earlier and based on it writing to 2 different files.

public static class AccidentWeatherReducer extends
            Reducer<Text, IntWritable, Text, IntWritable> {

        MultipleOutputs<Text, IntWritable> mos;

        @Override
        protected void setup(Context context) throws java.io.IOException,
                InterruptedException {
            mos = new MultipleOutputs<Text, IntWritable>(context);
        };

        @Override
        protected void reduce(Text key, Iterable<IntWritable> values,
                Context context) throws java.io.IOException,
                InterruptedException {
            int count = 0;
            for (IntWritable x : values) {
                int val = Integer.parseInt(x.toString());
                count += val;
            }
            if (key.toString().contains("W")) {
                mos.write("Weather",
                        key.toString().substring(1, key.toString().length()),
                        new IntWritable(count));
            } else if (key.toString().contains("L")) {
                mos.write("Light",
                        key.toString().substring(1, key.toString().length()),
                        new IntWritable(count));
            }
        };

        @Override
        protected void cleanup(Context context) throws java.io.IOException,
                InterruptedException {
            mos.close();
        };
    }

Finally, in the driver code, we will configure the naming conventions of output files and output format class for key and value .

public int run(String[] args) throws Exception {
        // TODO Auto-generated method stub
        Configuration conf = getConf();

        Job job = new Job(conf, "MI Demo");

        job.setJarByClass(AnalysisDemo.class);

        job.setMapperClass(AccidentWeatherMapper.class);

        job.setReducerClass(AccidentWeatherReducer.class);


        job.setInputFormatClass(TextInputFormat.class);


        job.setMapOutputKeyClass(Text.class);

        job.setMapOutputValueClass(IntWritable.class);

//  Here set the name of file and key/value types
        MultipleOutputs.addNamedOutput(job, "Weather", TextOutputFormat.class,
                Text.class, IntWritable.class);

        MultipleOutputs.addNamedOutput(job, "Light", TextOutputFormat.class,
                Text.class, IntWritable.class);

        Path in = new Path(args[0]);

        Path out = new Path(args[1]);

        FileInputFormat.setInputPaths(job, in);

        FileOutputFormat.setOutputPath(job, out);

        System.exit(job.waitForCompletion(true) ? 0 : 1);

        return 0;
    }

And that's it. Now you can use a command like this to see data in 2 different files.
 
hadoop jar /home/Desktop/Projects/Accident/AADemo.jar AnalysisDemo /Accident_Project/Input/Accidents.csv /Accident_Project/Output/Out12


Hope you enjoyed the quick demo of the Multiple Output Format.

Happy Hadooping !!!

Varun

Thursday, August 27, 2015

User Defined Functions ( UDFs) - PIG

Hi friends ,

Today I wanted to give a simple demo of some of the UDFs available in PIG. We will try to understand few UDFs with the help of some code . Let's quickly jump on 1st UDF:

Note: I have read and tried to understand the concepts from Hadoop - The definitive Guide. If you have time , please go through this book. You won't regret it.

Let's Suppose you have data like this :
1;"nyc, new york, usa";NULL
2;"stockton, california, usa";18
3;"moscow, yukon territory, russia";NULL
4;"porto, v.n.gaia, portugal";17
5;"farnborough, hants, united kingdom";NULL
6;"santa monica, california, usa";61

And you want to run a query which looks like :

Select * from table where Id > 2;
In PIG you will use following commands :
A = Load '/homeDesktop/Projects/Book/UserTest.csv' USING  PigStorage(';') 
>> AS (Id:int,Location:chararray,Age:chararray);                                      


 C = FILTER A BY Id >2;                                                             


 Dump C;


And here is the output :
(3,"moscow, yukon territory, russia",NULL)
(4,"porto, v.n.gaia, portugal",17)
(5,"farnborough, hants, united kingdom",NULL)
(6,"santa monica, california, usa",61)


This is Simple. Now consider this filter (Id > 2) is used many times. We can always write a simple UDF to do so.

1) Filter UDF
Create a java project to write MR code. Here name of function is the name of class that we will use in Pig Command. This class will extend FilterFunc.

Note: Filter UDFs are all subclasses of FilterFunc, which itself is a subclass of EvalFunc
public class FilterById extends FilterFunc {

// We will override exec method

// It will accept a tuple from which we will get object

// This object is used for creating filter clause that we used earlier


    @Override
    public Boolean exec(Tuple tuple) throws IOException {
        // TODO Auto-generated method stub

        if (tuple == null || tuple.size() == 0) {
            return false;
        }
        try {
            Object object = tuple.get(0);
            if (object == null) {
                return false;
            }
            int i = (Integer) object;
            return i >= 2;
        } catch (ExecException e) {
            // TODO: handle exception
            throw new IOException(e);
        }
        // return null;
    }





Once this is done , create the jar and register it using following command.
 REGISTER /home/edureka/Desktop/Projects/PigDemo/PDemo1.jar;   
 

Again , you can try following commands :
A = Load '/home/Desktop/Projects/Book/UserTest.csv' USING  PigStorage(';') 
>> AS (Id:int,Location:chararray,Age:chararray);                                     



 REGISTER /home/edureka/Desktop/Projects/PigDemo/PDemo1.jar;  

                     
 C = FILTER A BY FilterById(Id);                                                    
 Dump C; 



And here is the output :
(3,"moscow, yukon territory, russia",NULL)
(4,"porto, v.n.gaia, portugal",17)
(5,"farnborough, hants, united kingdom",NULL)
(6,"santa monica, california, usa",61)
  2) Now let's say you want to add some string to location column. We can use EvalFunc to write this UDF.
public class AddString extends EvalFunc<String> {

 @Override
 public String exec(Tuple tuple) throws IOException {
  // TODO Auto-generated method stub
  if (tuple == null || tuple.size() == 0) {
   return null;
  }
  try {
   Object object = tuple.get(0);
   if (object == null) {
    return null;
   }
   return ((String) object) + "- Added String";
  } catch (ExecException e) {
   throw new IOException(e);
  }
 }

}
  and then use following commands .
grunt> A = Load '/home/Desktop/Projects/Book/UserTest.csv' USING  PigStorage(';') 
>> AS (Id:int,Location:chararray,Age:chararray);                                      
grunt> REGISTER /home/Desktop/Projects/PigDemo/PDemo1.jar; 
C = FOREACH A GENERATE Id,AddString(Location),Age; 
Dump C;
 
 And this is the output.
(1,"nyc, new york, usa"- Added String,NULL)
(2,"stockton, california, usa"- Added String,18)
(3,"moscow, yukon territory, russia"- Added String,NULL)
(4,"porto, v.n.gaia, portugal"- Added String,17)
(5,"farnborough, hants, united kingdom"- Added String,NULL)
(6,"santa monica, california, usa"- Added String,61)
 

Wednesday, August 26, 2015

How to use MultipleInputFormat in Map Reduce

Hi Friends ,
Today I wanted to give a demo on MultipleInputFormat.In an ideal world we expect our data to come from one source and that too in particular format. for example - if the data exists in  csv file , we expect it to be comma separated and complete data in 1 file . That makes life easy ... isn't it ?

But what if the data exists in multiple different formats and coming from multiple different sources . Lets try to understand this with an example.

Lets say we need to run the following query :

Select name, count(*) from user .
This is a simple query if we need to run this on database but what if the data exists in 2 different formats and 2 different files.
1) CSV Format

Id,Name,Gender
1,Krishna,M
2,Tina,F
3,Umesh,M
4,Nakeeran,F
5,Varun,M
6,Varun,M


2) Tab Format

Id    Name    Gender
7    Tom     M
8    Harry    F
9    Paul    M
10    Nakeeran    F
11    Gopi    M
12    Varun    M



If we want to get the output similar to above written query's output using Mapreduce , we need to look into the different input formats  provided by hadoop.

Lets break the problem in small parts.
1) read the csv file -> we need to create a mapper for this
InputFormat-> TextInputFormat

public static class MiCsvMapper extends
            Mapper<LongWritable, Text, Text, IntWritable> {

        @Override
        protected void map(LongWritable key, Text value, Context context)
                throws java.io.IOException, InterruptedException {
            if (value.toString() != "" && value.toString().contains(",")) {
                String[] array = value.toString().split(",");

                String userInfo = array[0] + "," + array[1] + "," + array[2];

                context.write(new Text(array[1]), new IntWritable(1));

            }
        };
    }





2) read the tab file -> we need to create a mapper for this
InputFormat-> KeyValueTextInputFormat

public static class MiTabMapper extends
            Mapper<Text, Text, Text, IntWritable> {
        @Override
        protected void map(Text key, Text value, Context context)
                throws java.io.IOException, InterruptedException {
            if (value.toString() != "" && value.toString().contains("\t")) {
                String[] array = value.toString().split("\t");
                context.write(new Text(array[0]), new IntWritable(1));
            }
        }
    }





3) Now a simple reducer as if we have only 1 file. Reducer do not care about different sources as long as its getting data in desired format that it can process.

public static class MiReducer extends
            Reducer<Text, IntWritable, Text, IntWritable> {

        @Override
        protected void reduce(Text key, java.lang.Iterable<IntWritable> values,
                Context context) throws java.io.IOException,
                InterruptedException {

            int count = 0;

            for (IntWritable x : values) {
                int val = Integer.parseInt(x.toString());
                count += val;
            }

            context.write(key, new IntWritable(count));

        };
    }





4) But how do we tell Hadoop that we have 2 different formats of input file.
let's take  a look at Driver code.
 MultipleInputs.addInputPath method will take care of multiple files by passing the 4 parameters :
a) job Object
b) Input File path
c) Input    File Format
d) Mapper class

public int run(String[] args) throws Exception {
        // TODO Auto-generated method stub
        Configuration conf = getConf();

        Job job = new Job(conf, "MI Demo");

        job.setJarByClass(MIDemo.class);


        MultipleInputs.addInputPath(job, new Path(args[0]),
                TextInputFormat.class, MiCsvMapper.class);

        MultipleInputs.addInputPath(job, new Path(args[1]),
                KeyValueTextInputFormat.class, MiTabMapper.class);

        job.setReducerClass(MiReducer.class);


        job.setOutputFormatClass(TextOutputFormat.class);

        job.setOutputKeyClass(Text.class);

        job.setOutputValueClass(IntWritable.class);

        // Path in = new Path(args[0]);

        Path out = new Path(args[2]);

        // FileInputFormat.addInputPath(job, in);

        FileOutputFormat.setOutputPath(job, out);

        System.exit(job.waitForCompletion(true) ? 0 : 1);

        return 0;
    }




5) Now you can run the code with following command. Notice 2 input files and 1 output file.

hadoop jar /home/Desktop/Projects/MultipleInputDemo/MDemo.jar  MIDemo /MultiDemoInput/Users.csv /MultiDemoInput/UsersTab  /MultiDemoOutput/out2

6) Finally the output looks like :
Gopi    1
Harry    1
Krishna    1
Nakeeran    2
Name    2
Paul    1
Tina    1
Tom     1
Umesh    1
Varun    3
 
And that is how you can use MultipleInputFormat . Hope you understood the concept of MultipleInputFormat.

Have a great day...
Happy Hadooping !!!

Varun

Monday, August 24, 2015

TextInputFormat Vs KeyValueTextInputFormat

Lets try to understand the difference with the help of an example. Suppose we have following data .
Id,Name,Gender

1,Krishna,M
2,Tina,F
3,Umesh,M
4,Nakeeran,F
5,Varun,M

Now lets write Mapreduce code to print the input as output using TextInputFormat .

By default TextInputFormat have the key value pair in this format:

Key -> LongWritable
Value -> Text

Here is the mapper code.

Please note here my K1-> LongWritable line offset
                                 V1-> Text
We don't need any reducers and we will print all data as K2->Text and V2-> Text

public static class SMapperDemo extends
            Mapper<LongWritable, Text, Text, Text> {

        @Override
        protected void map(LongWritable key, Text value, Context context)
                throws java.io.IOException, InterruptedException {
            if (value.toString() != "" && value.toString().contains(",")) {
                String[] array = value.toString().split(",");

                String userInfo = array[0] + "," + array[1] + "," + array[2];

                context.write(new Text(key.toString()), new Text(userInfo));
            }
        };
    }



The driver code is simple as well.

@Override
    public int run(String[] args) throws Exception {
        // TODO Auto-generated method stub

        Configuration conf = getConf();

        

        Job job = new Job(conf, "Sql Demo");

        job.setJarByClass(SDemo.class);

        job.setMapperClass(SMapperDemo.class);

        job.setNumReduceTasks(0);

        job.setInputFormatClass(TextInputFormat.class);

        job.setOutputFormatClass(TextOutputFormat.class);

        job.setOutputKeyClass(Text.class);

        job.setOutputValueClass(Text.class);

        Path in = new Path(args[0]);

        Path out = new Path(args[1]);

        FileInputFormat.addInputPath(job, in);

        FileOutputFormat.setOutputPath(job, out);

        System.exit(job.waitForCompletion(true) ? 0 : 1);

        return 0;
    }



On running this code, it will print line offset as Key and Line itself as value. The output looks like :

0    Id,Name,Gender
15    1,Krishna,M
27    2,Tina,F
36    3,Umesh,M
46    4,Nakeeran,F
59    5,Varun,M


Now lets write Mapreduce code to print the input as output using KeyValueTextInputFormat . By default KeyValueTextInputFormat is tab separated  and its K1 and V1 is of type Text.

This is the driver code. I have done 2 changes here. Changed the default line separator to "," and set the input format as KeyValueTextInputFormat.

public int run(String[] args) throws Exception {
        // TODO Auto-generated method stub

        Configuration conf = getConf();

        conf.set("key.value.separator.in.input.line", ",");

        Job job = new Job(conf, "Sql Demo");

        job.setJarByClass(SDemo.class);

        job.setMapperClass(SMapperDemo.class);

        job.setNumReduceTasks(0);

        job.setInputFormatClass(KeyValueTextInputFormat.class);

        job.setOutputFormatClass(TextOutputFormat.class);

        job.setOutputKeyClass(Text.class);

        job.setOutputValueClass(Text.class);

        Path in = new Path(args[0]);

        Path out = new Path(args[1]);

        FileInputFormat.addInputPath(job, in);

        FileOutputFormat.setOutputPath(job, out);

        System.exit(job.waitForCompletion(true) ? 0 : 1);

        return 0;
    }


The mapper code is reading the input file . Since the separator is ",", the key will be the text before 1st comma and the value will be remaining line. ex:
Input Line -> 1,Krishna,M
Output Key -> 1
Output Value -> Krishna,M

@Override
        protected void map(Text key, Text value, Context context)
                throws java.io.IOException, InterruptedException {
            if (value.toString() != "" && value.toString().contains(",")) {
                String[] array = value.toString().split(",");




// array[2] will give index out of bound error since the value only has remaining // part of line after 1st comma 

                String userInfo = array[0] + "," + array[1];// + "," + array[2];

                context.write(new Text(key.toString()), new Text(userInfo));
            }
        };



And the output looks like :
Id    Name,Gender
1    Krishna,M
2    Tina,F
3    Umesh,M
4    Nakeeran,F
5    Varun,M


Here Id became key and remaining line became value.

Happy Hadooping!!

have a great day.

Varun

Pig Basics

Hey guys,

I just thought of writing few commands for some one who is beginner in PIG .
So let's get started. We will consider the data in  file in this format.

year,product,quantity
2000, iphone, 1000
2001, iphone, 1500
2002, iphone, 2000
2000, nokia,  1200
2001, nokia,  1500
2002, nokia,  900

1)  Load Command
A = LOAD '/home/Desktop/Projects/Book/UserTest' USING PigStorage(',') AS (year:chararray,product:chararray,quantity:int);

2) If you want to see the records.
Dump A;

3) To check the schema.
Describe A;

4) Filter :
B = Filter A BY year == '2000';';

5) group :

C = Group A BY year;
This command will give Tuples.

(2000,{(2000, nokia,  1200),(2000, iphone, 1000)})
(2001,{(2001, nokia,  1500),(2001, iphone, 1500 )})
(2002,{(2002, nokia,  900),(2002, iphone, 2000)})


 // here 1st field Key on which data is grouped and
// 2nd field is Bag of Tuples

You can also try this :

describe C;

C: {group: chararray, A: {(year: chararray,product: chararray,quantity: chararray)}}

Here "group" is the alias given to grouping field by Pig.
A is the data on which grouping is done. 

6)  max_quant = foreach C GENERATE group, MAX (A.quantity);

Dump max_quant;
(2000,1200)
(2001,1500)
(2002,2000)










7) ILLUSTRATE: ILLUSTRATE A; // TO GET IDEA OF DATA AND QUERY 

----------------------------------------------------------------------------
| A     | year:chararray    | product:chararray    | quantity:int    |
----------------------------------------------------------------------------
|       | 2001              |  iphone              |  1500                 |
----------------------------------------------------------------------------



That's it for the day. I think these commands are good enough to get anyone started with PIG..

Thanks
Varun


Sunday, August 23, 2015

Replicated Join Demo

Problem : How to do replicated join in Hadoop ?

Recently I learnt a new concept in Hadoop . By definition, Replicated Join means when one data set is too big and other data set is small enough that can be stored in memory , we can use Replicated Joins. Using this method, the join can be performed in the map side.

Here is the example of replicated join using Distributed Cache . I am using very simple xml files as my input data . Consider User data as small data that we will store in memory .


User Data

User Data
 












Comments Data










Please refer  link to create XmlInputFormat class . This is useful for reading XML data.
Now we can write Mapper Code. In the mapper's setup method, we are reading User xml file and storing in HashMap object.


public static class ReplicatedJoinMapper extends

   Mapper<LongWritable, Text, Text, Text> {

  private static final Log Logger = LogFactory

    .getLog(ReplicatedJoinMapper.class);

  private final String Empty_String = "null";


  private String joinType = null;

  private final HashMap<String, String> userInfo = new HashMap<String, String>();


  @Override

  protected void setup(Context context) throws java.io.IOException,

    InterruptedException {



   Path[] files = DistributedCache.getLocalCacheFiles(context

     .getConfiguration());



   if (files != null && files.length > 0) {

    try {

     if (files[0].getName().equals("User")) {

      // InputStream is = new

      // FileInputStream(files[0].toString());

      File is = new File(files[0].toString());


      DocumentBuilderFactory dbFactory = DocumentBuilderFactory

        .newInstance();


      DocumentBuilder dBuilder = dbFactory

        .newDocumentBuilder();

      Logger.info("-------------------------->2 = ----------->");

      Document doc = dBuilder.parse(is.toString().replaceAll(

        "[^\\x20-\\x7e\\x0A]", ""));



      doc.getDocumentElement().normalize();


      NodeList nList = doc.getElementsByTagName("employee");


      for (int temp = 0; temp < nList.getLength(); temp++) {


       Node nNode = nList.item(temp);


       if (nNode.getNodeType() == Node.ELEMENT_NODE) {


        Element eElement = (Element) nNode;


        String id = eElement.getElementsByTagName("id")

          .item(0).getTextContent();

      


        String name = eElement

          .getElementsByTagName("name").item(0)

          .getTextContent();

   

        String gender = eElement

          .getElementsByTagName("gender").item(0)

          .getTextContent();

   


        userInfo.put(id, id + "," + name + "," + gender);

        joinType = "inner";

       }

      }

     }


    } catch (Exception e) {

     // TODO: handle exception

     Logger.error(e.getMessage());

    }


   } else {

    Logger.info("-------------------------->file not found----------->");

   }

  }





// Here in map method we will read Comments XML file data


  @Override

  protected void map(LongWritable key, Text value, Context context)

    throws IOException, InterruptedException {

   try {


    InputStream is = new ByteArrayInputStream(value.toString()

      .getBytes());

    DocumentBuilderFactory dbFactory = DocumentBuilderFactory

      .newInstance();

    DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();

    Document doc = dBuilder.parse(is);


    doc.getDocumentElement().normalize();


    NodeList nList = doc.getElementsByTagName("employee");


    for (int temp = 0; temp < nList.getLength(); temp++) {


     Node nNode = nList.item(temp);


     if (nNode.getNodeType() == Node.ELEMENT_NODE) {


      Element eElement = (Element) nNode;


      String id = eElement.getElementsByTagName("id").item(0)

        .getTextContent();

     


      String comment = eElement

        .getElementsByTagName("comment").item(0)

        .getTextContent();

    

      String userInfoLine = userInfo.get(id);

  

      if (userInfoLine != null) {


// Inner Join

       Logger.info("--------------------------> writing context----------->");

       context.write(new Text(id), new Text(userInfoLine

         + "," + comment));

      } else {


// Outer join

       context.write(new Text(id), new Text(Empty_String));

      }

     }

    }


   } catch (Exception e) {

    // TODO: handle exception

    Logger.error(e.getCause());

   }

  };

 }





Finally we can write Driver code. Here we are using DistributedCache.addCacheFile method to provide User xml to each map tasks. The same file is read in set up method above.





@Override

 public int run(String[] args) throws Exception {


  final Log Logger = LogFactory.getLog(RJDemo.class);


  Configuration conf = getConf();


  String[] arg = new GenericOptionsParser(conf, args).getRemainingArgs();


  


// These tags are in my xml file


  conf.set("START_TAG_KEY", "<employee>");

  conf.set("END_TAG_KEY", "</employee>");





  Job job = new Job(conf, "SO DEMO");


   try {

   DistributedCache.addCacheFile(new URI("/SODemoInput/User"),

   job.getConfiguration());

   } catch (Exception e) {

   e.printStackTrace();

   System.out.println(e);

   }

  job.setJarByClass(RJDemo.class);


  job.setMapperClass(ReplicatedJoinMapper.class);


  job.setNumReduceTasks(0);


  job.setInputFormatClass(XmlInputFormat.class);


  job.setOutputFormatClass(TextOutputFormat.class);


  // job.setOutputKeyClass(Text.class);


  job.setOutputValueClass(Text.class);


  Path in = new Path(args[0]);


  Path out = new Path(args[1]);

  out.getFileSystem(conf).delete(out);


  FileInputFormat.addInputPath(job, in);


  FileOutputFormat.setOutputPath(job, out);


  System.exit(job.waitForCompletion(true) ? 0 : 1);


  return 0;

 }





Finally run this command to see output as:

hadoop jar  /home/Desktop/Projects/ReplicatedJoinDemo/RJDemo.jar RJDemo  /SODemoInput/Comments  /SODemoOutput/Out32



And the output looks like :










And that's it :)

 Have a good day ..
Happy Hadooping !!