All Products
Search
Document Center

Realtime Compute for Apache Flink:Ikhtisar fitur

Last Updated:Aug 04, 2026

Topik ini menjelaskan fitur-fitur DataFrame API melalui skenario lengkap analisis pesanan e-commerce dan penanganan tiket layanan pelanggan.

Buat objek DataFrame

Gunakan data lokal

Saat pengembangan dan debugging, Anda dapat dengan cepat membuat DataFrame dari dict Python, daftar catatan (records), atau DataFrame Pandas. DataFrame yang digunakan sepanjang topik ini dibuat sebagai berikut: tabel pengguna users, tabel pesanan orders, tabel event events, dan tabel profil pengguna profiles.

import pandas as pd
import pyflink.dataframe as pf
from pyflink.dataframe import col, lit, udf, udtf, DataType

# User table
users = pf.from_dict({
    "user_id": [1, 2, 3],
    "name": ["Alice", "Bob", "Charlie"],
    "city": ["Hangzhou", "Shanghai", "Beijing"],
})

# Order fact table
orders = pf.from_records(
    [
        (1001, 1, 99.9, "PAID"),
        (1002, 2, 35.5, "CREATED"),
        (1003, 1, 188.0, "PAID"),
        (1004, 3, 88.8, None),
    ],
    schema=["order_id", "user_id", "amount", "status"],
)

# User behavior event table
events = pf.from_records(
    [
        {"event_id": "e1", "user_id": 1, "event": "login"},
        {"event_id": "e2", "user_id": 2, "event": "pay"},
    ],
    schema=["event_id", "user_id", "event"],
)

# User profile table
profiles = pf.from_records(
    [
        (1, "gold"),
        (2, "silver"),
        (3, "gold"),
    ],
    schema=["user_id", "member_level"],
)

# You can also build from pandas DataFrame
pdf = pd.DataFrame({"id": [1, 2], "score": [0.8, 0.95]})
df_from_pandas = pf.from_pandas(pdf)

Baca data eksternal

Gunakan fungsi baca bawaan

Di lingkungan produksi, Anda dapat menggunakan fungsi baca bawaan DataFrame API untuk membaca dari sumber data umum.

kafka_events = pf.read_kafka(
    "broker-1:9092,broker-2:9092",
    topic="user_events",
    group_id="df-api-demo",
    startup_mode="earliest-offset",
    format="json",
    schema={
        "user_id": DataType.int64(),
        "event": DataType.string(),
        "event_time": DataType.timestamp(3),
        "payload": DataType.string(),
    },
)

Untuk fungsi baca lainnya, lihat Data ingestion.

Catatan

Saat membaca dari sistem eksternal, tipe bidang biasanya perlu dideklarasikan secara eksplisit. DataType menyediakan tipe umum, termasuk integer, float, string, boolean, tanggal-waktu, dan tipe komposit.

Gunakan konektor kustom

Anda dapat menggunakan fungsi read_generic untuk membaca data dari konektor atau konektor kustom lain yang didukung.

Parameter connector adalah pengenal konektor yang akan digunakan, dan options meneruskan parameter WITH dari konektor tersebut.

source = pf.read_generic(
    "datagen",
    schema={
        "id": DataType.int64(),
        "name": DataType.string(),
    },
    options={
        "number-of-rows": "1000",
        "fields.id.kind": "sequence",
        "fields.id.start": "1",
        "fields.id.end": "1000",
    },
)

Baca tabel Catalog

Jika data Anda sudah terdaftar di Flink Catalog, Anda dapat menggunakan read_catalog_table untuk membaca dari tabel Catalog.

pf.create_catalog(
    "lake",
    {
        "type": "paimon",
        "warehouse": "oss://my-bucket/warehouse",
    },
)

pf.use_catalog("lake")
pf.use_database("commerce")

historical_orders = pf.read_catalog_table("orders")

Pemrosesan data

Sebagian besar transformasi DataFrame menghasilkan DataFrame baru, sehingga sangat cocok untuk dirangkai (chaining). Operasi umum mencakup proyeksi, penyaringan, kolom turunan, penghapusan kolom, penggantian nama, agregasi, penggabungan (join), deduplikasi, operasi himpunan, ekspansi array, serta penanganan nilai yang hilang.

Proyeksi, penyaringan, dan kolom turunan

paid_orders berikut menyaring pesanan yang telah dibayar dari tabel pesanan orders dan mengelompokkannya berdasarkan jumlah:

paid_orders = (
    orders
    .select("order_id", "user_id", "amount", "status")
    .filter("status = 'PAID' AND amount > 0")
    .with_columns(
        amount_yuan=col("amount"),
        amount_level=(
            (col("amount") >= 100).then("high", "normal")
        ),
    )
    .drop("status", "amount")
    .rename({"amount_yuan": "amount"})
)

Anda juga dapat menggunakan sintaks subscript untuk mereferensikan atau memilih kolom:

amount_expr = paid_orders["amount"]
simple_view = paid_orders[["order_id", "user_id", "amount"]]
large_orders = paid_orders[col("amount") > 100]

Penanganan nilai null dan NaN

drop_null / fill_null menangani SQL NULL, sedangkan drop_nan / fill_nan menangani NaN pada kolom floating-point.

clean_orders berikut merepresentasikan tabel pesanan setelah pembersihan kualitas:

clean_orders = (
    orders
    .drop_null(subset=["order_id", "user_id"])
    .fill_null("UNKNOWN", subset=["status"])
    .fill_nan(0.0, subset=["amount"])
)

Deduplikasi

Gunakan drop_duplicates untuk menghapus duplikat berdasarkan semua bidang atau subset tertentu.

unique_events = events.drop_duplicates(
    subset=["event_id"]
)

Explode

Ketika suatu bidang berisi array, map, atau multiset, Anda dapat menggunakan explode untuk memperluas satu baris menjadi beberapa baris:

order_items = pf.from_records(
    [
        (1001, ["phone", "case"]),
        (1002, ["book"]),
    ],
    schema=["order_id", "items"],
)

item_rows = order_items.explode("items", "item")

Operasi himpunan

Beberapa DataFrame yang kompatibel secara struktural dapat digabungkan dengan union, union_all, intersect, intersect_all, minus, dan minus_all.

Catatan

Dalam mode streaming, hanya union_all yang didukung.

all_paid_orders = (
    paid_orders.select("order_id", "user_id", "amount")
    .union_all(historical_orders.select("order_id", "user_id", "amount"))
    .drop_duplicates(subset=["order_id"])
)

Agregasi

Gunakan group_by bersama dengan agg untuk melakukan agregasi berkelompok. Kolom pengelompokan tetap dipertahankan dalam DataFrame hasil.

user_summary berikut adalah tabel metrik pesanan yang diagregasi per pengguna, berisi jumlah pesanan, total jumlah, dan rata-rata jumlah:

user_summary = (
    clean_orders
    .filter("status = 'PAID'")
    .group_by("user_id")
    .agg(
        order_count=col("order_id").count,
        total_amount=col("amount").sum,
        avg_amount=col("amount").avg,
    )
)

Anda juga dapat melakukan agregasi pada seluruh tabel:

overall = clean_orders.agg(
    order_count=col("order_id").count,
    total_amount=col("amount").sum,
)

Join

join mendukung jenis join inner, left, right, full, dan outer.

Contoh berikut menggabungkan tabel pengguna users dengan tabel event events untuk menghasilkan tabel lebar enriched yang berisi nama pengguna, kota, dan event:

enriched = (
    users
    .join(
        events,
        on="user_id",
        how="left",
    )
    .select(
        "user_id",
        "name",
        "city",
        "event_id",
        "event",
    )
)

join_asof dapat digunakan untuk melakukan join tabel dimensi. Data sisi kanan harus berasal dari sumber tabel dimensi.

dim_users = pf.read_hologres(
    endpoint="hgpostcn-cn-xxx-cn-hangzhou.hologres.aliyuncs.com:80",
    db_name="commerce",
    table_name="dim_user_profile",
    username="${secret_values.hg_user}",
    password="${secret_values.hg_password}",
    schema={
        "user_id": DataType.int64(),
        "member_level": DataType.string(),
        "city": DataType.string(),
    },
    primary_key="user_id",
    binlog=False,
    cache="LRU",
)

events_with_profile = events.join_asof(
    dim_users,
    by="user_id",
    how="left",
)

Gunakan kueri SQL

Anda dapat menjalankan kueri SELECT dengan sql. SQL dapat mereferensikan variabel DataFrame dan user-defined function dalam cakupan saat ini tanpa registrasi manual.

Contoh berikut menggunakan SQL untuk memilih pengguna dengan pengeluaran tinggi dari ringkasan pesanan per pengguna user_summary dan menggabungkannya dengan tabel profil pengguna profiles:

top_users = pf.sql("""
    SELECT
        user_summary.user_id,
        profiles.member_level,
        user_summary.order_count,
        user_summary.total_amount
    FROM user_summary
    LEFT JOIN profiles
        ON user_summary.user_id = profiles.user_id
    WHERE user_summary.total_amount >= 200
""")

User-defined function

Ketika ekspresi standar, agregasi, atau SQL tidak dapat merepresentasikan logika bisnis, Anda dapat menggunakan user-defined function:

  • User-defined scalar function (udf) memproses satu baris input menjadi satu nilai hasil. DataFrame API mendukung UDF sinkron/asinkron dan UDF per baris/vektor. UDF dapat digunakan dalam with_column atau with_columns untuk menambahkan hasil sebagai kolom baru, atau dalam map atau map_batches untuk mentransformasi seluruh baris.

  • User-defined table function (udtf) memperluas satu baris input menjadi sejumlah baris output, masing-masing dengan satu atau lebih kolom. UDTF dapat dipanggil dengan join_lateral atau flat_map.

Contoh berikut menggunakan UDF untuk memproses data. Untuk penggunaan lengkap, lihat User-defined functions.

from typing import Iterator, TypedDict

@udf
def normalize_status(status: str) -> str:
    if status is None:
        return "UNKNOWN"
    return status.strip().upper()

orders_with_status = orders.with_column(
    "status_norm", normalize_status(col("status"))
)

@udtf
def review_reasons(status: str, amount: float) -> Iterator[str]:
    if status is None:
        yield "missing_status"
    elif status == "CREATED":
        yield "pending_payment"
    if amount is not None and amount >= 100:
        yield "high_value_order"

order_review_reasons = orders.join_lateral(
    review_reasons(col("status"), col("amount")).alias("review_reason"),
)

AI / LLM

DataFrame API menyediakan kemampuan AI/LLM melalui df.llm. Pertama-tama daftarkan penyedia model dengan set_model_provider, lalu panggil fungsi AI bawaan atau pemanggilan generik pada DataFrame.

Konfigurasi penyedia

Contoh berikut menggunakan model bawaan yang disediakan oleh Flink AI Service. Hanya parameter task yang wajib; Anda tidak perlu mengonfigurasi endpoint atau api_key. Flink AI Service harus diaktifkan sebelum Anda dapat menggunakan model bawaan. Untuk informasi lebih lanjut, lihat Flink AI Service (built-in models).

pf.set_model_provider("chat", pf.OpenAICompatProvider(task="chat/completions"))
pf.set_model_provider("embedding", pf.OpenAICompatProvider(task="embeddings"))

Pemanggilan generik

predict digunakan untuk pemanggilan LLM generik. Bidang output dapat ditentukan dengan output_type.

Sebagai contoh, questions berikut merepresentasikan pertanyaan tiket layanan pelanggan, dan answers adalah tabel hasil dengan kolom tambahan output yang berisi jawaban dari model.

questions = pf.from_records(
    [
        (1, "Why hasn't my order been shipped yet? My order number is 1. Charlie"),
        (2, "How do I apply for a refund? My email is bob@example.com. Order number: 2."),
    ],
    schema=["ticket_id", "question"],
)

answers = questions.llm.predict(
    "question",
    provider="chat",
    model="qwen3.6-plus",
    system_prompt="You are a helpful assistant.",
    user_prompt="Answer the customer support question concisely. Return only the answer.",
    temperature=0.2,
    config={"max-concurrent-operations": "20"},
)

Fungsi AI bawaan

Anda dapat menggunakan fungsi AI bawaan untuk tugas umum. Fungsi AI bawaan menambahkan kolom hasil ke DataFrame asli; untuk detailnya, lihat AI / LLM functions.

# classify
classified = questions.llm.ai_classify(
    "question",
    labels=["logistics", "refund", "account", "other"],
    provider="chat",
    model="qwen3.6-plus",
)

# sentiment analysis
sentiment = questions.llm.ai_sentiment("question", provider="chat", model="qwen3.6-plus")

# information extraction, schema described as JSON string
extracted = questions.llm.ai_extract(
    "question",
    '{"order_id":"STRING","intent":"STRING"}',
    provider="chat",
    model="qwen3.6-plus",
)

# summarize
summaries = questions.llm.ai_summarize(
    "question",
    max_length=50,
    provider="chat",
    model="qwen3.6-plus",
)

# mask sensitive data
masked = questions.llm.ai_mask(
    "question",
    entities=["PERSON", "PHONE", "EMAIL"],
    provider="chat",
    model="qwen3.6-plus",
)

# embedding
embeddings = questions.llm.ai_embed(
    "question",
    dimension=1024,
    provider="embedding",
    model="text-embedding-v4",
)

Fungsi AI di atas juga mendukung caching hasil inferensi berulang dalam tabel Fluss melalui parameter cache_table dan cache_key, yang mengurangi biaya token. Untuk detailnya, lihat Generic invocation.

Pencarian vektor

Anda dapat menggunakan vector_search untuk terhubung ke database vektor seperti Milvus dan menjalankan pencarian vektor.

documents = pf.read_milvus(
    endpoint="http://milvus.example.com",
    username="${secret_values.milvus_user}",
    password="${secret_values.milvus_password}",
    database_name="commerce",
    collection_name="support_docs",
    schema={
        "doc_id": DataType.int64(),
        "embedding": DataType.list(DataType.float32()),
        "title": DataType.string(),
    },
    search_metric="COSINE",
)

matched_docs = embeddings.llm.vector_search(
    documents,
    column_to_search="embedding",
    column_to_query="embedding",
    top_k=3,
    output_columns=["doc_id", "doc_embedding", "doc_title", "score"],
)

Operator multimodal

DataFrame API memungkinkan Anda memanggil operator multimodal secara praktis untuk membaca, menganalisis, dan mentransformasi data multimodal seperti gambar, audio, dan video. Untuk detailnya, lihat Multimodal operators. Untuk referensi API, lihat Multimodal Expressions.

Contoh berikut membaca konten gambar dari URL dan melakukan pemeriksaan validitas, decoding, ekstraksi metadata, penilaian kualitas, serta pembuatan gambar mini.

images = pf.from_dict({
    "image_id": ["1", "2"],
    "image_url": [
        "oss://bucket/products/1.jpg",
        "oss://bucket/products/2.jpg",
    ],
})

image_analysis = (
    images
    .with_column("image_bytes", col("image_url").fetch_content())
    .with_columns(
        image_valid=col("image_bytes").image.is_valid(
            pixel_limit=40_000_000
        ),
        metadata=col("image_bytes").image.metadata(),
    )
    .filter("image_valid")
    .with_column(
        "image",
        col("image_bytes").image.decode(
            on_error="null", mode="RGB", pixel_limit=40_000_000
        ),
    )
    .drop_null(subset=["image"])
    .with_columns(
        quality_score=col("image").image.quality_score(),
        thumbnail=(
            col("image")
            .image.resize(width=512, height=512)
            .image.encode(format="JPEG", quality=85)
        ),
    )
)

Data sink

Metode write DataFrame menulis DataFrame saat ini ke sistem target.

Gunakan fungsi sink bawaan

Anda dapat menggunakan fungsi sink bawaan DataFrame API untuk menulis data ke sistem umum.

enriched.write_kafka(
    "broker-1:9092,broker-2:9092",
    topic="user_summary",
    format="json",
    key_format="json",
    key_fields=["user_id"],
    delivery_guarantee="at-least-once",
)

Untuk fungsi sink lainnya, lihat Data sinks.

Gunakan konektor kustom

Anda dapat menggunakan fungsi write_generic untuk menulis data melalui konektor atau konektor kustom lain yang didukung.

Parameter connector adalah pengenal konektor yang akan digunakan, dan options meneruskan parameter WITH dari konektor tersebut.

enriched.write_generic(
    "blackhole",
    options={
        "sink.parallelism": "4",
    },
)

Jika konektor memerlukan batasan primary key, gunakan parameter primary_key:

enriched.write_generic(
    "my-custom-sink",
    primary_key="user_id",
    options={
        "endpoint": "example.com:9000",
        "format": "json",
    },
)

Tulis ke tabel Catalog

Jika tabel target sudah ada di Catalog, Anda juga dapat menggunakan write_catalog_table untuk menulis langsung:

matched_docs.write_catalog_table(
    "lake.commerce.support_ticket_matches",
    overwrite=True,
)

Tulis ke beberapa target

Saat Anda perlu menulis pipeline komputasi yang sama ke beberapa target, gunakan create_statement_set untuk membuat StatementSet, teruskan ke parameter statement_set pada setiap metode write, lalu eksekusi secara bersamaan:

statement_set = pf.create_statement_set()

enriched.write_kafka(
    "broker-1:9092,broker-2:9092",
    topic="user_summary",
    format="json",
    statement_set=statement_set,
)
enriched.write_json(
    "oss://my-bucket/user_summary/",
    statement_set=statement_set,
)

statement_set.execute()