Delta Lake ์ž์„ธํžˆ ์•Œ์•„๋ณด๊ธฐ: ์Šคํ‚ค๋งˆ ์ ์šฉ ๋ฐ ์ง„ํ™”

ํ—ค์ด ํ•˜๋ธŒ๋ฅด! ๋‚˜๋Š” ๊ธฐ์‚ฌ์˜ ๋ฒˆ์—ญ์„ ๋‹น์‹ ์˜ ๊ด€์‹ฌ์— ์ œ์‹œํ•ฉ๋‹ˆ๋‹ค "Delta Lake๋กœ ๋‹ค์ด๋น™: ์Šคํ‚ค๋งˆ ์ ์šฉ ๋ฐ ์ง„ํ™”" ๊ณผ์ • ์‹œ์ž‘์„ ์˜ˆ์ƒํ•˜์—ฌ ์ค€๋น„ํ•œ ์ €์ž Burak Yavuz, Brenner Heintz ๋ฐ Denny Lee ๋ฐ์ดํ„ฐ ์—”์ง€๋‹ˆ์–ด ์˜คํˆฌ์Šค์—์„œ.

Delta Lake ์ž์„ธํžˆ ์•Œ์•„๋ณด๊ธฐ: ์Šคํ‚ค๋งˆ ์ ์šฉ ๋ฐ ์ง„ํ™”

์šฐ๋ฆฌ์˜ ๊ฒฝํ—˜๊ณผ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ๋ฐ์ดํ„ฐ๋Š” ์ง€์†์ ์œผ๋กœ ์ถ•์ ๋˜๊ณ  ์ง„ํ™”ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์žก๊ธฐ ์œ„ํ•ด ์„ธ๊ณ„์— ๋Œ€ํ•œ ์šฐ๋ฆฌ์˜ ์ •์‹  ๋ชจ๋ธ์€ ์ƒˆ๋กœ์šด ๋ฐ์ดํ„ฐ์— ์ ์‘ํ•ด์•ผ ํ•˜๋ฉฐ, ๊ทธ ์ค‘ ์ผ๋ถ€๋Š” ์ƒˆ๋กœ์šด ์ฐจ์›, ์ฆ‰ ์šฐ๋ฆฌ๊ฐ€ ์ด์ „์— ์ „ํ˜€ ๋ชฐ๋ž๋˜ ๊ฒƒ์„ ๊ด€์ฐฐํ•˜๋Š” ์ƒˆ๋กœ์šด ๋ฐฉ๋ฒ•์„ ํฌํ•จํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ์ •์‹  ๋ชจ๋ธ์€ ์šฐ๋ฆฌ๊ฐ€ ์ƒˆ๋กœ์šด ์ •๋ณด๋ฅผ ๋ถ„๋ฅ˜ํ•˜๊ณ  ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ •์˜ํ•˜๋Š” ํ…Œ์ด๋ธ” ์Šคํ‚ค๋งˆ์™€ ํฌ๊ฒŒ ๋‹ค๋ฅด์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์ด๋กœ ์ธํ•ด ์Šคํ‚ค๋งˆ ๊ด€๋ฆฌ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ์‹œ๊ฐ„์ด ์ง€๋‚จ์— ๋”ฐ๋ผ ๋น„์ฆˆ๋‹ˆ์Šค ๋ชฉํ‘œ์™€ ์š”๊ตฌ ์‚ฌํ•ญ์ด ๋ณ€๊ฒฝ๋จ์— ๋”ฐ๋ผ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ๋„ ๋ณ€๊ฒฝ๋ฉ๋‹ˆ๋‹ค. Delta Lake๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ฐ์ดํ„ฐ๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ ์ƒˆ๋กœ์šด ์ธก์ •์„ ์‰ฝ๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๋Š” ๊ฐ„๋‹จํ•œ ์˜๋ฏธ ์ฒด๊ณ„์— ์•ก์„ธ์Šคํ•˜์—ฌ ํ…Œ์ด๋ธ” ์Šคํ‚ค๋งˆ๋ฅผ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๋„๊ตฌ์—๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ์‹ค์ˆ˜๋กœ ์˜ค๋ฅ˜๋‚˜ ๋ถˆํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋กœ ํ…Œ์ด๋ธ”์„ ์˜ค์—ผ์‹œํ‚ค๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•˜๋Š” Schema Enforcement์™€ ์ค‘์š”ํ•œ ๋ฐ์ดํ„ฐ์˜ ์ƒˆ ์—ด์„ ์ ์ ˆํ•œ ์œ„์น˜์— ์ž๋™์œผ๋กœ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋Š” Schema Evolution์ด ํฌํ•จ๋ฉ๋‹ˆ๋‹ค. ์ด ๊ธฐ์‚ฌ์—์„œ๋Š” ์ด๋Ÿฌํ•œ ๋„๊ตฌ์˜ ์‚ฌ์šฉ์— ๋Œ€ํ•ด ์ž์„ธํžˆ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค.

ํ…Œ์ด๋ธ” ์Šคํ‚ค๋งˆ ์ดํ•ด

Apache Spark์˜ ๊ฐ DataFrame์—๋Š” ๋ฐ์ดํ„ฐ ์œ ํ˜•, ์—ด ๋ฐ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ์™€ ๊ฐ™์€ ๋ฐ์ดํ„ฐ์˜ ํ˜•ํƒœ๋ฅผ ์ •์˜ํ•˜๋Š” ์Šคํ‚ค๋งˆ๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. Delta Lake๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ํ…Œ์ด๋ธ” ์Šคํ‚ค๋งˆ๊ฐ€ ํŠธ๋žœ์žญ์…˜ ๋กœ๊ทธ ๋‚ด์— JSON ํ˜•์‹์œผ๋กœ ์ €์žฅ๋ฉ๋‹ˆ๋‹ค.

์Šคํ‚ค๋งˆ ์ ์šฉ์ด๋ž€ ๋ฌด์—‡์ž…๋‹ˆ๊นŒ?

์Šคํ‚ค๋งˆ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ผ๊ณ ๋„ ํ•˜๋Š” ์Šคํ‚ค๋งˆ ์ ์šฉ์€ ํ…Œ์ด๋ธ” ์Šคํ‚ค๋งˆ์™€ ์ผ์น˜ํ•˜์ง€ ์•Š๋Š” ๋ ˆ์ฝ”๋“œ๋ฅผ ๊ฑฐ๋ถ€ํ•˜์—ฌ ๋ฐ์ดํ„ฐ ํ’ˆ์งˆ์„ ๋ณด์žฅํ•˜๋Š” Delta Lake์˜ ๋ณดํ˜ธ ๋ฉ”์ปค๋‹ˆ์ฆ˜์ž…๋‹ˆ๋‹ค. ์˜ˆ์•ฝ๋งŒ ๋ฐ›๋Š” ์ธ๊ธฐ ๋ ˆ์Šคํ† ๋ž‘์˜ ์—ฌ์ฃผ์ธ์ฒ˜๋Ÿผ ๊ทธ๋Š” ํ…Œ์ด๋ธ”์— ์ž…๋ ฅ๋œ ๋ฐ์ดํ„ฐ์˜ ๊ฐ ์—ด์ด ํ•ด๋‹น ์˜ˆ์ƒ ์—ด ๋ชฉ๋ก์— ์žˆ๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค(์ฆ‰, "์˜ˆ์•ฝ"์ด ์žˆ๋Š” ๊ฒฝ์šฐ). ๊ฐ๊ฐ), ๋ชฉ๋ก์— ์—†๋Š” ์—ด์ด ์žˆ๋Š” ํ•ญ๋ชฉ์€ ๋ชจ๋‘ ๊ฑฐ๋ถ€ํ•ฉ๋‹ˆ๋‹ค.

์Šคํ‚ค๋งˆ ์ ์šฉ์€ ์–ด๋–ป๊ฒŒ ์ž‘๋™ํ•ฉ๋‹ˆ๊นŒ?

Delta Lake๋Š” ์“ฐ๊ธฐ ์‹œ ์Šคํ‚ค๋งˆ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์ฆ‰, ํ…Œ์ด๋ธ”์— ๋Œ€ํ•œ ๋ชจ๋“  ์ƒˆ ์“ฐ๊ธฐ๋Š” ์“ฐ๊ธฐ ์‹œ๊ฐ„์— ๋Œ€์ƒ ํ…Œ์ด๋ธ”์˜ ์Šคํ‚ค๋งˆ์™€์˜ ํ˜ธํ™˜์„ฑ์„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. ์Šคํ‚ค๋งˆ๊ฐ€ ์ผ์น˜ํ•˜์ง€ ์•Š์œผ๋ฉด Delta Lake๋Š” ํŠธ๋žœ์žญ์…˜์„ ์™„์ „ํžˆ ๋˜๋Œ๋ฆฌ๊ณ (๋ฐ์ดํ„ฐ๊ฐ€ ๊ธฐ๋ก๋˜์ง€ ์•Š์Œ) ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œ์ผœ ์‚ฌ์šฉ์ž์—๊ฒŒ ๋ถˆ์ผ์น˜๋ฅผ ์•Œ๋ฆฝ๋‹ˆ๋‹ค.
Delta Lake๋Š” ๋‹ค์Œ ๊ทœ์น™์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ ˆ์ฝ”๋“œ๊ฐ€ ํ…Œ์ด๋ธ”๊ณผ ํ˜ธํ™˜๋˜๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. ์ž‘์„ฑ๋œ DataFrame:

  • ๋Œ€์ƒ ํ…Œ์ด๋ธ”์˜ ์Šคํ‚ค๋งˆ์— ์—†๋Š” ์ถ”๊ฐ€ ์—ด์„ ํฌํ•จํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๋ฐ˜๋Œ€๋กœ ๋“ค์–ด์˜ค๋Š” ๋ฐ์ดํ„ฐ์— ํ…Œ์ด๋ธ”์˜ ๋ชจ๋“  ์—ด์ด ํฌํ•จ๋˜์–ด ์žˆ์ง€ ์•Š์œผ๋ฉด ๋ชจ๋“  ๊ฒƒ์ด ์ •์ƒ์ž…๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ์—ด์—๋Š” ๋‹จ์ˆœํžˆ XNUMX ๊ฐ’์ด ํ• ๋‹น๋ฉ๋‹ˆ๋‹ค.
  • ๋Œ€์ƒ ํ…Œ์ด๋ธ”์˜ ์—ด ๋ฐ์ดํ„ฐ ์œ ํ˜•๊ณผ ๋‹ค๋ฅธ ์—ด ๋ฐ์ดํ„ฐ ์œ ํ˜•์„ ๊ฐ€์งˆ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๋Œ€์ƒ ํ…Œ์ด๋ธ”์˜ ์—ด์— StringType ๋ฐ์ดํ„ฐ๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์ง€๋งŒ DataFrame์˜ ํ•ด๋‹น ์—ด์— IntegerType ๋ฐ์ดํ„ฐ๊ฐ€ ํฌํ•จ๋œ ๊ฒฝ์šฐ ์Šคํ‚ค๋งˆ ์‹œํ–‰์—์„œ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜๊ณ  ์“ฐ๊ธฐ ์ž‘์—…์ด ์ˆ˜ํ–‰๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • ๋Œ€์†Œ๋ฌธ์ž๋งŒ ๋‹ค๋ฅธ ์—ด ์ด๋ฆ„์„ ํฌํ•จํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ์ด๋Š” ๋™์ผํ•œ ํ…Œ์ด๋ธ”์— ์ •์˜๋œ 'Foo' ๋ฐ 'foo'๋ผ๋Š” ์—ด์„ ๊ฐ€์งˆ ์ˆ˜ ์—†์Œ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. Spark๋Š” ๋Œ€/์†Œ๋ฌธ์ž๋ฅผ ๊ตฌ๋ถ„ํ•˜๊ฑฐ๋‚˜ ๊ตฌ๋ถ„ํ•˜์ง€ ์•Š๋Š”(๊ธฐ๋ณธ๊ฐ’) ๋ชจ๋“œ์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์ง€๋งŒ Delta Lake๋Š” ๋Œ€/์†Œ๋ฌธ์ž๋ฅผ ์œ ์ง€ํ•˜์ง€๋งŒ ์Šคํ‚ค๋งˆ ์ €์žฅ์†Œ ๋‚ด์—์„œ๋Š” ๊ตฌ๋ถ„ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. Parquet๋Š” ์—ด ์ •๋ณด๋ฅผ ์ €์žฅํ•˜๊ณ  ๋ฐ˜ํ™˜ํ•  ๋•Œ ๋Œ€์†Œ๋ฌธ์ž๋ฅผ ๊ตฌ๋ถ„ํ•ฉ๋‹ˆ๋‹ค. ๊ฐ€๋Šฅํ•œ ์˜ค๋ฅ˜, ๋ฐ์ดํ„ฐ ์†์ƒ ๋˜๋Š” ๋ฐ์ดํ„ฐ ์†์‹ค(Databricks์—์„œ ๊ฐœ์ธ์ ์œผ๋กœ ๊ฒฝํ—˜ํ•œ)์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ์ด ์ œํ•œ์„ ์ถ”๊ฐ€ํ•˜๊ธฐ๋กœ ๊ฒฐ์ •ํ–ˆ์Šต๋‹ˆ๋‹ค.

์ด๋ฅผ ์„ค๋ช…ํ•˜๊ธฐ ์œ„ํ•ด ์ƒˆ๋กœ ์ƒ์„ฑ๋œ ์—ด์„ ํ—ˆ์šฉํ•˜๋„๋ก ์•„์ง ๊ตฌ์„ฑ๋˜์ง€ ์•Š์€ Delta Lake ํ…Œ์ด๋ธ”์— ์ถ”๊ฐ€ํ•˜๋ ค๊ณ  ํ•  ๋•Œ ์•„๋ž˜ ์ฝ”๋“œ์—์„œ ์–ด๋–ค ์ผ์ด ๋ฐœ์ƒํ•˜๋Š”์ง€ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

# ะกะณะตะฝะตั€ะธั€ัƒะตะผ DataFrame ัััƒะด, ะบะพั‚ะพั€ั‹ะน ะผั‹ ะดะพะฑะฐะฒะธะผ ะฒ ะฝะฐัˆัƒ ั‚ะฐะฑะปะธั†ัƒ Delta Lake
loans = sql("""
            SELECT addr_state, CAST(rand(10)*count as bigint) AS count,
            CAST(rand(10) * 10000 * count AS double) AS amount
            FROM loan_by_state_delta
            """)

# ะ’ั‹ะฒะตัั‚ะธ ะธัั…ะพะดะฝัƒัŽ ัั…ะตะผัƒ DataFrame
original_loans.printSchema()

root
  |-- addr_state: string (nullable = true)
  |-- count: integer (nullable = true)
 
# ะ’ั‹ะฒะตัั‚ะธ ะฝะพะฒัƒัŽ ัั…ะตะผัƒ DataFrame
loans.printSchema()
 
root
  |-- addr_state: string (nullable = true)
  |-- count: integer (nullable = true)
  |-- amount: double (nullable = true) # new column
 
# ะŸะพะฟั‹ั‚ะบะฐ ะดะพะฑะฐะฒะธั‚ัŒ ะฝะพะฒั‹ะน DataFrame (ั ะฝะพะฒั‹ะผ ัั‚ะพะปะฑั†ะพะผ) ะฒ ััƒั‰ะตัั‚ะฒัƒัŽั‰ัƒัŽ ั‚ะฐะฑะปะธั†ัƒ
loans.write.format("delta") 
           .mode("append") 
           .save(DELTALAKE_PATH)

Returns:

A schema mismatch detected when writing to the Delta table.
 
To enable schema migration, please set:
'.option("mergeSchema", "true")'
 
Table schema:
root
-- addr_state: string (nullable = true)
-- count: long (nullable = true)
 
Data schema:
root
-- addr_state: string (nullable = true)
-- count: long (nullable = true)
-- amount: double (nullable = true)
 
If Table ACLs are enabled, these options will be ignored. Please use the ALTER TABLE command for changing the schema.

์ƒˆ ์—ด์„ ์ž๋™์œผ๋กœ ์ถ”๊ฐ€ํ•˜๋Š” ๋Œ€์‹  Delta Lake๋Š” ์Šคํ‚ค๋งˆ๋ฅผ ์ ์šฉํ•˜๊ณ  ๊ธฐ๋ก์„ ์ค‘์ง€ํ•ฉ๋‹ˆ๋‹ค. ๋ถˆ์ผ์น˜๋ฅผ ์ผ์œผํ‚ค๋Š” ์—ด(๋˜๋Š” ์—ด ์ง‘ํ•ฉ)์„ ํ™•์ธํ•˜๋Š” ๋ฐ ๋„์›€์ด ๋˜๋„๋ก Spark๋Š” ๋น„๊ต๋ฅผ ์œ„ํ•ด ์ถ”์  ์Šคํƒ์—์„œ ๋‘ ์Šคํ‚ค๋งˆ๋ฅผ ํŒํ•ฉ๋‹ˆ๋‹ค.

์Šคํ‚ค๋งˆ ์ ์šฉ์˜ ์ด์ ์€ ๋ฌด์—‡์ž…๋‹ˆ๊นŒ?

์Šคํ‚ค๋งˆ ์ ์šฉ์€ ์ƒ๋‹นํžˆ ์—„๊ฒฉํ•œ ๊ฒ€์‚ฌ์ด๋ฏ€๋กœ ์ƒ์‚ฐ ๋˜๋Š” ์†Œ๋น„ํ•  ์ค€๋น„๊ฐ€ ๋œ ๊นจ๋—ํ•˜๊ณ  ์™„์ „ํžˆ ๋ณ€ํ™˜๋œ ๋ฐ์ดํ„ฐ ์„ธํŠธ์˜ ๊ฒŒ์ดํŠธํ‚คํผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ํ›Œ๋ฅญํ•œ ๋„๊ตฌ์ž…๋‹ˆ๋‹ค. ์ผ๋ฐ˜์ ์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์ง์ ‘ ๊ณต๊ธ‰ํ•˜๋Š” ํ…Œ์ด๋ธ”์— ์ ์šฉ๋ฉ๋‹ˆ๋‹ค.

  • ๊ธฐ๊ณ„ ํ•™์Šต ์•Œ๊ณ ๋ฆฌ์ฆ˜
  • BI ๋Œ€์‹œ๋ณด๋“œ
  • ๋ฐ์ดํ„ฐ ๋ถ„์„ ๋ฐ ์‹œ๊ฐํ™” ๋„๊ตฌ
  • ๊ณ ๋„๋กœ ๊ตฌ์กฐํ™”๋˜๊ณ  ๊ฐ•๋ ฅํ•œ ์œ ํ˜•์˜ ์‹œ๋งจํ‹ฑ ์Šคํ‚ค๋งˆ๊ฐ€ ํ•„์š”ํ•œ ํ”„๋กœ๋•์…˜ ์‹œ์Šคํ…œ.

์ด ์ตœ์ข… ์žฅ์• ๋ฌผ์— ๋Œ€ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์ค€๋น„ํ•˜๊ธฐ ์œ„ํ•ด ๋งŽ์€ ์‚ฌ์šฉ์ž๋Š” ํ…Œ์ด๋ธ”์— ์ ์ง„์ ์œผ๋กœ ๊ตฌ์กฐ๋ฅผ ๋„์ž…ํ•˜๋Š” ๊ฐ„๋‹จํ•œ "๋ฉ€ํ‹ฐ ํ™‰" ์•„ํ‚คํ…์ฒ˜๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์ด์— ๋Œ€ํ•ด ์ž์„ธํžˆ ์•Œ์•„๋ณด๋ ค๋ฉด ๊ธฐ์‚ฌ๋ฅผ ์ฝ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Delta Lake๋ฅผ ์‚ฌ์šฉํ•œ ์ƒ์‚ฐ ๋“ฑ๊ธ‰ ๋จธ์‹  ๋Ÿฌ๋‹.

๋ฌผ๋ก  ์Šคํ‚ค๋งˆ ์ ์šฉ์€ ํŒŒ์ดํ”„๋ผ์ธ์˜ ์–ด๋Š ๊ณณ์—์„œ๋‚˜ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์ง€๋งŒ ์ด ๊ฒฝ์šฐ ํ…Œ์ด๋ธ”์— ๋Œ€ํ•œ ์ŠคํŠธ๋ฆฌ๋ฐ ์“ฐ๊ธฐ๊ฐ€ ์ขŒ์ ˆ๋  ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์„ ๋ช…์‹ฌํ•˜์‹ญ์‹œ์˜ค. ์˜ˆ๋ฅผ ๋“ค์–ด ์ˆ˜์‹  ๋ฐ์ดํ„ฐ์— ๋‹ค๋ฅธ ์—ด์„ ์ถ”๊ฐ€ํ•œ ๊ฒƒ์„ ์žŠ์–ด๋ฒ„๋ ธ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

๋ฐ์ดํ„ฐ ํฌ์„ ๋ฐฉ์ง€

์ด ์‹œ์ ์—์„œ ์™œ ๊ณผ๋Œ€ ๊ด‘๊ณ ์ธ์ง€ ๊ถ๊ธˆํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๊ฒฐ๊ตญ ๋•Œ๋•Œ๋กœ ์˜ˆ์ƒ์น˜ ๋ชปํ•œ "์Šคํ‚ค๋งˆ ๋ถˆ์ผ์น˜" ์˜ค๋ฅ˜๋กœ ์ธํ•ด ํŠนํžˆ Delta Lake๋ฅผ ์ฒ˜์Œ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ ์›Œํฌํ”Œ๋กœ์—์„œ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฌด์Šจ ์ผ์ด ์žˆ์–ด๋„ DataFrame์„ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•„์š”์— ๋”ฐ๋ผ ์Šคํ‚ค๋งˆ๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š” ๊ฒƒ์ด ์–ด๋–ป์Šต๋‹ˆ๊นŒ?

์˜› ์†๋‹ด์— "XNUMX ์˜จ์Šค์˜ ์˜ˆ๋ฐฉ์€ XNUMX ํŒŒ์šด๋“œ์˜ ์น˜๋ฃŒ ๊ฐ€์น˜๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค." ์–ด๋Š ์‹œ์ ์—์„œ ์Šคํ‚ค๋งˆ๋ฅผ ์ ์šฉํ•˜๋Š” ๋ฐ ์ฃผ์˜๋ฅผ ๊ธฐ์šธ์ด์ง€ ์•Š์œผ๋ฉด ๋ฐ์ดํ„ฐ ์œ ํ˜• ํ˜ธํ™˜์„ฑ ๋ฌธ์ œ๊ฐ€ ์ถ”์•…ํ•œ ๋จธ๋ฆฌ๋ฅผ ๋“œ๋Ÿฌ๋‚ผ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๊ฒ‰๋ณด๊ธฐ์— ๋™์งˆ์ ์ธ ์›์‹œ ๋ฐ์ดํ„ฐ ์†Œ์Šค์—๋Š” ์—ฃ์ง€ ์ผ€์ด์Šค, ๊นจ์ง„ ์—ด, ์ž˜๋ชป๋œ ํ˜•์‹์˜ ๋งคํ•‘ ๋˜๋Š” ๊ฟˆ๊พธ๋Š” ๋‹ค๋ฅธ ๋‘๋ ค์šด ๊ฒƒ๋“ค์ด ํฌํ•จ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. .์•…๋ชฝ ์†์—์„œ. ๊ฐ€์žฅ ์ข‹์€ ์ ‘๊ทผ ๋ฐฉ์‹์€ ์Šคํ‚ค๋งˆ ์ ์šฉ์„ ํ†ตํ•ด ๊ฒŒ์ดํŠธ์—์„œ ์ด๋Ÿฌํ•œ ์ ๋“ค์„ ๋ง‰๊ณ  ๋‚˜์ค‘์— ํ”„๋กœ๋•์…˜ ์ฝ”๋“œ์˜ ์–ด๋‘์šด ๊นŠ์ด๋ฅผ ๋ฐฐํšŒํ•˜๊ธฐ ์‹œ์ž‘ํ•˜๋Š” ์‹œ์ ์ด ์•„๋‹ˆ๋ผ ๋น› ์†์—์„œ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์Šคํ‚ค๋งˆ ์ ์šฉ์€ ์‚ฌ์šฉ์ž๊ฐ€ ์ง์ ‘ ๋ณ€๊ฒฝ์„ ํ™•์ธํ•˜์ง€ ์•Š๋Š” ํ•œ ํ…Œ์ด๋ธ”์˜ ์Šคํ‚ค๋งˆ๊ฐ€ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š๋Š”๋‹ค๋Š” ํ™•์‹ ์„ ์ค๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์ƒˆ ์—ด์ด ๋„ˆ๋ฌด ์ž์ฃผ ์ถ”๊ฐ€๋˜์–ด ์ด์ „์— ์ค‘์š”ํ•˜๊ณ  ์••์ถ•๋œ ํ…Œ์ด๋ธ”์ด ๋ฐ์ดํ„ฐ ๋ฒ”๋žŒ์œผ๋กœ ์ธํ•ด ๊ฐ€์น˜์™€ ์œ ์šฉ์„ฑ์„ ์žƒ์„ ๋•Œ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐ์ดํ„ฐ ํฌ์„์„ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜๋„์ ์œผ๋กœ ๋†’์€ ํ‘œ์ค€์„ ์„ค์ •ํ•˜๊ณ  ๋†’์€ ํ’ˆ์งˆ์„ ๊ธฐ๋Œ€ํ•˜๋„๋ก ์žฅ๋ คํ•จ์œผ๋กœ์จ ์Šคํ‚ค๋งˆ ์ ์šฉ์€ ์„ค๊ณ„๋œ ๋Œ€๋กœ ์ •ํ™•ํ•˜๊ฒŒ ์ž‘๋™ํ•˜์—ฌ ์„ฑ์‹คํ•จ์„ ์œ ์ง€ํ•˜๊ณ  ์Šคํ”„๋ ˆ๋“œ์‹œํŠธ๋ฅผ ๊นจ๋—ํ•˜๊ฒŒ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋„์™€์ค๋‹ˆ๋‹ค.

๋” ๊ณ ๋ คํ•œ ํ›„์— ๊ท€ํ•˜๊ฐ€ ์ •๋ง๋กœ ํ•„์š”ํ•œ ์ƒˆ ์—ด ์ถ”๊ฐ€ - ๋ฌธ์ œ ์—†์Šต๋‹ˆ๋‹ค. ์•„๋ž˜๋Š” ํ•œ ์ค„ ์ˆ˜์ •์ž…๋‹ˆ๋‹ค. ํ•ด๊ฒฐ์ฑ…์€ ํšŒ๋กœ ์ง„ํ™”์ž…๋‹ˆ๋‹ค!

์Šคํ‚ค๋งˆ ์ง„ํ™”๋ž€ ๋ฌด์—‡์ž…๋‹ˆ๊นŒ?

์Šคํ‚ค๋งˆ ์ง„ํ™”๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ์‹œ๊ฐ„์ด ์ง€๋‚จ์— ๋”ฐ๋ผ ๋ณ€๊ฒฝ๋˜๋Š” ๋ฐ์ดํ„ฐ์™€ ์ผ์น˜ํ•˜๋„๋ก ํ…Œ์ด๋ธ”์˜ ํ˜„์žฌ ์Šคํ‚ค๋งˆ๋ฅผ ์‰ฝ๊ฒŒ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค. ํ•˜๋‚˜ ์ด์ƒ์˜ ์ƒˆ ์—ด์„ ํฌํ•จํ•˜๋„๋ก ์Šคํ‚ค๋งˆ๋ฅผ ์ž๋™์œผ๋กœ ์กฐ์ •ํ•˜๊ธฐ ์œ„ํ•ด ์ถ”๊ฐ€ ๋˜๋Š” ๋ฎ์–ด์“ฐ๊ธฐ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ๋•Œ ๊ฐ€์žฅ ์ผ๋ฐ˜์ ์œผ๋กœ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.

์Šคํ‚ค๋งˆ ์ง„ํ™”๋Š” ์–ด๋–ป๊ฒŒ ์ž‘๋™ํ•ฉ๋‹ˆ๊นŒ?

์ด์ „ ์„น์…˜์˜ ์˜ˆ์— ๋”ฐ๋ผ ๊ฐœ๋ฐœ์ž๋Š” ์Šคํ‚ค๋งˆ ์ง„ํ™”๋ฅผ ์‰ฝ๊ฒŒ ์‚ฌ์šฉํ•˜์—ฌ ์Šคํ‚ค๋งˆ ๋ถˆ์ผ์น˜๋กœ ์ธํ•ด ์ด์ „์— ๊ฑฐ๋ถ€๋œ ์ƒˆ ์—ด์„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ถ”๊ฐ€ํ•˜์—ฌ ํšŒ๋กœ ์ง„ํ™”๊ฐ€ ํ™œ์„ฑํ™”๋ฉ๋‹ˆ๋‹ค. .option('mergeSchema', 'true') ์ŠคํŒŒํฌ ํŒ€์—๊ฒŒ .write ะธะปะธ .writeStream.

# ะ”ะพะฑะฐะฒัŒั‚ะต ะฟะฐั€ะฐะผะตั‚ั€ mergeSchema
loans.write.format("delta") 
           .option("mergeSchema", "true") 
           .mode("append") 
           .save(DELTALAKE_SILVER_PATH)

๊ทธ๋ž˜ํ”„๋ฅผ ๋ณด๋ ค๋ฉด ๋‹ค์Œ Spark SQL ์ฟผ๋ฆฌ๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.

# ะกะพะทะดะฐะนั‚ะต ะณั€ะฐั„ะธะบ ั ะฝะพะฒั‹ะผ ัั‚ะพะปะฑั†ะพะผ, ั‡ั‚ะพะฑั‹ ะฟะพะดั‚ะฒะตั€ะดะธั‚ัŒ, ั‡ั‚ะพ ะทะฐะฟะธััŒ ะฟั€ะพัˆะปะฐ ัƒัะฟะตัˆะฝะพ
%sql
SELECT addr_state, sum(`amount`) AS amount
FROM loan_by_state_delta
GROUP BY addr_state
ORDER BY sum(`amount`)
DESC LIMIT 10

Delta Lake ์ž์„ธํžˆ ์•Œ์•„๋ณด๊ธฐ: ์Šคํ‚ค๋งˆ ์ ์šฉ ๋ฐ ์ง„ํ™”
๋˜๋Š” ๋‹ค์Œ์„ ์ถ”๊ฐ€ํ•˜์—ฌ ์ „์ฒด Spark ์„ธ์…˜์— ๋Œ€ํ•ด ์ด ์˜ต์…˜์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. spark.databricks.delta.schema.autoMerge = True Spark ๊ตฌ์„ฑ์—. ๊ทธ๋Ÿฌ๋‚˜ ์Šคํ‚ค๋งˆ ์ ์šฉ์€ ๋” ์ด์ƒ ์˜๋„ํ•˜์ง€ ์•Š์€ ์Šคํ‚ค๋งˆ ๋ถˆ์ผ์น˜์— ๋Œ€ํ•ด ๊ฒฝ๊ณ ํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ ์ฃผ์˜ํ•ด์„œ ์‚ฌ์šฉํ•˜์‹ญ์‹œ์˜ค.

์š”์ฒญ์— ๋งค๊ฐœ๋ณ€์ˆ˜ ํฌํ•จ mergeSchema, DataFrame์—๋Š” ์žˆ์ง€๋งŒ ๋Œ€์ƒ ํ…Œ์ด๋ธ”์—๋Š” ์—†๋Š” ๋ชจ๋“  ์—ด์ด ์“ฐ๊ธฐ ํŠธ๋žœ์žญ์…˜์˜ ์ผ๋ถ€๋กœ ์Šคํ‚ค๋งˆ ๋์— ์ž๋™์œผ๋กœ ์ถ”๊ฐ€๋ฉ๋‹ˆ๋‹ค. ์ค‘์ฒฉ ํ•„๋“œ๋„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ ํ•ด๋‹น ๊ตฌ์กฐ ์—ด์˜ ๋์— ์ถ”๊ฐ€๋ฉ๋‹ˆ๋‹ค.

๋‚ ์งœ ์—”์ง€๋‹ˆ์–ด์™€ ๋ฐ์ดํ„ฐ ๊ณผํ•™์ž๋Š” ์ด ์˜ต์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ ์ด์ „ ์—ด์„ ๊ธฐ๋ฐ˜์œผ๋กœ ๊ธฐ์กด ๋ชจ๋ธ์„ ์ค‘๋‹จํ•˜์ง€ ์•Š๊ณ  ๊ธฐ์กด ๊ธฐ๊ณ„ ํ•™์Šต ์ƒ์‚ฐ ํ…Œ์ด๋ธ”์— ์ƒˆ ์—ด(์•„๋งˆ๋„ ์ตœ๊ทผ ์ถ”์ ๋œ ๋ฉ”ํŠธ๋ฆญ ๋˜๋Š” ์ด๋ฒˆ ๋‹ฌ ํŒ๋งค ์ˆ˜์น˜ ์—ด)์„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋‹ค์Œ ์œ ํ˜•์˜ ์Šคํ‚ค๋งˆ ๋ณ€๊ฒฝ์€ ํ…Œ์ด๋ธ”์„ ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜ ๋ฎ์–ด์“ฐ๋Š” ๋™์•ˆ ์Šคํ‚ค๋งˆ ์ง„ํ™”์˜ ์ผ๋ถ€๋กœ ํ—ˆ์šฉ๋ฉ๋‹ˆ๋‹ค.

  • ์ƒˆ ์—ด ์ถ”๊ฐ€(๊ฐ€์žฅ ์ผ๋ฐ˜์ ์ธ ์‹œ๋‚˜๋ฆฌ์˜ค)
  • NullType -> ๋‹ค๋ฅธ ์œ ํ˜•์—์„œ ๋ฐ์ดํ„ฐ ์œ ํ˜• ๋ณ€๊ฒฝ ๋˜๋Š” ByteType -> ShortType -> IntegerType์—์„œ ์Šน๊ฒฉ

์Šคํ‚ค๋งˆ ์ง„ํ™”์˜ ์ผ๋ถ€๋กœ ํ—ˆ์šฉ๋˜์ง€ ์•Š๋Š” ๊ธฐํƒ€ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์€ ๋‹ค์Œ์„ ์ถ”๊ฐ€ํ•˜์—ฌ ์Šคํ‚ค๋งˆ์™€ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฎ์–ด์จ์•ผ ํ•ฉ๋‹ˆ๋‹ค. .option("overwriteSchema", "true"). ์˜ˆ๋ฅผ ๋“ค์–ด "Foo" ์—ด์ด ์›๋ž˜ ์ •์ˆ˜์ด๊ณ  ์ƒˆ ์Šคํ‚ค๋งˆ๊ฐ€ ๋ฌธ์ž์—ด ๋ฐ์ดํ„ฐ ์œ ํ˜•์ธ ๊ฒฝ์šฐ ๋ชจ๋“  Parquet(data) ํŒŒ์ผ์„ ๋ฎ์–ด์จ์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  • ์—ด ์‚ญ์ œ
  • ๊ธฐ์กด ์—ด์˜ ๋ฐ์ดํ„ฐ ์œ ํ˜• ๋ณ€๊ฒฝ(์ œ์ž๋ฆฌ์—์„œ)
  • ๋Œ€์†Œ๋ฌธ์ž๋งŒ ๋‹ค๋ฅธ ์—ด ์ด๋ฆ„ ๋ฐ”๊พธ๊ธฐ(์˜ˆ: "Foo" ๋ฐ "foo")

๋งˆ์ง€๋ง‰์œผ๋กœ Spark 3.0์˜ ๋‹ค์Œ ๋ฆด๋ฆฌ์Šค์—์„œ๋Š” ๋ช…์‹œ์  DDL(ALTER TABLE ์‚ฌ์šฉ)์ด ์™„์ „ํžˆ ์ง€์›๋˜์–ด ์‚ฌ์šฉ์ž๊ฐ€ ํ…Œ์ด๋ธ” ์Šคํ‚ค๋งˆ์—์„œ ๋‹ค์Œ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • ์—ด ์ถ”๊ฐ€
  • ์—ด ์ฃผ์„ ๋ณ€๊ฒฝ
  • ํŠธ๋žœ์žญ์…˜ ๋กœ๊ทธ ๋ณด๊ด€ ๊ธฐ๊ฐ„ ์„ค์ •๊ณผ ๊ฐ™์ด ํ…Œ์ด๋ธ” ๋™์ž‘ ๋ฐฉ์‹์„ ๊ฒฐ์ •ํ•˜๋Š” ํ…Œ์ด๋ธ” ์†์„ฑ ์„ค์ •

์Šคํ‚ค๋งˆ ์ง„ํ™”์˜ ์ด์ ์€ ๋ฌด์—‡์ž…๋‹ˆ๊นŒ?

๋„์‹์  ์ง„ํ™”๋Š” ์–ธ์ œ๋“ ์ง€ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜๋„ํ•˜๋‹ค ํ…Œ์ด๋ธ”์˜ ์Šคํ‚ค๋งˆ๋ฅผ ๋ณ€๊ฒฝํ•ฉ๋‹ˆ๋‹ค(์‹ค์ˆ˜๋กœ DataFrame์— ์žˆ์œผ๋ฉด ์•ˆ ๋˜๋Š” ์—ด์„ ์ถ”๊ฐ€ํ•œ ๊ฒฝ์šฐ์™€ ๋ฐ˜๋Œ€) ์ด๋Š” ๋ช…์‹œ์ ์œผ๋กœ ์„ ์–ธํ•˜์ง€ ์•Š๊ณ ๋„ ์˜ฌ๋ฐ”๋ฅธ ์—ด ์ด๋ฆ„๊ณผ ๋ฐ์ดํ„ฐ ์œ ํ˜•์„ ์ž๋™์œผ๋กœ ์ถ”๊ฐ€ํ•˜๋ฏ€๋กœ ์Šคํ‚ค๋งˆ๋ฅผ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ํ•˜๋Š” ๊ฐ€์žฅ ์‰ฌ์šด ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค.

๊ฒฐ๋ก 

์Šคํ‚ค๋งˆ ์ ์šฉ์€ ํ…Œ์ด๋ธ”๊ณผ ํ˜ธํ™˜๋˜์ง€ ์•Š๋Š” ์ƒˆ ์—ด ๋˜๋Š” ๊ธฐํƒ€ ์Šคํ‚ค๋งˆ ๋ณ€๊ฒฝ์„ ๊ฑฐ๋ถ€ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๋†’์€ ํ‘œ์ค€์„ ์„ค์ •ํ•˜๊ณ  ์œ ์ง€ํ•จ์œผ๋กœ์จ ๋ถ„์„๊ฐ€์™€ ์—”์ง€๋‹ˆ์–ด๋Š” ๋ฐ์ดํ„ฐ์— ์˜์กดํ•˜์—ฌ ์ตœ๊ณ  ์ˆ˜์ค€์˜ ๋ฌด๊ฒฐ์„ฑ์„ ํ™•๋ณดํ•˜๊ณ  ๋ฐ์ดํ„ฐ์— ๋Œ€ํ•ด ๋ช…ํ™•ํ•˜๊ณ  ๊ฐ„๊ฒฐํ•˜๊ฒŒ ์ถ”๋ก ํ•˜์—ฌ ๋” ๋‚˜์€ ๋น„์ฆˆ๋‹ˆ์Šค ๊ฒฐ์ •์„ ๋‚ด๋ฆด ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋ฐ˜๋ฉด์— ์Šคํ‚ค๋งˆ ์ง„ํ™”๋Š” ๋‹จ์ˆœํ™”๋ฅผ ํ†ตํ•ด ์‹œํ–‰์„ ๋ณด์™„ํ•ฉ๋‹ˆ๋‹ค. ๊ฐ€์ •์˜ ์ž๋™ ์Šคํ‚ค๋งˆ ๋ณ€๊ฒฝ. ๊ฒฐ๊ตญ ์—ด์„ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ์ด ์–ด๋ ต์ง€ ์•Š์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์Šคํ‚ค๋งˆ ์ ์šฉ์€ ์–‘์ด๊ณ  ์Šคํ‚ค๋งˆ ์ง„ํ™”๋Š” ์Œ์ž…๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๊ธฐ๋Šฅ์„ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋ฉด ๋…ธ์ด์ฆˆ ๊ฐ์†Œ ๋ฐ ์‹ ํ˜ธ ํŠœ๋‹์ด ๊ทธ ์–ด๋Š ๋•Œ๋ณด๋‹ค ์‰ฌ์›Œ์ง‘๋‹ˆ๋‹ค.

๋˜ํ•œ ์ด ๊ธฐ์‚ฌ์— ๊ธฐ์—ฌํ•œ Mukul Murthy์™€ Pranav Anand์—๊ฒŒ๋„ ๊ฐ์‚ฌ๋“œ๋ฆฝ๋‹ˆ๋‹ค.

์ด ์‹œ๋ฆฌ์ฆˆ์˜ ๋‹ค๋ฅธ ๊ธฐ์‚ฌ:

Delta Lake ์‚ดํŽด๋ณด๊ธฐ: ํŠธ๋žœ์žญ์…˜ ๋กœ๊ทธ ํ’€๊ธฐ

๊ด€๋ จ ๊ธฐ์‚ฌ

Delta Lake๋ฅผ ์‚ฌ์šฉํ•œ ์ƒ์‚ฐ ๋“ฑ๊ธ‰ ๋จธ์‹  ๋Ÿฌ๋‹

๋ฐ์ดํ„ฐ ๋ ˆ์ดํฌ๋ž€ ๋ฌด์—‡์ž…๋‹ˆ๊นŒ?

๊ณผ์ •์— ๋Œ€ํ•ด ์ž์„ธํžˆ ์•Œ์•„๋ณด๊ธฐ

์ถœ์ฒ˜ : habr.com

์ฝ”๋ฉ˜ํŠธ๋ฅผ ์ถ”๊ฐ€