Poznámka:
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
Tato stránka popisuje koncepty vodoznaků a obsahuje doporučení k používání vodoznaků v běžných stavových operacích streamování.
Streamované dotazy hromadí stavová data v průběhu času. Vodoznaky automaticky odeberou stará data o stavu, aby se zabránilo chybám paměti a vyšší latenci zpracování.
Co je vodoznak?
Strukturované streamování během zpracování uchovává stav napříč mikrodávkovými dávkami. Streamovací dotazy používají stav k průběžné aktualizaci výsledků místo toho, aby po každé mikrodávce znovu přepočítávaly vše. Vodoznaky určují prahovou hodnotu, kdy dotaz přestane zpracovávat entitu stavu.
Mezi běžné příklady stavových entit patří:
- Agregace v časovém intervalu
- Jedinečné klíče v propojení mezi dvěma datovými proudy
Pokud chcete deklarovat vodoznak u streamovaného datového rámce, zadejte pole časového razítka a prahovou hodnotu zpoždění. Při příchodu nových dat správce stavu sleduje poslední časové razítko v zadaném poli a zpracovává pouze záznamy v rámci prahové hodnoty zpoždění.
Dotazy vždy zpracovávají záznamy, které přicházejí do prahové hodnoty. Dotazy můžou stále zpracovávat záznamy, které přicházejí mimo prahovou hodnotu, ale to není zaručené.
Následující příklad použije prahovou hodnotu meze 10 minut na počet oken:
Python
from pyspark.sql.functions import window
(df
.withWatermark("event_time", "10 minutes")
.groupBy(
window("event_time", "5 minutes"),
"id")
.count()
)
Scala
import org.apache.spark.sql.functions.window
df
.withWatermark("event_time", "10 minutes")
.groupBy(
window($"event_time", "5 minutes"),
$"id")
.count()
V tomto příkladu:
- Sloupec
event_timeslouží k definování 10minutového vodoznaku a 5minutového přeskakujícího okna. - Pro každý pozorovaný
idje zaznamenán počet pro každý nepřekrývající se 5minutový interval. - Informace o stavu jsou pro každé počítadlo uchovávány, dokud není konec okna o 10 minut starší než nejnověji pozorované
event_time.
Důležité
Při operaci groupBy() a window() odkazujte na sloupce podle názvu, "<colName>" nebo col("<colName>"), aby se zachovala značka času události. Ve Scale můžete také použít $colName.
Jak vodoznaky ovlivňují dobu zpracování a propustnost?
Režimy výstupu určují, kdy dotaz s vodoznaky zapisuje data do jímky. Vodoznaky jsou nezbytné pro řízení propustnosti ve stavovém streamování, protože snižují celkové množství informací o stavu v paměti. Pro všechny stavové operace se nepodporují všechny režimy výstupu. viz Vodoznaky a výstupní režim pro okenní agregace.
Výběr doby trvání vodoznaku má kompromisy:
- Kratší vodoznaky snižují latenci dotazů, protože dotazy ukládají méně informací o stavu a po dokončení každé doby trvání vodoznaku zapisují výsledky. Krátké vodoznaky ale mají nízkou toleranci pro pozdní data.
- Delší vodoznaky mají vysokou toleranci pro pozdní data. Dlouhé vodoznaky ale zvyšují latenci dotazů, protože dotazy musí ukládat další informace o stavu a čekat na zápis výsledků po delší době trvání vodoznaku.
Vodoznaky a výstupní režim pro okenní agregace
Následující tabulka ukazuje chování zpracování dotazů s agregací nad časovým razítkem a vodoznakem:
| Výstupní režim | Chování |
|---|---|
| Připojit | Dotaz zapíše řádky do cílové tabulky po překročení prahové hodnoty vodoznaku. Všechny zápisy jsou zpožděné na základě prahové hodnoty zpoždění. Starý stav agregace se po uplynutí prahové hodnoty zahodí. |
| Aktualizace | Dotaz zapíše řádky do cílové tabulky při výpočtu výsledků a dotaz může aktualizovat a přepsat řádky při příchodu nových dat. Starý stav agregace se po uplynutí prahové hodnoty zahodí. |
| Dokončit | Stav agregace se nezahodí. Dotaz přepíše cílovou tabulku pro každou aktivační událost. |
Vodoznaky a výstupní režimy pro spojení mezi streamy
Spojení více datových proudů podporují pouze režim přidávání. Dotazy zapisují odpovídající záznamy pro každou dávku.
Pro vnitřní spojení databricks doporučuje nastavit prahovou hodnotu meze u každého streamovaného zdroje dat, aby dotaz mohl zahodit informace o stavu starých záznamů. Bez použití vodoznaků se Structured Streaming při každém spuštění pokusí spojit všechny klíče z obou stran spojení, což může ovlivnit výkon.
U vnějších spojení je vodoznak povinný. Pokud je záznam nesouvisený, dotaz zapíše hodnotu null pro tento klíč. Protože spojení podporují pouze režim přidávání, nespárované záznamy se nezapíšou, dokud není překročen práh zpoždění.
Řiďte práh pozdních dat pomocí zásady více vodoznaků
U více vstupů strukturovaného streamování můžete nastavit více vodoznaků pro řízení prahových hodnot tolerance pro pozdní data. Vodoznaky umožňují řídit informace o stavu a latenci.
Streamovací dotaz může mít více vstupních datových proudů, které jsou sjednocovány nebo spojeny dohromady. U stavových operací může každý vstupní datový proud vyžadovat jinou prahovou hodnotu pro pozdní toleranci dat. Tyto prahové hodnoty zadejte pomocí withWatermark("eventTime", delay) pro každý vstupní proud. Následuje příklad dotazu s spojeními stream-stream.
Python
input_stream1 = ... # delays up to 1 hour
input_stream2 = ... # delays up to 2 hours
(input_stream1.withWatermark("eventTime1", "1 hour")
.join(
input_stream2.withWatermark("eventTime2", "2 hours"),
joinCondition)
)
Scala
val inputStream1 = ... // delays up to 1 hour
val inputStream2 = ... // delays up to 2 hours
inputStream1.withWatermark("eventTime1", "1 hour")
.join(
inputStream2.withWatermark("eventTime2", "2 hours"),
joinCondition)
Při spouštění dotazu se stavovými operacemi sleduje strukturované streamování jednotlivě maximální dobu události pro každý vstupní datový proud, vypočítá vodoznaky na základě odpovídajícího zpoždění a určí jeden globální vodoznak. Strukturované streamování ve výchozím nastavení používá jako globální vodoznak minimum. Pokud některý datový proud zaostává za ostatními, minimální globální vodotisk zabrání tomu, aby dotaz omylem označil data jako opožděná. K tomu může dojít například v případě, že některý z datových proudů přestane přijímat data kvůli selháním nadřazeného datového proudu. Globální vodoznak se bezpečně pohybuje tempem nejpomalejšího datového proudu a v případě potřeby zpožďuje výstup dotazu.
Chcete-li snížit latenci, nastavte spark.sql.streaming.multipleWatermarkPolicy na hodnotu max (výchozí hodnota je min), aby se jako globální vodoznak použil vodoznak nejrychlejšího datového proudu. Tato konfigurace však zahodí data z nejpomalejších datových proudů. Databricks doporučuje, abyste tuto konfiguraci použili opatrně.
Použití vodoznaků u jedinečných operací
Operace distinct sleduje všechny jedinečné záznamy v rámci stavu. Bez vodoznaku se stav natrvalo rozrůstá a může způsobit problémy s pamětí. Zadejte vodoznak v poli časového razítka pro svázaný stav a po uplynutí prahové hodnoty odeberte staré záznamy.
Následující příklad použije vodoznak na distinct operaci:
Python
streamingDf = spark.readStream. ... # columns: eventTime, id, value, ...
# Apply watermark before distinct operation
(streamingDf
.withWatermark("eventTime", "1 hour")
.distinct()
)
Scala
val streamingDf = spark.readStream. ... // columns: eventTime, id, value, ...
// Apply watermark before distinct operation
streamingDf
.withWatermark("eventTime", "1 hour")
.distinct()
V tomto příkladu dotaz streamování odebere duplicitní záznamy, které přicházejí do 1 hodiny od posledního zjištěného stavu eventTime. Dotaz zahodí informace o stavu pro odstranění duplicit po uplynutí prahové hodnoty.
Důležité
Chcete-li místo všech sloupců odstranit duplicity v konkrétních sloupcích, použijte dropDuplicates() nebo dropDuplicatesWithinWatermark() místo distinct. Viz Odstranění duplicit vodoznaku.
Přetažení duplicit v rámci vodoznaku
Ve službě Databricks Runtime 13.3 LTS nebo vyšší můžete k odstranění duplicitních záznamů v rámci prahové hodnoty meze použít jedinečný identifikátor.
Strukturované streamování zaručuje zpracování přesně jednou, ale neodstraňuje záznamy ze zdrojů dat. Slouží dropDuplicatesWithinWatermark k odebrání duplicit u libovolného pole, i když se pole liší mezi duplicitními záznamy, jako je čas události nebo čas příjezdu.
U dropDuplicatesWithinWatermark dotazů se záznamy, které dorazí v rámci prahové hodnoty watermarku, vždy deduplikují. Dotazy můžou také odstranit duplicitní záznamy, které přicházejí mimo prahovou hodnotu, ale to není zaručené. Pokud chcete zaručit, že dotazy zahodí všechny duplicity, nastavte prahovou hodnotu meze tak, aby byla větší než maximální rozdíl časového razítka mezi duplicitními událostmi.
Chcete-li použít metodu dropDuplicatesWithinWatermark , musíte zadat vodoznak:
Python
streamingDf = spark.readStream. ...
# deduplicate using guid column with watermark based on eventTime column
(streamingDf
.withWatermark("eventTime", "10 hours")
.dropDuplicatesWithinWatermark(["guid"])
)
Scala
val streamingDf = spark.readStream. ... // columns: guid, eventTime, ...
// deduplicate using guid column with watermark based on eventTime column
streamingDf
.withWatermark("eventTime", "10 hours")
.dropDuplicatesWithinWatermark(Seq("guid"))
Příklady případů použití
Následující příklady ukazují pokročilé případy použití oken:
Použití přeskakujících oken k výpočtu celkových tržeb za hodinu
Překlápěcí okna mají pevnou velikost a nepřekrývající se intervaly. Každý vstupní řádek patří přesně do jednoho okna. Pomocí přeskakujících oken můžete vypočítat diskrétní agregace časového období, jako jsou celkové tržby po hodinách:
Python
from pyspark.sql.functions import window, sum
hourly_sales = (orders
.withWatermark("timestamp", "1 hour")
.groupBy(window("timestamp", "1 hour"))
.agg(sum("amount").alias("total_sales"))
)
Scala
import org.apache.spark.sql.functions.{window, sum}
val hourlySales = orders
.withWatermark("timestamp", "1 hour")
.groupBy(window($"timestamp", "1 hour"))
.agg(sum($"amount").alias("total_sales"))
V tomto příkladu:
-
window("timestamp", "1 hour")seskupuje objednávky do nepřekrývajících se jednohodinových intervalů, například od 5 do 6 hodin a od 6 do 7 hodin. -
withWatermark("timestamp", "1 hour")uchovává agregaci každého okna ve stavu, dokud časové razítko konce okna nebude o 1 hodinu starší než maximální časové razítko objednávky.
Pomocí posuvných oken vypočítejte průběžné agregace
Posuvná okna mají pevnou velikost s intervaly, které se můžou překrývat. Jeden řádek může patřit do více oken. Pomocí posuvných oken můžete vypočítat souhrnné agregace, jako je prodej za 6 hodin:
Python
from pyspark.sql.functions import window, sum
rolling_sales = (orders
.withWatermark("timestamp", "1 hour")
.groupBy(window("timestamp", "6 hours", slideDuration="1 hour"))
.agg(sum("amount").alias("total_sales"))
)
Scala
import org.apache.spark.sql.functions.{window, sum}
val rollingSales = orders
.withWatermark("timestamp", "1 hour")
.groupBy(window($"timestamp", "6 hours", "1 hour"))
.agg(sum($"amount").alias("total_sales"))
V tomto příkladu:
-
window("timestamp", "6 hours", slideDuration="1 hour")seskupuje objednávky do 6hodinových intervalů, které se posouvají o 1 hodinu, například od 5:00 do 11:00 a od 6:00 do 12:00. -
withWatermark("timestamp", "1 hour")udržuje agregovanou hodnotu každého okna ve stavu, dokud časové razítko konce okna není o 1 hodinu starší než maximální časové razítko objednávky. -
slideDurationmusí být menší než nebo rovno hodnotěwindowDuration.
Pomocí oken relace můžete kontrolovat aktivitu uživatelů
Okna relací nemají pevnou velikost. Okno se otevře, když dorazí řádek, a uzavře se po uplynutí doby, během níž nedorazí žádné nové řádky. Pomocí oken relací můžete agregovat shluky aktivit oddělené dlouhými obdobími nečinnosti, například zobrazení stránek uživatelem během 30 minut:
Python
from pyspark.sql.functions import session_window, sum
sessionized_page_views = (activity
.withWatermark("timestamp", "1 hour")
.groupBy("user_id", session_window("timestamp", gapDuration="30 minutes"))
.agg(sum("page_views").alias("total_page_views"))
)
Scala
import org.apache.spark.sql.functions.{session_window, sum}
val sessionizedPageViews = activity
.withWatermark("timestamp", "1 hour")
.groupBy($"user_id", session_window($"timestamp", "30 minutes"))
.agg(sum($"page_views").alias("total_page_views"))
V tomto příkladu:
-
session_window("timestamp", gapDuration="30 minutes")otevře okno při příchodu prvního zobrazení stránky. Každé další zobrazení stránky, které dorazí do 30 minut, rozšiřuje okno. Když do 30 minut nedorazí žádné zobrazení stránky, okno se zavře a další zobrazení stránky spustí nové okno. -
withWatermark("timestamp", "1 hour")uchovává agregovaná data každé relace ve stavu, dokud časové razítko konce okna není o 1 hodinu starší než nejvyšší časové razítko zobrazení stránky. - Argument
timeColumnprowindow()asession_window()musí být typuTimestampTypeneboTimestampNTZType. - Slouží
current_timestamp()k definování oken na základě času zpracování, nikoli času události. - Dobu trvání okna můžete nastavit od mikrosekund až po dny. Doba trvání měsíce a delší nejsou podporována.
- Použijte výstupní režim
completes okenními agregacemi, aby se veškerý stav oken uchovával na neomezenou dobu. Pomocíappendvýstupního režimu s odpovídajícím vodoznakem můžete svázat růst stavu a zabránit problémům s pamětí u velkých datových sad. Další podrobnosti o chování výstupního režimu najdete v tématu Vodoznaky a režim výstupu pro agregace s okny.