Home / OCI

OCIHow-toAdvanced

OCI Object Storage: find and remove uncommitted multipart uploads that cost money and block bucket deletion

Failed large uploads leave invisible parts behind. They add to storage costs and can stop a bucket being deleted. Measure them, review before aborting, resume what's still needed and add a lifecycle rule.

Deepak Bhardwaj6 min read

Applies toOCI Object Storage, OCI CLI, OCI Monitoring

When to use this

One of these has happened:

  • Your storage bill is higher than the objects in the bucket explain.
  • oci os bucket delete fails even though object list returns nothing.
  • Backup or data pipeline jobs fail often during large uploads.

Large uploads go in parts. Parts stay in the bucket until the upload is committed or aborted. If a job crashes in between, they stay there, they aren't visible as objects, and you still pay to store them.

Step 1: measure them

The service publishes a metric for this. Check that it's available in your compartment:

export C=ocid1.compartment.oc1..your-compartment-id

oci monitoring metric list \
  --compartment-id "$C" \
  --namespace oci_objectstorage \
  --query 'data[].name' | sort -u

If you see UncommittedParts, query it for a bucket:

oci monitoring metric-data summarize-metrics-data \
  --compartment-id "$C" \
  --namespace oci_objectstorage \
  --query-text 'UncommittedParts[1h]{resourceDisplayName = "backup-bucket"}.max()' \
  --query 'data[]."aggregated-datapoints"[-1]'

Metric names and dimensions can change, so use what the list shows. The MQL cheat sheet can help you adjust the query.

For a quick view across many buckets, this script counts the open uploads in each. It needs jq:

for b in $(oci os bucket list --compartment-id "$C" --all --query 'data[].name' | jq -r '.[]'); do
  n=$(oci os multipart list --bucket-name "$b" --all 2>/dev/null | jq '.data | length // 0')
  echo "$b ${n:-0}"
done | sort -k2 -n -r

The CLI returns no output when nothing is found, which is why the script allows for an empty result.

Step 2: list the uploads

BUCKET=backup-bucket

oci os multipart list --bucket-name "$BUCKET" --all \
  --query 'data[].{object:object, started:"time-created", tier:"storage-tier", id:"upload-id"}' \
  --output table

Read the list before you remove anything:

What you see Meaning Action
Started minutes or hours ago May be running right now Leave it
Started days ago, job has finished Abandoned Abort it
Many uploads of the same object A job has been retrying Abort the old ones, and fix the job
A large upload that failed recently May be worth resuming Step 3

Step 3: resume instead of restarting

If a big upload failed partway through and the source file hasn't changed, you may be able to continue it:

oci os object resume-put \
  --bucket-name "$BUCKET" \
  --file /backup/full-2026-11-06.dmp \
  --upload-id <upload-id> \
  --part-size 128

Use the same part size as the original. If you don't know it, or the source file has changed, start again and abort the old upload.

Step 4: abort what's abandoned

Start with one upload:

oci os multipart abort \
  --bucket-name "$BUCKET" \
  --object-name "path/to/file.dmp" \
  --upload-id <upload-id> \
  --force

To clean up in bulk, build the list and read it first. This one selects uploads more than 7 days old:

CUTOFF=$(date -u -d '7 days ago' +%Y-%m-%dT%H:%M:%S)

oci os multipart list --bucket-name "$BUCKET" --all \
  | jq -r --arg c "$CUTOFF" '.data[] | select(."time-created" < $c) | [.object, ."upload-id", ."time-created"] | @tsv' \
  > stale-uploads.tsv

wc -l stale-uploads.tsv
head stale-uploads.tsv

Only after you've read it:

while IFS=$'\t' read -r obj id created; do
  echo "Aborting $obj (started $created)"
  oci os multipart abort --bucket-name "$BUCKET" --object-name "$obj" --upload-id "$id" --force
done < stale-uploads.tsv

Caution

Aborting permanently deletes the uploaded parts. A slow backup of a very large database can legitimately run for many hours, and aborting it will fail the job. Check with the job's owner, and pick a cutoff longer than your longest upload.

The comparison above is a plain string match, so it works because both timestamps use the same UTC format. Check the head output to confirm that it's what you expect. On macOS, date -d isn't available, so type the cutoff yourself.

Verify the result:

oci os multipart list --bucket-name "$BUCKET" --all --query 'length(data)'

Step 5: add a lifecycle rule

Automate it, so you don't have to do it again. The ABORT action cleans up old uploads.

Warning

oci os object-lifecycle-policy put replaces the whole policy. If you send only the new rule, your existing rules are lost. Read the current ones first, and include them.

oci os object-lifecycle-policy get --bucket-name "$BUCKET" > lifecycle-backup.json
cat lifecycle-backup.json

Add this rule to your list, and save it as rules.json:

[
  {
    "name": "abort-stale-multipart-7d",
    "action": "ABORT",
    "timeAmount": 7,
    "timeUnit": "DAYS",
    "isEnabled": true,
    "target": "multipart-uploads"
  }
]

Apply it:

oci os object-lifecycle-policy put --bucket-name "$BUCKET" --items file://rules.json

If the put fails or the rule does nothing, the service itself may need a policy. Each region has its own service name:

Allow service objectstorage-ap-mumbai-1 to manage object-family in compartment storage

See lifecycle policies for details, including how long rules take to run. It doesn't happen right away.

Step 6: delete a stubborn bucket

For a bucket that won't delete, clear all three kinds of content:

# Objects
oci os object list --bucket-name "$BUCKET" --all --query 'length(data)'

# Older versions, if versioning has ever been on
oci os object list-object-versions --bucket-name "$BUCKET" --all --query 'length(data)'

# Open uploads
oci os multipart list --bucket-name "$BUCKET" --all --query 'length(data)'

# Pre-authenticated requests and replication policies can also get in the way
oci os preauth-request list --bucket-name "$BUCKET" --all --query 'length(data)'
oci os replication list-replication-policies --bucket-name "$BUCKET"

Fix the source of the problem

Lifecycle rules treat the symptom. Failing uploads also need attention:

Cause What to change
Jobs killed by a timeout Allow more time, or reduce the amount per run
Unstable network path Use a service gateway or FastConnect
Too many parallel parts Reduce parallelism, and check for 429 responses
Custom code with no abort on failure Add an abort call to the error handling
S3-compatible tools Check their own cleanup and part size options

For RMAN specifically, see RMAN to Object Storage. For the upload commands themselves, see bulk upload and sync.

Prevent it next time

  • Add an ABORT rule to every bucket template. Set it to longer than your longest upload.
  • Alert on growth of the UncommittedParts metric.
  • Include this check when you look for idle and orphaned resources.
  • Fail jobs loudly, and log the upload ID, so that someone can resume or abort it.