← S3 compatibility matrix S3 resource guide · Core object operations

Core Object Operations

PUT, GET, DELETE, HeadObject, CopyObject, and multipart upload — the read/write path every S3 client depends on. All are real, DB-and-storage-backed handlers on the XNS S3 Gateway, not stubs.

No RAM cap — streaming above 40 MB 6 multipart verbs — real handlers 1 open bug — anon PUT 500s


What this area is

Core object operations are the read/write path every S3 client depends on.

Put an object in, get it back out, copy it, delete it, or split a large upload into parts. Every S3-compatible client — the AWS CLI, boto3, rclone, restic — ultimately calls down to these operations: PutObject, GetObject, HeadObject, DeleteObject/DeleteObjects, CopyObject, GetObjectAttributes, and the six-verb multipart family (CreateMultipartUpload, UploadPart, UploadPartCopy, CompleteMultipartUpload, AbortMultipartUpload, ListParts). This is the surface a compatibility claim is judged on first.

Clientboto3 · AWS CLI · rclone · restic
↓ SigV4-signed HTTPS
XNS S3 GatewayPUT · GET · HEAD · DELETE · COPY · Multipart
↓ cost_center-scoped
Erasure-coded storage80 data + 40 parity shards, up to 120 hosts


How XNS implements it

Every operation in this area is a real handler with actual persistence, not a canned response.

Object CRUD lives in read_handlers.go and write_handlers.go (~952 lines) in the S3 Gateway codebase. Multipart lives in multipart_handlers.go (~500 lines); server-side copy is header-matched (PUT + x-amz-copy-source) in copy_handlers.go.

Bytes land on an erasure-coded storage layer, not a single disk. The corp deployment convention is 80 data shards + 40 parity shards = 120 hosts per object — Reed-Solomon DataNum/ParityNum are per-cost-center configuration set via Muse, not a hardcoded constant, so a given deployment can run a different split. Requests land on the S3 data plane, port 9000 plain HTTP always, with additive TLS on 9443 when a certificate is configured; every request is SigV4-signed and cost_center-scoped — the gateway is multi-tenant, and object storage resolves per-tenant, not per single account.

PutObject
Streaming write, constant memory above 40 MB — no full-object buffering. write_handlers.go.
GetObject
Range requests supported; cache hit ~12 ms, cache miss ~340 ms median. read_handlers.go.
HeadObject / GetObjectAttributes
Metadata reads. Sources ETag/size/modtime from the same object_versions row that serves the bytes (BUG-239 fix) — under concurrent overwrite the served ETag always matches the served body.
DeleteObject / DeleteObjects (batch)
Single-object delete releases the shard reference (delete_sectorref); batch delete runs one gateway call per request, budgeted ≤30s per 1,000 keys.
CopyObject / UploadPartCopy
Server-side copy, header-matched route; no client round-trip of bytes.
Multipart (6 verbs)
Background multipart_reaper.go cleans abandoned uploads automatically.


How we conform to the S3 protocol

Measured against the public ceph/s3-tests suite — and published.

Object operations are verified against the same public conformance suite the rest of the industry is measured by — object CRUD, multipart, copy, and range/conditional requests, test by test. We publish the full per-test result, including what still fails, on the compatibility matrix rather than summarising it here.

Where we deliberately differ. An anonymous or cross-principal caller probing a deleted or foreign object gets a uniform 403 — not the AWS-standard 404-if-you-can’t-list / 403-if-you-can distinction. That difference is a choice, not a gap: it refuses to confirm whether an object exists to a caller who has no business knowing.

The reasoning behind that posture is set out under “What we chose to be different about” on the compatibility matrix, alongside every measured figure and its provenance.


How we compare

Matches every major S3-compatible vendor on the core operations.

Source: the same competitor cells published on the S3 compatibility matrix — “Core object operations” section — researched from each vendor’s own documentation, 2026-06-14.

Competitor figures from each vendor’s official documentation, researched 2026-06-14.
CapabilityXNSAWS S3CephMinIOWasabiB2Storj
PUT / GET / DELETE object
Multipart upload
Server-side CopyObjectx-amz-tagging rejected on copy
Range GET
Conditional requests (If-Match / If-None-Match)◐ undocumented
Max object sizeNo RAM cap48.8 TiB~5 TB5 TiB5 TB10 TBNo cap


How applications use it

Sign in, PUT or multipart-upload, then GET it back — optionally ranged or conditional.

Addressing: both path-style (https://endpoint/bucket/key) and virtual-hosted-style (https://bucket.endpoint/key) work — vhost is the boto3/AWS CLI default and is enabled when the gateway’s domain is configured. Trailing-slash bucket paths are normalized for mc, restic, and kopia.

boto3

# Point at your gateway, not AWS
import boto3
s3 = boto3.client(
    "s3",
    endpoint_url="https://relayer.example.com",   # or http://localhost:9000 for local/dev
    aws_access_key_id="AKIA...",
    aws_secret_access_key="...",
    region_name="us-east-1",   # GetBucketLocation always returns us-east-1
)
# PUT
s3.put_object(Bucket="my-bucket", Key="reports/q3.parquet", Body=data)
# Conditional PUT — first-write-wins, e.g. Terraform-style state locking
s3.put_object(Bucket="my-bucket", Key="state.tfstate", Body=data, IfNoneMatch="*")
# Byte-range GET — e.g. DuckDB/Polars reading a Parquet footer
resp = s3.get_object(Bucket="my-bucket", Key="reports/q3.parquet", Range="bytes=0-1048575")
# Multipart upload (boto3 switches automatically above its threshold)
s3.upload_file("large-backup.img", "my-bucket", "backups/large-backup.img")
# Server-side copy
s3.copy_object(Bucket="my-bucket", CopySource={"Bucket": "my-bucket", "Key": "reports/q3.parquet"},
                Key="archive/q3.parquet")

AWS CLI

aws s3api put-object --bucket my-bucket --key reports/q3.parquet --body q3.parquet \
  --endpoint-url https://relayer.example.com
aws s3api get-object --bucket my-bucket --key reports/q3.parquet --range bytes=0-1048575 out.parquet \
  --endpoint-url https://relayer.example.com
aws s3 cp large-backup.img s3://my-bucket/backups/large-backup.img \
  --endpoint-url https://relayer.example.com


Use cases

Where this matters in practice.

Five patterns account for most of the traffic on top of these operations: media and video delivery, ML training-data staging, backup and restore, analytical file reads, and state-locking for infrastructure tooling. Each one leans on a specific verb below — ranged GET, multipart upload, or conditional PUT — not the API surface as a whole.

Media and video asset storage

Served with byte-range GET so a player can seek without downloading the whole file.

ML training data staging

Uploaded via multipart so a multi-GB shard survives a flaky connection without a full restart.

Database and VM backup images

Written with multipart upload and restored with ranged GET for partial recovery.

Analytical file formats (Parquet, ORC)

Read with ranged GET against footer/metadata sections instead of full downloads.

Infrastructure state locking

Uses conditional PUT (If-None-Match: *) to guarantee first-write-wins.


Applications that lean on it heavily

Real software, named.

The tools below aren’t hypothetical integration targets. DuckDB, Polars, Terraform’s S3 backend, restic, kopia, and rclone all depend on these operations behaving correctly today — not just being present in a compatibility list.

DuckDB / Polars / Parquet readers

Ranged GET against Parquet footers is how these engines avoid downloading multi-GB files to read a schema or a column chunk.

Terraform's S3 backend

State-file locking depends on conditional PUT (If-None-Match) behaving correctly; a backend that silently allows a duplicate write corrupts state.

restic and kopia

Chunked backup tools that depend on multipart upload plus trailing-slash path normalization, both confirmed working (see the compatibility matrix's Tool compatibility table).

rclone

Mirrors and syncs large trees using multipart upload and server-side CopyObject to avoid re-transferring unchanged data.


What’s out of scope here

S3 Select is not routed here, and region is hardcoded.

The boundary, stated plainly

S3 Select (SelectObjectContent) is not exposed on this route table — a request gets a 404, not a 501. It's tracked as an integration-and-tooling boundary, not an object-CRUD one; see the integration & tooling resource page. Region is hardcoded to us-east-1GetBucketLocation always returns it, and region-specific endpoint hostnames aren't supported (single-region in this release).


FAQ

Object operations questions, answered directly.

No fixed RAM cap. PutObject streams to storage at constant memory above 40 MB (write_handlers.go, ~952 lines), unlike a buffer-everything-in-RAM implementation.

Yes. If-Match and If-None-Match are supported on PUT and GET, the same mechanism Terraform’s S3 backend uses for state-file locking (IfNoneMatch: “*” on first write).

Not required, but recommended above roughly 64 MB. CreateMultipartUpload / UploadPart / UploadPartCopy / CompleteMultipartUpload / AbortMultipartUpload / ListParts are all real handlers in multipart_handlers.go (~500 lines); boto3 and the AWS CLI switch to multipart automatically.



See the full conformance picture.

The object-operations rows above are one section of the full S3 compatibility matrix — buckets, versioning, lifecycle, encryption, access control, replication, and integration all get the same treatment.


Claims on this page last verified
© Copyright - SCP, Corp | Xa Net Services and Affiliates