Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions docs/docs/flink-queries.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,33 @@ SELECT * FROM table /*+ OPTIONS('tag'='t1') */;
SELECT * FROM table /*+ OPTIONS('streaming'='true', 'monitor-interval'='1s', 'start-tag'='t1', 'end-tag'='t2') */;
```

### Lookup Join

Iceberg supports Flink lookup join, which enriches a stream with data from an Iceberg dimension table:

```sql
-- The OPTIONS hint used in this section requires dynamic table options, which are disabled by default.
SET table.dynamic-table-options.enabled=true;

SELECT o.order_id, o.user_id, u.name, u.city
FROM orders AS o
LEFT JOIN iceberg_catalog.db.user_dim
FOR SYSTEM_TIME AS OF o.proc_time AS u
ON o.user_id = u.user_id;
```

Iceberg implements lookup join with a full cache: the whole projected dimension table is loaded into the cache, and every lookup is served from it without falling back to the table. The full cache is held in memory on the TaskManager heap, so lookup join targets dimension tables that fit comfortably there.

The cache is loaded by default when the lookup function is opened. Set lookup.full-cache.eager-load to false to load it on the first lookup instead, which blocks the data flow until the cache is fully loaded.

There is no background refresh: the cache keeps the data it was loaded with for the lifetime of the job, so the dimension table should be populated before the join starts.

The lookup options are:

| Option | Default | Description |
| ---------------------------- |---------|-----------------------------------------------------------------------------------------------------------------------------|
| lookup.full-cache.eager-load | true | Whether to load the full cache when the lookup function is opened, instead of on the first lookup. |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we usually do hiearchical. So maybe lookup.cache.full.eager-load, or since we don't have full, we can just say lookup.cache.eager-load?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used the lookup.full-cache. prefix because I’d like the refresh-related options we add later to be consistent with the existing Flink options.

https://github.com/apache/flink/blob/master/flink-table/flink-table-common/src/main/java/org/apache/flink/table/connector/source/lookup/LookupOptions.java

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense


## Reading with DataStream

Iceberg support streaming or batch read in Java API now.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,13 @@
import org.apache.flink.table.catalog.exceptions.TableAlreadyExistException;
import org.apache.flink.table.connector.sink.DynamicTableSink;
import org.apache.flink.table.connector.source.DynamicTableSource;
import org.apache.flink.table.connector.source.lookup.LookupOptions;
import org.apache.flink.table.factories.DynamicTableSinkFactory;
import org.apache.flink.table.factories.DynamicTableSourceFactory;
import org.apache.iceberg.catalog.TableIdentifier;
import org.apache.iceberg.exceptions.AlreadyExistsException;
import org.apache.iceberg.flink.source.IcebergTableSource;
import org.apache.iceberg.flink.source.lookup.IcebergLookupOptions;
import org.apache.iceberg.relocated.com.google.common.base.Preconditions;
import org.apache.iceberg.relocated.com.google.common.collect.Maps;
import org.apache.iceberg.relocated.com.google.common.collect.Sets;
Expand Down Expand Up @@ -132,6 +134,8 @@ public Set<ConfigOption<?>> optionalOptions() {
options.add(FlinkCreateTableOptions.CATALOG_TABLE);
options.add(FlinkCreateTableOptions.USE_DYNAMIC_ICEBERG_SINK);
options.add(FlinkCreateTableOptions.DYNAMIC_RECORD_GENERATOR_IMPL);
options.add(LookupOptions.CACHE_TYPE);
options.add(IcebergLookupOptions.FULL_CACHE_EAGER_LOAD);
return options;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,22 +33,30 @@
import org.apache.flink.table.connector.ProviderContext;
import org.apache.flink.table.connector.source.DataStreamScanProvider;
import org.apache.flink.table.connector.source.DynamicTableSource;
import org.apache.flink.table.connector.source.LookupTableSource;
import org.apache.flink.table.connector.source.ScanTableSource;
import org.apache.flink.table.connector.source.abilities.SupportsFilterPushDown;
import org.apache.flink.table.connector.source.abilities.SupportsLimitPushDown;
import org.apache.flink.table.connector.source.abilities.SupportsProjectionPushDown;
import org.apache.flink.table.connector.source.abilities.SupportsSourceWatermark;
import org.apache.flink.table.connector.source.lookup.LookupFunctionProvider;
import org.apache.flink.table.connector.source.lookup.LookupOptions;
import org.apache.flink.table.data.RowData;
import org.apache.flink.table.expressions.ResolvedExpression;
import org.apache.flink.table.factories.FactoryUtil;
import org.apache.flink.table.functions.LookupFunction;
import org.apache.flink.table.legacy.api.TableSchema;
import org.apache.flink.table.types.DataType;
import org.apache.flink.table.types.logical.RowType;
import org.apache.iceberg.expressions.Expression;
import org.apache.iceberg.flink.FlinkConfParser;
import org.apache.iceberg.flink.FlinkConfigOptions;
import org.apache.iceberg.flink.FlinkFilters;
import org.apache.iceberg.flink.FlinkReadOptions;
import org.apache.iceberg.flink.TableLoader;
import org.apache.iceberg.flink.source.assigner.SplitAssignerType;
import org.apache.iceberg.flink.source.lookup.IcebergFullCachingLookupFunction;
import org.apache.iceberg.flink.source.lookup.IcebergLookupOptions;
import org.apache.iceberg.relocated.com.google.common.base.Preconditions;
import org.apache.iceberg.relocated.com.google.common.collect.ImmutableList;
import org.apache.iceberg.relocated.com.google.common.collect.Lists;
Expand All @@ -61,7 +69,8 @@ public class IcebergTableSource
SupportsProjectionPushDown,
SupportsFilterPushDown,
SupportsLimitPushDown,
SupportsSourceWatermark {
SupportsSourceWatermark,
LookupTableSource {

private int[] projectedFields;
private Long limit;
Expand All @@ -72,6 +81,8 @@ public class IcebergTableSource
private final Map<String, String> properties;
private final boolean isLimitPushDown;
private final ReadableConfig readableConfig;
private final boolean caseSensitive;
private final FlinkConfParser flinkConfParser;

private IcebergTableSource(IcebergTableSource toCopy) {
this.loader = toCopy.loader;
Expand All @@ -82,6 +93,8 @@ private IcebergTableSource(IcebergTableSource toCopy) {
this.limit = toCopy.limit;
this.filters = toCopy.filters;
this.readableConfig = toCopy.readableConfig;
this.caseSensitive = toCopy.caseSensitive;
this.flinkConfParser = toCopy.flinkConfParser;
}

public IcebergTableSource(
Expand Down Expand Up @@ -109,6 +122,14 @@ private IcebergTableSource(
this.limit = limit;
this.filters = filters;
this.readableConfig = readableConfig;
this.flinkConfParser = new FlinkConfParser(properties, readableConfig);
this.caseSensitive =
flinkConfParser
.booleanConf()
.option(FlinkReadOptions.CASE_SENSITIVE)
.flinkConfig(FlinkReadOptions.CASE_SENSITIVE_OPTION)
.defaultValue(FlinkReadOptions.CASE_SENSITIVE_OPTION.defaultValue())
.parse();
}

@Override
Expand Down Expand Up @@ -237,4 +258,43 @@ public DynamicTableSource copy() {
public String asSummaryString() {
return "Iceberg table source";
}

@Override
public LookupRuntimeProvider getLookupRuntimeProvider(LookupContext context) {
int[][] lookupKeys = context.getKeys();
int[] keyIndices = new int[lookupKeys.length];
for (int i = 0; i < lookupKeys.length; i++) {
Preconditions.checkArgument(
lookupKeys[i].length == 1, "Iceberg lookup source doesn't support nested lookup key.");
keyIndices[i] = lookupKeys[i][0];
}

ResolvedSchema projected = getProjectedSchema();
RowType projectedRowType = (RowType) projected.toPhysicalRowDataType().getLogicalType();
List<Expression> pushedFilters = filters == null ? ImmutableList.of() : filters;

LookupOptions.LookupCacheType requestedCacheType =
flinkConfParser
.enumConfParser(LookupOptions.LookupCacheType.class)
.option(LookupOptions.CACHE_TYPE.key())
.parseOptional();
Preconditions.checkArgument(
requestedCacheType == null || requestedCacheType == LookupOptions.LookupCacheType.FULL,
"Iceberg lookup join only supports %s=FULL, but it is set to '%s'. NONE and PARTIAL are "
+ "not supported, because an Iceberg table cannot be point-looked-up.",
LookupOptions.CACHE_TYPE.key(),
requestedCacheType);

boolean eagerLoad =
flinkConfParser
.booleanConf()
.option(IcebergLookupOptions.FULL_CACHE_EAGER_LOAD.key())
.defaultValue(IcebergLookupOptions.FULL_CACHE_EAGER_LOAD.defaultValue())
.parse();

LookupFunction lookupFn =
new IcebergFullCachingLookupFunction(
loader, projectedRowType, keyIndices, pushedFilters, caseSensitive, eagerLoad);
return LookupFunctionProvider.of(lookupFn);
}
}
Loading
Loading