This is an automated email from the ASF dual-hosted git repository.

roryqi pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gravitino.git


The following commit(s) were added to refs/heads/main by this push:
     new c2d2cd59cb [#14382] docs(tms): Document in-process table maintenance 
(#14383)
c2d2cd59cb is described below

commit c2d2cd59cbf433c1523e3c09283a3edd4de5ce70
Author: MaSai <[email protected]>
AuthorDate: Mon Nov 2 21:09:35 2026 +0800

    [#14382] docs(tms): Document in-process table maintenance (#14383)
    
    ### What changes were proposed in this pull request?
    
    Document the in-process table maintenance service on its own page, and
    correct the documented Gravitino callback URI.
    
    - Add `docs/table-maintenance-service/service.md` for enabling the
    service, `timeTrigger`, the cron path, and `commitTrigger`.
    - Point `docs/table-maintenance-service/optimizer.md` at that page and
    leave the CLI workflow there.
    - Replace `gravitino.maintenance.gravitinoUri` with
    `gravitino.maintenance.gravitinoAuth.uri` in the TMS configuration page,
    and list the `uri` suffix in the `gravitinoAuth` table.
    
    ### Why are the changes needed?
    
    The existing optimizer pages describe the manual CLI. Operators enabling
    the in-process service had no matching usage guide, and that guide does
    not belong inside the CLI walkthrough. The configuration page also named
    `gravitino.maintenance.gravitinoUri`, which the server does not read.
    The callback base URI is `gravitino.maintenance.gravitinoAuth.uri`.
    
    Fixes #14382
    
    ### Does this PR introduce _any_ user-facing change?
    
    Documentation only. No API or configuration key is added or removed. The
    guide documents the existing `gravitino.maintenance.gravitinoAuth.uri`
    key.
    
    ### How was this patch tested?
    
    Docs-only change. Checked that links in
    `docs/table-maintenance-service/` resolve, that
    `gravitino.maintenance.gravitinoUri` is gone from the configuration
    page, and that the quick-start cron is the six-field Quartz expression
    `0 */5 * * * ?`.
    
    ---------
    
    Co-authored-by: Cursor <[email protected]>
---
 docs/index.md                                      |   3 +-
 .../optimizer-cli-reference.md                     |  84 +++++-----
 .../optimizer-configuration.md                     |  27 ++--
 .../optimizer-extension-guide.md                   |  30 ++--
 .../optimizer-troubleshooting.md                   |  10 +-
 docs/table-maintenance-service/optimizer.md        |   4 +-
 docs/table-maintenance-service/service.md          | 173 +++++++++++++++++++++
 7 files changed, 254 insertions(+), 77 deletions(-)

diff --git a/docs/index.md b/docs/index.md
index 4861407be7..ea844087e4 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -115,7 +115,8 @@ To manage table and partition statistics, see [Manage 
statistics in Gravitino](.
 
 * [**Model catalog**](./model-catalog.md)
 
-If you want to automate table maintenance workflows, see [Table Maintenance 
Service (Optimizer)](./table-maintenance-service/optimizer.md).
+To run maintenance inside the Gravitino server, see 
[Service](./table-maintenance-service/service.md).
+For the manual CLI workflow, see [Table Maintenance Service 
(Optimizer)](./table-maintenance-service/optimizer.md).
 Start with Gravitino built-in policies and built-in job templates, and extend 
via optimizer interfaces when needed.
 
 Catalogs with an asterisk (\*) aren’t in the standard release tarball and 
Docker image.  Gravitino provides a
diff --git a/docs/table-maintenance-service/optimizer-cli-reference.md 
b/docs/table-maintenance-service/optimizer-cli-reference.md
index 82fdec1ecc..b7f6ff8e2b 100644
--- a/docs/table-maintenance-service/optimizer-cli-reference.md
+++ b/docs/table-maintenance-service/optimizer-cli-reference.md
@@ -17,33 +17,33 @@ directory. Use `--conf-path` only when you need a custom 
config file.
 
 ## Command Quick Reference
 
-| Command (`--type`) | Required options | Optional options | Purpose |
-| --- | --- | --- | --- |
-| `submit-strategy-jobs` | `--identifiers`, `--strategy-name` | `--dry-run`, 
`--limit` | Recommend and optionally submit jobs |
-| `update-statistics` | `--calculator-name` | `--identifiers`, 
`--statistics-payload`, `--file-path` | Calculate and persist statistics |
-| `append-metrics` | `--calculator-name` | `--identifiers`, 
`--statistics-payload`, `--file-path` | Calculate and append metrics |
-| `monitor-metrics` | `--identifiers`, `--action-time` | `--range-seconds`, 
`--partition-path` | Evaluate rules with before/after metrics |
-| `list-table-metrics` | `--identifiers` | `--partition-path` | Query stored 
table or partition metrics |
-| `list-job-metrics` | `--identifiers` | None | Query stored job metrics |
-| `submit-update-stats-job` | `--identifiers` | `--dry-run`, `--update-mode`, 
`--updater-options`, `--spark-conf` | Submit built-in Iceberg update 
stats/metrics Spark jobs |
+| Command (`--type`)        | Required options                   | Optional 
options                                                  | Purpose              
                                   |
+|---------------------------|------------------------------------|-------------------------------------------------------------------|---------------------------------------------------------|
+| `submit-strategy-jobs`    | `--identifiers`, `--strategy-name` | 
`--dry-run`, `--limit`                                            | Recommend 
and optionally submit jobs                    |
+| `update-statistics`       | `--calculator-name`                | 
`--identifiers`, `--statistics-payload`, `--file-path`            | Calculate 
and persist statistics                        |
+| `append-metrics`          | `--calculator-name`                | 
`--identifiers`, `--statistics-payload`, `--file-path`            | Calculate 
and append metrics                            |
+| `monitor-metrics`         | `--identifiers`, `--action-time`   | 
`--range-seconds`, `--partition-path`                             | Evaluate 
rules with before/after metrics                |
+| `list-table-metrics`      | `--identifiers`                    | 
`--partition-path`                                                | Query 
stored table or partition metrics                 |
+| `list-job-metrics`        | `--identifiers`                    | None        
                                                      | Query stored job 
metrics                                |
+| `submit-update-stats-job` | `--identifiers`                    | 
`--dry-run`, `--update-mode`, `--updater-options`, `--spark-conf` | Submit 
built-in Iceberg update stats/metrics Spark jobs |
 
 ## Option Field Meanings
 
-| Option | Meaning | Used by |
-| --- | --- | --- |
-| `--identifiers` | Comma-separated identifiers. See [Identifier 
Rules](#identifier-rules) for command-specific formats. | All commands |
-| `--strategy-name` | Policy name to evaluate, for example 
`iceberg_compaction_default`. | `submit-strategy-jobs` |
-| `--dry-run` | Preview mode. Prints recommendations or job configs without 
submitting jobs. | `submit-strategy-jobs`, `submit-update-stats-job` |
-| `--limit` | Maximum number of strategy jobs to process. Must be `> 0`. | 
`submit-strategy-jobs` |
-| `--calculator-name` | Statistics/metrics calculator implementation name (for 
example `local-stats-calculator`). | `update-statistics`, `append-metrics` |
-| `--statistics-payload` | Inline JSON Lines content as input. Mutually 
exclusive with `--file-path`. | `update-statistics`, `append-metrics` |
-| `--file-path` | Path to JSON Lines input file. Mutually exclusive with 
`--statistics-payload`. | `update-statistics`, `append-metrics` |
-| `--action-time` | Action timestamp in epoch seconds used as evaluation 
anchor. | `monitor-metrics` |
-| `--range-seconds` | Time window (seconds) for monitor evaluation. Default is 
`86400` (24h). | `monitor-metrics` |
-| `--partition-path` | Partition path JSON array, for example 
`'[{"dt":"2026-01-01"}]'`. Requires exactly one identifier. | 
`monitor-metrics`, `list-table-metrics` |
-| `--update-mode` | Controls what built-in update job updates: `stats`, 
`metrics`, or `all` (default). | `submit-update-stats-job` |
-| `--updater-options` | Flat JSON map passed to updater logic. For 
`stats`/`all`, include `gravitino_uri` and `metalake`. | 
`submit-update-stats-job` |
-| `--spark-conf` | Flat JSON map of Spark and Iceberg catalog configs used by 
the job. | `submit-update-stats-job` |
+| Option                 | Meaning                                             
                                                 | Used by                      
                     |
+|------------------------|------------------------------------------------------------------------------------------------------|---------------------------------------------------|
+| `--identifiers`        | Comma-separated identifiers. See [Identifier 
Rules](#identifier-rules) for command-specific formats. | All commands          
                            |
+| `--strategy-name`      | Policy name to evaluate, for example 
`iceberg_compaction_default`.                                   | 
`submit-strategy-jobs`                            |
+| `--dry-run`            | Preview mode. Prints recommendations or job configs 
without submitting jobs.                         | `submit-strategy-jobs`, 
`submit-update-stats-job` |
+| `--limit`              | Maximum number of strategy jobs to process. Must be 
`> 0`.                                           | `submit-strategy-jobs`       
                     |
+| `--calculator-name`    | Statistics/metrics calculator implementation name 
(for example `local-stats-calculator`).            | `update-statistics`, 
`append-metrics`             |
+| `--statistics-payload` | Inline JSON Lines content as input. Mutually 
exclusive with `--file-path`.                           | `update-statistics`, 
`append-metrics`             |
+| `--file-path`          | Path to JSON Lines input file. Mutually exclusive 
with `--statistics-payload`.                       | `update-statistics`, 
`append-metrics`             |
+| `--action-time`        | Action timestamp in epoch seconds used as 
evaluation anchor.                                         | `monitor-metrics`  
                               |
+| `--range-seconds`      | Time window (seconds) for monitor evaluation. 
Default is `86400` (24h).                              | `monitor-metrics`      
                           |
+| `--partition-path`     | Partition path JSON array, for example 
`'[{"dt":"2026-01-01"}]'`. Requires exactly one identifier.   | 
`monitor-metrics`, `list-table-metrics`           |
+| `--update-mode`        | Controls what built-in update job updates: `stats`, 
`metrics`, or `all` (default).                   | `submit-update-stats-job`    
                     |
+| `--updater-options`    | Flat JSON map passed to updater logic. For 
`stats`/`all`, include `gravitino_uri` and `metalake`.    | 
`submit-update-stats-job`                         |
+| `--spark-conf`         | Flat JSON map of Spark and Iceberg catalog configs 
used by the job.                                  | `submit-update-stats-job`   
                      |
 
 Global option:
 
@@ -242,13 +242,13 @@ EvaluationResult{scopeType=TABLE, 
identifier=rest_catalog.db.t1, partitionPath=<
 
 The following Iceberg job templates ship with the service. A full maintenance 
pass collects statistics, compacts data files, expires the snapshot history 
that compaction just created, consolidates manifests, and removes old orphan 
files.
 
-| Job template                          | What it does                         
    |
-|---------------------------------------|-------------------------------------------|
-| `builtin-iceberg-update-stats`        | Collects file statistics and metrics 
     |
-| `builtin-iceberg-rewrite-data-files`  | Compacts small data files            
     |
-| `builtin-iceberg-expire-snapshots`    | Removes old snapshot metadata        
     |
-| `builtin-iceberg-remove-orphan-files` | Removes unreferenced files from 
storage   |
-| `builtin-iceberg-rewrite-manifests`   | Consolidates small manifest files    
     |
+| Job template                            | What it does                       
      |
+|-----------------------------------------|------------------------------------------|
+| `builtin-iceberg-update-stats`          | Collects file statistics and 
metrics     |
+| `builtin-iceberg-rewrite-data-files`    | Compacts small data files          
      |
+| `builtin-iceberg-expire-snapshots`      | Removes old snapshot metadata      
      |
+| `builtin-iceberg-remove-orphan-files`   | Removes unreferenced files from 
storage  |
+| `builtin-iceberg-rewrite-manifests`     | Consolidates small manifest files  
      |
 | `builtin-iceberg-update-manifest-stats` | Collects complete per-spec 
manifest maps |
 
 Each can be submitted directly over REST, and the first two are also what the 
policy-driven workflow submits on your behalf. See [Quick 
Start](./optimizer.md#walkthrough) for the policy-driven path.
@@ -303,9 +303,9 @@ Its `jobConf` is documented in 
[Configuration](./optimizer-configuration.md#job-
 `builtin-iceberg-update-manifest-stats` is a separate Spark job that collects 
two table-level
 statistics, including for partitioned tables:
 
-| Statistic | Object entry for each decimal spec ID |
-| --- | --- |
-| `custom-manifest-number-by-spec` | Long manifest count |
+| Statistic                          | Object entry for each decimal spec ID |
+|------------------------------------|---------------------------------------|
+| `custom-manifest-number-by-spec`   | Long manifest count                   |
 | `custom-avg-manifest-size-by-spec` | Double average manifest size in bytes |
 
 The job reads manifest metadata without scanning file statistics. It works 
after partition
@@ -428,14 +428,14 @@ The job calls Iceberg's `expire_snapshots` stored 
procedure through Spark SQL.
 
 `catalog_name` and `table_identifier` are required. The rest are optional.
 
-| Key              | Description                                               
           | Default                     |
-|------------------|-----------------------------------------------------------------------|-----------------------------|
-| `catalog_name`   | Iceberg catalog name as registered in Spark               
           | Required                    |
-| `table_identifier` | Fully qualified table name, such as `db.sample`         
           | Required                    |
-| `older_than`     | Expire snapshots older than this `yyyy-MM-dd HH:mm:ss` 
timestamp     | Five days ago               |
-| `retain_last`    | Minimum number of recent snapshots to keep regardless of 
age         | `1`                         |
-| `stream_results` | Streams intermediate delete results when present          
           | Disabled                    |
-| `spark_conf`     | JSON map of Spark configuration                           
           | None                        |
+| Key                | Description                                             
         | Default       |
+|--------------------|------------------------------------------------------------------|---------------|
+| `catalog_name`     | Iceberg catalog name as registered in Spark             
         | Required      |
+| `table_identifier` | Fully qualified table name, such as `db.sample`         
         | Required      |
+| `older_than`       | Expire snapshots older than this `yyyy-MM-dd HH:mm:ss` 
timestamp | Five days ago |
+| `retain_last`      | Minimum number of recent snapshots to keep regardless 
of age     | `1`           |
+| `stream_results`   | Streams intermediate delete results when present        
         | Disabled      |
+| `spark_conf`       | JSON map of Spark configuration                         
         | None          |
 
 `older_than` and `retain_last` work together, and `retain_last` wins. Setting 
`older_than` to yesterday with `retain_last` at `5` keeps five snapshots even 
if all five are older than yesterday.
 
diff --git a/docs/table-maintenance-service/optimizer-configuration.md 
b/docs/table-maintenance-service/optimizer-configuration.md
index 99da41585a..599aeef16e 100644
--- a/docs/table-maintenance-service/optimizer-configuration.md
+++ b/docs/table-maintenance-service/optimizer-configuration.md
@@ -50,11 +50,12 @@ Cron also injects callback identity into `updater_options`:
 
 - `metalake` — always from the policy namespace (policy 
`updaterOptions.metalake` cannot redirect
   sample/finish to another metalake)
-- `gravitino_uri` — from `gravitino.maintenance.gravitinoUri` when the policy 
does not set
-  `updaterOptions.gravitino_uri`. Submit fails closed if neither source 
provides a URI.
+- `gravitino_uri` — from `gravitino.maintenance.gravitinoAuth.uri` when the 
policy does not set
+  `updaterOptions.gravitino_uri`. A policy `updaterOptions.gravitino_uri` 
overrides the cluster
+  value. Submit fails closed if neither source provides a URI.
 
 ```properties
-gravitino.maintenance.gravitinoUri = http://localhost:8090
+gravitino.maintenance.gravitinoAuth.uri = http://localhost:8090
 ```
 
 ### TMS commit-job (IRC update → chained Spark)
@@ -88,15 +89,16 @@ unknown or blank suffixes fail fast when the server loads 
config. Supported `aut
 exactly four: **`none`**, **`simple`**, **`basic`**, **`oauth`**. When any 
Gravitino auth key is
 set, `authType` is required, and `basic` / `oauth` also require their 
mode-specific fields below.
 
-| Conf suffix       | `updaterOptions` key | Used when `authType` is | Meaning 
                                    |
-| ----------------- | -------------------- | ----------------------- | 
------------------------------------------- |
-| `authType`        | `auth_type`          | all modes               | `none`, 
`simple`, `basic`, or `oauth`       |
-| `username`        | `username`           | `simple` / `basic`      | User 
principal (`simple` optional; `basic` required) |
-| `password`        | `password`           | `basic`                 | 
Password or secret reference (required)     |
-| `oauthServerUri`  | `oauth_server_uri`   | `oauth`                 | OAuth2 
token server base URI (required)     |
-| `oauthPath`       | `oauth_path`         | `oauth`                 | Token 
path (for example `oauth2/token`) (required) |
+| Conf suffix       | `updaterOptions` key | Used when `authType` is | Meaning 
                                               |
+| ----------------- | -------------------- | ----------------------- | 
------------------------------------------------------ |
+| `uri`             | `gravitino_uri`      | any, including unset    | 
Gravitino callback base URI. Optional on its own.      |
+| `authType`        | `auth_type`          | all modes               | `none`, 
`simple`, `basic`, or `oauth`                  |
+| `username`        | `username`           | `simple` / `basic`      | User 
principal (`simple` optional; `basic` required)   |
+| `password`        | `password`           | `basic`                 | 
Password or secret reference (required)                |
+| `oauthServerUri`  | `oauth_server_uri`   | `oauth`                 | OAuth2 
token server base URI (required)                |
+| `oauthPath`       | `oauth_path`         | `oauth`                 | Token 
path (for example `oauth2/token`) (required)     |
 | `oauthCredential` | `oauth_credential`   | `oauth`                 | Client 
credential, often `client_id:secret` (required) |
-| `oauthScope`      | `oauth_scope`        | `oauth`                 | Token 
scope (IdP-specific) (required)       |
+| `oauthScope`      | `oauth_scope`        | `oauth`                 | Token 
scope (IdP-specific) (required)                  |
 
 **1. `none`** — Gravitino authorization disabled; omit the prefix or set:
 
@@ -327,7 +329,8 @@ Four things are worth confirming before assuming a 
configuration problem is a co
 
 ## Related
 
-- [Table Maintenance Service](./optimizer.md) for the concepts and the 
walkthrough
+- [Service](./service.md) for the in-process service, `timeTrigger`, and the 
cron path
+- [Table Maintenance Service](./optimizer.md) for the CLI walkthrough
 - [CLI Reference](./optimizer-cli-reference.md) for every command and the 
built-in job templates
 - [Troubleshooting](./optimizer-troubleshooting.md) when a command or job fails
 - [Extension Guide](./optimizer-extension-guide.md) for custom strategies and 
providers
diff --git a/docs/table-maintenance-service/optimizer-extension-guide.md 
b/docs/table-maintenance-service/optimizer-extension-guide.md
index 8193636414..ca1e15c844 100644
--- a/docs/table-maintenance-service/optimizer-extension-guide.md
+++ b/docs/table-maintenance-service/optimizer-extension-guide.md
@@ -20,21 +20,21 @@ The service supports three loading patterns:
 
 ## Extension Points and Config Keys
 
-| Area | Interface / type | Config key | Loading mode |
-| --- | --- | --- | --- |
-| Recommender statistics | `StatisticsProvider` | 
`gravitino.optimizer.recommender.statisticsProvider` | `Provider` SPI by 
`name()` |
-| Recommender strategy source | `StrategyProvider` | 
`gravitino.optimizer.recommender.strategyProvider` | `Provider` SPI by `name()` 
|
-| Recommender table metadata | `TableMetadataProvider` | 
`gravitino.optimizer.recommender.tableMetaProvider` | `Provider` SPI by 
`name()` |
-| Recommender job submission | `JobSubmitter` | 
`gravitino.optimizer.recommender.jobSubmitter` | `Provider` SPI by `name()` |
-| Strategy evaluation logic | `StrategyHandler` | 
`gravitino.optimizer.strategyHandler.<strategyType>.className` | Reflection by 
class name |
-| Job template adaptation | `GravitinoJobAdapter` | 
`gravitino.optimizer.jobAdapter.<jobTemplate>.className` | Reflection by class 
name |
-| Update statistics sink | `StatisticsUpdater` | 
`gravitino.optimizer.updater.statisticsUpdater` | `Provider` SPI by `name()` |
-| Update metrics sink | `MetricsUpdater` | 
`gravitino.optimizer.updater.metricsUpdater` | `Provider` SPI by `name()` |
-| Monitor metrics source | `MetricsProvider` | 
`gravitino.optimizer.monitor.metricsProvider` | `Provider` SPI by `name()` |
-| Monitor table-job relation | `TableJobRelationProvider` | 
`gravitino.optimizer.monitor.tableJobRelationProvider` | `Provider` SPI by 
`name()` |
-| Monitor evaluator | `MetricsEvaluator` | 
`gravitino.optimizer.monitor.metricsEvaluator` | Typed SPI 
(`ServiceLoader<MetricsEvaluator>`) |
-| Monitor callbacks | `MonitorCallback` | 
`gravitino.optimizer.monitor.callbacks` | `Provider` SPI by `name()` 
(comma-separated) |
-| CLI calculator | `StatisticsCalculator` | CLI `--calculator-name` | Typed 
SPI (`ServiceLoader<StatisticsCalculator>`) |
+| Area                        | Interface / type           | Config key        
                                             | Loading mode                     
                 |
+|-----------------------------|----------------------------|----------------------------------------------------------------|---------------------------------------------------|
+| Recommender statistics      | `StatisticsProvider`       | 
`gravitino.optimizer.recommender.statisticsProvider`           | `Provider` SPI 
by `name()`                        |
+| Recommender strategy source | `StrategyProvider`         | 
`gravitino.optimizer.recommender.strategyProvider`             | `Provider` SPI 
by `name()`                        |
+| Recommender table metadata  | `TableMetadataProvider`    | 
`gravitino.optimizer.recommender.tableMetaProvider`            | `Provider` SPI 
by `name()`                        |
+| Recommender job submission  | `JobSubmitter`             | 
`gravitino.optimizer.recommender.jobSubmitter`                 | `Provider` SPI 
by `name()`                        |
+| Strategy evaluation logic   | `StrategyHandler`          | 
`gravitino.optimizer.strategyHandler.<strategyType>.className` | Reflection by 
class name                          |
+| Job template adaptation     | `GravitinoJobAdapter`      | 
`gravitino.optimizer.jobAdapter.<jobTemplate>.className`       | Reflection by 
class name                          |
+| Update statistics sink      | `StatisticsUpdater`        | 
`gravitino.optimizer.updater.statisticsUpdater`                | `Provider` SPI 
by `name()`                        |
+| Update metrics sink         | `MetricsUpdater`           | 
`gravitino.optimizer.updater.metricsUpdater`                   | `Provider` SPI 
by `name()`                        |
+| Monitor metrics source      | `MetricsProvider`          | 
`gravitino.optimizer.monitor.metricsProvider`                  | `Provider` SPI 
by `name()`                        |
+| Monitor table-job relation  | `TableJobRelationProvider` | 
`gravitino.optimizer.monitor.tableJobRelationProvider`         | `Provider` SPI 
by `name()`                        |
+| Monitor evaluator           | `MetricsEvaluator`         | 
`gravitino.optimizer.monitor.metricsEvaluator`                 | Typed SPI 
(`ServiceLoader<MetricsEvaluator>`)     |
+| Monitor callbacks           | `MonitorCallback`          | 
`gravitino.optimizer.monitor.callbacks`                        | `Provider` SPI 
by `name()` (comma-separated)      |
+| CLI calculator              | `StatisticsCalculator`     | CLI 
`--calculator-name`                                        | Typed SPI 
(`ServiceLoader<StatisticsCalculator>`) |
 
 ## Implement a Custom Provider
 
diff --git a/docs/table-maintenance-service/optimizer-troubleshooting.md 
b/docs/table-maintenance-service/optimizer-troubleshooting.md
index 744ecf867d..1176f15fd5 100644
--- a/docs/table-maintenance-service/optimizer-troubleshooting.md
+++ b/docs/table-maintenance-service/optimizer-troubleshooting.md
@@ -107,11 +107,11 @@ Confirm it by checking the job run at 
`/api/metalakes/{metalake}/jobs/runs/{job_
 
 Observed in `1.2.0`:
 
-| Partitioning                                                                 
 | Rewrite |
-|-------------------------------------------------------------------------------|---------|
-| `p`, `p, c2`                                                                 
   | Works   |
-| `p, years(ts)`, `p, months(ts)`, `p, days(ts)`, `p, hours(ts)`               
   | Fails   |
-| `p, truncate(1, c2)`, `p, bucket(8, id)`                                     
   | Fails   |
+| Partitioning                                                   | Rewrite |
+|----------------------------------------------------------------|---------|
+| `p`, `p, c2`                                                   | Works   |
+| `p, years(ts)`, `p, months(ts)`, `p, days(ts)`, `p, hours(ts)` | Fails   |
+| `p, truncate(1, c2)`, `p, bucket(8, id)`                       | Fails   |
 
 ## Related
 
diff --git a/docs/table-maintenance-service/optimizer.md 
b/docs/table-maintenance-service/optimizer.md
index d121226286..bcf94e048a 100644
--- a/docs/table-maintenance-service/optimizer.md
+++ b/docs/table-maintenance-service/optimizer.md
@@ -23,8 +23,7 @@ Confirm your environment matches this list before starting an 
evaluation against
 
 - Compaction and orphan file cleanup have built-in strategies. Snapshot 
expiration is available as a directly submitted built-in job. Scheduling and 
minimum run intervals remain separate from strategy evaluation. Sort and 
cluster maintenance also require custom strategies.
 - Compaction applies to Iceberg tables only, and only where every partition 
uses an identity transform.
-- The service is driven through the CLI workflow rather than running on a 
schedule of its own.
-
+- The in-process service, `timeTrigger`, and the cron path are documented in 
[Service](./service.md). The rest of this page is the manual CLI workflow.
 
 ## How It Works
 
@@ -344,6 +343,7 @@ Policy-driven manifest maintenance through Policy -> 
Strategy -> Adapter is a fo
 
 ## Related
 
+- [Service](./service.md) for the in-process service, `timeTrigger`, and the 
cron path
 - [Configuration](./optimizer-configuration.md) for the three configuration 
layers
 - [CLI Reference](./optimizer-cli-reference.md) for every command and the 
built-in job templates
 - [Troubleshooting](./optimizer-troubleshooting.md) when something above does 
not behave
diff --git a/docs/table-maintenance-service/service.md 
b/docs/table-maintenance-service/service.md
new file mode 100644
index 0000000000..9114c96ba0
--- /dev/null
+++ b/docs/table-maintenance-service/service.md
@@ -0,0 +1,173 @@
+---
+title: "Service"
+slug: "/table-maintenance-service/service"
+keywords:
+  - table maintenance
+  - time trigger
+  - cron
+license: "This software is licensed under the Apache License version 2."
+---
+
+## Overview
+
+The table maintenance service runs inside the Gravitino server. Associate a 
policy with a tag and assign that tag to a catalog, schema, or table. The 
service keeps the nearest policy of each built-in type and submits a Spark job. 
A table tag beats a schema tag, and a schema tag beats a catalog tag. Two 
policies of the same type on the same object are a conflict, and that table is 
skipped.
+
+`timeTrigger` is a six-field Quartz cron (`second minute hour day-of-month 
month day-of-week`) on the policy. When it is due, the expand pool lists 
matching tables and inserts one cron-job per table. The cron pool then submits 
Spark. Operators do not create cron-job rows.
+
+`commitTrigger` is the other switch on the same policy. A successful Iceberg 
REST table update enqueues one chained job and does not pass through the expand 
pool. See [Commit Trigger](#commit-trigger).
+
+Passwords, OAuth secrets, and keytab paths belong in `gravitino.conf`, not in 
policy content.
+
+## Quick Start
+
+The steps below turn on a compaction schedule for one Iceberg table. They 
assume a metalake `test`, an Iceberg catalog `rest_catalog`, and a table 
`rest_catalog.db.t1` that already exists. Replace those names, and send 
`Accept: application/vnd.gravitino.v1+json` on every request.
+
+**1. Enable the service.** Register the maintenance REST callbacks in 
`gravitino.conf` and restart Gravitino. Client authentication and the job 
executor are in 
[Configuration](./optimizer-configuration.md#tms-cluster-static-client-auth).
+
+```properties
+gravitino.server.rest.extensionPackages = 
org.apache.gravitino.maintenance.web.rest.feature
+```
+
+**2. Create a compaction policy with a time trigger.**
+
+```shell
+curl -s -X POST -H "Accept: application/vnd.gravitino.v1+json" \
+  -H "Content-Type: application/json" \
+  -d '{
+    "name": "nightly_compaction",
+    "policyType": "system_iceberg_compaction",
+    "comment": "Compact small Iceberg files",
+    "enabled": true,
+    "content": {
+      "timeTrigger": "0 */5 * * * ?",
+      "commitTrigger": false,
+      "minIntervalMs": 3600000
+    }
+  }' \
+  http://localhost:8090/api/metalakes/test/policies
+```
+
+**3. Associate the policy with a tag, then assign the tag to the table.**
+
+```shell
+curl -s -X POST -H "Accept: application/vnd.gravitino.v1+json" \
+  -H "Content-Type: application/json" \
+  -d '{"name": "maintained", "comment": "Tables enrolled in TMS"}' \
+  http://localhost:8090/api/metalakes/test/tags
+
+curl -s -X POST -H "Accept: application/vnd.gravitino.v1+json" \
+  -H "Content-Type: application/json" \
+  -d '{"selector": {"type": "ALL_VALUES"}}' \
+  
http://localhost:8090/api/metalakes/test/tags/maintained/policies/nightly_compaction
+
+curl -s -X POST -H "Accept: application/vnd.gravitino.v1+json" \
+  -H "Content-Type: application/json" \
+  -d '{"tagsToAdd": ["maintained"]}' \
+  
http://localhost:8090/api/metalakes/test/objects/table/rest_catalog.db.t1/tags
+```
+
+**4. Confirm the derived policy, then wait for the next time trigger.**
+
+```shell
+curl -s -H "Accept: application/vnd.gravitino.v1+json" \
+  
"http://localhost:8090/api/metalakes/test/objects/table/rest_catalog.db.t1/policies?details=true";
+```
+
+After the trigger is due, list job runs and look for template 
`builtin-iceberg-maintenance-task`. See [Manage 
jobs](../manage-jobs-in-gravitino.md#list-jobs-get-a-job-and-cancel).
+
+## Configuration
+
+Set the maintenance REST extension in `gravitino.conf`. Client authentication, 
Iceberg REST auth, and the job executor are in 
[Configuration](./optimizer-configuration.md#tms-cluster-static-client-auth).
+
+| Configuration item                        | Description                      
                                                                    |
+|-------------------------------------------|------------------------------------------------------------------------------------------------------|
+| `gravitino.server.rest.extensionPackages` | Register the maintenance REST 
callbacks. Set to `org.apache.gravitino.maintenance.web.rest.feature`. |
+
+A policy `updaterOptions.gravitino_uri` overrides the cluster callback URI. 
Every other `gravitinoAuth` key from the cluster wins over the same key in the 
policy.
+
+Scheduler pool sizes have these defaults:
+
+| Configuration item                              | Default |
+|-------------------------------------------------|---------|
+| `gravitino.maintenance.scheduler.expandThreads` | `4`     |
+| `gravitino.maintenance.scheduler.cronThreads`   | `8`     |
+| `gravitino.maintenance.scheduler.commitThreads` | `4`     |
+
+## Time Trigger
+
+`timeTrigger` stores the cron string on the policy. `0 */5 * * * ?` runs at 
second 0 of every fifth minute. A five-field Unix cron is not accepted.
+
+Three objects decide which tables run:
+
+| Object          | What to set                                                
                                              |
+|-----------------|----------------------------------------------------------------------------------------------------------|
+| Policy          | `policyType`, `enabled`, and content fields `timeTrigger`, 
`commitTrigger`, and `minIntervalMs`.         |
+| Tag association | `POST 
/api/metalakes/{metalake}/tags/{tag}/policies/{policy}` with selector 
`ALL_VALUES` or `TAG_VALUE`. |
+| Tag assignment  | Assign the tag to the table, or to a catalog or schema 
ancestor.                                         |
+
+`minIntervalMs` is the cooldown after a finished run of that policy on that 
table. When the policy omits it, the server uses the per-type default in [Table 
maintenance 
cooldowns](../gravitino-server-config.md#table-maintenance-cooldowns).
+
+Built-in content types:
+
+| `policyType`                         | Time trigger | Commit trigger |
+|--------------------------------------|--------------|----------------|
+| `system_iceberg_compaction`          | Yes          | Yes            |
+| `system_iceberg_rewrite_manifests`   | Yes          | Yes            |
+| `system_iceberg_snapshot_expiration` | Yes          | Yes            |
+| `system_iceberg_orphan_file_removal` | Yes          | No             |
+
+Field lists for each type are in [Iceberg compaction 
policy](../iceberg-compaction-policy.md), [Iceberg rewrite manifests 
policy](../iceberg-rewrite-manifests-policy.md), and [Manage 
policies](../manage-policies-in-gravitino.md).
+
+## Cron Path
+
+The cron path is what `timeTrigger` does after it is due.
+
+1. The expand pool wakes on the policy cron.
+2. It lists schemas and tables from the catalog. A missing Gravitino schema or 
table is imported by `loadSchema` or `loadTable`.
+3. It drops tables that lost the nearest-policy check, that are already in 
flight, or that are still inside `minIntervalMs`.
+4. It inserts one cron-job per remaining table. The cron pool submits one 
`builtin-iceberg-maintenance-task` Spark job for that row.
+5. The job calls the maintenance REST callbacks below.
+
+TMS fills `action_ids` when it builds the job. Do not put `action_ids` in 
policy `jobOptions`.
+
+The job reads two bags that were snapshotted at enqueue time:
+
+- `gravitinoAuth` becomes `updater_options`, including `gravitino_uri` and 
`metalake`. `metalake` always comes from the policy namespace.
+- `ircAuth` becomes Iceberg REST catalog properties under 
`spark.sql.catalog.<catalog>.*`.
+
+On Kubernetes, leave `spark_master` empty. See [Built-in 
jobs](../k8s-job-executor.md#built-in-jobs).
+
+These endpoints are for the Spark job, not for operators. Schemas are in the 
[maintenance OpenAPI definition](../open-api/maintenance/maintenance.yaml). The 
commit path uses the same callbacks.
+
+| Callback                    | Method and path                                
                             |
+|-----------------------------|-----------------------------------------------------------------------------|
+| Before-maintenance metrics  | `POST 
/api/metalakes/{metalake}/maintenance/actions/{jobId}/metrics/before` |
+| Finish and release the slot | `POST 
/api/metalakes/{metalake}/maintenance/actions/{jobId}/finish`         |
+| After-maintenance metrics   | `POST 
/api/metalakes/{metalake}/maintenance/actions/{jobId}/metrics/after`  |
+
+## Commit Trigger
+
+Set `commitTrigger` to `true` on a policy that should run after a successful 
Iceberg REST table update. Orphan file removal cannot set `commitTrigger`. The 
same `minIntervalMs` cooldown applies.
+
+Commit-triggered maintenance needs the Iceberg REST auxiliary service and the 
listener:
+
+```properties
+gravitino.auxService.names = iceberg-rest
+gravitino.eventListener.names = post-commit-maintenance-listener
+gravitino.eventListener.post-commit-maintenance-listener.class = 
org.apache.gravitino.maintenance.listener.PostCommitMaintenanceListener
+```
+
+Without the listener, time triggers still run. Commit-triggered maintenance 
does not.
+
+1. An Iceberg REST table update reaches `PostCommitMaintenanceListener`.
+2. TMS loads the nearest policies with `commitTrigger` set to `true`, in 
order: compaction, manifest rewrite, then snapshot expiry.
+3. A policy still inside `minIntervalMs` is dropped from the chain.
+4. If the effective `gravitinoAuth` or `ircAuth` bags disagree across the 
remaining policies, the chain is not submitted.
+5. Otherwise one chained `builtin-iceberg-maintenance-task` job runs every 
remaining policy.
+
+## Related
+
+- [Configuration](./optimizer-configuration.md) for client authentication, 
Iceberg REST auth, and the job executor
+- [Table Maintenance Service](./optimizer.md) for the manual CLI workflow
+- [CLI Reference](./optimizer-cli-reference.md) for every command and the 
built-in job templates
+- [Iceberg Compaction Policy](../iceberg-compaction-policy.md) for tuning the 
built-in compaction strategy

Reply via email to