🍞

같은 데이터프레임이 서로 다른 값을 가지는 현상

스파크 애플리케이션에서 데이터프레임(Dataframe)을 사용하여 데이터를 처리하고 저장한다. 이번에 작업한 스파크 애플리케이션에서는 같은 데이터프레임이 서로 다른 값을 가지는 현상이 발생했다. 같은 데이터프레임이 어떻게 서로 다른 값을 가질 수 있을까. 어떻게 보면 같은 데이터프레임라고 했지만 실제는 같은 데이터프레임이 아니다. 그러나 적어도 나에게는 같은 데이터프레임처럼 보였다. 

이번에 데이터를 처리할 때, 랜덤 값을 만드는 부분이 있었다. 무작위의 값을 만들기 위해 UDF(User Defined Function)을 정의 했다.

12
val generateRandom
= org.apache.spark.sql.functions.udf((id: String) = scala.util.Random.shuffle(id.toltst).mkString)

generateRandom  UDF는 하나의 문자열을 입력 받아 그 문자열의 값을 가지고 랜덤하게 셔플하는 작업이다.

이 UDF를 가지고 아래와 같이 두 개의 데이터프레임을 만들었다.

123456
val test = spark.CreateDataFrame(sc.parallelize(Seq(Row("1734856123"), Row("781623748"), Row("5498203124"),
new StructType().add(StructField("id", StringType)))
val afterRandomized = test.withColumn(colName = "randomized", generateRandom(test("id")))
val afterRandomizedWithOther = afterRandomized.withColumn(colName = "other", org.apache.spark.sql.functions.Lit(Literal = "OTHER"))

afterRandomized 와 afterRandomizedWithOther  를 보자. 먼저 test 라는 테스트를 위한 데이터프레임을 만들고 이 테스트 데이터프레임을 이용하여 afterRandomized 랜덤 값을 가지는 데이터프레임을 만들었다. 그리고 afterRandomized 를 가지고 어떤 다른 값을 추가로 가진 afterRandomizedWithOther 도 만들었다. 

나는 당연하게도 afterRandomizedWithOther 는 afterRandomized 로부터 나온 데이터프레임이기 때문에 두 데이터프레임이 가진 랜덤 값이 같을 것이라고 생각했다. 그러나 그렇지 않았다.

123456789
|id         | randomized|
|1734856123 | 5171283346|
|781623748  | 723418768 |
|5498203124 | 4103259824|
|id         | randomized| other|
|1734856123 | 8512637314| OTHER|
|781623748  | 376848127 | OTHER|
|5498203124 | 9220438154| OTHER|

실제 데이터를 보자. 두 개의 데이터프레임 가진 randomized  값이 같은가, 아니다. 그러면 왜 아닐까. 사실 하나의 데이터프레임에서 또 다른 하나의 데이터프레임을 만들었다고 생각하지만 데이터프레임을 자체의 데이터를 만든 것이  아니다. 

대신에 해당 데이터프레임이 만들어지는 과정을 만들었다고 보는 것이 편하다. afterRandomized  만들어지는 과정은 test 데이터프레임으로부터 generateRandom 라는 UDF를 수행하는 과정이다.  마찬가지로afterRandomizedWithOther 만들어지는 과정은 test 데이터프레임으로부터 generateRandom 라는 UDF를 수행하고 .withColumn(colName = "other", org.apache.spark.sql.functions.Lit(Literal = "OTHER")) 을 수행하는 과정이다. 두 개의 데이터프레임이 비슷하게 보이지만 서로 독립적으로 자신이 가진 과정을 거쳐 값을 가지게 된다. 그렇다면 언제 그 값을 가지게 될까. 

데이터프레임이 데이터를 읽고, 처리하고, 결과로 만들어지는 순간은 다양하게 있을 수 있다. 쉽게 생각한다면 데이터프레임을 저장소에 데이터를 저장하거나 데이터의 개수를 세는 작업을 진행할 때 데이터가 결정된다. 위의 예에서도 화면에 해당 데이터프레임을 출력할 때, 각각 데이터프레임이 처리되어지고 데이터가 결정되어 화면에 보이는 것이다.

같아 보이는 두 데이터프레임이 같은 값을 가지게 만들기

서로 같아 보이지만 달랐던 두 데이터프레임이 서로 같은 값을 가지게 만들 수는 없을까. 앞서 이해한 상황을 바탕으로 해당 코드를 다시 살펴보자. 두 데이터프레임이 test 데이터프레임부터 generateRandom UDF까지의 처리 과정을 이미 진행한 후 데이터 결과를 얻고 이 데이터 결과를 서로 같이 가져가서 사용하면 어떨까? 그러니깐 랜덤 값을 먼저 만들고 이를 사용하게 만드는 것이다. 아래 코드를 보자.

12
val afterRandomized = test.withColumn(colName = "randomized", generateRandom(test("id")))
.persist()

persist() 는 해당 데이터프레임을 메모리나 디스크에 임시로 데이터를 적재한다. 이 과정에서 랜덤 값을 이미 결정해서 적재해야만 한다. 왜냐하면 데이터를 모른채로 데이터를 적재할 수 없으니깐. 그 다음에 해당 데이터를 필요한 시점에 앞서 적재한 데이터를 가져온다. 그러니깐 여기서는 두 데이터프레임을 화면에 출력할 때, persist() 로 적재한 데이터를 가져와서 처리한다. 이렇게 서로 다르게 보였던 값을 하나의 데이터를 만들어 처리할 수 있다.

반성

개발을 오래하지 않았지만 어느덧 6년이 넘었다. 2.3년차에 코더와 소프트웨어 개발자의 차이가 무엇일까, 고민한 적이 있었다. 많은 측면에서 차이가 있지만 그 중 한 측면에서는 코더는 동작하는 코드를 만드는데 중점을 둔다. 이에 반해 개발자는 동작하는 코드는 물론이고 어떻게 동작하고 왜 이렇게 동작해야하는지 설명할 수 있어야 한다. 아직 위의 설명도 많이 부족하지만, 그래도 코더가 아닌 소프트웨어 개발자로 가기 위함이다. 코드는 거짓말하지 않는다. 항상 사람이 실수할 뿐이다.