Cloudflare R2 — Object Storage không phí Egress cho Developer
Posted on: 4/27/2026 12:15:37 PM
Table of contents
- 1. Vấn đề — "Egress Tax" của Cloud Storage
- 2. Kiến trúc Cloudflare R2
- 3. Hai cách truy cập R2
- 4. Presigned URLs — Upload trực tiếp từ Browser
- 5. Multipart Upload — File lớn hàng GB
- 6. Event Notifications — Xử lý bất đồng bộ
- 7. Migrate từ S3 sang R2
- 8. Lifecycle Rules và Infrequent Access
- 9. Production Patterns
- 10. So sánh tổng quan với các Object Storage khác
- 11. Kết luận
Mỗi tháng, hóa đơn AWS S3 của bạn có bao nhiêu phần trăm là phí egress — tiền trả cho việc người dùng download file từ bucket? Với nhiều dự án, con số này chiếm 50-80% tổng chi phí storage. Cloudflare R2 sinh ra để giải quyết đúng vấn đề này: S3-compatible object storage với zero egress fees — không tốn một đồng nào cho bandwidth ra ngoài. Bài viết này phân tích sâu kiến trúc R2, tích hợp Workers tại edge, các pattern upload/download production-ready, và so sánh chi tiết chi phí với AWS S3.
1. Vấn đề — "Egress Tax" của Cloud Storage
AWS S3 tính phí egress từ $0.09/GB cho data transfer ra Internet. Nghe có vẻ nhỏ, nhưng khi ứng dụng scale lên, chi phí này tăng phi tuyến tính:
graph LR
subgraph "AWS S3 — Chi phí ẩn"
S3["S3 Bucket
$0.023/GB storage"] --> EG["Egress
$0.09/GB"]
EG --> USER["Người dùng"]
S3 --> CF["qua CloudFront
$0.085/GB"]
CF --> USER
end
subgraph "Cloudflare R2 — Minh bạch"
R2["R2 Bucket
$0.015/GB storage"] --> CDN["Cloudflare CDN
$0 egress"]
CDN --> USER2["Người dùng"]
end
style S3 fill:#ff9800,stroke:#fff,color:#fff
style EG fill:#e94560,stroke:#fff,color:#fff
style R2 fill:#4CAF50,stroke:#fff,color:#fff
style CDN fill:#4CAF50,stroke:#fff,color:#fff
Hình 1: So sánh luồng chi phí giữa AWS S3 và Cloudflare R2
| Kịch bản | AWS S3 + CloudFront | Cloudflare R2 | Tiết kiệm |
|---|---|---|---|
| Blog nhỏ — 50 GB storage, 500 GB egress/tháng | $1.15 + $42.50 = $43.65 | $0.75 + $0 = $0.75 | 98% |
| SaaS trung bình — 500 GB storage, 5 TB egress/tháng | $11.50 + $425 = $436.50 | $7.50 + $0 = $7.50 | 98% |
| Video platform — 5 TB storage, 50 TB egress/tháng | $115 + $4,250 = $4,365 | $75 + $0 = $75 | 98% |
| Enterprise — 50 TB storage, 200 TB egress/tháng | $1,150 + $17,000 = $18,150 | $750 + $0 = $750 | 96% |
Tại sao egress lại đắt?
Cloud provider tính phí egress vì bandwidth có chi phí thực (transit, peering, infrastructure). Nhưng Cloudflare có lợi thế đặc biệt: họ sở hữu một trong những mạng CDN lớn nhất thế giới (330+ PoP tại 120+ quốc gia) và có peering agreement với hầu hết ISP lớn. Chi phí bandwidth của Cloudflare gần bằng 0 nhờ mô hình kinh doanh chính từ security + performance services, không phải từ storage egress. R2 chỉ đơn giản là không chuyển chi phí bandwidth sang khách hàng.
2. Kiến trúc Cloudflare R2
R2 không phải là một bản copy đơn giản của S3. Nó được thiết kế từ đầu để tích hợp sâu với hệ sinh thái Cloudflare:
graph TB
CLIENT["Client
(Browser / Mobile / Server)"]
subgraph "Cloudflare Edge — 330+ PoP"
WORKER["Cloudflare Worker
Auth, Transform, Route"]
CACHE["Edge Cache
Tự động cache objects"]
end
subgraph "Cloudflare R2 Storage"
R2["R2 Bucket
S3-compatible API"]
IA["Infrequent Access
$0.01/GB/tháng"]
LIFECYCLE["Lifecycle Rules
Auto-transition"]
end
subgraph "Event System"
NOTIFY["Event Notifications"]
QUEUE["Cloudflare Queue"]
CONSUMER["Consumer Worker
Process events"]
end
CLIENT --> WORKER
CLIENT -->|"S3 API / Presigned URL"| R2
WORKER -->|"Bindings API"| R2
WORKER --> CACHE
CACHE --> R2
R2 --> LIFECYCLE
LIFECYCLE --> IA
R2 --> NOTIFY
NOTIFY --> QUEUE
QUEUE --> CONSUMER
style WORKER fill:#e94560,stroke:#fff,color:#fff
style R2 fill:#f76c02,stroke:#fff,color:#fff
style CACHE fill:#4CAF50,stroke:#fff,color:#fff
style QUEUE fill:#2196F3,stroke:#fff,color:#fff
Hình 2: Kiến trúc tổng thể Cloudflare R2 với Workers và Event Notifications
| Thành phần | Mô tả | Lợi ích |
|---|---|---|
| R2 Bucket | Object storage phân tán, S3-compatible API. Hỗ trợ objects đến 5 TB. | Migrate từ S3 không cần đổi code |
| Workers Binding | Truy cập R2 trực tiếp từ Worker qua in-process binding — không qua HTTP. | Latency cực thấp (~1-5ms), không tốn API call |
| Edge Cache | Objects phổ biến được cache tự động tại 330+ PoP gần người dùng. | Giảm latency cho read-heavy workload |
| Infrequent Access | Storage class rẻ hơn ($0.01/GB vs $0.015/GB) cho data ít truy cập. | Tiết kiệm 33% cho cold data |
| Event Notifications | Gửi event đến Cloudflare Queue khi object được tạo/xóa/sửa. | Trigger xử lý async (thumbnail, transcode, index) |
| Lifecycle Rules | Tự động chuyển objects sang IA hoặc xóa sau N ngày. | Quản lý chi phí tự động |
3. Hai cách truy cập R2
R2 cung cấp 2 API hoàn toàn khác nhau, phù hợp với các use case riêng biệt:
3.1. S3-compatible API — Cho server-to-server
Dùng bất kỳ S3 SDK nào (AWS SDK, boto3, @aws-sdk/client-s3) với endpoint R2:
// Node.js — Upload file lên R2 bằng AWS SDK v3
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
const r2Client = new S3Client({
region: "auto",
endpoint: "https://<ACCOUNT_ID>.r2.cloudflarestorage.com",
credentials: {
accessKeyId: process.env.R2_ACCESS_KEY_ID!,
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,
},
});
// Upload
await r2Client.send(new PutObjectCommand({
Bucket: "my-bucket",
Key: "uploads/avatar-123.webp",
Body: fileBuffer,
ContentType: "image/webp",
}));
// .NET — Upload file lên R2 bằng AWSSDK.S3
using Amazon.S3;
using Amazon.S3.Model;
var config = new AmazonS3Config
{
ServiceURL = $"https://{accountId}.r2.cloudflarestorage.com",
ForcePathStyle = true // R2 yêu cầu path-style
};
var s3Client = new AmazonS3Client(
accessKeyId, secretAccessKey, config);
await s3Client.PutObjectAsync(new PutObjectRequest
{
BucketName = "my-bucket",
Key = "uploads/avatar-123.webp",
InputStream = fileStream,
ContentType = "image/webp"
});
3.2. Workers API — Cho edge processing
Khi xử lý tại edge, Workers binding nhanh hơn S3 API vì không qua HTTP:
// wrangler.toml
// [[r2_buckets]]
// binding = "MY_BUCKET"
// bucket_name = "my-bucket"
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
const key = url.pathname.slice(1); // bỏ leading /
switch (request.method) {
case "GET": {
const object = await env.MY_BUCKET.get(key);
if (!object) return new Response("Not Found", { status: 404 });
const headers = new Headers();
object.writeHttpMetadata(headers);
headers.set("etag", object.httpEtag);
headers.set("cache-control", "public, max-age=86400");
return new Response(object.body, { headers });
}
case "PUT": {
const contentType = request.headers.get("content-type") ?? "";
await env.MY_BUCKET.put(key, request.body, {
httpMetadata: { contentType },
customMetadata: { uploadedBy: "worker" },
});
return new Response(JSON.stringify({ key }), { status: 201 });
}
case "DELETE": {
await env.MY_BUCKET.delete(key);
return new Response(null, { status: 204 });
}
default:
return new Response("Method Not Allowed", { status: 405 });
}
},
};
Workers Binding vs S3 API — Khi nào dùng cái nào?
Workers Binding: Khi bạn cần xử lý logic tại edge (auth, resize ảnh, transform data) trước/sau khi đọc/ghi R2. Latency ~1-5ms, không tốn Class A/B operation fees.
S3 API: Khi server backend (Node.js, .NET, Python) cần truy cập R2 trực tiếp, hoặc khi dùng tools có sẵn S3 support (Terraform, rclone, cyberduck). Tốn operation fees nhưng tương thích với toàn bộ hệ sinh thái S3.
4. Presigned URLs — Upload trực tiếp từ Browser
Pattern phổ biến nhất cho file upload: client lấy presigned URL từ server, rồi upload trực tiếp lên R2 mà không cần proxy qua backend.
sequenceDiagram
participant B as Browser
participant W as Worker / API Server
participant R2 as Cloudflare R2
B->>W: POST /api/upload/presign
{filename, contentType}
W->>W: Validate user, generate presigned URL
W-->>B: {uploadUrl, key}
B->>R2: PUT uploadUrl
(file binary)
R2-->>B: 200 OK
B->>W: POST /api/upload/confirm
{key}
W->>R2: HEAD key (verify exists)
R2-->>W: 200 + metadata
W-->>B: {url: "https://cdn.example.com/key"}
Hình 3: Presigned URL flow — Browser upload trực tiếp, không qua backend
// Worker — Tạo presigned URL cho upload
import { AwsClient } from "aws4fetch";
const r2 = new AwsClient({
accessKeyId: R2_ACCESS_KEY_ID,
secretAccessKey: R2_SECRET_ACCESS_KEY,
});
export default {
async fetch(request: Request, env: Env): Promise<Response> {
if (request.method !== "POST") {
return new Response("Method Not Allowed", { status: 405 });
}
const { filename, contentType } = await request.json();
const key = `uploads/${crypto.randomUUID()}/${filename}`;
// Tạo presigned PUT URL — hết hạn sau 1 giờ
const url = new URL(
`https://${env.ACCOUNT_ID}.r2.cloudflarestorage.com/${env.BUCKET_NAME}/${key}`
);
url.searchParams.set("X-Amz-Expires", "3600");
const signed = await r2.sign(
new Request(url, {
method: "PUT",
headers: { "Content-Type": contentType },
}),
{ aws: { signQuery: true } }
);
return Response.json({
uploadUrl: signed.url,
key,
});
},
};
// Frontend — Upload file dùng presigned URL
async function uploadFile(file: File) {
// Bước 1: Lấy presigned URL
const { uploadUrl, key } = await fetch("/api/upload/presign", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
filename: file.name,
contentType: file.type,
}),
}).then(r => r.json());
// Bước 2: Upload trực tiếp lên R2
await fetch(uploadUrl, {
method: "PUT",
headers: { "Content-Type": file.type },
body: file,
});
// Bước 3: Xác nhận với backend
const result = await fetch("/api/upload/confirm", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ key }),
}).then(r => r.json());
return result.url;
}
5. Multipart Upload — File lớn hàng GB
Với file trên 100 MB, multipart upload chia file thành nhiều part nhỏ (5-100 MB mỗi part), upload song song, và R2 tự ghép lại. Nếu 1 part fail, chỉ cần retry part đó.
// Worker — Multipart upload qua Workers API
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const key = "videos/large-file.mp4";
// Bước 1: Khởi tạo multipart upload
const mpu = await env.MY_BUCKET.createMultipartUpload(key, {
httpMetadata: { contentType: "video/mp4" },
});
// Bước 2: Upload từng part (5 MB minimum, trừ part cuối)
const partSize = 10 * 1024 * 1024; // 10 MB
const body = await request.arrayBuffer();
const uploadedParts: R2UploadedPart[] = [];
for (let i = 0; i * partSize < body.byteLength; i++) {
const start = i * partSize;
const end = Math.min(start + partSize, body.byteLength);
const chunk = body.slice(start, end);
const part = await mpu.uploadPart(i + 1, chunk);
uploadedParts.push(part);
}
// Bước 3: Complete — R2 ghép tất cả parts
const object = await mpu.complete(uploadedParts);
return Response.json({
key: object.key,
size: object.size,
etag: object.httpEtag,
});
},
};
Lưu ý quan trọng về Multipart Upload
1. Minimum part size: Mỗi part (trừ part cuối) phải ≥ 5 MB. Vi phạm sẽ lỗi.
2. Maximum parts: Tối đa 10,000 parts per upload.
3. Auto-abort: Multipart upload chưa complete sẽ tự động bị hủy sau 7 ngày. Các parts đã upload chiếm storage và tính phí cho đến khi bị abort.
4. Resume: Dùng resumeMultipartUpload(key, uploadId) để tiếp tục upload bị gián đoạn — không cần bắt đầu lại từ đầu.
6. Event Notifications — Xử lý bất đồng bộ
R2 Event Notifications cho phép trigger Worker khi object thay đổi — pattern lý tưởng cho image processing, video transcoding, search indexing:
graph LR
UPLOAD["Upload ảnh
avatar.jpg"] --> R2["R2 Bucket"]
R2 -->|"Event: object-create"| QUEUE["Cloudflare Queue"]
QUEUE --> WORKER["Consumer Worker"]
WORKER -->|"Resize 3 kích thước"| R2_THUMB["R2 Bucket
/thumbs/"]
WORKER -->|"Phân tích nội dung"| AI["Workers AI
Image Classification"]
WORKER -->|"Cập nhật metadata"| DB["D1 Database"]
style R2 fill:#f76c02,stroke:#fff,color:#fff
style QUEUE fill:#2196F3,stroke:#fff,color:#fff
style WORKER fill:#e94560,stroke:#fff,color:#fff
Hình 4: Event-driven pipeline — Upload ảnh → Resize + AI classify + Update DB
// wrangler.toml — Cấu hình Event Notifications
// [[r2_buckets]]
// binding = "MY_BUCKET"
// bucket_name = "my-bucket"
//
// [[queues.consumers]]
// queue = "r2-events"
// max_batch_size = 10
// max_batch_timeout = 5
// Consumer Worker — Xử lý event từ R2
export default {
async queue(
batch: MessageBatch<R2EventNotification>,
env: Env
): Promise<void> {
for (const message of batch.messages) {
const event = message.body;
if (event.action === "PutObject") {
const key = event.object.key;
// Chỉ xử lý ảnh
if (key.match(/\.(jpg|jpeg|png|webp)$/i)) {
const original = await env.MY_BUCKET.get(key);
if (!original) continue;
const imageData = await original.arrayBuffer();
// Tạo thumbnail 200x200
const thumb = await resizeImage(imageData, 200, 200);
await env.MY_BUCKET.put(
`thumbs/${key}`,
thumb,
{ httpMetadata: { contentType: "image/webp" } }
);
// Cập nhật database
await env.DB.prepare(
"UPDATE files SET thumbnail_key = ? WHERE key = ?"
).bind(`thumbs/${key}`, key).run();
}
}
message.ack();
}
},
};
7. Migrate từ S3 sang R2
Cloudflare cung cấp 2 công cụ migration chính thức, phù hợp với các tình huống khác nhau:
| Công cụ | Cách hoạt động | Phù hợp |
|---|---|---|
| Super Slurper | Copy toàn bộ bucket từ S3/GCS sang R2. Chạy nền, xử lý petabytes data. | Migration một lần, cần copy toàn bộ data trước khi switch. |
| Sippy | Incremental migration — khi client request object chưa có trong R2, Sippy tự động fetch từ S3, lưu vào R2, và trả về. Lần sau request lại sẽ lấy từ R2. | Zero-downtime migration, không cần copy toàn bộ upfront. |
sequenceDiagram
participant C as Client
participant R2 as Cloudflare R2
participant S3 as AWS S3 (source)
Note over R2: Sippy đã enabled
C->>R2: GET /images/photo.jpg
R2->>R2: Check local storage
alt Object tồn tại trong R2
R2-->>C: 200 OK (từ R2)
else Object chưa có
R2->>S3: Fetch /images/photo.jpg
S3-->>R2: Object data
R2->>R2: Lưu vào R2 storage
R2-->>C: 200 OK (đã cache)
end
Note over C,S3: Lần request tiếp theo sẽ lấy từ R2,
không cần gọi S3 nữa
Hình 5: Sippy migration — Lazy copy từ S3, không downtime, không trả egress S3 trước
Chiến lược migration thực tế
Bước 1: Bật Sippy trên R2 bucket, trỏ đến S3 bucket nguồn.
Bước 2: Đổi DNS/CDN trỏ sang R2. Client request sẽ được Sippy phục vụ — object chưa có sẽ tự fetch từ S3.
Bước 3: Song song, chạy Super Slurper để copy phần data còn lại (objects chưa được request).
Bước 4: Khi Super Slurper xong, tắt Sippy. R2 đã có toàn bộ data.
Cách này không phải trả egress S3 cho toàn bộ data — chỉ trả cho phần Super Slurper copy (nếu dùng Sippy, Cloudflare cover egress fee cho phần lazy-fetch).
8. Lifecycle Rules và Infrequent Access
R2 đơn giản hóa storage class chỉ còn 2 tier — không có hàng chục tier gây confusion như S3:
| Tiêu chí | R2 Standard | R2 Infrequent Access | S3 Standard | S3 Glacier |
|---|---|---|---|---|
| Storage | $0.015/GB | $0.01/GB | $0.023/GB | $0.004/GB |
| Egress | $0 | $0 | $0.09/GB | $0.09/GB + retrieval |
| Retrieval fee | Không | $0.01/GB | Không | $0.03-0.05/GB |
| Min duration | Không | 30 ngày | Không | 90-180 ngày |
| Availability | Ngay lập tức | Ngay lập tức | Ngay lập tức | Vài phút → vài giờ |
# Wrangler CLI — Quản lý lifecycle rules
# Thêm rule: chuyển sang IA sau 90 ngày, xóa sau 365 ngày
npx wrangler r2 bucket lifecycle add my-bucket \
--prefix "logs/" \
--transition-to-ia-after 90 \
--expire-after 365
# Liệt kê rules hiện tại
npx wrangler r2 bucket lifecycle list my-bucket
# Xóa rule
npx wrangler r2 bucket lifecycle remove my-bucket --id rule-id-123
9. Production Patterns
9.1. CDN Cache + R2 — Tối ưu read-heavy workload
// Worker — Serve R2 objects với CDN caching
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
const key = url.pathname.slice(1);
// Check Cache API trước
const cache = caches.default;
let response = await cache.match(request);
if (response) return response;
// Không có cache → lấy từ R2
const object = await env.MY_BUCKET.get(key);
if (!object) {
return new Response("Not Found", { status: 404 });
}
const headers = new Headers();
object.writeHttpMetadata(headers);
headers.set("etag", object.httpEtag);
// Cache static assets 1 năm, dynamic content 1 giờ
const isStatic = key.match(/\.(js|css|woff2|webp|avif|svg)$/);
headers.set(
"cache-control",
isStatic
? "public, max-age=31536000, immutable"
: "public, max-age=3600, s-maxage=86400"
);
response = new Response(object.body, { headers });
// Lưu vào edge cache
request.method === "GET" && cache.put(request, response.clone());
return response;
},
};
9.2. Access Control — Signed URLs + CORS
// Worker — Kiểm tra auth trước khi cho download
export default {
async fetch(request: Request, env: Env): Promise<Response> {
// Verify JWT token
const token = request.headers.get("Authorization")?.replace("Bearer ", "");
if (!token) return new Response("Unauthorized", { status: 401 });
const payload = await verifyJWT(token, env.JWT_SECRET);
if (!payload) return new Response("Forbidden", { status: 403 });
const key = new URL(request.url).pathname.slice(1);
// Kiểm tra quyền truy cập file
const allowed = await checkPermission(env.DB, payload.userId, key);
if (!allowed) return new Response("Forbidden", { status: 403 });
const object = await env.MY_BUCKET.get(key);
if (!object) return new Response("Not Found", { status: 404 });
const headers = new Headers();
object.writeHttpMetadata(headers);
headers.set("cache-control", "private, no-store");
return new Response(object.body, { headers });
},
};
10. So sánh tổng quan với các Object Storage khác
| Tiêu chí | Cloudflare R2 | AWS S3 | GCS | Azure Blob | Backblaze B2 |
|---|---|---|---|---|---|
| Egress | $0 | $0.09/GB | $0.12/GB | $0.087/GB | $0.01/GB |
| Storage | $0.015/GB | $0.023/GB | $0.020/GB | $0.018/GB | $0.006/GB |
| Free tier | 10 GB + 10M ops | 5 GB (12 tháng) | 5 GB | 5 GB (12 tháng) | 10 GB |
| S3 compatible | Có | Gốc | Có (XML API) | Không | Có |
| Edge integration | Workers binding | Lambda@Edge | Cloud Functions | Azure Functions | Không |
| CDN tích hợp | Cloudflare CDN | CloudFront | Cloud CDN | Azure CDN | Cloudflare (partner) |
| Storage classes | 2 (Standard + IA) | 6+ | 4 | 4 | 1 |
| Event system | Queues | SNS/SQS/Lambda | Pub/Sub | Event Grid | Webhooks |
Khi nào KHÔNG nên dùng R2?
1. Cần multi-region replication: R2 lưu data tại 1 region duy nhất (tự động chọn gần nhất). S3 có Cross-Region Replication cho compliance/DR.
2. Hệ sinh thái AWS sâu: Nếu dự án dùng nhiều AWS services (Lambda, SQS, DynamoDB), S3 tích hợp tự nhiên hơn — R2 cần thêm lớp kết nối.
3. Archival storage cực rẻ: S3 Glacier Deep Archive chỉ $0.00099/GB — rẻ hơn R2 IA rất nhiều cho data lưu trữ dài hạn hiếm khi truy cập.
4. Compliance đặc thù: S3 có nhiều chứng chỉ hơn (FedRAMP High, HIPAA, PCI DSS Level 1). R2 đang bổ sung dần.
5. Analytics trên storage: S3 Select, Athena query trực tiếp trên S3. R2 không có tương đương.
11. Kết luận
Cloudflare R2 không phải là "S3 rẻ hơn" — nó là một mô hình kinh doanh khác biệt cho object storage, loại bỏ hoàn toàn "egress tax" mà cloud providers truyền thống đã tính trong hơn một thập kỷ. Với S3-compatible API, tích hợp sâu Workers tại edge, event notifications, và Sippy migration không downtime — R2 đã sẵn sàng cho production ở mọi quy mô.
Nếu ứng dụng của bạn phục vụ nhiều file cho người dùng cuối (ảnh, video, documents, static assets), R2 có thể giảm 90-98% chi phí storage so với S3. Hãy bắt đầu với free tier (10 GB storage, 10 triệu operations/tháng), migrate dần bằng Sippy, và chỉ trả tiền khi thực sự scale.
Nguồn tham khảo
- Cloudflare R2 Documentation — Cloudflare Docs
- R2 Pricing — Cloudflare Docs
- R2 Event Notifications, GCS Migration, Infrequent Access — Cloudflare Blog
- Workers API Reference — Cloudflare R2 Docs
- Presigned URLs — Cloudflare R2 Docs
- Sippy Incremental Migration — Cloudflare R2 Docs
- Cloudflare R2 vs AWS S3 — Cloudflare
Disclaimer: The opinions expressed in this blog are solely my own and do not reflect the views or opinions of my employer or any affiliated organizations. The content provided is for informational and educational purposes only and should not be taken as professional advice. While I strive to provide accurate and up-to-date information, I make no warranties or guarantees about the completeness, reliability, or accuracy of the content. Readers are encouraged to verify the information and seek independent advice as needed. I disclaim any liability for decisions or actions taken based on the content of this blog.