Ver3.0 ๐ ์๋ฐ ๋ฐ์ดํฐ ์ฒ๋ฆฌ์ Apache Spark๋ฅผ ํ์ฉํ ๋น ๋ฐ์ดํฐ ๋ถ์ ์๋ฒฝ ๊ฐ์ด๋

๐ ์๋ฐ ๋ฐ์ดํฐ ์ฒ๋ฆฌ์ Apache Spark๋ฅผ ํ์ฉํ ๋น ๋ฐ์ดํฐ ๋ถ์ ์๋ฒฝ ๊ฐ์ด๋
๋์ฉ๋ ๋ฐ์ดํฐ๋ฅผ ๋น ๋ฅด๊ณ ํจ์จ์ ์ผ๋ก ์ฒ๋ฆฌํ๋ ํ๋์ ์ ๊ทผ๋ฒ
๐ฏ ๋น ๋ฐ์ดํฐ ์๋, ์ Spark์ธ๊ฐ?
์๋
! ์ค๋์ ์๋ฐ ๊ฐ๋ฐ์๋ผ๋ฉด ๊ผญ ์์์ผ ํ Apache Spark์ ๋ํด ์ด์ผ๊ธฐํด๋ณด๋ ค๊ณ ํด. ๐
์์ฆ ๋ฐ์ดํฐ๊ฐ ์ผ๋ง๋ ๋ง์์ก๋์ง ์์? ๋งค์ผ 2.5ํธํธ๋ฆฌ์ธ ๋ฐ์ดํธ์ ๋ฐ์ดํฐ๊ฐ ์์ฑ๋๊ณ ์์ด. ์ด๊ฒ ์ผ๋ง๋ ํฐ ์ซ์๋๋ฉด, 1 ๋ค์ 0์ด 18๊ฐ ๋ถ๋ ์ด๋ง์ด๋งํ ์์ด์ผ! ๐คฏ
์ ํต์ ์ธ ๋ฐ์ดํฐ ์ฒ๋ฆฌ ๋ฐฉ์์ผ๋ก๋ ์ด๋ฐ ๋์ฉ๋ ๋ฐ์ดํฐ๋ฅผ ๋ค๋ฃจ๊ธฐ๊ฐ ์ ๋ง ํ๋ค์ด. ๊ทธ๋์ ๋ฑ์ฅํ ๊ฒ ๋ฐ๋ก Apache Spark์ผ. Spark๋ 2009๋
UC ๋ฒํด๋ฆฌ์ AMPLab์์ ์์๋ ํ๋ก์ ํธ๋ก, ํ์ฌ๋ Apache ์ฌ๋จ์ ์ต์์ ํ๋ก์ ํธ๋ก ์๋ฆฌ์ก์์ด.
โข ๋ฒ์ฉ์ฑ: ๋ฐฐ์น ์ฒ๋ฆฌ, ์คํธ๋ฆฌ๋ฐ, ๋จธ์ ๋ฌ๋, ๊ทธ๋ํ ์ฒ๋ฆฌ ๋ชจ๋ ๊ฐ๋ฅ
โข ๋ค์ํ ์ธ์ด ์ง์: Java, Scala, Python, R ๋ชจ๋ ์ฌ์ฉ ๊ฐ๋ฅ
โข ํ์ฅ์ฑ: ๋จ์ผ ๋ ธ๋๋ถํฐ ์์ฒ ๊ฐ ๋ ธ๋๊น์ง ํ์ฅ ๊ฐ๋ฅ
๐ก Apache Spark์ ์ํคํ ์ฒ ์ดํดํ๊ธฐ
Spark๋ฅผ ์ ๋๋ก ํ์ฉํ๋ ค๋ฉด ๋จผ์ ๊ทธ ๊ตฌ์กฐ๋ฅผ ์ดํดํด์ผ ํด. ๋ง์น ์๋์ฐจ๋ฅผ ์ด์ ํ๊ธฐ ์ ์ ์์ง์ด ์ด๋ป๊ฒ ์๋ํ๋์ง ์๋ฉด ๋ ์ ๋ค๋ฃฐ ์ ์๋ ๊ฒ์ฒ๋ผ ๋ง์ด์ผ! ๐
Spark๋ ๋ง์คํฐ-์์ปค ์ํคํ
์ฒ๋ฅผ ์ฌ์ฉํด. ์ฝ๊ฒ ๋งํ๋ฉด, ํ ๋ช
์ ๊ฐ๋
(Driver)์ด ์ฌ๋ฌ ๋ช
์ ์์
์(Executor)์๊ฒ ์ผ์ ๋ถ๋ฐฐํ๋ ๊ตฌ์กฐ์ผ.
โข ์ฌ์ฉ์์ main() ํจ์๊ฐ ์คํ๋๋ ํ๋ก์ธ์ค
โข SparkContext๋ฅผ ์์ฑํ๊ณ ์ ์ฒด ์์ ์ ์กฐ์จ
โข ์์ ์ Task๋ก ๋ถํ ํ๊ณ Executor์ ํ ๋น
2. Cluster Manager (ํด๋ฌ์คํฐ ๋งค๋์ )
โข ํด๋ฌ์คํฐ์ ๋ฆฌ์์ค๋ฅผ ๊ด๋ฆฌ
โข Standalone, YARN, Mesos, Kubernetes ๋ฑ ์ง์
3. Executor (์คํ์)
โข ์ค์ ์์ ์ ์ํํ๋ ์์ปค ํ๋ก์ธ์ค
โข ๋ฐ์ดํฐ๋ฅผ ๋ฉ๋ชจ๋ฆฌ๋ ๋์คํฌ์ ์บ์ฑ
โข Task๋ฅผ ์คํํ๊ณ ๊ฒฐ๊ณผ๋ฅผ Driver์ ๋ฐํ
๐ Spark์ ๋ฐ์ดํฐ ์ถ์ํ: RDD, DataFrame, Dataset
Spark๋ ๋ฐ์ดํฐ๋ฅผ ๋ค๋ฃจ๋ ์ธ ๊ฐ์ง ์ฃผ์ ์ถ์ํ ๊ฐ๋ ์ ์ ๊ณตํด. ๊ฐ๊ฐ์ ํน์ง์ ์์๋ณด์! ๐
โข ๋ถ๋ณ์ฑ(Immutable): ํ ๋ฒ ์์ฑ๋๋ฉด ๋ณ๊ฒฝํ ์ ์์ด
โข ๋ถ์ฐ: ์ฌ๋ฌ ๋ ธ๋์ ๊ฑธ์ณ ๋ถ์ฐ ์ ์ฅ
โข ๋ณต์๋ ฅ: ์ฅ์ ๋ฐ์ ์ ์๋์ผ๋ก ๋ณต๊ตฌ ๊ฐ๋ฅ
โข ์ง์ฐ ํ๊ฐ(Lazy Evaluation): ์ค์ ์ก์ ์ด ํธ์ถ๋ ๋๊น์ง ์คํ ์ ํจ
โข ์ปฌ๋ผ๋ช ๊ณผ ๋ฐ์ดํฐ ํ์ ์ด ์ ์๋จ
โข SQL ์ฟผ๋ฆฌ ์ฌ์ฉ ๊ฐ๋ฅ
โข Catalyst ์ตํฐ๋ง์ด์ ๋ก ์ฑ๋ฅ ์ต์ ํ
โข Python์ Pandas DataFrame๊ณผ ์ ์ฌํ API
โข ์ปดํ์ผ ํ์์ ํ์ ์ฒดํฌ
โข ๊ฐ์ฒด ์งํฅ ํ๋ก๊ทธ๋๋ฐ ์คํ์ผ
โข DataFrame์ ์ต์ ํ + RDD์ ํ์ ์์ ์ฑ
โ๏ธ Java์์ Spark ์์ํ๊ธฐ
์, ์ด์ ๋ณธ๊ฒฉ์ ์ผ๋ก Java๋ก Spark๋ฅผ ์ฌ์ฉํด๋ณด์! ๐ฎ
๋จผ์ ๊ฐ๋ฐ ํ๊ฒฝ์ ์ค์ ํด์ผ ํด. Maven์ด๋ Gradle์ ์ฌ์ฉํ๋ค๋ฉด ์์กด์ฑ์ ์ถ๊ฐํ๋ ๊ฒ๋ถํฐ ์์์ด์ผ.
๐ฆ Maven ์์กด์ฑ ์ค์
<dependencies>
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-core_2.12</artifactId>
<version>3.5.0</version>
</dependency>
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-sql_2.12</artifactId>
<version>3.5.0</version>
</dependency>
</dependencies>
โข Java 8 ์ด์ ํ์ (Java 11 ๋๋ 17 ๊ถ์ฅ)
โข ์ต์ ์์ ๋ฒ์ ์ฌ์ฉ์ ๊ถ์ฅ (ํ์ฌ 3.5.x ์๋ฆฌ์ฆ)
๐ ์ฒซ ๋ฒ์งธ Spark ์ ํ๋ฆฌ์ผ์ด์
๊ฐ์ฅ ๊ธฐ๋ณธ์ ์ธ "Word Count" ์์ ๋ก ์์ํด๋ณด์. ์ด๊ฑด ๋น ๋ฐ์ดํฐ์ "Hello World"๋ผ๊ณ ํ ์ ์์ด! ๐
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.SparkConf;
import scala.Tuple2;
import java.util.Arrays;
public class WordCountExample {
public static void main(String[] args) {
// Spark ์ค์ ์์ฑ
SparkConf conf = new SparkConf()
.setAppName("Word Count Example")
.setMaster("local[*]"); // ๋ก์ปฌ ๋ชจ๋๋ก ์คํ
// SparkContext ์์ฑ
JavaSparkContext sc = new JavaSparkContext(conf);
// ํ
์คํธ ํ์ผ ์ฝ๊ธฐ
JavaRDD<String> lines = sc.textFile("input.txt");
// ๋จ์ด ๋ถ๋ฆฌ ๋ฐ ์นด์ดํ
JavaRDD<String> words = lines.flatMap(
line -> Arrays.asList(line.split(" ")).iterator()
);
// ๊ฐ ๋จ์ด๋ฅผ (๋จ์ด, 1) ํํ๋ก ๋ณํ
JavaPairRDD<String, Integer> pairs = words.mapToPair(
word -> new Tuple2<>(word, 1)
);
// ๊ฐ์ ํค(๋จ์ด)๋ผ๋ฆฌ ํฉ์ฐ
JavaPairRDD<String, Integer> wordCounts = pairs.reduceByKey(
(a, b) -> a + b
);
// ๊ฒฐ๊ณผ ์ถ๋ ฅ
wordCounts.foreach(tuple -> {
System.out.println(tuple._1() + ": " + tuple._2());
});
// SparkContext ์ข
๋ฃ
sc.close();
}
}
โข setAppName(): ์ ํ๋ฆฌ์ผ์ด์ ์ด๋ฆ ์ค์
โข setMaster(): ์คํ ๋ชจ๋ ์ค์ (local[*]๋ ๋ก์ปฌ์ ๋ชจ๋ ์ฝ์ด ์ฌ์ฉ)
2. JavaSparkContext: Spark ๊ธฐ๋ฅ์ ์ง์ ์
โข RDD ์์ฑ ๋ฐ ๊ด๋ฆฌ
โข ํด๋ฌ์คํฐ ๋ฆฌ์์ค ์ ๊ทผ
3. Transformation vs Action:
โข Transformation (flatMap, mapToPair, reduceByKey): ์ง์ฐ ํ๊ฐ, ์๋ก์ด RDD ๋ฐํ
โข Action (foreach, collect, count): ์ฆ์ ์คํ, ๊ฒฐ๊ณผ ๋ฐํ
๐ DataFrame API๋ก ๋ ์ฝ๊ฒ ๋ฐ์ดํฐ ๋ค๋ฃจ๊ธฐ
RDD๋ ์ข์ง๋ง, ์ค๋ฌด์์๋ DataFrame API๋ฅผ ๋ ๋ง์ด ์ฌ์ฉํด. SQL์ฒ๋ผ ์ง๊ด์ ์ด๊ณ , ์ฑ๋ฅ๋ ๋ ์ข๊ฑฐ๋ ! โก
DataFrame์ ์ฌ์ฉํ๋ฉด ๋ณต์กํ ๋ฐ์ดํฐ ์ฒ๋ฆฌ๋ฅผ ํจ์ฌ ๊ฐ๋จํ๊ฒ ํํํ ์ ์์ด. ๋ง์น ์ฌ๋ฅ๋ท์์ ๋ค์ํ ์ฌ๋ฅ์ ์ฝ๊ฒ ์ฐพ์ ์ ์๋ ๊ฒ์ฒ๋ผ, DataFrame๋ ๋ฐ์ดํฐ๋ฅผ ์ฝ๊ฒ ๋ค๋ฃฐ ์ ์๊ฒ ํด์ค!
๐ฏ SparkSession ์์ฑํ๊ธฐ
DataFrame์ ์ฌ์ฉํ๋ ค๋ฉด ๋จผ์ SparkSession์ ๋ง๋ค์ด์ผ ํด. ์ด๊ฑด Spark 2.0๋ถํฐ ๋์ ๋ ํตํฉ ์ง์ ์ ์ด์ผ.
import org.apache.spark.sql.SparkSession;
import org.apache.spark.sql.Dataset;
import org.apache.spark.sql.Row;
public class DataFrameExample {
public static void main(String[] args) {
// SparkSession ์์ฑ
SparkSession spark = SparkSession.builder()
.appName("DataFrame Example")
.master("local[*]")
.getOrCreate();
// CSV ํ์ผ ์ฝ๊ธฐ
Dataset<Row> df = spark.read()
.option("header", "true")
.option("inferSchema", "true")
.csv("data.csv");
// ๋ฐ์ดํฐ ๊ตฌ์กฐ ํ์ธ
df.printSchema();
// ์ฒ์ 5๊ฐ ํ ์ถ๋ ฅ
df.show(5);
// SparkSession ์ข
๋ฃ
spark.close();
}
}
๐ ๋ฐ์ดํฐ ํ์ ๋ฐ ๋ณํ
DataFrame์ผ๋ก ํ ์ ์๋ ๋ค์ํ ์์ ๋ค์ ์ดํด๋ณด์! ๐ฌ
// ํน์ ์ปฌ๋ผ ์ ํ
Dataset<Row> names = df.select("name", "age");
// ์กฐ๊ฑด ํํฐ๋ง
Dataset<Row> adults = df.filter(df.col("age").gt(18));
// ๊ทธ๋ฃนํ ๋ฐ ์ง๊ณ
Dataset<Row> avgByDept = df.groupBy("department")
.avg("salary");
// ์ ๋ ฌ
Dataset<Row> sorted = df.orderBy(df.col("salary").desc());
// ์๋ก์ด ์ปฌ๋ผ ์ถ๊ฐ
Dataset<Row> withBonus = df.withColumn(
"bonus",
df.col("salary").multiply(0.1)
);
// ์ปฌ๋ผ๋ช
๋ณ๊ฒฝ
Dataset<Row> renamed = df.withColumnRenamed("old_name", "new_name");
// ์ค๋ณต ์ ๊ฑฐ
Dataset<Row> distinct = df.distinct();
// ๊ฒฐ์ธก์น ์ฒ๋ฆฌ
Dataset<Row> filled = df.na().fill(0); // null์ 0์ผ๋ก ์ฑ์ฐ๊ธฐ
Dataset<Row> dropped = df.na().drop(); // null์ด ์๋ ํ ์ ๊ฑฐ
| ๋ฉ์๋ | ์ค๋ช | ์์ |
|---|---|---|
| select() | ์ปฌ๋ผ ์ ํ | df.select("name", "age") |
| filter() | ํ ํํฐ๋ง | df.filter("age > 20") |
| groupBy() | ๊ทธ๋ฃนํ | df.groupBy("dept").count() |
| join() | ์กฐ์ธ | df1.join(df2, "id") |
| agg() | ์ง๊ณ | df.agg(sum("salary")) |
๐พ ๋ค์ํ ๋ฐ์ดํฐ ์์ค ์ฝ๊ณ ์ฐ๊ธฐ
Spark๋ ์ ๋ง ๋ค์ํ ๋ฐ์ดํฐ ํฌ๋งท์ ์ง์ํด. CSV, JSON, Parquet, Avro, ORC ๋ฑ๋ฑ... ๋ญ๋ ์ง ๋ค ์ฝ์ ์ ์์ด! ๐
// JSON ์ฝ๊ธฐ
Dataset<Row> jsonDF = spark.read().json("data.json");
// Parquet ์ฝ๊ธฐ (์ปฌ๋ผ ๊ธฐ๋ฐ ์ ์ฅ ํฌ๋งท, ๋งค์ฐ ํจ์จ์ !)
Dataset<Row> parquetDF = spark.read().parquet("data.parquet");
// JDBC๋ก ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฝ๊ธฐ
Dataset<Row> jdbcDF = spark.read()
.format("jdbc")
.option("url", "jdbc:mysql://localhost:3306/mydb")
.option("dbtable", "users")
.option("user", "username")
.option("password", "password")
.load();
// ๋ฐ์ดํฐ ์ ์ฅ
df.write()
.mode("overwrite") // append, overwrite, ignore, error
.parquet("output.parquet");
// ํํฐ์
์ ๋๋ ์ ์ ์ฅ (์ฑ๋ฅ ์ต์ ํ!)
df.write()
.partitionBy("year", "month")
.parquet("partitioned_output");
JSON: ๊ตฌ์กฐํ๋ ๋ฐ์ดํฐ, ์ค์ฒฉ ๊ตฌ์กฐ ์ง์, ํ์ง๋ง ์ฉ๋ ํผ
Parquet: ์ปฌ๋ผ ๊ธฐ๋ฐ, ์์ถ ํจ์จ ์ข์, ๋น ๋ฐ์ดํฐ์ ์ต์ โญ
Avro: ํ ๊ธฐ๋ฐ, ์คํค๋ง ์งํ ์ง์, ์คํธ๋ฆฌ๋ฐ์ ์ ํฉ
ORC: Parquet๊ณผ ์ ์ฌ, Hive ์ต์ ํ
๐ญ Spark SQL: SQL๋ก ๋น ๋ฐ์ดํฐ ๋ค๋ฃจ๊ธฐ
SQL์ ์ข์ํ๋ค๋ฉด ์ข์ ์์์ด์ผ! Spark๋ ์์ ํ SQL ์ง์์ ์ ๊ณตํด. DataFrame์ ํ ์ด๋ธ์ฒ๋ผ ๋ค๋ฃฐ ์ ์์ด์ ์ ๋ง ํธ๋ฆฌํด! ๐
๐ Temporary View ์์ฑํ๊ธฐ
// DataFrame์ ์์ ๋ทฐ๋ก ๋ฑ๋ก
df.createOrReplaceTempView("employees");
// SQL ์ฟผ๋ฆฌ ์คํ
Dataset<Row> result = spark.sql(
"SELECT department, AVG(salary) as avg_salary " +
"FROM employees " +
"WHERE age > 25 " +
"GROUP BY department " +
"ORDER BY avg_salary DESC"
);
result.show();
// ๋ณต์กํ ์ฟผ๋ฆฌ๋ ๊ฐ๋ฅ!
Dataset<Row> complexQuery = spark.sql(
"SELECT e.name, e.salary, d.dept_name " +
"FROM employees e " +
"JOIN departments d ON e.dept_id = d.id " +
"WHERE e.salary > (SELECT AVG(salary) FROM employees)"
);
df.createGlobalTempView("global_employees")
์ ๊ทผํ ๋๋ global_temp ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ๋ช ์ํด์ผ ํด:
spark.sql("SELECT * FROM global_temp.global_employees")
๐ง User Defined Functions (UDF)
๋ด์ฅ ํจ์๋ก ๋ถ์กฑํ ๋๋ ์ง์ ํจ์๋ฅผ ๋ง๋ค ์ ์์ด! ๐ ๏ธ
import org.apache.spark.sql.api.java.UDF1;
import org.apache.spark.sql.types.DataTypes;
// UDF ์ ์
spark.udf().register("upperCase",
(String str) -> str.toUpperCase(),
DataTypes.StringType
);
// SQL์์ ์ฌ์ฉ
Dataset<Row> result = spark.sql(
"SELECT name, upperCase(name) as upper_name FROM employees"
);
// DataFrame API์์๋ ์ฌ์ฉ ๊ฐ๋ฅ
import static org.apache.spark.sql.functions.*;
Dataset<Row> dfResult = df.withColumn(
"upper_name",
callUDF("upperCase", col("name"))
);
// ๋ ๋ณต์กํ UDF ์์
spark.udf().register("calculateBonus",
(Double salary, String level) -> {
double multiplier = switch(level) {
case "Senior" -> 0.2;
case "Mid" -> 0.15;
case "Junior" -> 0.1;
default -> 0.05;
};
return salary * multiplier;
},
DataTypes.DoubleType
);
Dataset<Row> bonuses = spark.sql(
"SELECT name, salary, level, " +
"calculateBonus(salary, level) as bonus " +
"FROM employees"
);
โก ์ฑ๋ฅ ์ต์ ํ ์ ๋ต
Spark๋ฅผ ์ด๋ค๊ณ ๋ฌด์กฐ๊ฑด ๋น ๋ฅธ ๊ฑด ์๋์ผ. ์ ๋๋ก ์ต์ ํํด์ผ ์ง์ง ์ฑ๋ฅ์ ๋ผ ์ ์์ด! ๐
์ฌ๊ธฐ์ ์๊ฐํ๋ ๊ธฐ๋ฒ๋ค์ ์ค๋ฌด์์ ์ ๋ง ์ค์ํด. ์ด๊ฑธ ์๊ณ ๋ชจ๋ฅด๊ณ ์ ์ฐจ์ด๊ฐ ์ฒ๋ฆฌ ์๊ฐ์ ๋ช ๋ฐฐ๋ ์ฐจ์ด๋๊ฒ ๋ง๋ค ์ ์๊ฑฐ๋ !
๐ฏ 1. ํํฐ์ ๋ (Partitioning)
๋ฐ์ดํฐ๋ฅผ ์ ์ ํ ๋๋๋ ๊ฒ ์ฑ๋ฅ์ ํต์ฌ์ด์ผ. ํํฐ์ ์ด ๋๋ฌด ๋ง์๋, ๋๋ฌด ์ ์ด๋ ๋ฌธ์ ๊ฐ ๋ผ.
// ํ์ฌ ํํฐ์
์ ํ์ธ
int numPartitions = df.rdd().getNumPartitions();
System.out.println("ํํฐ์
์: " + numPartitions);
// ํํฐ์
์ ์กฐ์
Dataset<Row> repartitioned = df.repartition(100);
// ํํฐ์
์ ์ค์ด๊ธฐ (์
ํ ์์ด)
Dataset<Row> coalesced = df.coalesce(10);
// ํน์ ์ปฌ๋ผ ๊ธฐ์ค์ผ๋ก ํํฐ์
๋
Dataset<Row> partitionedByDept = df.repartition(
col("department")
);
โข ๋๋ฌด ๋ง์ผ๋ฉด: ์ค๋ฒํค๋ ์ฆ๊ฐ, ์ค์ผ์ค๋ง ๋น์ฉ ์์น
โข ๋๋ฌด ์ ์ผ๋ฉด: ๋ณ๋ ฌ์ฑ ์ ํ, ๋ฉ๋ชจ๋ฆฌ ๋ถ์กฑ ์ํ
โข ๊ถ์ฅ ๊ณต์: (์ ์ฒด ๋ฐ์ดํฐ ํฌ๊ธฐ / 128MB) ร 2~3๋ฐฐ
โข ์ฝ์ด ์ ๊ณ ๋ ค: ์ต์ํ ์ฝ์ด ์์ 2~3๋ฐฐ๋ ๋์ด์ผ ํจ
๐พ 2. ์บ์ฑ๊ณผ ํผ์์คํด์ค (Caching & Persistence)
๊ฐ์ ๋ฐ์ดํฐ๋ฅผ ์ฌ๋ฌ ๋ฒ ์ฌ์ฉํ๋ค๋ฉด ์บ์ฑ์ ํ์์ผ! ๐
// ๋ฉ๋ชจ๋ฆฌ์ ์บ์ฑ
df.cache();
// ๋๋
df.persist(StorageLevel.MEMORY_ONLY());
// ๋ฉ๋ชจ๋ฆฌ์ ๋์คํฌ ๋ชจ๋ ์ฌ์ฉ
df.persist(StorageLevel.MEMORY_AND_DISK());
// ์ง๋ ฌํํด์ ์ ์ฅ (๋ฉ๋ชจ๋ฆฌ ์ ์ฝ)
df.persist(StorageLevel.MEMORY_ONLY_SER());
// ์บ์ ํด์
df.unpersist();
โข ๊ฐ์ DataFrame์ ์ฌ๋ฌ ๋ฒ ์ฌ์ฉ
โข ๋ฐ๋ณต์ ์ธ ๋จธ์ ๋ฌ๋ ํ์ต
โข ๋ํํ ๋ฐ์ดํฐ ๋ถ์
์บ์ฑํ์ง ๋ง์์ผ ํ ๋:
โข ํ ๋ฒ๋ง ์ฌ์ฉํ๋ ๋ฐ์ดํฐ
โข ๋ฐ์ดํฐ๊ฐ ๋๋ฌด ์ปค์ ๋ฉ๋ชจ๋ฆฌ ๋ถ์กฑ ์ฐ๋ ค
โข ๊ณ์ฐ ๋น์ฉ์ด ๋ฎ์ ๊ฐ๋จํ ๋ณํ
๐ 3. ๋ธ๋ก๋์บ์คํธ ์กฐ์ธ (Broadcast Join)
์์ ํ ์ด๋ธ๊ณผ ํฐ ํ ์ด๋ธ์ ์กฐ์ธํ ๋ ๋ธ๋ก๋์บ์คํธ๋ฅผ ์ฌ์ฉํ๋ฉด ์์ฒญ๋๊ฒ ๋นจ๋ผ์ ธ! โก
import static org.apache.spark.sql.functions.broadcast;
// ์์ DataFrame์ ๋ธ๋ก๋์บ์คํธ
Dataset<Row> smallDF = spark.read().csv("small_data.csv");
Dataset<Row> largeDF = spark.read().csv("large_data.csv");
// ๋ธ๋ก๋์บ์คํธ ์กฐ์ธ
Dataset<Row> joined = largeDF.join(
broadcast(smallDF),
"key"
);
// ์๋ ๋ธ๋ก๋์บ์คํธ ์๊ณ๊ฐ ์ค์ (๊ธฐ๋ณธ 10MB)
spark.conf().set(
"spark.sql.autoBroadcastJoinThreshold",
"50MB"
);
โข ์ผ๋ฐ์ ์ผ๋ก 100MB ์ดํ ๋ฐ์ดํฐ์ ์ ํฉ
โข ๋๋ฌด ํฐ ๋ฐ์ดํฐ๋ฅผ ๋ธ๋ก๋์บ์คํธํ๋ฉด OutOfMemory ๋ฐ์ ๊ฐ๋ฅ
โข ๋๋ผ์ด๋ฒ ๋ฉ๋ชจ๋ฆฌ๋ ์ถฉ๋ถํด์ผ ํจ
๐จ 4. ๋ฐ์ดํฐ ์คํ ์ฒ๋ฆฌ (Data Skew)
๋ฐ์ดํฐ๊ฐ ํน์ ํํฐ์ ์ ๋ชฐ๋ฆฌ๋ ํ์์ "์คํ"๋ผ๊ณ ํด. ์ด๊ฑธ ํด๊ฒฐํ์ง ์์ผ๋ฉด ์ผ๋ถ ํ์คํฌ๋ง ์์ฒญ ์ค๋ ๊ฑธ๋ ค! ๐ฐ
// ์คํ ํ์ธ
df.groupBy("key").count().orderBy(desc("count")).show();
// ์ํธ ๊ธฐ๋ฒ์ผ๋ก ์คํ ํด๊ฒฐ
import org.apache.spark.sql.functions.*;
Dataset<Row> salted = df.withColumn(
"salted_key",
concat(col("key"), lit("_"),
(rand().multiply(10)).cast("int"))
);
// ์คํ๋ ํค์ ๋ํ ํน๋ณ ์ฒ๋ฆฌ
Dataset<Row> skewedKeys = df.groupBy("key")
.count()
.filter(col("count").gt(1000000));
Dataset<Row> normalData = df.join(
broadcast(skewedKeys),
Seq("key"),
"left_anti"
);
Dataset<Row> skewedData = df.join(
broadcast(skewedKeys),
Seq("key"),
"inner"
);
๐ 5. ํ๋ก์ ์ ํธ์๋ค์ด & ํ๋ฆฌ๋์ผ์ดํธ ํธ์๋ค์ด
ํ์ํ ์ปฌ๋ผ๋ง ์ฝ๊ณ , ํํฐ๋ง์ ์ต๋ํ ๋นจ๋ฆฌ ํ๋ ๊ฒ ํต์ฌ์ด์ผ! ๐ฏ
// โ ๋์ ์: ๋ชจ๋ ์ปฌ๋ผ์ ์ฝ์ ํ ํํฐ๋ง
Dataset<Row> bad = spark.read()
.parquet("large_data.parquet")
.filter(col("age").gt(30))
.select("name", "age");
// โ
์ข์ ์: ํ์ํ ์ปฌ๋ผ๋ง ์ฝ๊ณ ์กฐ๊ธฐ ํํฐ๋ง
Dataset<Row> good = spark.read()
.parquet("large_data.parquet")
.select("name", "age")
.filter(col("age").gt(30));
// Parquet ํ์ผ์ ๊ฒฝ์ฐ ์๋์ผ๋ก ํธ์๋ค์ด๋จ
// ํ์ง๋ง ๋ช
์์ ์ผ๋ก ์์๋ฅผ ์กฐ์ ํ๋ฉด ๋ ์ข์
๐ Spark Streaming: ์ค์๊ฐ ๋ฐ์ดํฐ ์ฒ๋ฆฌ
์ง๊ธ๊น์ง๋ ๋ฐฐ์น ์ฒ๋ฆฌ๋ฅผ ๋ค๋ค์ด. ์ด์ ์ค์๊ฐ ์คํธ๋ฆฌ๋ฐ ๋ฐ์ดํฐ๋ฅผ ์ฒ๋ฆฌํ๋ ๋ฐฉ๋ฒ์ ์์๋ณด์! ๐ฌ
Spark Streaming์ ์ค์๊ฐ์ผ๋ก ๋ค์ด์ค๋ ๋ฐ์ดํฐ๋ฅผ ๋ง์ดํฌ๋ก ๋ฐฐ์น๋ก ์ฒ๋ฆฌํด. ์นดํ์นด, ํ๋ผ, ํค๋ค์์ค ๋ฑ ๋ค์ํ ์์ค์์ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์ ์ ์์ด.
๐ก Structured Streaming ๊ธฐ๋ณธ
import org.apache.spark.sql.streaming.StreamingQuery;
import org.apache.spark.sql.streaming.Trigger;
// ์คํธ๋ฆฌ๋ฐ ์์ค ์ ์ (ํ์ผ ๊ธฐ๋ฐ ์์ )
Dataset<Row> streamingDF = spark.readStream()
.format("csv")
.option("header", "true")
.schema(schema) // ์คํค๋ง ๋ช
์ ํ์!
.load("input_directory/");
// ์คํธ๋ฆฌ๋ฐ ๋ฐ์ดํฐ ์ฒ๋ฆฌ
Dataset<Row> processed = streamingDF
.filter(col("value").gt(100))
.groupBy(
window(col("timestamp"), "10 minutes"),
col("category")
)
.count();
// ๊ฒฐ๊ณผ ์ถ๋ ฅ (์ฝ์)
StreamingQuery query = processed.writeStream()
.outputMode("complete")
.format("console")
.trigger(Trigger.ProcessingTime("10 seconds"))
.start();
query.awaitTermination();
๐ Kafka์ ์ฐ๋ํ๊ธฐ
์ค๋ฌด์์๋ Kafka๋ฅผ ๊ฐ์ฅ ๋ง์ด ์ฌ์ฉํด. ์ฌ๋ฅ๋ท ๊ฐ์ ํ๋ซํผ์์๋ ์ค์๊ฐ ์๋ฆผ์ด๋ ๋ก๊ทธ ์ฒ๋ฆฌ์ ์ด๋ฐ ๊ธฐ์ ์ ํ์ฉํ ์ ์์ด! ๐จ
// Kafka์์ ์ฝ๊ธฐ
Dataset<Row> kafkaDF = spark.readStream()
.format("kafka")
.option("kafka.bootstrap.servers", "localhost:9092")
.option("subscribe", "topic1,topic2")
.option("startingOffsets", "earliest")
.load();
// Kafka ๋ฉ์์ง๋ ๋ฐ์ด๋๋ฆฌ๋ก ์ค๋ฏ๋ก ๋ณํ ํ์
Dataset<Row> parsedDF = kafkaDF
.selectExpr("CAST(key AS STRING)", "CAST(value AS STRING)")
.select(from_json(col("value"), schema).as("data"))
.select("data.*");
// ์ฒ๋ฆฌ ํ ๋ค์ Kafka๋ก ์ฐ๊ธฐ
StreamingQuery kafkaQuery = parsedDF
.selectExpr("CAST(id AS STRING) AS key",
"to_json(struct(*)) AS value")
.writeStream()
.format("kafka")
.option("kafka.bootstrap.servers", "localhost:9092")
.option("topic", "output_topic")
.option("checkpointLocation", "/tmp/checkpoint")
.start();
โข ์๋ก์ด ํ๋ง ์ถ๋ ฅ
โข ์ง๊ณ ์๋ ์ฟผ๋ฆฌ์ ์ ํฉ
โข ๊ฐ์ฅ ๋น ๋ฆ
2. Complete Mode:
โข ์ ์ฒด ๊ฒฐ๊ณผ ํ ์ด๋ธ ์ถ๋ ฅ
โข ์ง๊ณ ์ฟผ๋ฆฌ์ ์ฌ์ฉ
โข ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋ ๋์
3. Update Mode:
โข ๋ณ๊ฒฝ๋ ํ๋ง ์ถ๋ ฅ
โข ์ง๊ณ ์ฟผ๋ฆฌ์ ์ ํฉ
โข Complete์ Append์ ์ค๊ฐ
โฐ ์๋์ฐ ์ฐ์ฐ๊ณผ ์ํฐ๋งํฌ
์๊ฐ ๊ธฐ๋ฐ ์ง๊ณ๋ฅผ ํ ๋๋ ์๋์ฐ์ ์ํฐ๋งํฌ๋ฅผ ์ ์ดํดํด์ผ ํด! โฑ๏ธ
// ์ฌ๋ผ์ด๋ฉ ์๋์ฐ
Dataset<Row> windowedCounts = streamingDF
.withWatermark("timestamp", "10 minutes")
.groupBy(
window(col("timestamp"), "10 minutes", "5 minutes"),
col("word")
)
.count();
// ์ธ์
์๋์ฐ
Dataset<Row> sessionWindow = streamingDF
.withWatermark("timestamp", "30 minutes")
.groupBy(
session_window(col("timestamp"), "5 minutes"),
col("userId")
)
.count();
// ํ
๋ธ๋ง ์๋์ฐ (๊ฒน์น์ง ์์)
Dataset<Row> tumblingWindow = streamingDF
.withWatermark("timestamp", "10 minutes")
.groupBy(
window(col("timestamp"), "10 minutes"),
col("category")
)
.agg(
sum("amount").as("total"),
avg("amount").as("average")
);
โข ๋ฆ๊ฒ ๋์ฐฉํ๋ ๋ฐ์ดํฐ(Late Data) ์ฒ๋ฆฌ
โข ์ํ ์ ์ฅ ํฌ๊ธฐ ์ ํ
โข ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ ์ต์ ํ
์: withWatermark("timestamp", "10 minutes")
โ ํ์ฌ ์ต๋ ์ด๋ฒคํธ ์๊ฐ - 10๋ถ ์ด์ ๋ฐ์ดํฐ๋ ๋ฒ๋ฆผ
๐ค Spark MLlib: ๋จธ์ ๋ฌ๋ ํ์ดํ๋ผ์ธ
Spark๋ ๋จธ์ ๋ฌ๋๋ ์ง์ํด! MLlib๋ฅผ ์ฌ์ฉํ๋ฉด ๋๊ท๋ชจ ๋ฐ์ดํฐ๋ก ๋จธ์ ๋ฌ๋ ๋ชจ๋ธ์ ํ์ตํ ์ ์์ด. ๐ง
๐ง ๋ฐ์ดํฐ ์ ์ฒ๋ฆฌ
import org.apache.spark.ml.feature.*;
import org.apache.spark.ml.Pipeline;
import org.apache.spark.ml.PipelineStage;
// ๋ฌธ์์ด์ ์ซ์๋ก ๋ณํ
StringIndexer indexer = new StringIndexer()
.setInputCol("category")
.setOutputCol("categoryIndex");
// ์-ํซ ์ธ์ฝ๋ฉ
OneHotEncoder encoder = new OneHotEncoder()
.setInputCol("categoryIndex")
.setOutputCol("categoryVec");
// ํน์ฑ ๋ฒกํฐ ์์ฑ
VectorAssembler assembler = new VectorAssembler()
.setInputCols(new String[]{"age", "salary", "categoryVec"})
.setOutputCol("features");
// ํน์ฑ ์ค์ผ์ผ๋ง
StandardScaler scaler = new StandardScaler()
.setInputCol("features")
.setOutputCol("scaledFeatures")
.setWithStd(true)
.setWithMean(true);
๐ ๋ถ๋ฅ ๋ชจ๋ธ ํ์ต
import org.apache.spark.ml.classification.LogisticRegression;
import org.apache.spark.ml.classification.LogisticRegressionModel;
import org.apache.spark.ml.evaluation.BinaryClassificationEvaluator;
// ๋ฐ์ดํฐ ๋ถํ
Dataset<Row>[] splits = df.randomSplit(new double[]{0.8, 0.2}, 42);
Dataset<Row> trainingData = splits[0];
Dataset<Row> testData = splits[1];
// ๋ก์ง์คํฑ ํ๊ท ๋ชจ๋ธ
LogisticRegression lr = new LogisticRegression()
.setMaxIter(10)
.setRegParam(0.01)
.setElasticNetParam(0.8)
.setFeaturesCol("scaledFeatures")
.setLabelCol("label");
// ํ์ดํ๋ผ์ธ ๊ตฌ์ฑ
Pipeline pipeline = new Pipeline()
.setStages(new PipelineStage[]{
indexer, encoder, assembler, scaler, lr
});
// ๋ชจ๋ธ ํ์ต
PipelineModel model = pipeline.fit(trainingData);
// ์์ธก
Dataset<Row> predictions = model.transform(testData);
// ํ๊ฐ
BinaryClassificationEvaluator evaluator =
new BinaryClassificationEvaluator()
.setLabelCol("label")
.setRawPredictionCol("rawPrediction")
.setMetricName("areaUnderROC");
double auc = evaluator.evaluate(predictions);
System.out.println("AUC: " + auc);
๐ฏ ํ์ดํผํ๋ผ๋ฏธํฐ ํ๋
import org.apache.spark.ml.tuning.*;
import org.apache.spark.ml.param.ParamMap;
// ํ๋ผ๋ฏธํฐ ๊ทธ๋ฆฌ๋ ์ ์
ParamGridBuilder paramGrid = new ParamGridBuilder()
.addGrid(lr.regParam(), new double[]{0.01, 0.1, 1.0})
.addGrid(lr.elasticNetParam(), new double[]{0.0, 0.5, 1.0})
.addGrid(lr.maxIter(), new int[]{10, 50, 100});
// ๊ต์ฐจ ๊ฒ์ฆ
CrossValidator cv = new CrossValidator()
.setEstimator(pipeline)
.setEvaluator(evaluator)
.setEstimatorParamMaps(paramGrid.build())
.setNumFolds(5)
.setParallelism(4);
// ์ต์ ๋ชจ๋ธ ์ฐพ๊ธฐ
CrossValidatorModel cvModel = cv.fit(trainingData);
// ์ต์ ํ๋ผ๋ฏธํฐ ํ์ธ
LogisticRegressionModel bestModel =
(LogisticRegressionModel) cvModel.bestModel()
.stages()[cvModel.bestModel().stages().length - 1];
System.out.println("์ต์ RegParam: " + bestModel.getRegParam());
System.out.println("์ต์ ElasticNetParam: " +
bestModel.getElasticNetParam());
ํ๊ท: Linear Regression, Decision Tree Regression, Random Forest Regression, GBT Regression
ํด๋ฌ์คํฐ๋ง: K-Means, Gaussian Mixture, LDA, Bisecting K-Means
ํ์ ํํฐ๋ง: ALS (Alternating Least Squares)
์ฐจ์ ์ถ์: PCA, SVD
๐ ๋๋ฒ๊น ๊ณผ ๋ชจ๋ํฐ๋ง
Spark ์ ํ๋ฆฌ์ผ์ด์ ์ ์ด์ํ๋ค ๋ณด๋ฉด ๋ฌธ์ ๊ฐ ์๊ธฐ๊ธฐ ๋ง๋ จ์ด์ผ. ํจ๊ณผ์ ์ผ๋ก ๋๋ฒ๊น ํ๊ณ ๋ชจ๋ํฐ๋งํ๋ ๋ฐฉ๋ฒ์ ์์๋ณด์! ๐
๐ Spark UI ํ์ฉํ๊ธฐ
Spark๋ ๊ฐ๋ ฅํ ์น UI๋ฅผ ์ ๊ณตํด. ๊ธฐ๋ณธ์ ์ผ๋ก http://localhost:4040์์ ์ ๊ทผํ ์ ์์ด.
2. Stages: ๊ฐ ์คํ ์ด์ง์ ์ธ๋ถ ์ ๋ณด์ ํ์คํฌ
3. Storage: ์บ์๋ RDD/DataFrame ์ ๋ณด
4. Environment: Spark ์ค์ ๋ฐ ํ๊ฒฝ ๋ณ์
5. Executors: Executor ์ํ ๋ฐ ๋ฆฌ์์ค ์ฌ์ฉ๋
6. SQL: SQL ์ฟผ๋ฆฌ ์คํ ๊ณํ ๋ฐ ์ฑ๋ฅ
๐ ์คํ ๊ณํ ๋ถ์
// ๋
ผ๋ฆฌ์ ์คํ ๊ณํ ํ์ธ
df.explain(true);
// ์ถ๋ ฅ ์์:
// == Parsed Logical Plan ==
// == Analyzed Logical Plan ==
// == Optimized Logical Plan ==
// == Physical Plan ==
// ๊ฐ๋จํ ์คํ ๊ณํ
df.explain();
// ์ฝ์คํธ ๊ธฐ๋ฐ ์ต์ ํ ์ ๋ณด ํฌํจ
df.explain("cost");
๐ ๋ก๊น ์ค์
import org.apache.log4j.Logger;
import org.apache.log4j.Level;
// ๋ก๊ทธ ๋ ๋ฒจ ์ค์
Logger.getLogger("org").setLevel(Level.WARN);
Logger.getLogger("akka").setLevel(Level.WARN);
// ์ปค์คํ
๋ก๊ฑฐ
Logger logger = Logger.getLogger(MySparkApp.class);
logger.info("์ ํ๋ฆฌ์ผ์ด์
์์");
logger.warn("๊ฒฝ๊ณ ๋ฉ์์ง");
logger.error("์๋ฌ ๋ฐ์", exception);
โ ๏ธ ์ผ๋ฐ์ ์ธ ๋ฌธ์ ์ ํด๊ฒฐ์ฑ
โข Executor ๋ฉ๋ชจ๋ฆฌ ๋ถ์กฑ
โข ๋๋ฌด ํฐ ๋ฐ์ดํฐ๋ฅผ collect()
โข ํํฐ์ ์ ๋ถ์กฑ
ํด๊ฒฐ์ฑ :
โข Executor ๋ฉ๋ชจ๋ฆฌ ์ฆ๊ฐ: --executor-memory 8g
โข ํํฐ์ ์ ์ฆ๊ฐ: df.repartition(200)
โข collect() ๋์ take() ์ฌ์ฉ
โข ๋ฉ๋ชจ๋ฆฌ ์ค๋ฒํค๋ ์ฆ๊ฐ: spark.executor.memoryOverhead
โข ๋ฐ์ดํฐ ์คํ
โข ๋นํจ์จ์ ์ธ ์กฐ์ธ
โข ๊ณผ๋ํ ์ ํ
ํด๊ฒฐ์ฑ :
โข ์คํ ์ฒ๋ฆฌ (์ํธ ๊ธฐ๋ฒ)
โข ๋ธ๋ก๋์บ์คํธ ์กฐ์ธ ์ฌ์ฉ
โข ํํฐ์ ํค ์ฌ์ค์
โข ์ค๊ฐ ๊ฒฐ๊ณผ ์บ์ฑ
โข ์ ํ ๋ฐ์ดํฐ๊ฐ ๋๋ฌด ํผ
โข ๋คํธ์ํฌ ํ์์์
โข ๋์คํฌ ๊ณต๊ฐ ๋ถ์กฑ
ํด๊ฒฐ์ฑ :
โข ์ ํ ํํฐ์ ์ ์กฐ์ : spark.sql.shuffle.partitions
โข ์์ถ ํ์ฑํ: spark.shuffle.compress=true
โข ํ์์์ ์ฆ๊ฐ: spark.network.timeout
โข ์์ ๋๋ ํ ๋ฆฌ ๊ณต๊ฐ ํ๋ณด
๐ ์ค์ ํ๋ก์ ํธ: ๋ก๊ทธ ๋ถ์ ์์คํ
์ด์ ๋ฐฐ์ด ๋ด์ฉ์ ์ข ํฉํด์ ์ค์ ํ๋ก์ ํธ๋ฅผ ๋ง๋ค์ด๋ณด์! ์น ์๋ฒ ๋ก๊ทธ๋ฅผ ๋ถ์ํ๋ ์์คํ ์ด์ผ. ๐ฏ
๐ ํ๋ก์ ํธ ์๊ตฌ์ฌํญ
๊ธฐ๋ฅ:
โข ์๊ฐ๋๋ณ ํธ๋ํฝ ๋ถ์
โข ์ธ๊ธฐ ํ์ด์ง ์์
โข ์๋ฌ ๋ฐ์ ํจํด ๋ถ์
โข ์ฌ์ฉ์ ํ๋ ๋ถ์
โข ์ค์๊ฐ ๋์๋ณด๋ ์ ๋ฐ์ดํธ
๐ป ์ ์ฒด ์ฝ๋ ๊ตฌํ
import org.apache.spark.sql.*;
import org.apache.spark.sql.types.*;
import static org.apache.spark.sql.functions.*;
public class LogAnalysisSystem {
public static void main(String[] args) {
// SparkSession ์์ฑ
SparkSession spark = SparkSession.builder()
.appName("Log Analysis System")
.config("spark.sql.shuffle.partitions", "200")
.config("spark.sql.adaptive.enabled", "true")
.getOrCreate();
// ๋ก๊ทธ ์คํค๋ง ์ ์
StructType schema = new StructType()
.add("timestamp", DataTypes.TimestampType)
.add("ip", DataTypes.StringType)
.add("method", DataTypes.StringType)
.add("url", DataTypes.StringType)
.add("status", DataTypes.IntegerType)
.add("size", DataTypes.LongType)
.add("referer", DataTypes.StringType)
.add("userAgent", DataTypes.StringType);
// ๋ก๊ทธ ํ์ผ ์ฝ๊ธฐ
Dataset<Row> logs = spark.read()
.schema(schema)
.json("logs/*.json")
.cache(); // ์ฌ๋ฌ ๋ฒ ์ฌ์ฉํ๋ฏ๋ก ์บ์ฑ
// 1. ์๊ฐ๋๋ณ ํธ๋ํฝ ๋ถ์
Dataset<Row> hourlyTraffic = logs
.withColumn("hour", hour(col("timestamp")))
.groupBy("hour")
.agg(
count("*").as("requests"),
sum("size").as("totalBytes"),
countDistinct("ip").as("uniqueUsers")
)
.orderBy("hour");
hourlyTraffic.show();
// 2. ์ธ๊ธฐ ํ์ด์ง Top 10
Dataset<Row> topPages = logs
.groupBy("url")
.agg(
count("*").as("hits"),
avg("size").as("avgSize")
)
.orderBy(desc("hits"))
.limit(10);
topPages.show();
// 3. ์๋ฌ ๋ถ์
Dataset<Row> errors = logs
.filter(col("status").geq(400))
.groupBy("status", "url")
.count()
.orderBy(desc("count"));
errors.show();
// 4. ์ฌ์ฉ์ ์ธ์
๋ถ์
Dataset<Row> userSessions = logs
.withWatermark("timestamp", "30 minutes")
.groupBy(
col("ip"),
session_window(col("timestamp"), "30 minutes")
)
.agg(
count("*").as("pageViews"),
collect_list("url").as("visitedPages"),
min("timestamp").as("sessionStart"),
max("timestamp").as("sessionEnd")
);
// 5. ๊ฒฐ๊ณผ ์ ์ฅ
hourlyTraffic.write()
.mode("overwrite")
.partitionBy("hour")
.parquet("output/hourly_traffic");
topPages.write()
.mode("overwrite")
.json("output/top_pages");
// 6. ์ค์๊ฐ ๋ชจ๋ํฐ๋ง์ ์ํ ์คํธ๋ฆฌ๋ฐ
Dataset<Row> streamingLogs = spark.readStream()
.schema(schema)
.json("streaming_logs/");
Dataset<Row> realtimeStats = streamingLogs
.withWatermark("timestamp", "10 minutes")
.groupBy(
window(col("timestamp"), "1 minute"),
col("status")
)
.count();
StreamingQuery query = realtimeStats.writeStream()
.outputMode("update")
.format("console")
.trigger(Trigger.ProcessingTime("10 seconds"))
.start();
query.awaitTermination();
spark.close();
}
}
๐ ์ฑ๋ฅ ์ต์ ํ ์ ์ฉ
// ์ต์ ํ๋ ๋ฒ์
public class OptimizedLogAnalysis {
public static Dataset<Row> analyzeWithOptimization(
SparkSession spark, String inputPath) {
// 1. ํํฐ์
ํ๋ฃจ๋์ ์ํ ํํฐ์
์ปฌ๋ผ ํ์ฉ
Dataset<Row> logs = spark.read()
.parquet(inputPath)
.filter(col("date").geq("2024-01-01")) // ํํฐ์
ํํฐ
.repartition(200, col("ip")); // ์คํ ๋ฐฉ์ง
// 2. ๋ธ๋ก๋์บ์คํธ ์กฐ์ธ์ผ๋ก IP ์ ๋ณด ์ถ๊ฐ
Dataset<Row> ipInfo = spark.read()
.parquet("ip_database.parquet")
.select("ip", "country", "city");
Dataset<Row> enriched = logs.join(
broadcast(ipInfo),
"ip"
);
// 3. ์ค๊ฐ ๊ฒฐ๊ณผ ์บ์ฑ
enriched.cache();
// 4. ์ง๊ณ ์ต์ ํ
Dataset<Row> stats = enriched
.groupBy("country", "hour")
.agg(
count("*").as("requests"),
approx_count_distinct("ip", 0.05).as("uniqueUsers"), // ๊ทผ์ฌ์น
sum("size").as("totalBytes")
);
// 5. ๊ฒฐ๊ณผ๋ฅผ ํํฐ์
๋ณ๋ก ์ ์ฅ
stats.write()
.mode("overwrite")
.partitionBy("country")
.parquet("output/stats_by_country");
return stats;
}
}
๐ ๋ฒ ์คํธ ํ๋ํฐ์ค์ ํ
์ค๋ฌด์์ Spark๋ฅผ ์ฌ์ฉํ๋ฉด์ ์ป์ ๋ ธํ์ฐ๋ค์ ๊ณต์ ํ ๊ฒ! ๐ก
โข ํํฐ์ ๋: ์์ฃผ ํํฐ๋งํ๋ ์ปฌ๋ผ์ผ๋ก ํํฐ์
โข ๋ฒํทํ : ์กฐ์ธ์ด ๋ง๋ค๋ฉด ๋ฒํทํ ๊ณ ๋ ค
โข ์์ถ: Snappy (์๋) vs Gzip (์์ถ๋ฅ )
โข Driver ๋ฉ๋ชจ๋ฆฌ: collect() ์ฌ์ฉ ์ ์ถฉ๋ถํ ํ ๋น
โข ์ค๋ฒํค๋: ์ ์ฒด ๋ฉ๋ชจ๋ฆฌ์ 10-15% ์์ฝ
โข ์บ์ฑ ์ ๋ต: ์ ๋ง ํ์ํ ๊ฒ๋ง ์บ์ฑ
โข ์กฐ๊ธฐ ํํฐ๋ง: ๊ฐ๋ฅํ ๋นจ๋ฆฌ ๋ฐ์ดํฐ ์ค์ด๊ธฐ
โข ํ์ ์์ ์ฑ: Dataset API ํ์ฉ (Java/Scala)
โข UDF ์ต์ํ: ๋ด์ฅ ํจ์๊ฐ ํจ์ฌ ๋น ๋ฆ
โข ์ถ์ธก ์คํ: ๋๋ฆฐ ํ์คํฌ ์ฌ์คํ
โข ์์ถ: ๋คํธ์ํฌ ํธ๋ํฝ ๊ฐ์
โข ์ง๋ ฌํ: Kryo ์ฌ์ฉ ๊ถ์ฅ
โ๏ธ ์ถ์ฒ ์ค์ ๊ฐ
SparkConf conf = new SparkConf()
// ๊ธฐ๋ณธ ์ค์
.set("spark.app.name", "MyApp")
.set("spark.master", "yarn")
// ๋ฉ๋ชจ๋ฆฌ ์ค์
.set("spark.executor.memory", "8g")
.set("spark.executor.memoryOverhead", "1g")
.set("spark.driver.memory", "4g")
// ์ฝ์ด ์ค์
.set("spark.executor.cores", "4")
.set("spark.executor.instances", "10")
// ์
ํ ์ต์ ํ
.set("spark.sql.shuffle.partitions", "200")
.set("spark.sql.adaptive.enabled", "true")
.set("spark.sql.adaptive.coalescePartitions.enabled", "true")
// ์์ถ
.set("spark.sql.parquet.compression.codec", "snappy")
.set("spark.shuffle.compress", "true")
// ์ง๋ ฌํ
.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer")
.set("spark.kryo.registrationRequired", "false")
// ๋์ ํ ๋น
.set("spark.dynamicAllocation.enabled", "true")
.set("spark.dynamicAllocation.minExecutors", "2")
.set("spark.dynamicAllocation.maxExecutors", "20")
// ์ถ์ธก ์คํ
.set("spark.speculation", "true")
.set("spark.speculation.multiplier", "1.5");
๐ ํด๋ผ์ฐ๋ ํ๊ฒฝ์์์ Spark
์์ฆ์ ํด๋ผ์ฐ๋์์ Spark๋ฅผ ๋ง์ด ์ฌ์ฉํด. ๊ฐ ํด๋ผ์ฐ๋ ํ๋ซํผ์ ํน์ง์ ์์๋ณด์! โ๏ธ
โ๏ธ AWS EMR (Elastic MapReduce)
โข S3์ ๊ธด๋ฐํ ํตํฉ
โข ์คํ ์ธ์คํด์ค๋ก ๋น์ฉ ์ ๊ฐ
โข EMR Studio๋ก ๋ ธํธ๋ถ ํ๊ฒฝ ์ ๊ณต
์ฌ์ฉ ์์:
spark.read().parquet("s3://my-bucket/data/")
โ๏ธ Google Cloud Dataproc
โข BigQuery์ ํตํฉ
โข ์ด ๋จ์ ๊ณผ๊ธ
โข ์๋ ์ค์ผ์ผ๋ง
์ฌ์ฉ ์์:
spark.read().format("bigquery").load("project.dataset.table")
โ๏ธ Azure HDInsight / Databricks
โข Databricks๋ Spark ์ฐฝ์์๊ฐ ๋ง๋ ํ๋ซํผ
โข ํ์ ๋ ธํธ๋ถ ํ๊ฒฝ
โข Delta Lake ์ง์
์ฌ์ฉ ์์:
spark.read().format("delta").load("/mnt/delta/table")
๐ฏ ๋ง์น๋ฉฐ
์! ์ฌ๊ธฐ๊น์ง ์ ๋ง ๊ธด ์ฌ์ ์ด์์ด. ๐
์ฐ๋ฆฌ๋ Apache Spark์ ๊ธฐ์ด๋ถํฐ ์ค์ ํ์ฉ๊น์ง ๋ชจ๋ ๊ฒ์ ๋ค๋ค์ด. RDD, DataFrame, SQL, Streaming, MLlib, ๊ทธ๋ฆฌ๊ณ ์ฑ๋ฅ ์ต์ ํ๊น์ง... ์ ๋ง ๋ง์ ๋ด์ฉ์ด์์ง? ๐
Spark๋ ๋น
๋ฐ์ดํฐ ์ฒ๋ฆฌ์ ์ฌ์ค์ ํ์ค์ด ๋์์ด. ์ฌ๋ฅ๋ท ๊ฐ์ ํ๋ซํผ์์๋ ๋์ฉ๋ ์ฌ์ฉ์ ๋ฐ์ดํฐ๋ฅผ ๋ถ์ํ๊ฑฐ๋, ์ถ์ฒ ์์คํ
์ ๊ตฌ์ถํ๊ฑฐ๋, ์ค์๊ฐ ์๋ฆผ์ ์ฒ๋ฆฌํ ๋ ์ด๋ฐ ๊ธฐ์ ๋ค์ด ํ์ฉ๋ ์ ์์ด.
์ฒ์์๋ ๋ณต์กํด ๋ณด์ผ ์ ์์ง๋ง, ํ๋์ฉ ์ฐจ๊ทผ์ฐจ๊ทผ ์ตํ๋ค ๋ณด๋ฉด ์ด๋์ Spark ๋ง์คํฐ๊ฐ ๋์ด ์์ ๊ฑฐ์ผ! ๐
2๋จ๊ณ (2-3์ฃผ): DataFrame๊ณผ Spark SQL ๋ง์คํฐ
3๋จ๊ณ (2-3์ฃผ): ์ฑ๋ฅ ์ต์ ํ ๊ธฐ๋ฒ ํ์ต
4๋จ๊ณ (3-4์ฃผ): Streaming๊ณผ MLlib ์ค์ต
5๋จ๊ณ (๊ณ์): ์ค์ ํ๋ก์ ํธ์ ๋ฌธ์ ํด๊ฒฐ
๊พธ์คํ ์ค์ตํ๊ณ , ์ปค๋ฎค๋ํฐ์ ์ฐธ์ฌํ๊ณ , ์ต์ ํธ๋ ๋๋ฅผ ๋ฐ๋ผ๊ฐ๋ ๊ฒ ์ค์ํด!
โข GitHub: github.com/apache/spark
โข Stack Overflow: Spark ํ๊ทธ๋ก ์ง๋ฌธ/๋ต๋ณ
โข Databricks Blog: ์ต์ ๊ธฐ์ ๊ณผ ๋ฒ ์คํธ ํ๋ํฐ์ค
โข Spark Summit: ์ฐ๋ก ์ปจํผ๋ฐ์ค ์์
โข ์ฌ๋ฅ๋ท: ๋น ๋ฐ์ดํฐ ์ ๋ฌธ๊ฐ์ ์ง์ ๊ณต์ ๊ฐ๋ฅ!
Happy Sparking! ๐
๋น
๋ฐ์ดํฐ์ ์ธ๊ณ์์ ์ฌ๋ฌ๋ถ์ ์ฑ๊ณต์ ์์ํฉ๋๋ค!
๊ด๋ จ ํค์๋
๋๊ธ ์์ฑ
์ด ๊ธ์ ๋ํ ์ฌ๋ฌ๋ถ์ ์๊ฐ์ ๋ค๋ ค์ฃผ์ธ์
๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค
๋๊ธ์ ์์ฑํ๋ ค๋ฉด ๋จผ์ ๋ก๊ทธ์ธํด์ฃผ์ธ์.
๋๊ธ 0
์ง์์ธ์ ์ฒ - ์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
- ์ ์๊ถ ๋ฐ ์์ ๊ถ: ๋ณธ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ๋ ์ AI ๊ธฐ์ ๋ก ์์ฑ๋์์ผ๋ฉฐ, ๋ํ๋ฏผ๊ตญ ์ ์๊ถ๋ฒ ๋ฐ ๊ตญ์ ์ ์๊ถ ํ์ฝ์ ์ํด ๋ณดํธ๋ฉ๋๋ค.
- AI ์์ฑ ์ปจํ ์ธ ์ ๋ฒ์ ์ง์: ๋ณธ AI ์์ฑ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ์ง์ ์ฐฝ์๋ฌผ๋ก ์ธ์ ๋๋ฉฐ, ๊ด๋ จ ๋ฒ๊ท์ ๋ฐ๋ผ ์ ์๊ถ ๋ณดํธ๋ฅผ ๋ฐ์ต๋๋ค.
- ์ฌ์ฉ ์ ํ: ์ฌ๋ฅ๋ท์ ๋ช ์์ ์๋ฉด ๋์ ์์ด ๋ณธ ์ปจํ ์ธ ๋ฅผ ๋ณต์ , ์์ , ๋ฐฐํฌ, ๋๋ ์์ ์ ์ผ๋ก ํ์ฉํ๋ ํ์๋ ์๊ฒฉํ ๊ธ์ง๋ฉ๋๋ค.
- ๋ฐ์ดํฐ ์์ง ๊ธ์ง: ๋ณธ ์ปจํ ์ธ ์ ๋ํ ๋ฌด๋จ ์คํฌ๋ํ, ํฌ๋กค๋ง, ๋ฐ ์๋ํ๋ ๋ฐ์ดํฐ ์์ง์ ๋ฒ์ ์ ์ฌ์ ๋์์ด ๋ฉ๋๋ค.
- AI ํ์ต ์ ํ: ์ฌ๋ฅ๋ท์ AI ์์ฑ ์ปจํ ์ธ ๋ฅผ ํ AI ๋ชจ๋ธ ํ์ต์ ๋ฌด๋จ ์ฌ์ฉํ๋ ํ์๋ ๊ธ์ง๋๋ฉฐ, ์ด๋ ์ง์ ์ฌ์ฐ๊ถ ์นจํด๋ก ๊ฐ์ฃผ๋ฉ๋๋ค.
์์ง ๋๊ธ์ด ์์ต๋๋ค.