Showing posts with label spark. Show all posts
Showing posts with label spark. Show all posts

Tuesday, September 20, 2016

Zeppelin and Spark: Merge Multiple CSVs into Parquet

Introduction

The purpose of this article is to demonstrate how to load multiple CSV files on an HDFS filesystem into a single Dataframe and write to Parquet.

Two approaches are demonstrated.  The first approach is not recommended, but is shown for completeness.


First Approach

One approach might be to define each path:
%pyspark

import locale
locale.setlocale(locale.LC_ALL, 'en_US')

p1 = "/data/output/followers/mitshu/ec2-52-39-251-219.us-west-2.compute.amazonaws.com/0-ec2-52-39-251-219.us-west-2.compute.amazonaws.com/twitterFollowers.csv"
p2 = "/data/output/followers/mitshu/ec2-52-42-100-207.us-west-2.compute.amazonaws.com/0-ec2-52-42-100-207.us-west-2.compute.amazonaws.com/twitterFollowers.csv"
p3 = "/data/output/followers/mitshu/ec2-52-42-198-4.us-west-2.compute.amazonaws.com/0-ec2-52-42-198-4.us-west-2.compute.amazonaws.com/twitterFollowers.csv"
p4 = "/data/output/followers/mitshu/ec2-54-70-37-224.us-west-2.compute.amazonaws.com/0-ec2-54-70-37-224.us-west-2.compute.amazonaws.com/twitterFollowers.csv"

and then open each CSV at that path as an RDD and transform to a dataframe:
%pyspark

rdd_m1 = sc.textFile(p1)
print rdd_m1.take(5)

df_m1 = rdd_m1.\
    map(lambda x: x.split("\t")).\
    filter(lambda x: len(x) == 6). \
    map(lambda x: {
        'id':x[0],
        'profile_id':x[1],
        'profile_name':x[2],
        'follower_id':x[3],
        'follwer_name':x[4],
        'unknown':x[5]})\
    .toDF()
df_m1.limit(5).show()
df_m1.registerTempTable("df_m1")
This would need to be repeated for each dataframe.

The dataframes could then be merged using the unionAll operator.
%pyspark
import pandas as pd

df = df_m1.unionAll(df_m2).unionAll(df_m3).unionAll(df_m4)

print "DF 1: {0}".format(df_m1.count())
print "DF 2: {0}".format(df_m2.count())
print "DF 3: {0}".format(df_m3.count())
print "DF 4: {0}".format(df_m4.count())
print "Merged Dataframe: {0}".format(df.count())


and finally written to parquet.
%pyspark

df.write.parquet("/data/output/followers/mitshu/joined.prq")


Easier Approach

Notice the convenient way of reading multiple CSV in nested directories into a single RDD:
%pyspark

path="/data/output/followers/mitshu/*/*/*.csv"
rdd = sc.textFile(path)
print "count = {}".format(rdd.count())
This is clearly better than defining each path individually.


There are multiple ways to transform RDDs into Dataframes (DFs):
%pyspark

def to_json(r):
    j = {}
    t = r.split("\t")
    j['num_followers'] = t[0]
    j['followed_userid'] = t[1]
    j['followed_handle'] = t[2]
    j['follower_userid'] = t[3]
    j['follower_handle'] = t[4]
    return j
    
df = rdd.map(to_json).toDF()
print "count = {}".format(df.count())
df.show()
This is not necessarily superior to the first approach; but it is an alternative to consider.

 

Load from Parquet

For subsequent analysis, load from Parquet using this code:
%pyspark

df = sqlContext.read.parquet("/data/output/followers/mitshu/joined.prq")
df.limit(5).show()

 

References

  1. [Blogger] Writing to Parquet

Tuesday, July 12, 2016

Zeppelin and Spark: Transforming a CSV to Parquet

Transform a CSV file to Parquet Format

Apache Parquet is a columnar storage format available to any project in the Hadoop ecosystem.  Parquet is built to support very efficient compression and encoding schemes.  Twitter is starting to convert some of its major data source to Parquet in order to take advantage of the compression and deserialization savings.

CSV to RDD

Load the CSV file into an RDD
%pyspark

rdd = sc.textFile("/path/to/input/myfile.csv")
print rdd.take(5)

and the output looks like this:
[u'19999174,Logicknot,Cameron MacKinnon,0,Male, , , ', u'433249647,LogieTatjana,Tatjana Logie \u2653,0,Female, , , ', u'1346426538,Logistic_soares,Luciana Soares,0,Female, , , ', u'18355981,Loh69,Laurent Grad,0,Male, , , ', u'2976559335,LohMarta,Marta Loh,0,Female, , , ']


RDD to DF

Now we need to convert the RDD into a Dataframe (DF):
%pyspark

df = rdd.\
    map(lambda x: x.split(",")).\
    filter(lambda x: len(x) == 8). \
    map(lambda x: {
        'userid':x[0],
        'twitterhandle':x[1],
        'full_name':x[2],
        'country':x[3],
        'gender':x[4],
        'age':x[5],
        'age_min':x[6],
        'age_max':x[7]})\
    .toDF()
df.limit(5).show()

The bad news is that this code might look complicated. The good news is that the code is a lot simpler than it appears.

The first map condition splits each record on the delimiter (a comma).  Now we're dealing with a list of 8 tokens per record.  The second condition (the filter) will reject any line that does not have 8 tokens.  The third, and final, map condition will take each token in the list and create a heading for it.

The output looks like this:
+---+-------+-------+-------+-----------------+------+---------------+----------+
|age|age_max|age_min|country|        full_name|gender|  twitterhandle|    userid|
+---+-------+-------+-------+-----------------+------+---------------+----------+
|   |       |       |      0|Cameron MacKinnon|  Male|      Logicknot|  19999174|
|   |       |       |      0|  Tatjana Logie ♓|Female|   LogieTatjana| 433249647|
|   |       |       |      0|   Luciana Soares|Female|Logistic_soares|1346426538|
|   |       |       |      0|     Laurent Grad|  Male|          Loh69|  18355981|
|   |       |       |      0|        Marta Loh|Female|       LohMarta|2976559335|
+---+-------+-------+-------+-----------------+------+---------------+----------+


Note that we could have greatly simplified the code by just doing this:
%pyspark

df = rdd.\
    map(lambda x: x.split(",")).toDF()
df.limit(5).show()

And then we would have got this:
+----------+---------------+-----------------+---+------+---+---+---+
|        _1|             _2|               _3| _4|    _5| _6| _7| _8|
+----------+---------------+-----------------+---+------+---+---+---+
|  19999174|      Logicknot|Cameron MacKinnon|  0|  Male|   |   |   |
| 433249647|   LogieTatjana|  Tatjana Logie ♓|  0|Female|   |   |   |
|1346426538|Logistic_soares|   Luciana Soares|  0|Female|   |   |   |
|  18355981|          Loh69|     Laurent Grad|  0|  Male|   |   |   |
|2976559335|       LohMarta|        Marta Loh|  0|Female|   |   |   |
+----------+---------------+-----------------+---+------+---+---+---+

But presumably it's important to have column headers; hence the additional code in the first snippet above.

DF to Parquet

The final step is to transform the Dataframe into a Parquet file.

This can be accomplished in a single line:
%pyspark

df.write.parquet("/path/to/output/myfile")


Parquet to DF

Read the parquet file using this code:
%pyspark

df = sqlContext.read.parquet("/path/to/output/myfile")
df.limit(5).show()

And the output, as expected, looks like this:
+---+-------+-------+-------+----------------+------+---------------+----------+
|age|age_max|age_min|country|       full_name|gender|  twitterhandle|    userid|
+---+-------+-------+-------+----------------+------+---------------+----------+
|   |       |       |      0|nelson rodriguez|  Male| ojooooopublico|2771575827|
|   |       |       |      0|    Mary Garciaа|Female|  ojor_ozefofuw|3056364751|
|   |       |       |      0|   Andres nucleo|  Male|ojosrastafara12|1035247920|
|   |       |       |      0|          Omaira|Female|      ojovalles| 183602059|
|   |       |       |      0|   Olivia Scotti|Female|       ojscotti|3072660401|
+---+-------+-------+-------+----------------+------+---------------+----------+

This saved us a significant amount of time. By saving as a parquet file, we not only achieve space efficiency on our cluster, but have the ability to rapidly load the parquet data into a Dataframe with the column headings we specified earlier.

Monday, May 16, 2016

PySpark: Operations Overview

Reslient Distributed Dataset:  A dataset is distributed across the cluster nodes.  No single node has all the data.  The data is recoverable when a single node fails. 

an RDD is distributed across the worker nodes


Collect
<RDD>.collect() pulls back all the data from an RDD into the driver program.  You don't necessarily want to re-assemble a large dataset, distributed onto multiple nodes onto a single driver node over the network.  The function call will probably hang for a while and then die.

If you are doing exploratory data analysis, collect() can be useful when run in conjunction with the sample() function to decrease the size of the data.
rdd.sample(withReplacement=False, fraction=0.01, seed=1).collect()
Because I provided a seed, the random sample will be consistent.  Each time I call this function, I will see the same thing.

Count
The most basic form of count() tells you how many items are present in your RDD.
rdd.count()
it is very useful, and very simple.  There are no optional parameters.

There are a couple of interesting variations:
rdd.countApprox(timeout=200, confidence=0.5)
This function returns an approximate count.  If your dataset is extremely large, and you don't care exactly how many items you have, this is a great function to use. 

4 Paragraphs from a Zeppelin notebook showing
count() and countApprox() in action


Counting distinct elements in a large dataset can be a daunting task.
rdd.countApproxDistinct(relativeSD=0.05)
This function makes it easy by returning an approximate count of the number of distinct elements in the RDD.  Under the covers, the function uses the HyperLogLog algorithm.  This algorithm hashes data into buckets, then looks at smallest data in bucket, then estimates how many values are likely in bucket, then sees has an element of that size, then uses a harmonic mean of all its estimates.

By making the SD parameter smaller, the count will be more accurate and the function runtime will be slower.

First
Sometimes you just need one item in the RDD to play with.  You can use first() to pull the first item from the rdd.
rdd.first()
This function is simple.  There are no optional parameters, and it does respect any sorting that you've done.  You can't use first() on an empty RDD.

Limit a DataFrame (df) to 1 result
or use first() on the underlying RDD

Take
The take() function is a general purpose "return data to the driver" command.
rdd.take(1)
is almost the same as
rdd.first()
although take() returns a list instead of an element

take(n) vs first()



If I take as many rows as are in my collection, that's the same as using collection
rdd.take(rdd.count())
is functionally equivalent to
rdd.collect()
As a rule:
  • if you want one item, use first()
  • if you want all the items, use collect()
  • if you want 0 < x < MAX, use take(n)
Both first() and collect() are optimized for their own particular functions and are better than take(), under these specialized circumstances.  The take() function looks at one partition, and then estimates how many partitions will be needed to provide the number of elements you requested.  The function then returns all the elements you have requested to the driver.

Take Sample

This function allows you to pull a random sample of elements from your RDD into your driver program
rdd.takeSample(withReplacement=False, num=1, seed=1)
When you sample, you need to provide the target sample and if you want replacement while sampling.

Sampling without replacement means you can only select a given element once.  If the RDD has 100 elements, and you want a sample with 10,000 elements, and you set withReplacement=False the sample will only have 100 elements.  Each element can only be selected once.

Sampling with replacement means each element in the input has an equal chance of being picked for each element in the output.  Using the example above, an RDD with 100 elements could be used to generate a random sample with 10,000 elements.  Each input element would be repeated multiple times in the output.

Another way to look at this is that if you had an input RDD like this:
    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
and you selected
rdd.takeSample(withReplacement=True, num=rdd.count(), seed=1)
in theory, you could end up with an output (the random sample) that looked like this:
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
this is very unlikely, but it could happen.

Use of takeSample() with the optional seed parameter

You can also provide a random seed when sampling.  Using the parameter enables repeatable research as it enables others to replicate your findings.  Use of this parameter guarantees the function will always make the same random sample selections.

Take Ordered

This function is similar to sorting the entire RDD, then performing a take() function.

Without this function, given a large RDD and a desire for an ordered sample, you would have to reduce over each partition keeping the first n elements by sort.  Then the elements would need to be combined in a reduce, then a final combine performed in the driver.  At each point, would only keep n items.  This is all handled behind the scenes.

This function is only fast if n is small.  Don't use this function when n ~= rdd.count().  As the value of n approaches the size of your rdd, it's best to just sort the rdd then use take() or takeSample()

rdd.takeOrdered(10)
 
the "userid" column is sorted in ascending order



References

  1. [StackOverflow] Take Ordered
    1. Good overview on a variety of takeOrdered(n, key) variations.

Tuesday, March 24, 2015

Deploy a Scala job to a Spark Cluster using SBT

Environment

  1. sbt 0.13.8
  2. Scala 2.11.6
  3. Spark 1.2.1
  4. Debian Linux (Ubuntu 14.10)



Scala Program


This simple program written in Scala will analyze a local file on my system, count the number of times that lines containing a and b occur, and print the total to the console.
/*** SimpleApp.scala ***/
import org.apache.spark.SparkContext
import org.apache.spark.SparkContext._

object SimpleApp {
  def main(args: Array[String]) {
    val logFile = "/home/craig/spark/README.md" // Should be some file on your system
    val sc = new SparkContext("local", "Simple App", "$SPARK_HOME", List("target/scala-2.11/simple-project_2.11-1.0.jar"))
    val logData = sc.textFile(logFile, 2).cache()
    val numAs = logData.filter(line => line.contains("a")).count()
    val numBs = logData.filter(line => line.contains("b")).count()
    println("Lines with a: %s, Lines with b: %s".format(numAs, numBs))
  }
}



Directory Structure


I've created the following directory structure in my home directory:
mkdir -p ~/apps/simple/src/main/scala

The scala program above will be saved under the "src/main/scala" directory.


Build Script


Create a build script called simple.sbt under the "~/apps/simple/" directory:
name := "Simple Project"

version := "1.0"

scalaVersion := "2.11.6"

libraryDependencies += "org.apache.spark" %% "spark-core" % "1.2.1"
libraryDependencies += "org.apache.hadoop" % "hadoop-client" % "2.6.0"

resolvers += "Akka Repository" at "http://repo.akka.io/releases/"

The lines highlighted in red should be replaced with your specific version numbers.


Building the Program


Navigate to ~/apps/simple and type "sbt" on the terminal session:
craig@spark:~/apps/simple$ sbt
[info] Set current project to Simple Project (in build file:/home/craig/apps/simple/)
> 

Type "package" at the prompt:
> package
[info] Updating {file:/home/craig/apps/simple/}simple...
[info] Resolving jline#jline;2.12.1 ...
[info] Done updating.
[info] Compiling 1 Scala source to /home/craig/apps/simple/target/scala-2.11/classes...
[info] Packaging /home/craig/apps/simple/target/scala-2.11/simple-project_2.11-1.0.jar ...
[info] Done packaging.
[success] Total time: 18 s, completed Mar 24, 2015 11:15:18 AM

My directory structure now looks like this:
craig@spark:~/apps/simple$ tree
.
├── simple.sbt
├── src
│   └── main
│       └── scala
│           └── SimpleApp.scala
└── target
    ├── resolution-cache
    │   ├── reports
    │   │   ├── ivy-report.css
    │   │   ├── ivy-report.xsl
    │   │   ├── simple-project-simple-project_2.11-compile-internal.xml
    │   │   ├── simple-project-simple-project_2.11-compile.xml
    │   │   ├── simple-project-simple-project_2.11-docs.xml
    │   │   ├── simple-project-simple-project_2.11-optional.xml
    │   │   ├── simple-project-simple-project_2.11-plugin.xml
    │   │   ├── simple-project-simple-project_2.11-pom.xml
    │   │   ├── simple-project-simple-project_2.11-provided.xml
    │   │   ├── simple-project-simple-project_2.11-runtime-internal.xml
    │   │   ├── simple-project-simple-project_2.11-runtime.xml
    │   │   ├── simple-project-simple-project_2.11-scala-tool.xml
    │   │   ├── simple-project-simple-project_2.11-sources.xml
    │   │   ├── simple-project-simple-project_2.11-test-internal.xml
    │   │   └── simple-project-simple-project_2.11-test.xml
    │   └── simple-project
    │       └── simple-project_2.11
    │           └── 1.0
    │               ├── resolved.xml.properties
    │               └── resolved.xml.xml
    ├── scala-2.11
    │   ├── classes
    │   │   ├── SimpleApp$$anonfun$1.class
    │   │   ├── SimpleApp$$anonfun$2.class
    │   │   ├── SimpleApp.class
    │   │   └── SimpleApp$.class
    │   └── simple-project_2.11-1.0.jar
    └── streams
        ├── compile
        │   ├── compile
        │   │   └── $global
        │   │       └── streams
        │   │           └── out
        │   ├── compileIncremental
        │   │   └── $global
        │   │       └── streams
        │   │           ├── export
        │   │           └── out
        │   ├── copyResources
        │   │   └── $global
        │   │       └── streams
        │   │           ├── copy-resources
        │   │           └── out
        │   ├── dependencyClasspath
        │   │   └── $global
        │   │       └── streams
        │   │           └── export
        │   ├── externalDependencyClasspath
        │   │   └── $global
        │   │       └── streams
        │   │           └── export
        │   ├── $global
        │   │   └── $global
        │   │       └── discoveredMainClasses
        │   │           └── data
        │   ├── incCompileSetup
        │   │   └── $global
        │   │       └── streams
        │   │           └── inc_compile_2.11
        │   ├── internalDependencyClasspath
        │   │   └── $global
        │   │       └── streams
        │   │           └── export
        │   ├── mainClass
        │   │   └── $global
        │   │       └── streams
        │   │           └── out
        │   ├── managedClasspath
        │   │   └── $global
        │   │       └── streams
        │   │           └── export
        │   ├── packageBin
        │   │   └── $global
        │   │       └── streams
        │   │           ├── inputs
        │   │           ├── out
        │   │           └── output
        │   ├── unmanagedClasspath
        │   │   └── $global
        │   │       └── streams
        │   │           └── export
        │   └── unmanagedJars
        │       └── $global
        │           └── streams
        │               └── export
        └── $global
            ├── dependencyPositions
            │   └── $global
            │       └── streams
            │           └── update_cache_2.11
            │               ├── input_dsp
            │               └── output_dsp
            ├── $global
            │   └── $global
            │       └── streams
            │           └── out
            ├── ivyConfiguration
            │   └── $global
            │       └── streams
            │           └── out
            ├── ivySbt
            │   └── $global
            │       └── streams
            │           └── out
            ├── projectDescriptors
            │   └── $global
            │       └── streams
            │           └── out
            └── update
                └── $global
                    └── streams
                        ├── out
                        └── update_cache_2.11
                            ├── inputs
                            └── output

73 directories, 50 files



Running the Program


Inside your working directory (~/apps/simple), execute this command:
$SPARK_HOME/bin/./spark-submit --class "SimpleApp" --master local[8] target/scala-2.11/simple-project_2.11-1.0.jar

Successful output on my workstation looks like this:
Using Spark's default log4j profile: org/apache/spark/log4j-defaults.properties
15/03/24 11:20:37 INFO SecurityManager: Changing view acls to: craig
15/03/24 11:20:37 INFO SecurityManager: Changing modify acls to: craig
15/03/24 11:20:37 INFO SecurityManager: SecurityManager: authentication disabled; ui acls disabled; users with view permissions: Set(craig); users with modify permissions: Set(craig)
15/03/24 11:20:38 INFO Slf4jLogger: Slf4jLogger started
15/03/24 11:20:38 INFO Remoting: Starting remoting
15/03/24 11:20:38 INFO Remoting: Remoting started; listening on addresses :[akka.tcp://sparkDriver@10.0.4.15:49222]
15/03/24 11:20:38 INFO Utils: Successfully started service 'sparkDriver' on port 49222.
15/03/24 11:20:38 INFO SparkEnv: Registering MapOutputTracker
15/03/24 11:20:38 INFO SparkEnv: Registering BlockManagerMaster
15/03/24 11:20:38 INFO DiskBlockManager: Created local directory at /tmp/spark-91e5f424-b1e6-4d51-a010-a4e0ac788725/spark-2d07771d-f3ad-4c70-bb5b-1329be484b4f
15/03/24 11:20:38 INFO MemoryStore: MemoryStore started with capacity 265.1 MB
15/03/24 11:20:38 WARN NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable
15/03/24 11:20:38 INFO HttpFileServer: HTTP File server directory is /tmp/spark-cf38f85d-efd0-4941-b4f6-d245b9d8380c/spark-f903d7b0-c67c-48f0-9eb0-4a3279903d2a
15/03/24 11:20:38 INFO HttpServer: Starting HTTP Server
15/03/24 11:20:39 INFO Utils: Successfully started service 'HTTP file server' on port 50465.
15/03/24 11:20:39 INFO Utils: Successfully started service 'SparkUI' on port 4040.
15/03/24 11:20:39 INFO SparkUI: Started SparkUI at http://10.0.4.15:4040
15/03/24 11:20:39 INFO SparkContext: Added JAR target/scala-2.11/simple-project_2.11-1.0.jar at http://10.0.4.15:50465/jars/simple-project_2.11-1.0.jar with timestamp 1427221239211
15/03/24 11:20:39 INFO Executor: Starting executor ID <driver> on host localhost
15/03/24 11:20:39 INFO AkkaUtils: Connecting to HeartbeatReceiver: akka.tcp://sparkDriver@10.0.4.15:49222/user/HeartbeatReceiver
15/03/24 11:20:39 INFO NettyBlockTransferService: Server created on 54739
15/03/24 11:20:39 INFO BlockManagerMaster: Trying to register BlockManager
15/03/24 11:20:39 INFO BlockManagerMasterActor: Registering block manager localhost:54739 with 265.1 MB RAM, BlockManagerId(<driver>, localhost, 54739)
15/03/24 11:20:39 INFO BlockManagerMaster: Registered BlockManager
15/03/24 11:20:39 INFO MemoryStore: ensureFreeSpace(180608) called with curMem=0, maxMem=278019440
15/03/24 11:20:39 INFO MemoryStore: Block broadcast_0 stored as values in memory (estimated size 176.4 KB, free 265.0 MB)
15/03/24 11:20:40 INFO MemoryStore: ensureFreeSpace(25432) called with curMem=180608, maxMem=278019440
15/03/24 11:20:40 INFO MemoryStore: Block broadcast_0_piece0 stored as bytes in memory (estimated size 24.8 KB, free 264.9 MB)
15/03/24 11:20:40 INFO BlockManagerInfo: Added broadcast_0_piece0 in memory on localhost:54739 (size: 24.8 KB, free: 265.1 MB)
15/03/24 11:20:40 INFO BlockManagerMaster: Updated info of block broadcast_0_piece0
15/03/24 11:20:40 INFO SparkContext: Created broadcast 0 from textFile at SimpleApp.scala:9
15/03/24 11:20:40 INFO FileInputFormat: Total input paths to process : 1
15/03/24 11:20:40 INFO SparkContext: Starting job: count at SimpleApp.scala:10
15/03/24 11:20:40 INFO DAGScheduler: Got job 0 (count at SimpleApp.scala:10) with 2 output partitions (allowLocal=false)
15/03/24 11:20:40 INFO DAGScheduler: Final stage: Stage 0(count at SimpleApp.scala:10)
15/03/24 11:20:40 INFO DAGScheduler: Parents of final stage: List()
15/03/24 11:20:40 INFO DAGScheduler: Missing parents: List()
15/03/24 11:20:40 INFO DAGScheduler: Submitting Stage 0 (FilteredRDD[2] at filter at SimpleApp.scala:10), which has no missing parents
15/03/24 11:20:40 INFO MemoryStore: ensureFreeSpace(2720) called with curMem=206040, maxMem=278019440
15/03/24 11:20:40 INFO MemoryStore: Block broadcast_1 stored as values in memory (estimated size 2.7 KB, free 264.9 MB)
15/03/24 11:20:40 INFO MemoryStore: ensureFreeSpace(1950) called with curMem=208760, maxMem=278019440
15/03/24 11:20:40 INFO MemoryStore: Block broadcast_1_piece0 stored as bytes in memory (estimated size 1950.0 B, free 264.9 MB)
15/03/24 11:20:40 INFO BlockManagerInfo: Added broadcast_1_piece0 in memory on localhost:54739 (size: 1950.0 B, free: 265.1 MB)
15/03/24 11:20:40 INFO BlockManagerMaster: Updated info of block broadcast_1_piece0
15/03/24 11:20:40 INFO SparkContext: Created broadcast 1 from broadcast at DAGScheduler.scala:838
15/03/24 11:20:40 INFO DAGScheduler: Submitting 2 missing tasks from Stage 0 (FilteredRDD[2] at filter at SimpleApp.scala:10)
15/03/24 11:20:40 INFO TaskSchedulerImpl: Adding task set 0.0 with 2 tasks
15/03/24 11:20:40 INFO TaskSetManager: Starting task 0.0 in stage 0.0 (TID 0, localhost, PROCESS_LOCAL, 1361 bytes)
15/03/24 11:20:40 INFO Executor: Running task 0.0 in stage 0.0 (TID 0)
15/03/24 11:20:40 INFO Executor: Fetching http://10.0.4.15:50465/jars/simple-project_2.11-1.0.jar with timestamp 1427221239211
15/03/24 11:20:40 INFO Utils: Fetching http://10.0.4.15:50465/jars/simple-project_2.11-1.0.jar to /tmp/spark-4055924d-8bde-4216-9932-526612364a63/spark-ac26b090-87ab-4f55-b0b9-3c8571d43307/fetchFileTemp507742889710724975.tmp
15/03/24 11:20:40 INFO Executor: Adding file:/tmp/spark-4055924d-8bde-4216-9932-526612364a63/spark-ac26b090-87ab-4f55-b0b9-3c8571d43307/simple-project_2.11-1.0.jar to class loader
15/03/24 11:20:40 INFO CacheManager: Partition rdd_1_0 not found, computing it
15/03/24 11:20:40 INFO HadoopRDD: Input split: file:/home/craig/spark/README.md:0+1814
15/03/24 11:20:40 INFO deprecation: mapred.tip.id is deprecated. Instead, use mapreduce.task.id
15/03/24 11:20:40 INFO deprecation: mapred.task.id is deprecated. Instead, use mapreduce.task.attempt.id
15/03/24 11:20:40 INFO deprecation: mapred.task.is.map is deprecated. Instead, use mapreduce.task.ismap
15/03/24 11:20:40 INFO deprecation: mapred.task.partition is deprecated. Instead, use mapreduce.task.partition
15/03/24 11:20:40 INFO deprecation: mapred.job.id is deprecated. Instead, use mapreduce.job.id
15/03/24 11:20:40 INFO MemoryStore: ensureFreeSpace(6208) called with curMem=210710, maxMem=278019440
15/03/24 11:20:40 INFO MemoryStore: Block rdd_1_0 stored as values in memory (estimated size 6.1 KB, free 264.9 MB)
15/03/24 11:20:40 INFO BlockManagerInfo: Added rdd_1_0 in memory on localhost:54739 (size: 6.1 KB, free: 265.1 MB)
15/03/24 11:20:40 INFO BlockManagerMaster: Updated info of block rdd_1_0
15/03/24 11:20:40 INFO Executor: Finished task 0.0 in stage 0.0 (TID 0). 2326 bytes result sent to driver
15/03/24 11:20:40 INFO TaskSetManager: Starting task 1.0 in stage 0.0 (TID 1, localhost, PROCESS_LOCAL, 1361 bytes)
15/03/24 11:20:40 INFO Executor: Running task 1.0 in stage 0.0 (TID 1)
15/03/24 11:20:40 INFO CacheManager: Partition rdd_1_1 not found, computing it
15/03/24 11:20:40 INFO HadoopRDD: Input split: file:/home/craig/spark/README.md:1814+1815
15/03/24 11:20:40 INFO MemoryStore: ensureFreeSpace(5400) called with curMem=216918, maxMem=278019440
15/03/24 11:20:40 INFO TaskSetManager: Finished task 0.0 in stage 0.0 (TID 0) in 260 ms on localhost (1/2)
15/03/24 11:20:40 INFO MemoryStore: Block rdd_1_1 stored as values in memory (estimated size 5.3 KB, free 264.9 MB)
15/03/24 11:20:40 INFO BlockManagerInfo: Added rdd_1_1 in memory on localhost:54739 (size: 5.3 KB, free: 265.1 MB)
15/03/24 11:20:40 INFO BlockManagerMaster: Updated info of block rdd_1_1
15/03/24 11:20:40 INFO Executor: Finished task 1.0 in stage 0.0 (TID 1). 2326 bytes result sent to driver
15/03/24 11:20:40 INFO TaskSetManager: Finished task 1.0 in stage 0.0 (TID 1) in 37 ms on localhost (2/2)
15/03/24 11:20:40 INFO DAGScheduler: Stage 0 (count at SimpleApp.scala:10) finished in 0.299 s
15/03/24 11:20:40 INFO TaskSchedulerImpl: Removed TaskSet 0.0, whose tasks have all completed, from pool 
15/03/24 11:20:40 INFO DAGScheduler: Job 0 finished: count at SimpleApp.scala:10, took 0.448259 s
15/03/24 11:20:40 INFO SparkContext: Starting job: count at SimpleApp.scala:11
15/03/24 11:20:40 INFO DAGScheduler: Got job 1 (count at SimpleApp.scala:11) with 2 output partitions (allowLocal=false)
15/03/24 11:20:40 INFO DAGScheduler: Final stage: Stage 1(count at SimpleApp.scala:11)
15/03/24 11:20:40 INFO DAGScheduler: Parents of final stage: List()
15/03/24 11:20:40 INFO DAGScheduler: Missing parents: List()
15/03/24 11:20:40 INFO DAGScheduler: Submitting Stage 1 (FilteredRDD[3] at filter at SimpleApp.scala:11), which has no missing parents
15/03/24 11:20:40 INFO MemoryStore: ensureFreeSpace(2720) called with curMem=222318, maxMem=278019440
15/03/24 11:20:40 INFO MemoryStore: Block broadcast_2 stored as values in memory (estimated size 2.7 KB, free 264.9 MB)
15/03/24 11:20:40 INFO MemoryStore: ensureFreeSpace(1950) called with curMem=225038, maxMem=278019440
15/03/24 11:20:40 INFO BlockManager: Removing broadcast 1
15/03/24 11:20:40 INFO MemoryStore: Block broadcast_2_piece0 stored as bytes in memory (estimated size 1950.0 B, free 264.9 MB)
15/03/24 11:20:40 INFO BlockManager: Removing block broadcast_1
15/03/24 11:20:40 INFO MemoryStore: Block broadcast_1 of size 2720 dropped from memory (free 277795172)
15/03/24 11:20:40 INFO BlockManager: Removing block broadcast_1_piece0
15/03/24 11:20:40 INFO MemoryStore: Block broadcast_1_piece0 of size 1950 dropped from memory (free 277797122)
15/03/24 11:20:40 INFO BlockManagerInfo: Added broadcast_2_piece0 in memory on localhost:54739 (size: 1950.0 B, free: 265.1 MB)
15/03/24 11:20:40 INFO BlockManagerMaster: Updated info of block broadcast_2_piece0
15/03/24 11:20:40 INFO BlockManagerInfo: Removed broadcast_1_piece0 on localhost:54739 in memory (size: 1950.0 B, free: 265.1 MB)
15/03/24 11:20:40 INFO SparkContext: Created broadcast 2 from broadcast at DAGScheduler.scala:838
15/03/24 11:20:40 INFO BlockManagerMaster: Updated info of block broadcast_1_piece0
15/03/24 11:20:40 INFO DAGScheduler: Submitting 2 missing tasks from Stage 1 (FilteredRDD[3] at filter at SimpleApp.scala:11)
15/03/24 11:20:40 INFO TaskSchedulerImpl: Adding task set 1.0 with 2 tasks
15/03/24 11:20:40 INFO ContextCleaner: Cleaned broadcast 1
15/03/24 11:20:40 INFO TaskSetManager: Starting task 0.0 in stage 1.0 (TID 2, localhost, PROCESS_LOCAL, 1361 bytes)
15/03/24 11:20:40 INFO Executor: Running task 0.0 in stage 1.0 (TID 2)
15/03/24 11:20:40 INFO BlockManager: Found block rdd_1_0 locally
15/03/24 11:20:40 INFO Executor: Finished task 0.0 in stage 1.0 (TID 2). 1757 bytes result sent to driver
15/03/24 11:20:40 INFO TaskSetManager: Starting task 1.0 in stage 1.0 (TID 3, localhost, PROCESS_LOCAL, 1361 bytes)
15/03/24 11:20:40 INFO Executor: Running task 1.0 in stage 1.0 (TID 3)
15/03/24 11:20:40 INFO TaskSetManager: Finished task 0.0 in stage 1.0 (TID 2) in 25 ms on localhost (1/2)
15/03/24 11:20:40 INFO BlockManager: Found block rdd_1_1 locally
15/03/24 11:20:40 INFO Executor: Finished task 1.0 in stage 1.0 (TID 3). 1757 bytes result sent to driver
15/03/24 11:20:40 INFO TaskSetManager: Finished task 1.0 in stage 1.0 (TID 3) in 15 ms on localhost (2/2)
15/03/24 11:20:40 INFO TaskSchedulerImpl: Removed TaskSet 1.0, whose tasks have all completed, from pool 
15/03/24 11:20:40 INFO DAGScheduler: Stage 1 (count at SimpleApp.scala:11) finished in 0.040 s
15/03/24 11:20:40 INFO DAGScheduler: Job 1 finished: count at SimpleApp.scala:11, took 0.067147 s
Lines with a: 60, Lines with b: 29



References

  1. [Spark] Quick Start Guide
    1. Contains the SimpleApp.scala program that was modified for this article

Installing SBT (Simple Build Tool) on Ubuntu

Environment

  1. SBT 0.13.8
  2. Ubuntu 14.10
sbt is an open source build tool for Scala and Java projects, similar to Java's Maven or Ant. Its main features are: native support for compiling Scala code and integrating with many Scala test frameworks.



Installing SBT


The following commands have been tested, and are operational, under Ubuntu 14.10
mkdir ~/sbt
cd ~/sbt
wget https://dl.bintray.com/sbt/native-packages/sbt/0.13.8/sbt-0.13.8.tgz
sudo tar -zxvf sbt-0.13.8.tgz

This script will create an sbt folder in the home directory, get the latest (at the time of this article) sbt tgz file and unpackage it.


Modifying the Path


Modify both the PATH and CLASSPATH to point to the new Scala installation.

I like to use nano to edit my environment file:
sudo nano /etc/environment

The text in bold was added:
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/home/craig/sbt/bin"  
...  
export SBT_HOME=/home/craig/sbt

Once the environment file is saved, reload it:
source /etc/environment



Verify the Installation


If the installation and environment editing were both successful, you should be able to find the version of scala on the terminal session:
craig@spark:~$ sbt --version
sbt launcher version 0.13.8



References

  1. [SBT] Installing SBT on Debian

Monday, March 23, 2015

Spark Architecture and Design

Cluster Mode Overview

Spark applications run as independent sets of processes on a cluster, coordinated by the SparkCOntext object in your main program (aka Driver Program).
Fig 1: Cluster Mode Overview



What is RDD?


Write programs in terms of transformations on distributed datasets.
    Resilient Distributed Datasets
  1. Collections of objects spread across a cluster, stored in RAM or on Disk
  2. Built through parallel transformations
  3. Automatically rebuilt on failure Operations
    Operations:
  1. Transformations (e.g. map, filter, groupBy)
  2. Actions (e.g. count, collect, save)



References

  1. Cluster Design
    1. [Spark] Cluster Mode Overview
  2. RDD
    1. The RDD API by Example
      1. Zhen He's page at La Trobe University.  
      2. Current with Spark 1.1.0
      3. A helpful introduction to the RDD API.
    2. [DataBricks, PDF] Spark Tutorial Summit 2013
      1. Introductory level talk