-
Notifications
You must be signed in to change notification settings - Fork 3.4k
Flink: Close catalog opened by the dynamic sink serializer cache #17437
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 2 commits
d4b2b6b
6d539f2
68de894
e3ab69a
2ea25d5
ff5fa12
0a43689
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,6 +18,8 @@ | |
| */ | ||
| package org.apache.iceberg.flink.sink.dynamic; | ||
|
|
||
| import java.io.Closeable; | ||
| import java.io.IOException; | ||
| import java.io.Serializable; | ||
| import java.util.Map; | ||
| import javax.annotation.Nullable; | ||
|
|
@@ -28,11 +30,14 @@ | |
| import org.apache.iceberg.PartitionSpec; | ||
| import org.apache.iceberg.Schema; | ||
| import org.apache.iceberg.Table; | ||
| import org.apache.iceberg.catalog.Catalog; | ||
| import org.apache.iceberg.catalog.TableIdentifier; | ||
| import org.apache.iceberg.flink.CatalogLoader; | ||
| import org.apache.iceberg.flink.FlinkSchemaUtil; | ||
| import org.apache.iceberg.relocated.com.google.common.base.Preconditions; | ||
| import org.apache.iceberg.relocated.com.google.common.collect.Maps; | ||
| import org.slf4j.Logger; | ||
| import org.slf4j.LoggerFactory; | ||
|
|
||
| /** | ||
| * A Cache which holds Flink's {@link RowDataSerializer} for a given table name and schema. This | ||
|
|
@@ -48,6 +53,8 @@ | |
| @Internal | ||
| class TableSerializerCache implements Serializable { | ||
|
|
||
| private static final Logger LOG = LoggerFactory.getLogger(TableSerializerCache.class); | ||
|
|
||
| private final CatalogLoader catalogLoader; | ||
| private final int maximumSize; | ||
| private transient Map<String, SerializerInfo> serializers; | ||
|
|
@@ -120,9 +127,22 @@ private class SerializerInfo { | |
| } | ||
|
|
||
| private void update() { | ||
| Table table = catalogLoader.loadCatalog().loadTable(TableIdentifier.parse(tableName)); | ||
| schemas = table.schemas(); | ||
| specs = table.specs(); | ||
| // The serializer has no teardown hook, so the freshly loaded catalog is closed here, after | ||
| // reading the table metadata, to avoid leaking one per cache miss. | ||
| Catalog catalog = catalogLoader.loadCatalog(); | ||
| try { | ||
| Table table = catalog.loadTable(TableIdentifier.parse(tableName)); | ||
| schemas = table.schemas(); | ||
| specs = table.specs(); | ||
| } finally { | ||
| if (catalog instanceof Closeable) { | ||
| try { | ||
| ((Closeable) catalog).close(); | ||
| } catch (IOException e) { | ||
| LOG.warn("Failed to close catalog {}", catalog.name(), e); | ||
| } | ||
| } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would it be feasible to let the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On this I looked into having the loader manage and reuse the catalog. the problem I ran into is that the serializer has no teardown hook, so as far as I can tell nothing at this layer could ever close a held catalog. Does that match your thinking that the reuse part belongs in the TaskManager level cache? While checking, I noticed loadCatalog() has six other call sites in the sink that don't cloae the catalog either. The operator-level ones do have close() hooks, would it be worth a similar interim fix for those, or better to leave them for the TaskManager level cache work? For the Closeable check here.... would it make sense to switch update() to TableLoader.fromCatalog() in a try with resources, like FlinkSink and IcebergSink do for one shot loads? |
||
| } | ||
| } | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -25,16 +25,20 @@ | |
| import static org.apache.iceberg.types.Types.StringType; | ||
| import static org.assertj.core.api.Assertions.assertThat; | ||
|
|
||
| import java.io.IOException; | ||
| import java.util.Map; | ||
| import java.util.function.Supplier; | ||
| import org.apache.flink.api.java.tuple.Tuple3; | ||
| import org.apache.flink.table.runtime.typeutils.RowDataSerializer; | ||
| import org.apache.iceberg.PartitionSpec; | ||
| import org.apache.iceberg.Schema; | ||
| import org.apache.iceberg.Table; | ||
| import org.apache.iceberg.catalog.Catalog; | ||
| import org.apache.iceberg.catalog.Namespace; | ||
| import org.apache.iceberg.catalog.TableIdentifier; | ||
| import org.apache.iceberg.flink.CatalogLoader; | ||
| import org.apache.iceberg.flink.HadoopCatalogExtension; | ||
| import org.apache.iceberg.inmemory.InMemoryCatalog; | ||
| import org.junit.jupiter.api.Test; | ||
| import org.junit.jupiter.api.extension.RegisterExtension; | ||
|
|
||
|
|
@@ -121,4 +125,48 @@ void testCacheSize() { | |
| cache = new TableSerializerCache(CATALOG_EXTENSION.catalogLoader(), 1000); | ||
| assertThat(cache.maximumSize()).isEqualTo(1000); | ||
| } | ||
|
|
||
| @Test | ||
| void testClosesCatalogAfterSchemaLookup() { | ||
| CloseCountingInMemoryCatalog catalog = new CloseCountingInMemoryCatalog(); | ||
| catalog.initialize("tracking", Map.of()); | ||
| catalog.createNamespace(Namespace.of("db")); | ||
| Table table = catalog.createTable(TableIdentifier.of("db", "table"), schema1); | ||
| cache = new TableSerializerCache(new SingleCatalogLoader(catalog), 10); | ||
|
|
||
| // schema/spec ids are unknown, so this misses the cache and loads a catalog to resolve them | ||
| cache.serializerWithSchemaAndSpec( | ||
| "db.table", table.schema().schemaId(), PartitionSpec.unpartitioned().specId()); | ||
|
|
||
| assertThat(catalog.closeCount).isEqualTo(1); | ||
| } | ||
|
|
||
| private static class CloseCountingInMemoryCatalog extends InMemoryCatalog { | ||
| private int closeCount = 0; | ||
|
|
||
| @Override | ||
| public void close() throws IOException { | ||
| super.close(); | ||
| closeCount += 1; | ||
| } | ||
| } | ||
|
|
||
| private static class SingleCatalogLoader implements CatalogLoader { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we replace this with |
||
| private final Catalog catalog; | ||
|
|
||
| private SingleCatalogLoader(Catalog catalog) { | ||
| this.catalog = catalog; | ||
| } | ||
|
|
||
| @Override | ||
| public Catalog loadCatalog() { | ||
| return catalog; | ||
| } | ||
|
|
||
| @Override | ||
| @SuppressWarnings({"checkstyle:NoClone", "checkstyle:SuperClone"}) | ||
| public CatalogLoader clone() { | ||
| return this; | ||
| } | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.