Asosiy qismga o'tish

Lakehouse: ko'l va ombor birlashgani

16-bob 8 kod bloki 2075 so'z

Bu bobdagi kod shu fayllar bilan ishlaydio'z kompyuteringizda ishga tushiring

Ikki bino va ular orasidagi yuk mashinasi

Kompaniyada ikkita bino bor.

Birinchi bino — ombor (data warehouse). Ichkarida hammasi tartibli: javonlar raqamlangan, har quti yorliqlangan, ro'yxat bor. Buxgalter kirsa, kerakli hujjatni bir daqiqada topadi.

Lekin omborga hamma narsani qo'ya olmaysiz. Qabulxonada qoida bor: "faqat rasmiylashtirilgan hujjatlar". Video kassetami? Qabul qilinmaydi. Tartibsiz qog'ozlar to'plamimi? Avval rasmiylashtiring.

Ikkinchi bino — ko'l (data lake). Bu ulkan angar. U yerga hamma narsani tashlash mumkin: videolar, rasmlar, log fayllar, tartibsiz qog'ozlar. Arzon va cheksiz.

Lekin u yerdan biror narsa topish — alohida kasb. Ro'yxat yo'q, tartib yo'q.

Natijada kompaniyada ikki nusxa ma'lumot paydo bo'ladi va ular orasida doimiy yuk mashinasi qatnaydi: ko'ldan ombor uchun ma'lumot tayyorlanadi, tozalanadi, ko'chiriladi.

python19 qator
diagramma(
    {
        "Manbalar": (0, 1.8, 1.9, 1.0, "manba"),
        "KO'L\n(arzon, tartibsiz)": (2.7, 3.2, 2.4, 1.1, "saqlash"),
        "OMBOR\n(qimmat, tartibli)": (2.7, 0.4, 2.4, 1.1, "saqlash"),
        "ETL\n(yuk mashinasi)": (6.0, 1.8, 2.1, 1.0, "qayta"),
        "ML / Data Science": (9.2, 3.2, 2.4, 1.0, "xizmat"),
        "Hisobot / BI": (9.2, 0.4, 2.4, 1.0, "xizmat"),
    },
    [
        ("Manbalar", "KO'L\n(arzon, tartibsiz)"),
        ("KO'L\n(arzon, tartibsiz)", "ETL\n(yuk mashinasi)"),
        ("ETL\n(yuk mashinasi)", "OMBOR\n(qimmat, tartibli)"),
        ("KO'L\n(arzon, tartibsiz)", "ML / Data Science"),
        ("OMBOR\n(qimmat, tartibli)", "Hisobot / BI"),
    ],
    nom="kol_ombor.png", kenglik=13,
    izoh="Eski tuzilish: ikki nusxa, ular orasida doimiy ko'chirish.",
)

Eski tuzilish: ikki nusxa, ular orasida doimiy ko'chirish.

Eski tuzilish: ikki nusxa, ular orasida doimiy ko'chirish.

Ikki nusxaning narxi

Bu tuzilish o'n yil ishlatilgan va u ishlaydi. Lekin uch narxi bor.

Pul. Bir xil ma'lumotni ikki joyda saqlaysiz. Ombor qimmat — u tez disk va litsenziya talab qiladi.

Vaqt. Ko'ldagi ma'lumot ombordan doim orqada. ETL har kecha ishlaydi, demak hisobot kechagi holatni ko'rsatadi.

Ishonch. Eng yomoni shu. Ma'lumot muhandisi ko'ldan hisoblaydi, tahlilchi ombordan hisoblaydi — ikki xil raqam chiqadi. Yig'ilishda ikki kishi bir-biriga raqam ko'rsatadi va hech kim qaysi biri to'g'riligini bilmaydi.

Bu holatni ko'rgan har bir jamoa xuddi shu savolni beradi: nega ikkita nusxa kerak?

Lakehouse g'oyasi

Javob: 15-bobdan keyin — kerak emas.

Va sababini tushunish uchun savolni boshqacha qo'yish kerak. Nega ombor qimmat va tartibli, ko'l esa arzon va tartibsiz edi?

Ko'pchilik "chunki ombor kuchliroq dastur" deb o'ylaydi. Noto'g'ri.

Haqiqiy sabab: omborda ro'yxat bor edi, ko'lda esa yo'q. Ombor har qutini qayd qiladi, har o'zgarishni yozadi, shuning uchun u savollarga javob bera oladi. Ko'l esa shunchaki fayllar uyumi — javob beradigan hech narsa yo'q.

Ya'ni farq saqlashda emas, hisobotda edi.

Endi 15-bobni eslang. Biz nima qildik? Fayllar uyumiga ro'yxat qo'shdik. Fayllarni o'zgartirmadik — ichida baribir Parquet. Faqat ustiga metama'lumot qatlami qo'ydik.

Va shu zahoti ko'l ombor xususiyatlarini oldi: sxema, tranzaksiya, tarix, atomik o'zgarish.

Demak ikkita bino kerak emas edi. Bitta bino yetardi — unga shunchaki ro'yxat qo'yish kerak edi.

Bu — lakehouse: ko'l (lake) narxi + ombor (house) tartibi.

python9 qator
import pandas as pd
show(pd.DataFrame([
    {"": "Narxi", "Ko'l": "Arzon", "Ombor": "Qimmat", "Lakehouse": "Arzon"},
    {"": "Sxema", "Ko'l": "Yo'q", "Ombor": "Bor", "Lakehouse": "Bor"},
    {"": "Tranzaksiya", "Ko'l": "Yo'q", "Ombor": "Bor", "Lakehouse": "Bor"},
    {"": "Tarix", "Ko'l": "Yo'q", "Ombor": "Cheklangan", "Lakehouse": "Time travel"},
    {"": "Har xil ma'lumot", "Ko'l": "Ha", "Ombor": "Yo'q", "Lakehouse": "Ha"},
    {"": "Nusxalar soni", "Ko'l": "1", "Ombor": "1", "Lakehouse": "1 (jami)"},
]))
Ko'lOmborLakehouse
NarxiArzonQimmatArzon
SxemaYo'qBorBor
TranzaksiyaYo'qBorBor
TarixYo'qCheklanganTime travel
Har xil ma'lumotHaYo'qHa
Nusxalar soni111 (jami)

Bitta ma'lumot, ikki xil iste'molchi

Eng muhim xususiyatni o'lchaymiz: bir xil jadvalni turli vositalar o'qiy oladimi?

15-bobda rostuy.elon jadvalini yaratgandik. Avval uni tayyorlaymiz.

python17 qator
python3 -c "
import sys, shutil; sys.path.insert(0, 'src')
import pandas as pd, pyarrow as pa
from quvur.muz import katalog

USTUN = ['elon_id','tuman','narx_mln','xonalar','tavsif_uzunligi','broker']
d = pd.read_csv('../datasets/ijara.csv').drop_duplicates('elon_id')

shutil.rmtree('ombor', ignore_errors=True)
c = katalog()
c.create_namespace_if_not_exists('rostuy')
ar = pa.Table.from_pandas(d[USTUN], preserve_index=False)
t = c.create_table('rostuy.elon', schema=ar.schema)
t.append(ar)
print(f'lakehouse jadvali: {len(t.scan().to_arrow()):,} qator')
print(f'fayllar          : {len(list(t.scan().plan_files()))}')
"
Chiqish (qaytish kodi 0, 1051 ms)
lakehouse jadvali: 11,906 qator
fayllar          : 1

Endi uch xil vosita bilan o'qiymiz — hech qanday ko'chirishsiz:

python35 qator
python3 -c "
import sys, time; sys.path.insert(0, 'src')
from quvur.muz import katalog

c = katalog()
t = c.load_table('rostuy.elon')

# 1) pyiceberg - to'g'ridan-to'g'ri
t0 = time.perf_counter()
ark = t.scan().to_arrow()
a = (time.perf_counter()-t0)*1000
print(f'1. pyiceberg  : {len(ark):>6,} qator  {a:>7.1f} ms')

# 2) pandas - tahlilchi uslubi
t0 = time.perf_counter()
df = t.scan().to_pandas()
ort = df.groupby('tuman')['narx_mln'].mean().sort_values(ascending=False)
b = (time.perf_counter()-t0)*1000
print(f'2. pandas     : {len(df):>6,} qator  {b:>7.1f} ms')

# 3) DuckDB - SQL uslubi (Iceberg qaytargan arrow ustida)
import duckdb
con = duckdb.connect()
t0 = time.perf_counter()
r = con.execute('SELECT tuman, avg(narx_mln) m FROM ark GROUP BY tuman ORDER BY m DESC').fetchall()
d_ms = (time.perf_counter()-t0)*1000
print(f'3. DuckDB SQL : {len(r):>6,} guruh  {d_ms:>7.1f} ms')
print()
print('Eng qimmat tuman:')
print(f'  pandas : {ort.index[0]:14s} {ort.iloc[0]:.3f}')
print(f'  DuckDB : {r[0][0]:14s} {r[0][1]:.3f}')
print()
print(f'natijalar bir xilmi: {abs(ort.iloc[0] - r[0][1]) < 1e-9}')
con.close()
"
Chiqish (qaytish kodi 0, 1019 ms)
1. pyiceberg  : 11,906 qator     10.4 ms
2. pandas     : 11,906 qator     11.3 ms
3. DuckDB SQL :     10 guruh      6.9 ms

Eng qimmat tuman:
  pandas : Yakkasaroy     7.816
  DuckDB : Yakkasaroy     7.816

natijalar bir xilmi: True

Uchala vosita bir xil fayllarni o'qidi va bir xil javob berdi. Hech narsa ko'chirilmadi, ikkinchi nusxa yaratilmadi.

Mana lakehouse ning asosiy va'dasi: tahlilchi SQL yozadi, ma'lumot olimi pandas ishlatadi, quvur Spark bilan yozadi — manba bitta.

"Bitta nusxa" hamma muammoni hal qilmaydi

Lakehouse jismoniy nusxani yo'q qiladi. Lekin mantiqiy nusxa qolaveradi.

Misol: tahlilchi "faol e'lonlar" ni holat='faol' deb hisoblaydi, ma'lumot olimi esa "oxirgi 30 kunda yangilangan" deb hisoblaydi. Bitta jadval, ikki xil ta'rif, yana ikki xil raqam.

Buni hal qiladigan narsa format emas — kelishilgan ta'rif. Odatda bu dbt kabi vosita bilan umumiy modelga yoziladi: "faol" nima ekani bir joyda ta'riflanadi va hamma o'shani ishlatadi.

Qatlamlar: bronza, kumush, oltin

Lakehouse da hamma narsa bitta joyda, lekin bitta qozonda emas. Amalda uch qatlam ishlatiladi.

Restoran oshxonasini tasavvur qiling:

  • Bronza — bozordan kelgan mahsulot. Yuvilmagan, to'g'ralmagan, qanday kelgan bo'lsa shunday.
  • Kumush — yuvilgan, tozalangan, to'g'ralgan. Ishlatishga tayyor.
  • Oltin — tayyor taom. Mijozga chiqariladi.

Endi eng muhim savol: nega bronzani saqlash kerak? U axlat-ku — yuvilmagan sabzi.

Sabab shunda: bir kuni siz tozalash qoidangizda xato topasiz.

Masalan, olti oy davomida narx_mln > 200 bo'lgan e'lonlarni "xato" deb tashlab kelgansiz. Keyin ma'lum bo'ladiki, bular xato emas — bu lyuks kvartiralar, va ular mijozlarning eng daromadli qismi edi.

Endi ularni qaytarib bo'ladimi?

  • Bronza saqlangan bo'lsa — ha. Qoidani tuzatasiz, kumushni qaytadan quriladi, hammasi joyida.
  • Bronza saqlanmagan bo'lsa — yo'q. O'sha e'lonlar butunlay yo'qolgan. Olti oylik ma'lumot qaytmaydi.

Mana qoida: bronza — sizning "bekor qilish" tugmangiz.

Shuning uchun bronzaga hech qachon tegmaydilar: tozalamaydilar, tuzatmaydilar, ustiga yozmaydilar. U faqat qo'shiladi.

python9 qator
import pandas as pd
show(pd.DataFrame([
    {"Qatlam": "Bronza", "Nima": "Xom manba, o'zgartirilmagan",
     "Kim ishlatadi": "Muhandis", "Nega kerak": "Qayta hisoblash imkoni"},
    {"Qatlam": "Kumush", "Nima": "Tozalangan, sxema qo'llangan",
     "Kim ishlatadi": "Ma'lumot olimi", "Nega kerak": "Ishonchli asos"},
    {"Qatlam": "Oltin", "Nima": "Yig'ilgan, biznes ta'rifi",
     "Kim ishlatadi": "Tahlilchi, direktor", "Nega kerak": "Tez hisobot"},
]))
QatlamNimaKim ishlatadiNega kerak
BronzaXom manba, o'zgartirilmaganMuhandisQayta hisoblash imkoni
KumushTozalangan, sxema qo'llanganMa'lumot olimiIshonchli asos
OltinYig'ilgan, biznes ta'rifiTahlilchi, direktorTez hisobot

Uchala qatlamni quramiz va hajmini solishtiramiz:

python47 qator
python3 -c "
import sys, pathlib; sys.path.insert(0, 'src')
import pandas as pd, pyarrow as pa
from quvur.muz import katalog

c = katalog()
c.create_namespace_if_not_exists('bronza')
c.create_namespace_if_not_exists('kumush')
c.create_namespace_if_not_exists('oltin')

xom = pd.read_csv('../datasets/ijara.csv')

# BRONZA: qanday kelgan bo'lsa shunday
b = pa.Table.from_pandas(xom, preserve_index=False)
try: c.drop_table('bronza.elon')
except Exception: pass
tb = c.create_table('bronza.elon', schema=b.schema); tb.append(b)

# KUMUSH: takrorni tashlaymiz, qoidaga mos bo'lmaganini olib tashlaymiz
k = xom.drop_duplicates('elon_id')
k = k[(k['narx_mln'] > 0) & (k['maydon'] >= 10) & (k['xonalar'].between(1, 10))]
kt = pa.Table.from_pandas(k, preserve_index=False)
try: c.drop_table('kumush.elon')
except Exception: pass
tk = c.create_table('kumush.elon', schema=kt.schema); tk.append(kt)

# OLTIN: biznes savoliga tayyor javob
o = (k.groupby('tuman')
       .agg(elonlar=('elon_id','count'),
            ort_narx=('narx_mln','mean'),
            broker_ulushi=('broker','mean'))
       .reset_index())
ot = pa.Table.from_pandas(o, preserve_index=False)
try: c.drop_table('oltin.tuman_xulosa')
except Exception: pass
to = c.create_table('oltin.tuman_xulosa', schema=ot.schema); to.append(ot)

def hajm(t):
    return sum(pathlib.Path(f.file.file_path.replace('file://','')).stat().st_size
               for f in t.scan().plan_files())

print(f'{\"qatlam\":10s} {\"qatorlar\":>10s} {\"hajm KB\":>10s}')
print('-' * 34)
for nom, t in (('bronza', tb), ('kumush', tk), ('oltin', to)):
    n = len(t.scan().to_arrow())
    print(f'{nom:10s} {n:>10,} {hajm(t)/1024:>10.1f}')
"
Chiqish (qaytish kodi 0, 1636 ms)
qatlam       qatorlar    hajm KB
----------------------------------
bronza         12,000      166.1
kumush         11,906      164.4
oltin              10        2.0

Raqamlarni diqqat bilan o'qing — ular kutilganidek emas.

Bronza va kumush deyarli bir xil hajmda: tozalash atigi 94 qatorni olib tashladi, chunki bizning ma'lumot allaqachon toza edi. Haqiqiy manbalarda bu farq ancha katta bo'ladi (takroriy yozuvlar, buzuq qatorlar, kerak bo'lmagan ustunlar).

Haqiqiy sakrash oltin qatlamda: 164 KB dan 2 KB ga. Sabab oddiy — oltin qatlam ma'lumotni saqlamaydi, u javobni saqlaydi. 11 906 qator o'rniga 10 ta tuman.

Direktorning savoliga javob shuning uchun tezroq keladi:

python25 qator
python3 -c "
import sys, time; sys.path.insert(0, 'src')
from quvur.muz import katalog
import pandas as pd

c = katalog()
kumush = c.load_table('kumush.elon')
oltin = c.load_table('oltin.tuman_xulosa')

t0 = time.perf_counter()
d = kumush.scan().to_pandas()
r1 = d.groupby('tuman')['narx_mln'].mean().sort_values(ascending=False)
a = (time.perf_counter()-t0)*1000

t0 = time.perf_counter()
r2 = oltin.scan().to_pandas().sort_values('ort_narx', ascending=False)
b = (time.perf_counter()-t0)*1000

print(f'kumushdan hisoblash : {a:7.1f} ms')
print(f'oltindan o\\'qish     : {b:7.1f} ms')
print(f'oltin {a/b:.1f}x tez')
print()
print(f'javob bir xilmi: {abs(r1.iloc[0] - r2[\"ort_narx\"].iloc[0]) < 1e-9}')
print(f'  eng qimmat tuman: {r2[\"tuman\"].iloc[0]} ({r2[\"ort_narx\"].iloc[0]:.3f} mln)')
"
Chiqish (qaytish kodi 0, 944 ms)
kumushdan hisoblash :    40.8 ms
oltindan o'qish     :    12.2 ms
oltin 3.4x tez

javob bir xilmi: True
  eng qimmat tuman: Yakkasaroy (7.816 mln)

Farq bu yerda kichik ko'rinadi, chunki kumush qatlam ham atigi 12 ming qator. Milliard qatorli jadvalda oltin qatlam soniyalarni daqiqalarga emas, millisekundlarga aylantiradi — chunki o'qiladigan qatorlar soni hajmga bog'liq emas, u doim 10 ta bo'lib qoladi.

Oltin qatlam ko'payib ketadi

Har yangi savol uchun yangi oltin jadval yaratish oson. Olti oydan keyin 80 ta oltin jadval bo'ladi va ularning yarmi bir-biriga zid.

Sabab: ikki jadval "faol e'lon" ni turlicha hisoblagan, ikkalasi ham to'g'ri ko'rinadi.

Qoida: oltin jadvallar kumushdan quriladi va ta'riflar bitta joyda yoziladi. Yangi oltin jadval qo'shishdan oldin "bu savolga mavjud jadval javob bera oladimi?" deb so'rang.

Qaysi biri sizga kerak

Lakehouse moda bo'lgani uchun tanlanmasligi kerak. Uchta holat bor:

Sizda Tavsiya
Ma'lumot bir necha GB, faqat SQL kerak Oddiy baza (Postgres) yetarli
Terabaytlar, faqat hisobot kerak Klassik ombor ham yaxshi ishlaydi
Terabaytlar + ML + har xil format Lakehouse

Eng ko'p uchraydigan xato

Kichik kompaniya 50 GB ma'lumot bilan lakehouse quradi: Iceberg, katalog, Spark, uch qatlam.

Natija: infratuzilmani saqlash uchun bir kishi to'liq band, hisobot esa Postgres dagidan sekinroq.

Lakehouse masshtab muammosini hal qiladi. Masshtab muammosi bo'lmasa, u faqat murakkablik qo'shadi. 50 GB — Postgres uchun katta emas.

Tozalash

python9 qator
python3 -c "
import sys; sys.path.insert(0, 'src')
from quvur.muz import katalog
c = katalog()
for t in ('bronza.elon', 'kumush.elon', 'oltin.tuman_xulosa'):
    try: c.drop_table(t); print(f'{t} o\\'chirildi')
    except Exception: pass
print('rostuy.elon saqlanadi')
"
Chiqish (qaytish kodi 0, 1168 ms)
bronza.elon o'chirildi
kumush.elon o'chirildi
oltin.tuman_xulosa o'chirildi
rostuy.elon saqlanadi

Xulosa

Tushuncha Asosiy fikr
Ko'l Arzon, hamma format, lekin tartibsiz
Ombor Tartibli va tez, lekin qimmat va cheklangan
Ikki nusxa narxi Pul, kechikish va ikki xil raqam
Lakehouse Ko'l narxi + ombor tartibi (Iceberg orqali)
Bitta manba pandas, SQL va Spark bir xil fayllarni o'qiydi
Mantiqiy nusxa Format hal qilmaydi — kelishilgan ta'rif kerak
Bronza Xom, o'zgartirilmagan; qayta hisoblash uchun
Kumush Tozalangan; ishonchli asos
Oltin Yig'ilgan; hisobot uchun tez
Qachon kerak emas Ma'lumot kichik bo'lsa — Postgres yetarli

Talaba ishi

  1. Bronza qatlamga takroriy elon_id lar bor. Nechta? Kumushda ular nima bo'ldi?
  2. Yangi oltin jadval yarating: xonalar soni bo'yicha o'rtacha narx. Uni kumushdan quring.
  3. Kumush qatlamga yangi qoida qo'shing (tavsif_uzunligi > 0). Nechta qator yo'qoldi va oltin qatlamdagi raqamlar o'zgardimi?
  4. Bronza jadvalni o'chirib ko'ring va kumushni qayta qurishga urinib ko'ring. Nega bronza qatlam saqlanishi kerakligini tushuntiring.

Bu bobni bepul o'qidingiz.

Kitob o'rgatadi, lekin sizning kodingiz nega ishlamaganini ayta olmaydi. Kursda uy vazifangiz tekshiriladi.

Telegramda yozish Narxlar