You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
[Bug] TaskManager Metaspace never reclaimed: ~18.2 MB leaked per finished SQL job that ships the Paimon connector as a user jar (Flink 1.20.5 session cluster) #9795
I searched in the issues and found nothing similar.
Closest existing reports, both still open, neither matching this measurement set: #2780 (Flink S3 cause TaskManager Metaspace OOM, Paimon 0.6.0 / Flink 1.18, attributes it to an unclosed S3AFileSystem) and #5998 (paimon-s3 vs flink-s3-fs-hadoop plugin conflict). On the Flink side: FLINK-28248 (open, no fix version, but the reproducer and root cause there are Beam-specific), FLINK-19005 (closed by documenting JDBC drivers as a cause — excluded here, see below), FLINK-32203 (log4j monitorInterval — excluded here, see below).
Apache Flink 1.20.5, standalone session cluster on Kubernetes.
JVM: Temurin 17.0.19 (the official Flink image ships a JRE), G1GC.
TaskManager -XX:MaxMetaspaceSize=768m, JobManager 2048m.
Storage: S3 (MinIO), catalog declared as s3://warehouse/paimon with s3.endpoint / s3.path.style.access (Paimon-native S3, deliberately nots3a:// + fs.s3a.*).
Minimal reproduce step
In a long-running session cluster, submit this repeatedly (each invocation is a separate short batch job; the Paimon jar is shipped as a user jar via -j):
-- q.sqlSET'pipeline.name'='probe';
SET'execution.runtime-mode'='batch';
CREATE CATALOG lake WITH (
'type'='paimon',
'warehouse'='s3://warehouse/paimon',
's3.endpoint'='http://minio:9000',
's3.path.style.access'='true',
's3.access-key'='...','s3.secret-key'='...');
SELECTCOUNT(*) FROM lake.`mydb`.`small_pk_table`;
The JobManager in the same cluster unloads two thirds of everything it loads, and was observed reclaiming 150–180 MB at a time without any restart (restartCount=0, uptime 3 days). The TaskManagers essentially never unload. So this is not GC tuning, not the JVM, and not the G1 configuration — it is user-classloader retention that is specific to the TaskManager side.
Causes we were able to exclude (so nobody repeats the work)
log4j monitorInterval (FLINK-32203). The log4j-console.properties actually used by JM/TM contains no monitorInterval, and a SIGQUIT thread dump shows no log4j configuration-watch thread on the TaskManager. (monitorInterval=30 exists only in log4j-cli/session.properties, i.e. the short-lived client JVM.)
JDBC drivers (FLINK-19005). Reproduces with the Paimon jar alone; no JDBC connector or driver is involved in the query above.
Per-job lingering threads. A TaskManager holding ~570 MB of Metaspace (≈31 retained classloaders at 18.2 MB each) has only 287 JVM threads in total, with 4 distinct s3a-* thread pools and 1java-sdk-http-connection-reaper. Thread counts do not scale with the number of retained classloaders, so the mechanism described in [Bug] Flink S3 cause TaskManager Metaspace OOM #2780 (one unclosed S3AFileSystem per job) does not by itself account for what we see here — although an unclosed filesystem would still be a real leak and may explain part of it.
"GC simply never ran". Worth stating explicitly because it is an easy wrong turn: Status.JVM.GarbageCollector.G1_Old_Generation.Count is 0 on every TaskManager and on the JobManager, so that metric cannot be used to argue either way. The JobManager nevertheless unloads 67 % of its classes, which proves reclamation does happen; the TaskManagers just don't benefit from it.
What we could not do, and why
We cannot name the object that retains the classloader, because the official Flink image ships a JRE — there is no jcmd, jmap or jstack in the TaskManager container, so we cannot take a heap dump or run VM.classloader_stats.
If a maintainer wants it, we are happy to run one TaskManager on a JDK-based image and attach a heap dump / VM.classloader_stats output, or to test a patch. Please just say which you'd prefer.
Why we think Paimon is the right place for this
The retained classloader is the one carrying the Paimon Flink connector jar, and the workload is nothing but "create a Paimon catalog, read one small table, exit". Everything else in the job is stock Flink. #2780 already points at Paimon's S3 filesystem lifecycle (S3AFileSystem created but never close()d), which would be exactly this class of problem; this report adds a current-version (Paimon 1.2/1.3, Flink 1.20.5) reproducer with a quantified per-job cost and the JM-vs-TM unload contrast, in case that helps narrow it down.
Impact and current mitigation
In a session cluster used for periodic small Paimon queries this is effectively unbounded: our TaskManagers reach the 768 MB cap and have to be rotated. We currently automate a rolling TaskManager restart at 85 % Metaspace, which takes ~28 s and is transparent to running CDC jobs — but it is obviously a workaround, not a fix.
Are you willing to submit a PR?
I'm willing to submit a PR! (happy to help with reproduction, measurements, or testing a fix; we can also run the JDK-image heap dump described above)
Search before asking
Closest existing reports, both still open, neither matching this measurement set: #2780 (
Flink S3 cause TaskManager Metaspace OOM, Paimon 0.6.0 / Flink 1.18, attributes it to an unclosedS3AFileSystem) and #5998 (paimon-s3vsflink-s3-fs-hadoopplugin conflict). On the Flink side: FLINK-28248 (open, no fix version, but the reproducer and root cause there are Beam-specific), FLINK-19005 (closed by documenting JDBC drivers as a cause — excluded here, see below), FLINK-32203 (log4jmonitorInterval— excluded here, see below).Paimon version
Paimon Flink connector 1.2.0 / 1.3.1 (
paimon-flink-1.20, 52.8 MB shaded jar),paimon-s3-1.3.1.jar.Compute Engine
Apache Flink 1.20.5, standalone session cluster on Kubernetes.
JVM: Temurin 17.0.19 (the official Flink image ships a JRE), G1GC.
TaskManager
-XX:MaxMetaspaceSize=768m, JobManager2048m.Storage: S3 (MinIO), catalog declared as
s3://warehouse/paimonwiths3.endpoint/s3.path.style.access(Paimon-native S3, deliberately nots3a://+fs.s3a.*).Minimal reproduce step
In a long-running session cluster, submit this repeatedly (each invocation is a separate short batch job; the Paimon jar is shipped as a user jar via
-j):Table under test: Paimon primary-key table,
parquet+zstd,bucket=4, 24 columns, 11 rows. All jobs finish normally (FINISHED, correct result).What doesn't meet your expectations?
Each finished job permanently costs ~18.2 MB of TaskManager Metaspace, and it is never returned.
Controlled measurement (cluster-wide sum of
Status.JVM.Memory.Metaspace.Usedacross all 9 TaskManagers):Long-run behaviour, from 15-minute polling — 3,008 samples across 48 distinct TaskManager instances:
The decisive contrast — same cluster, same JVM, same GC settings:
The JobManager in the same cluster unloads two thirds of everything it loads, and was observed reclaiming 150–180 MB at a time without any restart (
restartCount=0, uptime 3 days). The TaskManagers essentially never unload. So this is not GC tuning, not the JVM, and not the G1 configuration — it is user-classloader retention that is specific to the TaskManager side.Causes we were able to exclude (so nobody repeats the work)
monitorInterval(FLINK-32203). Thelog4j-console.propertiesactually used by JM/TM contains nomonitorInterval, and a SIGQUIT thread dump shows no log4j configuration-watch thread on the TaskManager. (monitorInterval=30exists only inlog4j-cli/session.properties, i.e. the short-lived client JVM.)s3a-*thread pools and 1java-sdk-http-connection-reaper. Thread counts do not scale with the number of retained classloaders, so the mechanism described in [Bug] Flink S3 cause TaskManager Metaspace OOM #2780 (one unclosedS3AFileSystemper job) does not by itself account for what we see here — although an unclosed filesystem would still be a real leak and may explain part of it.Status.JVM.GarbageCollector.G1_Old_Generation.Countis 0 on every TaskManager and on the JobManager, so that metric cannot be used to argue either way. The JobManager nevertheless unloads 67 % of its classes, which proves reclamation does happen; the TaskManagers just don't benefit from it.What we could not do, and why
We cannot name the object that retains the classloader, because the official Flink image ships a JRE — there is no
jcmd,jmaporjstackin the TaskManager container, so we cannot take a heap dump or runVM.classloader_stats.If a maintainer wants it, we are happy to run one TaskManager on a JDK-based image and attach a heap dump /
VM.classloader_statsoutput, or to test a patch. Please just say which you'd prefer.Why we think Paimon is the right place for this
The retained classloader is the one carrying the Paimon Flink connector jar, and the workload is nothing but "create a Paimon catalog, read one small table, exit". Everything else in the job is stock Flink. #2780 already points at Paimon's S3 filesystem lifecycle (
S3AFileSystemcreated but neverclose()d), which would be exactly this class of problem; this report adds a current-version (Paimon 1.2/1.3, Flink 1.20.5) reproducer with a quantified per-job cost and the JM-vs-TM unload contrast, in case that helps narrow it down.Impact and current mitigation
In a session cluster used for periodic small Paimon queries this is effectively unbounded: our TaskManagers reach the 768 MB cap and have to be rotated. We currently automate a rolling TaskManager restart at 85 % Metaspace, which takes ~28 s and is transparent to running CDC jobs — but it is obviously a workaround, not a fix.
Are you willing to submit a PR?