Repository navigation
Conversation
afb8b6a to
2ec038d
Compare
2ec038d to
53f7abb
Compare
06f44a2 to
83cdff5
Compare
1b106d3 to
ca0623e
Compare
JingsongLi
left a comment
There was a problem hiding this comment.
Requirement fit: SUPPORTED. Per-file-type cache routing has practical value for REST-vended OSS endpoints. Implementation review found one P2 issue in initialization failure handling.
Validation: 177/188 selected local tests passed; 11 could not initialize Jindo native on macOS ARM. The supported subset passed the normal Maven verification profiles. Same-head Linux CI passed IoCacheRoutingTest (156), JindoIoCacheRoutingTest (10), and JindoFileIOTest (5). A source-free production-class fault-injection probe shows an initialization IOException remains checked on the origin path but becomes UncheckedIOException on the new target path, bypassing ManifestSidecar.read fallback. Invoking the actual OrphanFilesClean.retryReadingFiles helper with a one-shot initialization failure succeeds on the origin path in two attempts; the target path exits in one attempt. Unwrapping the target-path exception in a source-free control restores that fallback. The endpoint public API was also compared against the original production classes (six boundaries each). No live OSS/cache cluster was available.
ca0623e to
dd23040
Compare
JingsongLi
left a comment
There was a problem hiding this comment.
Requirement fit: SUPPORTED. Implementation: FINDINGS.
Rechecked revision dd23040, including the new exists routing and origin-only pre-write check. The previously reported P2 is still reproducible: cache-target initialization leaves SocketTimeoutException wrapped in UncheckedIOException, so OrphanFilesClean does not retry and ManifestSidecar.read cannot fall back. The equivalent origin path retries successfully in two attempts and preserves its checked exception. Please address the target computeIfAbsent exception unwrapping in addition to the routing changes.
Validation: normal Maven install and all 176 locally supported routing/cache-policy/user-agent/delete-hook tests passed (159 routing cases). The ten JindoIoCacheRoutingTest cases could not initialize the Jindo native library on macOS ARM; this is the previously identified runtime limitation. Same-head initialization fault-injection probes reconfirmed both public IOException handling and the actual orphan retry/sidecar fallback paths. No live OSS/cache endpoint was available. No additional actionable defect was established in the routing revision.
When the catalog options carry io-cache endpoints instead of a JindoCache
RPC address, JindoFileIO now reads immutable files through a cache target
chosen by file type and keeps every other request on the OSS endpoint.
Before, it logged "fallback to no-cache" and used OSS for everything.
- io-cache.endpoint names one target. io-cache.targets with
io-cache.target.<name>.endpoint (and optional .region and
.path-style-access) names several, and io-cache.routes maps file types
to them. io-cache.whitelist and io-cache.policy keep their meaning, and
io-cache.origin.endpoint names the OSS endpoint when fs.oss.endpoint
points at a cache for older clients.
- Only read and meta requests use a target; exists counts as meta. Writes,
including the existence check before a two-phase write, listings,
deletes, renames, copies and non-oss paths stay on OSS, and a
client-side dlf.oss-endpoint turns routing off.
- Files rewritten in place (FileType.isMutable) and metadata named by a
sequence id never use a target.
- Data files are recognized by name: a data or changelog file prefix,
including a custom one, followed by {uuid}-{count}.{extension}.
Manifests, indexes and statistics share the {uuid}-{count} shape but
have no extension, so a custom prefix such as manifest- cannot mix them
up; the manifest sidecar is excluded explicitly. Other names keep their
FileType, and names Paimon does not write stay on OSS.
- Errors from a target are thrown as they are, without retrying on OSS.
A target that fails to initialize throws the same checked IOException
as the OSS file system does, so retries and fallbacks still apply.
- The JindoCache RPC mode is unchanged.
The routing cases in src/test/resources/io-cache are shared with pypaimon
and paimon-rust.
dd23040 to
db6dd66
Compare
deleteQuietly checks a failed delete with exists(), and readFileUtf8 turns a failed read into FileNotFoundException when exists() says the file is gone. With cache routing, exists() of a cacheable file goes to a cache, which can still hold a file deleted on OSS or report a file missing that OSS has. Both helpers now ask OSS instead.
Some cache endpoints, such as an OSS accelerator, also take writes and send them to OSS over their own write path, whose bandwidth adds to the direct one. With write in io-cache.policy, newOutputStream, two-phase multipart writes and hadoopOptions(path, "write") of cacheable files (data files, manifests, indexes) now use the file type's target. The existence check before a two-phase write, atomic writes, renames, deletes, copies, listings and writes of snapshots, hints and temp files stay on OSS. A policy with only write routes writes only.
…opyFile The routing FileIO no longer overrides these helpers. - deleteQuietly only logs a warning when exists() still finds a deleted file, so asking a cache changes a log line, not a result. - readFileUtf8 reports a failed read as not found only when exists() says the file is gone. A cacheable file is never probed before it is written, so a cache does not report an existing one as missing. - copyFile is a read plus a write, and each is routed like any other read or write now that writes follow io-cache.policy.
Replace the JSON test vectors with assertions grouped by topic in IoCacheRoutingTest, and drop their license exclusion from the root pom.
exists answers whether a file is still there: commit recovery checks the files it replays, RemoveUnexistingManifests and changelog expiration check whether a file is gone. A cache that keeps the metadata of deleted objects answers yes after the file was deleted on OSS, so a recovery could publish a snapshot that references a deleted file. meta now covers file status only; exists uses a target only when io-cache.policy lists exists, so the server vends it only for targets that answer existence as OSS does. The JindoCache mode keeps exists under meta.
…es them
The broad FileType classifier also matched names such as manifest.parquet,
stat-2024.parquet or foo.index, which a Format Table may hold as ordinary
files and replace in place, so a routed read could return the old file
from a cache. A metadata or index file now uses a target only under the
exact name Paimon writes: manifest-, manifest-list-, index-manifest- and
stat-{uuid}-{n} (a manifest also with .avro.sidecar), index-{uuid}-{n},
and {kind}-global-index-{uuid}.index. Data files and their file indexes
keep the existing name rule. Every other name goes to the origin.
paimon-rust's TableCommit names input-changelog manifests
manifest-{uuid}-changelog-{count}, optionally with .avro.sidecar. They are
written once under a unique name like every other manifest, so the
metadata name rule now accepts the -changelog infix for manifests (not
for manifest lists) and a Java reader of such a table routes them the
same way as paimon-rust does.
JingsongLi
left a comment
There was a problem hiding this comment.
The cache-routing use case has end-to-end value. I ran 36 focused Java tests with the standard Maven checks successfully. Another 14 local cases could not initialize Jindo Native on macOS/aarch64, so I do not count them as verified locally. The exact-head Linux CI checks are green. A production-class probe of the native-format handoff reproduces the configuration mismatch below; the probe exercises the actual Jindo, LanceUtils and VortexUtils read/write option translators without requiring native library initialization.
Lance and Vortex take hadoopOptions(path, op) and build their own clients, which address the endpoint as virtual-hosted HTTPS and cannot reach an HTTP, IP or path-style io-cache target. With routing, hadoopOptions now returns the OSS options; the routed options stay package-private for JindoFileIO and its tests. JindoCache mode is unchanged.
… io-cache routing
The postpone writer names its files
{prefix}-u-{commit user}-s-{write id}-w-{uuid}-{count}.{extension}, which did
not match {prefix}{uuid}-{count}.{extension}, so their reads and writes always
used the origin. paimon-rust names its managed BLOB packs
{prefix}{uuid}.managed.blob, without the count this client writes. Both are
written once under a unique name and are now data files, with the .index and
.blobref files named after them, so all three clients route the same names
(apache/paimon-rust#1014).
io-cache routing chose the endpoint of a request from the file name alone, for every table a FileIO served. Review of apache/paimon-rust#1014 found that a name shows neither who owns a file nor which prefixes a table has used, and this client has the same gaps: - Format, Object, Iceberg and Lance tables hold files that other writers may replace in place under the same name. Their FileIO is now origin only. - A row may point at any file. A blob-descriptor-field value, or a BLOB written from a descriptor, names a file outside the table that its writer may replace, even when it is named like a data file. A FileStoreTable now gives io-cache routing its location, and only files under it may use a cache target; files under data-file.external-paths are read from origin, as without routing. Descriptor readers created from the catalog context serve no table, so their FileIO is origin only. - Data files named with the data-file.prefix or changelog-file.prefix of the schema, of a copy's options or of an older schema were not recognized. The view passes the prefixes of the table's schema, and the router lists the branch's older schemas on OSS, once, the first time a file under the table is named like a data file with none of the known prefixes. FileIO gets two default no-op methods, originOnly() and withTableView(). AbstractFileStoreTable's constructor applies the view, so every copy, time travel and branch takes the one of its own schema. RESTTokenFileIO, ResolvingFileIO and CachingFileIO apply the view to the FileIO they wrap; a JindoFileIO view shares the file systems and the presigner of its base and closes nothing. IoCacheTableRoutingTest writes and reads through a FileSystemCatalog over a FileIO whose target keeps the first copy it served while origin holds a new one, as the paimon-rust review does: descriptor fields of primary-key and append tables, a BLOB copied from a descriptor, data files under an external path, a table read after ALTER of its prefix, and a branch with its own prefix. Each case but the branch one fails without its part of this change; the jindo tests cover the router.
…given The previous commit added originOnly() and withTableView() to FileIO as default methods returning this. A FileIO decorator that forwards every method, such as the java.lang.reflect.Proxy wrappers in Spark's FormatTablePartitionManagementTest, forwards these defaults as well: the delegate returned itself, so a Format Table got the bare delegate instead of the decorator, and the decorator's injected failures were lost. Any wrapper that forwards all FileIO methods would drop itself the same way, and a default method on the public FileIO interface cannot tell a wrapper from the FileIO it wraps. FileIO is unchanged now. The two methods move to IoCacheViewableFileIO, implemented by the FileIOs that have io-cache routing or wrap one: JindoFileIO, RESTTokenFileIO, ResolvingFileIO and CachingFileIO, which forward to a delegate only when it implements the interface. Every caller goes through IoCacheViews.originOnly() and IoCacheViews.withTableView(), which return any other FileIO as it is, so Format, Object, Iceberg and Lance tables and every FileStoreTable keep a decorator they are given. TableFileIOViewTest gives a forwarding Proxy to a Format Table, an Object Table and a FileSystemCatalog table and checks each keeps that instance; it fails on the previous commit. The same test compared the prefixes of a branch's schemas in listing order, which is the file system's: LocalFileIO lists the schema directory in File.list order, which differs between machines. Routing only checks whether a prefix is known, so the test now ignores the order.
JingsongLi
left a comment
There was a problem hiding this comment.
Requirement fit: SUPPORTED. Per-request OSS endpoint routing has end-to-end value. The previous native-format target handoff is repaired by returning origin options to those consumers. Implementation: FINDINGS.
A clean JDK 8 reactor package passed. Locally, 124 focused tests passed, including actual table read/write/view controls; 21 more could not initialize Jindo Native on macOS/aarch64. I inspected the exact-head Linux JDK 8 CI log: JindoIoCacheRoutingTest (20), JindoFileIOTest (5), and IoCacheRoutingTest (23) all passed, and the full current-head CI matrix is green. A production REST cache/Jindo configuration probe independently reproduces the catalog-only endpoint configuration gap below; it checks actual routing parameters without executing Jindo HTTP.
… it was built from RESTTokenFileIO keeps the FileIO of each vended token in a static cache, so every RESTTokenFileIO in the JVM that holds the same token shares it. The FileIO is built from the catalog options merged with the token, and io-cache routing may come from the catalog options alone: io-cache.origin.endpoint, the targets, the policy, the whitelist and the routes. Two catalogs with one token but different catalog-only routing therefore shared the first one's JindoFileIO, and the second read and wrote through the first one's origin and cache. The cache key is now the token together with an immutable copy of the catalog options, taken once per RESTTokenFileIO, and the FileIO is built from that same copy. The catalog's dlf.oss-endpoint and explicit io-cache.enabled are already merged into the token at refresh. Copies of one catalog's RESTTokenFileIO, including those deserialized on workers, still share one FileIO. RESTTokenFileIOTest checks that the same token under other catalog options gets its own FileIO. JindoIoCacheRoutingTest builds two RESTTokenFileIOs with one token and different catalog-only origin and target endpoints through the real REST cache and JindoFileIO configuration, and checks that each sends reads and writes to its own endpoints and gives Lance its own origin. Both fail without the change.
… io-cache tests A review of the Rust client found two requests that missed their target: the size check of a ROW sidecar went to origin while its read used the target, and a REST branch whose schema only the catalog keeps knew no older prefix of the files it inherited from main. Neither happens here. DataEvolutionSplitRead sizes the sidecar with getFileStatus on the table's FileIO, which follows the meta rule, and a branch reads every older schema from its own schema directory, which SchemaFilePrefixHistory already lists; without those schemas the read itself fails before any data request. These tests keep both: a ROW sidecar is classified as data and sized and read through the target, and a branch created from a tag reads the files it inherited under main's older prefix through the target.
The view support added four public types to org.apache.paimon.fs: IoCacheViewableFileIO, IoCacheViews, TableFileIOView and FilePrefixHistory. They exist only for io-cache routing, so they now live in one interface, org.apache.paimon.fs.iocache.IoCacheViewableFileIO, marked as internal: the static helpers of IoCacheViews become its static methods, and TableFileIOView and FilePrefixHistory become its nested TableView and PrefixHistory. org.apache.paimon.fs gains no type and FileIO is unchanged. Behavior is the same; every user and test moves to the new names.
Purpose
A REST catalog can vend OSS cache endpoints with the table token, for example an OSS accelerator or a cache cluster in front of the bucket. Today
JindoFileIOignores them unless a JindoCache RPC address is set: it logs "fallback to no-cache" and reads everything from OSS. This PR sends each request to a cache endpoint or to the origin, by operation and file type.How a request is routed
Every request goes either to a cache target or to the origin,
io-cache.origin.endpoint(by defaultfs.oss.endpoint):data-file.external-pathsor an object a BLOB descriptor points to, goes to the origin. A FileIO that serves no table reads such row-data URIs from the origin too. Paths that are notoss://, operations not listed inio-cache.policy(read,metafor file status,exists,write), temporary files, files rewritten in place and files named by a sequence id (snapshot-N,schema-N,changelog/changelog-N) go to the origin.data-file.prefixandchangelog-file.prefixof the table's current and earlier schemas, followed by{uuid}-{count}.{extension}, the same after the postpone writer's-u-{commit user}-s-{write id}-w-, or{uuid}.managed.blobfor a paimon-rust managed BLOB pack; its file index adds.index. Metadata ismanifest-,manifest-list-,index-manifest-orstat-followed by{uuid}-{count}, a changelog manifest written by paimon-rust ismanifest-{uuid}-changelog-{count}, and a manifest may add.avro.sidecar; a bucket index isindex-{uuid}-{count}; a global index is<kind>-global-index-{uuid}.index. Any other name, such as an external Format Table file calledmanifest.parquet, goes to the origin.io-cache.whitelist. Withio-cache.routes, the first rule naming the type picks the target; without it, the first target with an endpoint takes every type. A type no rule names goes to the origin.A target only ever sees files written once under a unique name, so it can be a read cache that keeps metadata.
existsis the exception: Paimon uses it to ask whether a file is still there, for example before replaying a commit on recovery, and a cache that keeps the metadata of deleted objects answers yes after the file is gone. Soexistsuses a target only when the policy lists it, which suits a target that answers existence as OSS does. The origin sees everything else, so it must answer every request as OSS does: OSS itself, or an endpoint that keeps no metadata cache, such as an OSS accelerator. Format, Object, Iceberg and Lance tables never use a target: other writers may replace their files in place under the same name, whatever the name looks like. A Format Table that should go through an OSS accelerator gets it by making the accelerator the origin.meta,read,exists,writemeta,readUsage
A read cache in front of the bucket
Reads and file status of immutable files use the cache; existence checks and everything else use OSS.
An endpoint that also takes writes
Data files are read, written and checked through the OSS accelerator, whose read, write and request paths to OSS add to the direct quotas; everything else uses OSS.
existsis listed because the accelerator keeps no metadata cache.The accelerator as origin, a read cache for data
Data files and global indexes are read through the cache. Snapshot probes, hint files, manifests, listings and all writes go through the accelerator.
The accelerator as origin, data straight to OSS
Data files are read and written on the OSS endpoint; metadata and the other request-heavy traffic go through the accelerator, so the two quotas add up.
Two targets by file type
Manifests and statistics are read through the accelerator, data files and global indexes through the cache, and the rest, existence checks included, uses OSS.
Options
Vended with the table token or set in the catalog options.
io-cache.enabledio-cache.policyexistsis a new valueread,meta(file status),exists,write;noneturns it off. Endpoint routing matches whole tokens; JindoCache keeps its substring match andexistsundermeta.io-cache.whitelist-pathio-cache.whitelistmeta,data,bucket-index,global-index,file-index, or*(the default).io-cache.endpointdefault.io-cache.targetswithio-cache.target.<name>.endpointio-cache.target.<name>.region,io-cache.target.<name>.path-style-accessdefaultincluded.io-cache.routesio-cache.origin.endpointfs.oss.endpoint, which may point at a cache for clients without routing.fs.jindocache.namespace.rpc.address(JindoCache mode),dlf.oss-endpoint(turns routing off) and the table optionsdata-file.prefix/changelog-file.prefix(read to recognise data files) are existing options this PR only reads.In
JindoFileIOnewInputStream,getFileStatusandexistsfollow the read, meta and exists rules, andnewOutputStreamand two-phase multipart writes follow the write rule. Lance and Vortex build their own clients fromhadoopOptions(path, op), which cannot route per request, so under routing it gives them the origin's options.FileIOin theAbstractFileStoreTableconstructor, which every copy, time travel, branch and fallback branch goes through: the view routes only paths under the table's location and knows the prefixes of the table's schema; the prefixes of the branch's earlier schemas are listed once, through the origin, on the first request whose data file name has none of the current prefixes, and only when routing is on. Format, Object, Iceberg and Lance tables take an origin-only view.RESTTokenFileIO,ResolvingFileIOandCachingFileIOpass the view to the FileIO they wrap when it supports views; aJindoFileIOview shares the file systems of the FileIO it was made from, and closing it closes nothing.UriReaderFactorycreates for a BLOB descriptor serves no table, so it reads from the origin; a reader over a table's FileIO follows that table's view.copyFile,deleteQuietlyandreadFileUtf8keep theFileIOdefaults; each read, status lookup and write inside them is routed like any other.RESTTokenFileIOcaches one FileIO per token and catalog options. Routing may be configured in the catalog options alone, so catalogs that share a vended token but set different origin or targets keep their own endpoints, while copies of one catalog'sRESTTokenFileIO, including deserialized ones, still share one FileIO.IOExceptionas the origin, so callers such asOrphanFilesCleanandManifestSidecar.readkeep their retry and fallback.fs.jindocache.namespace.rpc.addressset, the existing JindoCache mode is used, unchanged. A client-sidedlf.oss-endpointturns routing off.#10351 (pypaimon) and apache/paimon-rust#1014 implement the same rules.
Tests
IoCacheRoutingTest: the endpointJindoFileIO.hadoopOptionspicks for each request with one and two targets, the policy (existson its own included), whitelist, routes and endpoint options, and the type of each file name, including custom prefixes that collide with reserved names, entropy and postpone directories, sidecars (a ROW sidecar is sized and read through the target of its data file), temporary names, metadata directories, and external names that only look like metadata or indexes (manifest.parquet,stat-2024.parquet,foo.index). It also covers target names, policy parsing and serialization.JindoIoCacheRoutingTest: options of each target, which operations use a target with and withoutwriteandexistsin the policy, the two-phase write checking the origin, target errors and target initialization errors thrown as checkedIOExceptions, JindoCache RPC mode taking precedence, one file system per target and bucket, andclosereleasing them.TableFileIOViewTestloads tables from aFileSystemCatalogover a FileIO that records its views: copies, branches, external paths, and Format, Object, Iceberg and Lance tables; a FileIO without views, including a forwardingProxy, stays the same instance in a Format Table, an Object Table and a catalog table.IoCacheTableRoutingTestwrites and reads real tables through aFileSystemCatalogwhose FileIO routes asJindoFileIOdoes, with a target that keeps the first copy it served while the origin is replaced:blob-descriptor-fieldvalues pointing outside the table (primary-key and append tables), BLOB sources copied into the table and data files underdata-file.external-pathscome from the origin; files under an olderdata-file.prefixafterALTER TABLEand a fresh load, branch files under the branch's prefix, and the files a branch created from a tag inherited under main's older prefix use the target.UriReaderFactoryTest,RESTTokenFileIOTest,ResolvingFileIOTestandCachingFileIOTestcover the origin-only descriptor FileIO, the views the wrappers forward and the token FileIO cached per catalog options;JindoIoCacheRoutingTestbuilds twoRESTTokenFileIOs with one token and different catalog-only origin and target endpoints through the real REST cache andJindoFileIOconfiguration, and checks that each sends reads and writes to its own endpoints and gives Lance its own origin;IoCacheRoutingTestandJindoIoCacheRoutingTestalso cover table views (roots, prefixes, earlier schemas loaded once and a failure to load them, shared file systems), postpone names and managed BLOB packs.TestJindoCacheEnablechecks that the JindoCache policy matching is unchanged;JindoFileIOTest,TestJindoUserAgentandTestHadoopCompliantFileIODeleteHookstill pass.Verified end to end
All three clients (Java, Python, Rust) created, wrote, read, time-travelled and dropped partitions of tables against local OSS mock endpoints that log every request, with one target, two targets by file type, write routing, and an OSS accelerator as origin with a read cache or OSS as the target. Every request a target saw was one these rules send to it. The pypaimon side (#10351) also ran its cases through a real OSS accelerator, as target and as origin.
API and Format
No storage format change; routing is configured through options. No new types in
org.apache.paimon.fs, and theFileIOinterface is unchanged. The views live on one internal interface,org.apache.paimon.fs.iocache.IoCacheViewableFileIO(originOnly(),withTableView(TableView)), whose nestedTableViewandPrefixHistorydescribe a view; it is internal to io-cache routing, not a public API.JindoFileIO,RESTTokenFileIO,ResolvingFileIOandCachingFileIOimplement it; callers go through its staticoriginOnly(FileIO)andwithTableView(FileIO, TableView), which return any other FileIO as given, so a FileIO that forwards every method, such as aProxyin a test, keeps its place.Documentation
No docs page yet; the options are listed above.