A complete content delivery network platform with separation of concerns, global edge caching, and multi-tenant isolation.
[Global Edge Network - 300+ Points of Presence]
|
v
[Cloudflare / Bunny / AWS / Custom]
|
v
[Your Origin Infrastructure]
+------------------+------------------+
| | |
v v v
[API Server] [Storage] [Database]
api.domain cdn.domain MySQL/PgSQL
graph TB
subgraph Edge["Global Edge Network (PoPs)"]
PoP_Maputo["PoP Maputo (Mozambique)"]
PoP_Tokyo["PoP Tokyo (Japan)"]
PoP_NY["PoP New York (USA)"]
PoP_London["PoP London (UK)"]
PoP_SP["PoP Sao Paulo (Brazil)"]
end
subgraph Origin["Your Origin Infrastructure"]
API["API Gateway<br/>api.yourdomain.com<br/>PHP 8.1+"]
Auth["Auth Service<br/>Token Validation"]
Image["Image Processor<br/>GD Library"]
Queue["Job Queue<br/>Redis/Beanstalkd"]
end
subgraph Data["Data Layer"]
DB[("Database<br/>MySQL/PostgreSQL")]
FS[("File Storage<br/>Local/S3/R2")]
Cache[("Cache<br/>Redis/Memcached")]
end
PoP_Maputo --> API
PoP_Tokyo --> API
PoP_NY --> API
PoP_London --> API
PoP_SP --> API
API --> Auth
API --> Image
API --> Queue
Auth --> DB
Image --> FS
Queue --> Cache
FS --> PoP_Maputo
FS --> PoP_Tokyo
FS --> PoP_NY
FS --> PoP_London
FS --> PoP_SP
classDef edge fill:#2E7D32,stroke:#1B5E20,color:#fff
classDef origin fill:#1565C0,stroke:#0D47A1,color:#fff
classDef data fill:#E65100,stroke:#BF360C,color:#fff
class PoP_Maputo,PoP_Tokyo,PoP_NY,PoP_London,PoP_SP edge
class API,Auth,Image,Queue origin
class DB,FS,Cache data
The edge layer consists of geographically distributed cache servers that serve content from locations closest to end users.
Characteristics:
- Geographic distribution: 300+ global locations (including Maputo, Mozambique)
- Cache storage: SSD/NVMe (varies by provider)
- Cache duration: Configurable (1 hour to 1 year)
- Eviction policy: LRU (Least Recently Used)
- SSL termination: At edge (free certificates with most providers)
Supported CDN Providers:
| Provider | Points of Presence | Free Tier | Best For |
|---|---|---|---|
| Cloudflare | 300+ (including Maputo) | Unlimited bandwidth | General purpose, DDoS protection |
| Bunny.net | 120+ | $1 trial | Video streaming, gaming assets |
| AWS CloudFront | 400+ | 1TB/month free | AWS ecosystem integration |
| Akamai | 4,000+ | Enterprise only | Financial, healthcare, high security |
| Custom Varnish | Self-managed | Depends on infrastructure | Complete control, compliance |
The application layer handles all dynamic operations: upload, delete, authentication, and image processing.
Directory Structure:
app/
├── Core/ # Core abstractions
│ ├── Auth.php # Session/token management
│ ├── Uploader.php # File handling and validation
│ ├── Mailer.php # Email notifications
│ └── ORMHelper.php # Database abstraction
├── Middleware/ # Request filters
│ └── ApiAuthMiddleware.php
└── router/ # Route definitions (file-based)
├── api/v1/
│ ├── upload.php
│ ├── delete.php
│ ├── info.php
│ └── resize.php
└── dashboard/ # Web interface routes
Technology Stack Details:
- Framework: Fluxor PHP (file-based routing, security-first)
- ORM: Cycle ORM (DataMapper pattern, database agnostic)
- Image Processing: GD Library (no external dependencies)
The data layer consists of three complementary storage systems.
Primary Database (MySQL/PostgreSQL):
-- Optimized indexes for high throughput
CREATE INDEX idx_files_user_deleted ON files(user_id, deleted_at);
CREATE INDEX idx_tokens_hash_user ON api_tokens(token_hash, user_id);
CREATE INDEX idx_bandwidth_reset ON users(bandwidth_reset_month);
CREATE INDEX idx_notifications_user_read ON notifications(user_id, is_read);File Storage Structure:
storage/
├── u/ # User storage root
│ ├── {username}/ # Per-user directory (isolated)
│ │ ├── {path}/ # User-defined folders
│ │ │ └── {files}
│ │ └── ...
│ └── ...
└── cache/ # Resized images cache
├── {md5_hash}.webp
├── {md5_hash}.jpg
└── ...
sequenceDiagram
participant Client
participant API as API Server
participant Auth as Auth Service
participant DB as Database
participant FS as File Storage
participant Queue as Job Queue
Client->>API: POST /api/v1/upload
Note over Client,API: Headers: X-User-UUID, X-Token<br/>Body: multipart/form-data
API->>Auth: Validate token
Auth->>DB: SELECT * FROM api_tokens WHERE token_hash = ?
DB-->>Auth: Token data + user_id
Auth-->>API: User object + permissions
alt Has upload permission
API->>API: Validate file (size, type, MIME)
API->>DB: SELECT storage_used, storage_limit FROM users
DB-->>API: Current usage
alt Has quota available
API->>FS: Store file in /storage/u/{username}/{path}
FS-->>API: File stored
API->>DB: INSERT INTO files (user_id, uuid, path, size)
API->>DB: UPDATE users SET storage_used_bytes += size
API->>Queue: Add to processing queue (optional)
API-->>Client: 201 Created + CDN URL
else Quota exceeded
API-->>Client: 403 Forbidden (Quota exceeded)
end
else No permission
API-->>Client: 403 Forbidden
end
sequenceDiagram
participant Client
participant DNS as DNS Resolver
participant PoP as CDN PoP (Maputo)
participant Origin as Origin Server
Client->>DNS: Resolve cdn.yourdomain.com
DNS-->>Client: PoP IP (geo-routed to Maputo)
Client->>PoP: GET /u/johndoe/photo.jpg
alt Cache HIT
PoP-->>Client: 200 OK
Note over PoP,Client: Cache-Control: max-age=31536000<br/>CF-Cache-Status: HIT<br/>Served in <10ms
else Cache MISS
PoP->>Origin: GET /storage/u/johndoe/photo.jpg
Origin-->>PoP: 200 OK + Cache Headers
PoP->>PoP: Store in edge cache (SSD)
PoP-->>Client: 200 OK
Note over PoP,Origin: First request: 50-200ms<br/>Cache-Control: max-age=31536000<br/>CF-Cache-Status: MISS
end
sequenceDiagram
participant Client
participant PoP as CDN PoP
participant API as API Server
participant Processor as Image Processor
participant Cache as Cache Storage
participant FS as File Storage
Client->>PoP: GET /u/johndoe/photo.jpg?w=800&format=webp
PoP->>API: Forward to resize endpoint
API->>API: Parse URL parameters
API->>Processor: Process image (w=800, format=webp)
alt Cached result exists
Processor->>Cache: Check cache/{hash}.webp
Cache-->>Processor: File exists (cache HIT)
Processor-->>API: Return cached file
else No cache
Processor->>FS: Load original image
FS-->>Processor: Original file
Processor->>Processor: Resize to 800px
Processor->>Processor: Convert to WebP
Processor->>Processor: Apply filters (if any)
Processor->>Cache: Store result (cache MISS)
Processor-->>API: Return processed image
end
API-->>PoP: 200 OK (image/webp)
PoP->>PoP: Cache processed image at edge
PoP-->>Client: 200 OK
Note over Client,PoP: Next request: Edge cache HIT
erDiagram
USERS ||--o{ FILES : owns
USERS ||--o{ API_TOKENS : has
USERS ||--|| CREDITS : has
USERS ||--o{ USER_ROLES : assigned
USERS }o--|| PLANS : subscribes
USERS ||--o{ NOTIFICATIONS : receives
CREDITS ||--o{ CREDIT_TRANSACTIONS : generates
USERS {
bigint id PK
varchar36 uuid UK
varchar100 name
varchar50 username UK
varchar255 email UK
varchar255 password_hash
int plan_id FK
bigint storage_limit_bytes
bigint storage_used_bytes
bigint bandwidth_limit_bytes
bigint bandwidth_used_bytes
int bandwidth_reset_month
varchar20 status
text suspended_reason
datetime suspended_at
datetime last_login_at
datetime created_at
datetime updated_at
}
FILES {
bigint id PK
bigint user_id FK
varchar36 uuid UK
varchar500 storage_path
varchar255 original_name
bigint size_bytes
varchar100 mime_type
int width
int height
int duration_seconds
datetime created_at
datetime deleted_at
}
API_TOKENS {
bigint id PK
bigint user_id FK
varchar255 token_hash UK
varchar100 name
json permissions
datetime expires_at
datetime last_used_at
datetime created_at
}
PLANS {
int id PK
varchar50 name
varchar50 slug UK
text description
bigint storage_limit_bytes
bigint bandwidth_limit_bytes
bigint max_file_size_bytes
text allowed_mime_types
decimal10_2 price
varchar3 currency
boolean is_active
int sort_order
datetime created_at
datetime updated_at
}
CREDITS {
bigint id PK
bigint user_id FK
decimal10_2 amount
varchar3 currency
datetime created_at
datetime updated_at
}
NOTIFICATIONS {
bigint id PK
bigint user_id FK
varchar30 type
varchar255 title
text message
boolean is_read
json metadata
datetime created_at
datetime read_at
}
-- Performance-critical indexes for high-traffic scenarios
CREATE INDEX idx_files_user_deleted ON files(user_id, deleted_at);
CREATE INDEX idx_files_created ON files(created_at DESC);
CREATE INDEX idx_tokens_hash ON api_tokens(token_hash);
CREATE INDEX idx_tokens_user_expires ON api_tokens(user_id, expires_at);
CREATE INDEX idx_users_bandwidth_reset ON users(bandwidth_reset_month);
CREATE INDEX idx_users_status_plan ON users(status, plan_id);
CREATE INDEX idx_notifications_user_unread ON notifications(user_id, is_read);
CREATE INDEX idx_credits_user ON credits(user_id);
CREATE INDEX idx_transactions_user_created ON credit_transactions(user_id, created_at);Ntsava uses Cycle ORM which provides database abstraction. The same code works on:
| Database | Version | Tested | Production Ready |
|---|---|---|---|
| MySQL | 5.7+ | Yes | Yes |
| MariaDB | 10.2+ | Yes | Yes |
| PostgreSQL | 12+ | Yes | Yes |
| SQLite | 3.35+ | Yes | Development only |
Configured drivers (db/core/connection.php):
| Database | Version | Tested | Production Ready |
|---|---|---|---|
| MySQL | 5.7+ | Yes | Yes |
| MariaDB | 10.2+ | Yes | Yes |
| SQLite | 3.35+ | Yes | Development only |
All queries use ORM methods — no vendor-specific SQL:
$users = ORMHelper::getRepository(User::class)
->findAll(['status' => 'active']);
$file = ORMHelper::findOneBy(File::class, 'uuid', $uuid);graph LR
subgraph Client
A[Store Token] --> B[Add Headers]
end
subgraph Server
C[Extract UUID + Token] --> D[Hash Token SHA256]
D --> E{Valid Token?}
E -->|Yes| F[Get User]
E -->|No| G[Return 401]
F --> H{Has Permission?}
H -->|Yes| I[Execute Request]
H -->|No| J[Return 403]
end
B --> C
I --> K[Return Response]
G --> K
J --> K
| Operation | Required Permission | Token Types | Rate Limit (per hour) |
|---|---|---|---|
| Upload file | upload |
API, Web | 100 |
| Delete file | delete |
API, Web | 50 |
| Get file info | read |
API, Web | 1000 |
| List files | read |
API, Web | 100 |
| Generate token | admin |
Web only | 10 |
| Delete account | owner |
Web only | 1 |
# .htaccess security headers (Apache)
Header set X-Content-Type-Options "nosniff"
Header set X-Frame-Options "DENY"
Header set X-XSS-Protection "1; mode=block"
Header set Referrer-Policy "strict-origin-when-cross-origin"
Header set Content-Security-Policy "default-src 'self'"
Header set Strict-Transport-Security "max-age=31536000; includeSubDomains"# Nginx security headers
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;graph TB
subgraph Browser["Browser Cache (Client)"]
B1[Memory Cache]
B2[Disk Cache]
end
subgraph Edge["CDN Edge Cache"]
E1[RAM Cache - Fast]
E2[SSD Cache - Large]
end
subgraph Origin["Origin Cache"]
O1[Redis/Memcached]
O2[OPcache - PHP]
end
subgraph Storage["Persistent Storage"]
S1[Disk Storage]
S2[Backup Storage]
end
B1 --> B2
B2 --> E1
E1 --> E2
E2 --> O1
O1 --> O2
O2 --> S1
S1 --> S2
| Content Type | Cache TTL | CDN TTL | Browser TTL | Stale-While-Revalidate |
|---|---|---|---|---|
| Images (JPEG/PNG) | 1 year | 30 days | 7 days | 1 day |
| Images (WebP/AVIF) | 1 year | 30 days | 7 days | 1 day |
| Videos (MP4/WebM) | 1 year | 90 days | 30 days | 7 days |
| CSS/JavaScript | 1 year | 30 days | 7 days | 1 hour |
| API Responses | No cache | No cache | No cache | N/A |
| HTML pages | 1 hour | 1 hour | 1 hour | 5 minutes |
CDN edge cache: Governed by Cache-Control: public, max-age=31536000, immutable headers set in storage/.htaccess. No programmatic CDN purge is implemented. Stale content is avoided by appending a 6-character random suffix to every uploaded filename (e.g., photo_a1b2c3.jpg), so each upload gets a unique URL.
Resize cache (storage/cache/): Automatically pruned on each ResizeService instantiation — any cached file older than 7 days is deleted before serving new requests.
graph TB
LB[Load Balancer<br/>HAProxy / Nginx]
subgraph API_Servers["API Server Cluster"]
A1[API Node 1<br/>4 CPU / 8GB RAM]
A2[API Node 2<br/>4 CPU / 8GB RAM]
A3[API Node N<br/>4 CPU / 8GB RAM]
end
subgraph Database["Database Cluster"]
DB_M[Master - Write Operations]
DB_S1[Slave 1 - Read Operations]
DB_S2[Slave 2 - Read Operations]
end
subgraph Storage["Storage Layer"]
FS1[Storage Node 1<br/>SSD - Hot Data]
FS2[Storage Node 2<br/>SSD - Hot Data]
FS3[Storage Node N<br/>HDD - Cold Data]
end
LB --> A1
LB --> A2
LB --> A3
A1 --> DB_M
A1 --> DB_S1
A2 --> DB_M
A2 --> DB_S2
A3 --> DB_M
A3 --> DB_S1
A1 --> FS1
A2 --> FS2
A3 --> FS3
Scale Up (Add nodes):
- CPU usage > 70% for 5 minutes
- Memory usage > 80% for 5 minutes
- Requests per second > 1000 per node
- Queue length > 10,000 pending jobs
- Response time > 500ms for 5 minutes
Scale Down (Remove nodes):
- CPU usage < 30% for 30 minutes
- Memory usage < 50% for 30 minutes
- Requests per second < 100 per node
- Minimum 2 nodes running (high availability)
Hot Storage (SSD - 3x replication):
- Recent files (< 30 days old)
- Frequently accessed (> 100 views/day)
- Replicated to 3 geographic regions
- Access time: < 10ms
Warm Storage (HDD - 2x replication):
- Older files (30-365 days old)
- Infrequently accessed (10-100 views/day)
- Replicated to 2 regions
- Access time: 20-50ms
Cold Storage (Archive - 1x + backup):
- Old files (> 365 days old)
- Rarely accessed (< 10 views/day)
- Single region + offline backup
- Access time: 100-500ms (retrieval from archive)
Application Metrics:
- Request rate (by endpoint: upload, delete, info, resize)
- Error rate (4xx client errors, 5xx server errors)
- Response time (p50, p95, p99 percentiles)
- Active users (by plan: Free, Plus, Pro, Business)
- Storage usage (per user, per plan, global total)
- Bandwidth consumption (per user, per region)
CDN Metrics:
- Cache hit ratio (per Point of Presence)
- Bandwidth served (edge vs origin comparison)
- Popular content (top 100 files by requests)
- Geographic distribution (requests by country/city)
- PoP performance (latency by location: Maputo, Tokyo, etc.)
Infrastructure Metrics:
- CPU / Memory usage per node
- Database connection pool utilization
- Disk I/O and available space
- Network throughput (inbound/outbound)
There is no dedicated /api/v1/health endpoint. To verify the application is up, send a request to the root and confirm an HTTP 200 response:
curl -I https://api.yourdomain.com/
# Expected: HTTP/2 200To verify database connectivity directly:
php -r "new PDO('mysql:host=localhost;dbname=ntsava_cdn', 'user', 'pass');"
echo $? # 0 = success- Use CDN caching for all static assets (images, videos, CSS, JS)
- Enable compression (Brotli preferred over Gzip, 20-30% better)
- Implement lazy loading for images below the fold
- Use modern formats (WebP/AVIF) with JPEG/PNG fallback
- Set appropriate cache TTLs based on content volatility
- Monitor cache hit ratios daily, investigate if below 85%
- Block PHP execution in storage directory completely
- Validate file types by MIME, not just file extension
- Implement rate limiting per token and per user
- Use prepared statements (ORM handles this automatically)
- Encrypt sensitive data (tokens, API keys, personal info)
- Run regular security audits (monthly recommended)
- Leverage CDN free tiers (Cloudflare offers unlimited)
- Use S3/R2-compatible storage for cost-effective archiving
- Implement lifecycle policies (auto-delete files older than X days)
- Monitor bandwidth usage by user, alert on anomalies
- Compress images on upload (reduce origin storage costs)
- Remove unused cached files weekly (cron job)
graph TD
Start[Start] --> Q1{Monthly<br/>Visitors?}
Q1 -->|< 100K| Small[Single Server<br/>Shared Hosting / VPS]
Q1 -->|100K - 1M| Medium[Multi-Server<br/>Load Balanced]
Q1 -->|> 1M| Large[Global CDN +<br/>Multi-Region]
Small --> Q2{Storage<br/>Size?}
Q2 -->|< 500GB| S1[Single VPS<br/>Local Storage]
Q2 -->|> 500GB| S2[VPS + S3/R2<br/>Object Storage]
Medium --> Q3{Availability<br/>Required?}
Q3 -->|99.9%| M1[2 App Servers<br/>1 DB Master + 1 Replica]
Q3 -->|99.99%| M2[3+ App Servers<br/>DB Cluster + Redis Cache]
Large --> Q4{Budget?}
Q4 -->|$$| L1[Cloudflare Enterprise<br/>AWS Global Accelerator]
Q4 -->|$$$| L2[Custom PoPs<br/>Varnish + BGP Anycast]
S1 --> Deploy[Deploy Ntsava CDN]
S2 --> Deploy
M1 --> Deploy
M2 --> Deploy
L1 --> Deploy
L2 --> Deploy
Ntsava CDN provides a production-ready, database-agnostic content delivery platform that scales from a single server to global edge networks. The architecture emphasizes:
- Separation of concerns (API vs static storage)
- Database agnosticism (MySQL, PostgreSQL, SQLite)
- CDN agnosticism (Cloudflare, Bunny, AWS, Akamai, Custom)
- Security-first design (token auth, permissions, validation)
- Performance optimization (multi-level caching, CDN edge)
- Monitoring and observability (health checks, metrics)