# MinIO AIStor EDGE.2026-01-31T00-03-18Z

Released: January 31, 2026

This release focuses on Tables/Iceberg catalog performance and reliability. Coalesced distributed read locks reduce RPC overhead from N calls to 1 for N concurrent readers, directly addressing ServerBusy errors observed at 2920+ concurrent workers. Shard-based pagination reduces memory consumption by loading one shard at a time instead of all 16, with cache affinity routing for efficient pagination. Error handling improvements fix 98% of observed 500 errors by properly mapping wrapped errors to Iceberg-compliant status codes. Server resilience is enhanced with async LDAP/OIDC initialization that prevents cascading Kubernetes pod restarts when identity providers are temporarily unavailable.

**Release Statistics:** 93 commits, 326 files changed, +45,889/-11,590 lines

---

## Breaking Changes

### Tables: DROP TABLE Defaults to Server-Side Purge (#2848)

**Change:** The `purgeRequested` parameter now defaults to `true` when absent (previously `false`).

**Technical Details:**

- `parseBooleanQueryParam(..., "purgeRequested", true)` replaces `false`
- GetConfig additionally returns `s3.delete-enabled=false`
- This differs from the standard Iceberg REST spec where default is `false`

**Why:** Works around [Apache Iceberg bug #11023](https://github.com/apache/iceberg/issues/11023) where Spark's `DROP TABLE ... PURGE` doesn't forward the purge flag to the REST catalog. Spark then attempts client-side file deletion after the table mapping is removed, resulting in 403 errors. This behavior matches Lakekeeper and Tabular.io catalogs.

**Impact:** Tables dropped without an explicit `purgeRequested=false` will have their data purged server-side. The Iceberg Java client omits this parameter entirely when purge=false, so with our new default, absence means purge.

**Workaround:** Explicitly set `purgeRequested=false` in DROP TABLE requests if you need to preserve table data.

---

## New Features

### Async LDAP/OIDC Initialization (#2781)

MinIO AIStor now distinguishes between identity provider **configuration errors** and **connectivity errors**:

| Error Type                                | Behavior                              |
| ----------------------------------------- | ------------------------------------- |
| Configuration error (invalid settings)    | Blocks server startup                 |
| Connectivity error (provider unreachable) | Allows startup, retries in background |

**Implementation:**

- New functions: `openid.LookupConfigWithDeferred()`, `ldap.LookupWithDeferred()`
- Return struct with separate `ConfigError` and `ConnectivityErr` fields
- Background retry every 30 seconds via `retryPendingProviders()` goroutine
- State tracked via atomic bools: `openIDProviderReady`, `ldapProviderReady`
- Log message: "OpenID provider(s) unreachable, will retry in background"

**Impact:** Prevents 503 "iam-offline" healthcheck failures and cascading container restarts in Kubernetes when identity providers experience temporary outages.

### Tables: Register View v1 Endpoint (#2813)

Added the official [Iceberg REST Catalog Register View endpoint](https://github.com/apache/iceberg/blob/main/open-api/rest-catalog-open-api.yaml#L1838) per the approved specification. The `override` parameter is retained as an AIStor extension.

### Tables: Include Paths in List Responses (#2885)

New header `X-Minio-Tables-Include-Paths: true` for ListTables and ListViews. When set, response identifiers include the `Path` field with storage location.

```bash
curl -X GET http://127.0.0.1:9000/_iceberg/v1/warehouse/namespaces/ns/tables \
  -H "X-Minio-Tables-Include-Paths: true" \
  --aws-sigv4 "aws:amz:us-east-1:s3tables" --user minioadmin:minioadmin
```

### Tables: ListNamespace Policy Restriction by Parent (#2812)

ListNamespace IAM permissions can now be restricted by parent namespace, enabling hierarchical access control for multi-tenant deployments.

### Per-Bucket Purge-on-Delete for Spark (#2779)

Added `spark` as a valid `PurgeOnDelete` value in versioning configuration:

```bash
mc version enable mycluster/bucket --purge-on-delete spark
```

Enables purge-on-delete selectively for Spark clients on specific buckets, providing finer control than the cluster-wide `_MINIO_SPARK_LEGACY_OPTIMIZE` setting.

### Pool Filter for Inspect Data (#2759)

The inspect-data admin endpoint accepts a `pool` query parameter to extract data from a specific storage pool. Useful for inspecting leftover data in decommissioned pools.

### Block Bucket Operations on Warehouse Buckets (#2695)

Direct S3 bucket operations (versioning, lifecycle, encryption, tagging, replication) are now blocked on warehouse buckets with error `errTablesWarehouseBucketOperation`, preventing accidental misconfiguration.

### Prevent Warehouse Buckets as Replication Targets (#2717)

Warehouse buckets cannot be configured as bucket replication targets.

### Validate Bucket Configs Before Warehouse Upgrade (#2757)

Warehouse upgrade is blocked when the source bucket has lifecycle, encryption, tagging, versioning, or bucket replication configured. Site replication rules are automatically removed during upgrade.

---

## Performance Improvements

### Coalesced Read Locks for Distributed Locking (#2768)

**Implementation:** New `cmd/coalesced-lock.go` (497 lines)

Multiple goroutines requesting read locks on the same resource now share a single distributed lock via reference counting instead of each acquiring independent locks.

**Architecture:**

- `globalCoalescedLocks`: xsync.Map keyed by resource path
- `coalescedDistLock`: Tracks holders via `rHolders map[uint64]*readerHolder`
- `rGen`: Generation counter for lock acquisition cycles
- Cleanup interval: 5 minutes (`coalescedLockCleanupInterval`)

**How it works:**

1. First reader acquires the distributed RLock
2. Subsequent readers call `addHolderLocked()` to join existing lock
3. Each holder gets a unique ID for proper release tracking
4. When all holders release, the distributed lock is released

**Impact:** Reduces N distributed RPC calls to 1 for N concurrent readers. Directly addresses ServerBusy errors observed at 2920+ concurrent workers where lock timeouts occurred due to RPC amplification.

### Tables: Shard-Based Pagination with Cache Affinity (#2889)

**Implementation:** New `cmd/tables-list-cache.go` (306 lines)

ListTables and ListViews now load one shard at a time instead of all 16 shards.

**Architecture:**

- `tablesListCacheMgr`: Uses xsync.Map for concurrent access
- `tablesListCacheEntry`: Stores shard items, index, timestamps
- Cache parameters: `maxAge=15min`, `maxIdle=3min`, cleanup every minute
- Pagination token encodes `NodeName` for cross-node routing

**How it works:**

1. First page request loads shard 0, caches it, returns token with node name
2. Subsequent requests route to the same node via `proxyPaginatedListRequest()`
3. Node retrieves cached shard data without re-reading storage
4. When shard exhausted, moves to next shard

**Impact:** Up to 16x memory reduction for first page of large table listings.

### Tables: Metadata Pointer Caching (#2900)

Extended table cache to store UUID-to-metadata-location mappings, reducing storage I/O for LoadTable operations on frequently accessed tables.

### Tables: Eliminate Redundant Metadata Lookups (#2774)

Removed redundant `GetObjectInfo()` calls by reusing the ETag from `GetObjectNInfo()` response via `ObjInfo.ETag`. Improves latency for LoadTable, LoadView, CommitTable, CommitView, and multi-table transactions.

### Tables: MakeTableLive Optimization (#2775)

Consolidated staged-to-live table transitions into single `MakeTableLive()` function that loads the shard once, replacing separate calls to GetTable, RemoveTable, AddTable, and Save.

### Tables: Binary Search for Pagination (#2804)

Replaced O(n) linear search with O(log n) `sort.Search` for finding pagination start indices in ListWarehouses, ListTables, and ListViews.

### Tables: Reduced Memory Allocations (#2776)

- Switched to pooled msgp readers (`msgpNewReader`/`msgpNewSmallReader`) for registry decoding
- Added lightweight `TableExists()` that checks registry without loading full metadata
- Optimized path construction with pre-sized `strings.Builder` (~174MB savings in profiled workloads)

### Tables: Per-Warehouse Namespace Locks (#2884)

RegisterTable and RegisterView now acquire per-warehouse namespace registry locks instead of the global warehouse registry lock, eliminating serialization of registrations across unrelated warehouses.

### Allocation-Free Path Comparison (#2660)

New `path.Equal()` function compares paths for equivalence without allocating strings. Uses fast path for identical strings (common case) and segment-by-segment comparison for edge cases.

### TTFB Measurement Improvement (#2843)

Time-to-first-byte metrics now measure from "last request read" to first response byte, rather than from request start. Provides meaningful TTFB for upload operations like PutObject where the previous measurement included upload time.

---

## Bug Fixes

### Tables: Proper Error Code Mapping for Wrapped Errors (#2824)

**The problem:** Errors like `TablesNamespaceNotFoundError` were wrapped with context by intermediate layers (`fmt.Errorf("catalog lookup: %w", err)`). The original type switch in `toTablesAPIError()` only handled unwrapped errors, causing wrapped errors to fall through to the 500 handler.

**The fix:** Added `errors.As()` checks for `TablesViewNotFoundError`, `TablesTableNotFoundError`, and `TablesNamespaceNotFoundError` to properly unwrap and identify error types.

**Impact:** Fixes approximately 98% of observed 500 errors. Clients now receive proper Iceberg error codes: `NoSuchTableException`, `NoSuchViewException`, `NoSuchNamespaceException` with HTTP 404.

### Tables: ListTables/ListViews Return 404 on Concurrent Namespace Deletion (#2886)

When namespaces are deleted concurrently during list operations, the API now correctly returns HTTP 404 (`NoSuchNamespaceException`) instead of HTTP 500.

### Tables: Conflict Detection Returns 409 Instead of 500 (#2834, #2882)

Fixed optimistic locking conflict detection in CommitTable and CommitView:

- **#2834:** Incorrect error wrapping with `fmt.Errorf("%w: %w", ...)` caused type detection to fail
- **#2882:** CommitView used `WriteMetadataPointerWithEtagVersionWithLock` with `NoLock: true` but didn't hold a lock, creating a TOCTOU race

Both now properly return HTTP 409 with `CommitFailedException`.

### Tables: DeleteNamespace Cleanup Synchronous (#2827)

Staged table cleanup during DeleteNamespace is now synchronous. Previously, background cleanup could race with subsequent DeleteWarehouse calls, causing `BucketNotEmpty` errors.

### Tables: Single Table Lazy Loading (#2829)

After cluster restart, individual tables can be lazily loaded on first S3 access before full tabular refresh completes. Also adds fast-path in `AuthorizeS3Action` to skip table authorization for non-warehouse buckets.

### Tables: Notify Peers After Warehouse Creation (#2890)

Peers are now notified to load updated bucket metadata after creating a warehouse, preventing cache synchronization issues.

### Tables: Allow S3 Operations on Warehouse Buckets (#2881)

Removed warehouse bucket filtering from ListBuckets and HeadBucket handlers. `mc ls` now works on warehouse buckets.

### Tables: Require Explicit List Permission (#2787)

Removed fallback that allowed GetTable/GetView permission to list all tables/views. Users must now have explicit `s3tables:ListTables` or `s3tables:ListViews` permission.

### Tables: Max Parity for Catalog Metadata (#2771)

All objects under `.minio.sys/catalog/` now use maximum parity for improved durability.

### Tables: Atomic RenameTable with Recovery (#2724)

RenameTable operations are now atomic with transaction log recovery.

### Security: Redact "passwd" Environment Variables (#2891)

Added "passwd" to sensitive keywords for redaction. `MINIO_CERT_PASSWD` and similar variables are now redacted in diagnostics.

### OpenID Access Key Claims Preservation (#2823)

Fixed erroneous deletion of OpenID access key claims when errors occur during claim fetching.

### Kubernetes OIDC Nil Pointer Fix (#2831)

Fixed panic when HTTP requests to Kubernetes OIDC/JWKS endpoints fail by moving `defer xhttp.DrainBody(resp.Body)` after error check.

### Filter Disabled Replication Targets (#2769)

`mc replicate backlog --full` and `mc replicate resync-backlog` now skip disabled replication targets.

### Scanner Refreshes Bucket Configs During Scan (#2808)

Long-running namespace scans now refresh bucket configurations every minute, allowing lifecycle, replication, and object lock changes to take effect without waiting for the next scan cycle.

### License Management Fixes (#2765, #2810, #2772)

- Consolidated into single `globalLicenseManager`
- Fixed race where health checks passed before license validation
- Fixed license update when new license has same expiry
- Fixed config license lookup ordering

### Fix EC Mismatch Merging (#2807)

Fixed `mergeXLV2Versions` to properly evaluate majority EC values when mismatches occur. Regression from minio/minio#20351.

### Drive max_timeout KVS Config (#2766)

`drive max_timeout` now reads from KVS config set via `mc admin config set`, following standard precedence: env var → KVS config → default.

### Coalesced Write Lock Error (#2802)

Fixed coalesced write locks returning `context.Canceled` instead of `OperationConflict` on contention by checking `ctx.Err()` instead of `newCtx.Err()`.

### Race Condition Fixes

| PR    | Issue                                | Fix                                     |
| ----- | ------------------------------------ | --------------------------------------- |
| #2753 | WaitGroup race in netperf            | Move `wg.Add()` before goroutine spawn  |
| #2760 | Storage metric persistence load race | Synchronize DecodeMsg and IOStats.Add   |
| #2805 | Historic metrics initialization race | Initialize metrics before starting load |
| #2751 | Lock file race in log recorder       | Create new lock before removing old     |
| #2752 | Log recorder overflow notification   | Synchronize notification handling       |
| #2739 | Scheduler PutConfig race             | Prevent recreating run metadata         |

### Inventory Sequence Number (#2750)

Fixed inventory sequence number generation that was hex-encoding timestamps, causing duplicate values.

### Delta Sharing JWT Isolation (#2743)

Fixed JWT token isolation for Delta Sharing API endpoints.

---

## Security Updates

### Go 1.25.6 CVE Fixes (#2758)

Updated Go runtime with CVE fixes from Go 1.25.6. The zip vulnerability affects only admin `ImportBucketMetadata` and `ImportIAM` handlers (requiring admin credentials) with minimal customer impact.

---

## Security & Compliance

### Software Bill of Materials (SBOM)

This release includes comprehensive SBOM documentation:

- [SPDX JSON](sbom-EDGE.2026-01-31T00-03-18Z.spdx.json) - Standard SBOM format
- [CycloneDX JSON](sbom-EDGE.2026-01-31T00-03-18Z.cyclonedx.json) - Security scanner compatible
- [Go Modules](go-modules-EDGE.2026-01-31T00-03-18Z.txt) - Human-readable dependency list

---

## Upgrade Instructions

For detailed upgrade instructions: https://docs.min.io/enterprise/aistor-object-store/upgrade-aistor-server/

Platform-specific guides:

- **Linux/Bare Metal**: https://docs.min.io/enterprise/aistor-object-store/upgrade-aistor-server/upgrade-aistor-linux/
- **Kubernetes with Helm**: https://docs.min.io/enterprise/aistor-object-store/upgrade-aistor-server/upgrade-aistor-kubernetes-helm/

### New Configuration Options

| Option                         | Type              | Description                                             |
| ------------------------------ | ----------------- | ------------------------------------------------------- |
| `X-Minio-Tables-Include-Paths` | Header            | Include storage paths in ListTables/ListViews responses |
| `spark` for `PurgeOnDelete`    | Versioning config | Enable purge-on-delete for Spark clients per bucket     |
| `pool`                         | Query parameter   | Filter inspect-data to specific storage pool            |

### Support

- SUBNET Support: https://subnet.min.io
- Documentation: https://docs.min.io

---

## Container Images

- `quay.io/minio/aistor/minio:EDGE.2026-01-31T00-03-18Z`
- `quay.io/minio/aistor/minio:edge`
- `quay.io/minio/aistor/minio:EDGE.2026-01-31T00-03-18Z.fips`

## Downloads

### darwin-arm64

- [minio.EDGE.2026-01-31T00-03-18Z](https://dl.min.io/aistor/minio/edge/darwin-arm64/archive/minio.EDGE.2026-01-31T00-03-18Z)

### linux-amd64

- [minio.EDGE.2026-01-31T00-03-18Z](https://dl.min.io/aistor/minio/edge/linux-amd64/archive/minio.EDGE.2026-01-31T00-03-18Z)
- [minio-20260131000318.0.0-1.x86_64.rpm](https://dl.min.io/aistor/minio/edge/linux-amd64/archive/minio-20260131000318.0.0-1.x86_64.rpm)
- [minio_20260131000318.0.0_amd64.deb](https://dl.min.io/aistor/minio/edge/linux-amd64/archive/minio_20260131000318.0.0_amd64.deb)

### linux-amd64 (FIPS)

- [minio.EDGE.2026-01-31T00-03-18Z.fips](https://dl.min.io/aistor/minio/edge/linux-amd64/archive/minio.EDGE.2026-01-31T00-03-18Z.fips)

### linux-arm64

- [minio.EDGE.2026-01-31T00-03-18Z](https://dl.min.io/aistor/minio/edge/linux-arm64/archive/minio.EDGE.2026-01-31T00-03-18Z)
- [minio-20260131000318.0.0-1.aarch64.rpm](https://dl.min.io/aistor/minio/edge/linux-arm64/archive/minio-20260131000318.0.0-1.aarch64.rpm)
- [minio_20260131000318.0.0_arm64.deb](https://dl.min.io/aistor/minio/edge/linux-arm64/archive/minio_20260131000318.0.0_arm64.deb)

### windows-amd64

- [minio.exe.EDGE.2026-01-31T00-03-18Z](https://dl.min.io/aistor/minio/edge/windows-amd64/archive/minio.exe.EDGE.2026-01-31T00-03-18Z)

