Learn OpenStack · core/storage/object
Swift Architecture
Understand the Three-Level Namespace
Every piece of data in Swift lives at exactly one address: account/container/object.
- Account — the top-level namespace, mapped one-to-one to a Keystone project. The account name is
AUTH_<project-id>by default. An account has its own SQLite database that lists all its containers. - Container — a flat bucket inside an account. A container has its own SQLite database that lists all its objects. There is no container-within-container nesting; the hierarchy is simulated with object name prefixes and the
delimiterquery parameter. - Object — the actual data blob, up to 5 GiB (single PUT) or unlimited with large object manifests. Objects have user-defined metadata stored as HTTP headers (
X-Object-Meta-*).
All three layers are managed by separate server processes. They share no code path once a request leaves the proxy.
Understand the Proxy Server
The proxy server (swift-proxy-server) is the sole entry point for all client requests. Every PUT, GET, DELETE, HEAD, and POST passes through exactly one proxy process. The proxy is stateless and horizontally scalable; any number of proxy nodes can run behind a load balancer.
Request routing
When a request arrives, the proxy:
- Validates the request URL, extracts account, container, and object names.
- Runs the WSGI middleware pipeline (auth, rate limiting, caching, feature middleware — see below).
- Hashes the object path (or account/container path) against the appropriate ring to determine which storage nodes hold the three (or N) replicas.
- Issues concurrent backend requests to those storage nodes.
- Returns the first successful response to the client (for GETs and HEADs) or waits for a quorum of successes (for PUTs and DELETEs).
A quorum for a write is floor(replicas / 2) + 1. With three replicas, two successful backend writes constitute a successful PUT. The proxy returns 201 Created to the client even if the third replica is momentarily unavailable; the replicator catches up later.
Auth middleware
Swift uses a WSGI middleware chain. Authentication can be handled by:
keystoneauth— standard production middleware; validates tokens against Keystone, maps project-scoped tokens to Swift accounts.tempauth— built-in single-process auth for testing and all-in-one deployments; configured entirely inproxy-server.conf.swauth— legacy third-party auth system; rarely used in new deployments.
The auth middleware runs early in the pipeline. After authentication, the middleware sets REMOTE_USER and HTTP_X_AUTH_TOKEN so downstream middleware knows the identity.
Rate limiting
The ratelimit middleware enforces per-account and per-container request rate limits. Limits are configured as requests per second (RPS) in proxy-server.conf:
[filter:ratelimit]
use = egg:swift#ratelimit
clock_accuracy = 1000
max_sleep_time_seconds = 60
log_sleep_time_seconds = 0
rate_buffer_seconds = 5
account_ratelimit = 0
# Per-container: container_ratelimit_<size_bucket> = <rps>
container_ratelimit_0 = 0
container_ratelimit_10 = 50
container_ratelimit_50 = 75
Requests that exceed the limit receive 498 Rate Limited.
Concurrency model
Each proxy worker is a separate OS process (controlled by workers in [DEFAULT]). Inside each worker, Swift uses eventlet (green threads) for non-blocking I/O. A single worker process handles hundreds of concurrent connections without blocking on network I/O.
Understand the Account, Container, and Object Servers
Account server
swift-account-server manages account-level metadata and the list of containers in each account. Each account has one SQLite database file stored on a data disk. The account server handles:
GET /v1/<account>— list containers (reads from SQLite)PUT /v1/<account>— create account (create SQLite DB)DELETE /v1/<account>— mark account as deletedHEAD /v1/<account>— return account metadataPOST /v1/<account>— update account metadata
The account server runs on the same nodes as object servers, typically on port 6202.
Container server
swift-container-server manages container-level metadata and the list of objects in each container. Each container has one SQLite database file. The container server handles:
GET /v1/<account>/<container>— list objectsPUT /v1/<account>/<container>— create containerDELETE /v1/<account>/<container>— delete container (must be empty)HEAD /v1/<account>/<container>— return container metadataPOST /v1/<account>/<container>— update container metadata (ACLs, versioning, etc.)
Container servers run on the same nodes as object servers, typically on port 6201.
Object server
swift-object-server manages the actual data blobs. Each object is stored as a file on a local XFS or ext4 filesystem. Objects are stored in a well-known path derived from the ring partition and object hash:
/srv/node/<device>/objects/<partition>/<suffix>/<object-hash>/<timestamp>.data
The .data extension holds the object body and its metadata in extended file attributes (xattrs). A .meta file stores POST-only metadata updates. A .ts (tombstone) file records deletions so that replication can propagate deletes to lagging replicas.
Object servers run on port 6200 by default.
Auditor, replicator, and updater
Each server type has companion background daemons:
| Daemon | Purpose |
|---|---|
swift-object-auditor |
Reads every object file and verifies its md5 checksum; quarantines corrupted objects |
swift-object-replicator |
Pushes missing or out-of-date objects to partner nodes; uses rsync or the Swift replication protocol |
swift-object-updater |
Replays failed container database updates (e.g., object counts) that could not be sent when the container server was unavailable |
swift-container-auditor |
Verifies container database integrity |
swift-container-replicator |
Replicates container SQLite databases to partner nodes via HTTP merge |
swift-container-updater |
Updates account databases with container statistics |
swift-account-auditor |
Verifies account database integrity |
swift-account-replicator |
Replicates account SQLite databases to partner nodes |
swift-account-reaper |
Deletes all data belonging to deleted accounts |
Understand the Ring Architecture
The ring is the core data structure that maps every account, container, and object to the set of storage devices that hold it. There are three separate rings: one for accounts, one for containers, and one for objects (or one per storage policy if multiple policies are configured).
Consistent hashing
Swift uses a modified consistent hashing algorithm. Rather than hashing directly to devices, Swift introduces an intermediate layer: partitions.
The object path (or account/container path) is MD5-hashed, and the top N bits of the MD5 digest select a partition. Every partition is pre-assigned to a fixed set of devices by the ring. When a request arrives at the proxy, it hashes the path, looks up the partition, and finds the device list in the ring — O(1) lookup, no coordination required.
Partition power
The number of partitions is 2^partition_power. A partition power of 18 creates 262,144 partitions. This is enough granularity for millions of devices and billions of objects. Partition power is set at ring creation and cannot be changed without rebuilding the ring.
Guidelines: - Small cluster (< 5 nodes, < 50 drives): partition power 10–12 - Medium cluster (10–50 nodes): partition power 14–16 - Large cluster (50+ nodes, 100+ drives): partition power 18–20 - Very large (1,000+ drives): partition power 22+
Replicas
Each partition is assigned to exactly replica_count devices. With the default of 3, every partition maps to 3 devices. The proxy reads from or writes to all three concurrently.
Zones and regions
Swift uses zones and regions to model failure domains:
- Zone — a failure domain within a region (e.g., a rack, a power domain, a network switch). Swift ensures each replica of a partition lands in a distinct zone. If a zone goes offline, only one replica is affected.
- Region — a geographic site or data center. Swift can span multiple regions. Each region contains one or more zones. Replicas are distributed across regions first, then zones.
The ring builder enforces replica placement: before placing a replica on a device in the same zone as an existing replica for the same partition, it exhausts all other zones. Same for regions.
Devices and weight
Each device in the ring has a weight (a floating-point number proportional to its storage capacity). A 4 TB drive gets twice the weight of a 2 TB drive. The ring builder distributes partitions proportionally to weight, so larger drives hold more partitions.
A device entry contains:
- region — integer region ID
- zone — integer zone ID
- ip — IP address of the storage server
- port — port number (6202 for account, 6201 for container, 6200 for object)
- device — device name as mounted under /srv/node/ (e.g., sdb, sdc)
- weight — proportional capacity
- meta — free-form annotation (rack number, drive model, etc.)
Ring file format
The ring is serialized as a gzipped Python pickle (.ring.gz). The proxy and storage servers load it at startup and can reload it on SIGHUP or at a configurable interval. The ring file is typically a few hundred kilobytes even for large clusters because it stores only partition-to-device mappings.
The builder file (.builder) is a separate, larger file used only by swift-ring-builder to reconstruct and rebalance the ring. Builder files must be kept safe — losing a builder file makes future ring changes much harder.
Handoff partitions
When a device is unavailable, the ring designates handoff devices — additional devices beyond the primary replica set that should temporarily store a partition's data. When the primary device comes back online, the replicator moves the data from handoff to primary and removes the handoff copy.
Understand the Eventually Consistent Model
Swift is AP (Available, Partition-tolerant) in CAP terms. It prioritises availability and partition tolerance over strong consistency.
Key properties:
- Writes return success after a quorum of replicas acknowledge. A minority of replicas may be momentarily stale.
- Reads return the response from the first replica to respond. If replicas disagree, the proxy uses
X-Timestampmetadata on each response to identify the most recent version and serve it (this is called read repair in some systems; Swift calls it the object server's reconciliation behaviour). - Deletes are recorded as tombstone files (
.ts), not immediate physical deletion, so that replication can propagate the delete to all replicas even if some were offline at delete time. Tombstones are eventually removed by the auditor after a configurable grace period (reclaim_age, default 604800 seconds / 7 days). - Eventual consistency means that after a write to quorum, a client reading from a lagging replica may briefly see stale data. In practice the replicator's run interval (default: 30 seconds) bounds the staleness window.
Understand Storage Policies
A storage policy is a named configuration that defines how objects are stored. Every container is assigned to exactly one policy at creation time; the policy cannot be changed afterward.
Two policy types exist:
Replication policy (default)
Objects are stored as N complete copies, one per replica. This is the default and simplest model. It uses the standard object ring. Configuration in /etc/swift/swift.conf:
[storage-policy:0]
name = Policy-0
default = yes
policy_type = replication
Erasure coding policy
Objects are split into data fragments and parity fragments using a configurable EC scheme. The fragments are distributed across storage nodes. Common schemes:
| Scheme | Data fragments | Parity fragments | Minimum nodes needed |
|---|---|---|---|
liberasurecode_rs_vand 10 4 |
10 | 4 | 14 |
liberasurecode_rs_vand 6 3 |
6 | 3 | 9 |
isa_l_rs_vand 10 4 |
10 | 4 | 14 |
The EC policy stores only k/(k+m) of the data size per node (e.g., 10/14 ≈ 71% vs. 100% for 3-replica), while tolerating up to m simultaneous node/drive failures.
[storage-policy:1]
name = ec-policy
policy_type = erasure_coding
ec_type = liberasurecode_rs_vand
ec_num_data_fragments = 10
ec_num_parity_fragments = 4
ec_object_segment_size = 1048576
Each storage policy has its own object ring. The ring file for policy 0 is object.ring.gz; for policy N (N > 0) it is object-N.ring.gz.
Understand the WSGI Pipeline
Swift's proxy server is a WSGI application assembled from a chain of middleware. The pipeline is defined in proxy-server.conf under [pipeline:main]. A typical production pipeline:
[pipeline:main]
pipeline = catch_errors gatekeeper healthcheck proxy-logging cache listing_formats bulk tempurl ratelimit authtoken keystoneauth copy container_quotas account_quotas slo dlo proxy-logging proxy-server
Each entry is a filter (middleware) except the final proxy-server, which is the application itself. Filters are processed left to right. The most important filters:
| Filter | Purpose |
|---|---|
catch_errors |
Catches unhandled exceptions and returns 500; prevents stack traces leaking to clients |
gatekeeper |
Strips disallowed headers from incoming requests (prevents header injection) |
healthcheck |
Returns 200 OK for /healthcheck (load balancer probe endpoint) |
proxy-logging |
Logs access in combined log format (appears twice to log both before and after auth) |
cache |
Caches account and container metadata in Memcached |
listing_formats |
Enables JSON/XML/plain format negotiation for container listings |
bulk |
Handles bulk delete and tar archive extract requests |
tempurl |
Validates and serves temporary URL requests |
ratelimit |
Enforces per-account, per-container request rate limits |
authtoken |
Validates Keystone tokens (keystonemiddleware); caches validation results |
keystoneauth |
Maps validated Keystone identity to Swift ACLs and roles |
copy |
Implements server-side object copy (COPY method and PUT with X-Copy-From) |
container_quotas |
Enforces per-container byte and object count quotas |
account_quotas |
Enforces per-account byte quotas |
slo |
Static Large Object: handles manifest PUTs and transparent multi-segment GETs |
dlo |
Dynamic Large Object: assembles segments by prefix at GET time |
proxy-server |
Core proxy: ring lookup, backend I/O, response assembly |
The ordering is significant. authtoken/keystoneauth must come before any middleware that requires an authenticated identity. slo/dlo must come before proxy-server. tempurl must come before authtoken so that temp URL requests can bypass normal token auth.