When to use this
One of these has happened:
- Your storage bill is higher than the objects in the bucket explain.
oci os bucket deletefails even thoughobject listreturns 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 putreplaces 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
ABORTrule to every bucket template. Set it to longer than your longest upload. - Alert on growth of the
UncommittedPartsmetric. - 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.