Xử lý các lỗi phổ biến: 'database locked' và 'connection refused'
Trong môi trường Serverless, lỗi database locked thường xuất hiện khi nhiều request đồng thời cố gắng ghi dữ liệu vào cùng một trang SQLite, vượt quá giới hạn mặc định của Turso. Lỗi connection refused xảy ra khi endpoint không truy cập được hoặc cấu hình mạng sai.
Giải quyết lỗi 'database locked' bằng Retry Logic
Để xử lý lỗi này, ứng dụng của bạn không được crash ngay lập tức mà cần thực hiện cơ chế Exponential Backoff (chờ đợi theo cấp số nhân) trước khi thử lại. Điều này giảm tải cho database và cho phép các transaction đang chờ được giải phóng.
Dưới đây là đoạn code mẫu bằng Node.js sử dụng thư viện libsql để xử lý lỗi này:
const { createClient } = require('@libsql/client');
const client = createClient({
url: process.env.TURSO_DATABASE_URL,
authToken: process.env.TURSO_AUTH_TOKEN,
});
async function executeWithRetry(sql, params = []) {
const maxRetries = 3;
let attempt = 0;
while (attempt < maxRetries) {
try {
const result = await client.execute({ sql, args: params });
console.log('Thực thi thành công:', result);
return result;
} catch (error) {
if (error.message.includes('database is locked')) {
attempt++;
if (attempt >= maxRetries) throw error;
const delay = Math.pow(2, attempt) * 100; // 200ms, 400ms, 800ms
console.warn(`Lỗi locked. Chờ ${delay}ms và thử lại lần thứ ${attempt}...`);
await new Promise(r => setTimeout(r, delay));
} else {
throw error; // Lỗi khác không phải locked thì throw ngay
}
}
}
}
// Test chạy
executeWithRetry('INSERT INTO users (name) VALUES (?)', ['TestUser'])
.then(() => console.log('Hoàn tất'))
.catch(err => console.error('Thất bại:', err.message));
Kết quả mong đợi: Nếu gặp lỗi locked, log sẽ hiện thông báo chờ và tự động thực thi lại thành công trong lần thử sau. Nếu quá 3 lần vẫn lỗi, lỗi sẽ được ném ra để xử lý ngoại lệ.
Xử lý lỗi 'connection refused' và kiểm tra Endpoint
Lỗi này thường do biến môi trường TURSO_DATABASE_URL bị sai hoặc firewall chặn cổng 443 (HTTPS). Trước khi debug code, hãy kiểm tra khả năng truy cập trực tiếp từ server Ubuntu.
Thực hiện lệnh curl để kiểm tra độ trễ và phản hồi của Turso Cloudflare Edge:
curl -v https://your-database-name.turso.io 2>&1 | grep -E "HTTP|Connection|time"
Kết quả mong đợi: Bạn sẽ thấy dòng HTTP/2 200 hoặc HTTP/2 403 (nếu không có token). Nếu thấy Connection refused hoặc Could not resolve host, hãy kiểm tra lại biến môi trường và cấu hình DNS của server.
Cấu hình Role-Based Access Control (RBAC) cho Database
Turso cho phép bạn tạo các token với quyền hạn cụ thể thay vì dùng token admin toàn quyền. Việc này giúp hạn chế rủi ro khi token bị lộ, ứng dụng chỉ có thể đọc hoặc ghi chứ không thể xóa database.
Tạo token với quyền Read-Only
Để tạo một token chỉ dành cho ứng dụng frontend hoặc service chỉ cần đọc dữ liệu, hãy sử dụng lệnh turso token create với flag --role read.
turso token create --name "readonly-service" --role read
Kết quả mong đợi: CLI sẽ trả về một chuỗi token dài (bắt đầu bằng turso_). Lưu ý: Token chỉ hiển thị 1 lần, hãy copy ngay lập tức.
Tạo token với quyền Read-Write
Đối với backend API cần ghi dữ liệu, bạn cần cấp quyền read-write. Token này có thể thực hiện INSERT, UPDATE, DELETE nhưng không thể tạo/xóa database mới hoặc tạo user khác.
turso token create --name "backend-api" --role read-write
Kết quả mong đợi: Nhận được token mới. Bạn sẽ sử dụng token này làm giá trị cho biến TURSO_AUTH_TOKEN trong ứng dụng backend.
Verify quyền của token
Để đảm bảo token mới hoạt động đúng như mong đợi, hãy thử thực thi câu lệnh SQL bị cấm đối với quyền đó.
export TURSO_AUTH_TOKEN="turso_..._readonly_token_here"
turso execute --database your-database-name "DROP TABLE users;"
Kết quả mong đợi: Lệnh sẽ trả về lỗi Permission denied hoặc 403 Forbidden, chứng tỏ RBAC đang hoạt động tốt. Nếu dùng token read-write, lệnh này sẽ thành công.
Sử dụng Turso Cloudflare Workers để làm Gateway bảo mật
Thay vì để ứng dụng trực tiếp truy cập database, bạn có thể đặt một Cloudflare Worker làm lớp trung gian (Gateway). Worker này sẽ xác thực JWT, ẩn token Turso thật và chỉ chuyển tiếp các request hợp lệ. Đây là mô hình bảo mật tiêu chuẩn cho Serverless.
Khởi tạo dự án Worker
Tạo thư mục mới và khởi tạo dự án Worker bằng npm:
mkdir turso-gateway && cd turso-gateway
npm init -y
npm install wrangler
npx wrangler init turso-gateway --yes
Kết quả mong đợi: Thư mục turso-gateway được tạo với file src/index.ts và file cấu hình wrangler.toml.
Cấu hình Secrets trong Worker
Đừng commit token vào code. Hãy lưu trữ token Turso và khóa bí mật (secret key) cho JWT vào Secrets của Cloudflare.
Đầu tiên, xác định token Turso (Read-Write) và một secret key tùy chọn (ví dụ: "my-super-secret-key"). Sau đó, đẩy vào Cloudflare:
npx wrangler secret put TURSO_AUTH_TOKEN
# Khi được hỏi, dán token turso_... của bạn vào
npx wrangler secret put JWT_SECRET
# Khi được hỏi, nhập chuỗi bí mật của bạn vào
Kết quả mong đợi: CLI báo Secrets successfully updated.
Viết logic Gateway trong src/index.ts
File này sẽ nhận request, kiểm tra header Authorization, ký hiệu JWT, và nếu hợp lệ thì dùng libsql để gọi Turso. Dưới đây là nội dung hoàn chỉnh của file src/index.ts:
import { createClient } from '@libsql/client';
import { env } from 'cloudflare:workers';
export interface Env {
TURSO_AUTH_TOKEN: string;
JWT_SECRET: string;
}
export default {
async fetch(request: Request, env: Env): Promise {
const url = new URL(request.url);
// Chỉ cho phép phương thức POST cho API endpoint /api/query
if (url.pathname !== '/api/query' || request.method !== 'POST') {
return new Response('Method Not Allowed', { status: 405 });
}
// 1. Xác thực JWT từ header Authorization
const authHeader = request.headers.get('Authorization');
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return new Response('Unauthorized: Missing Bearer Token', { status: 401 });
}
const token = authHeader.split(' ')[1];
// Giả lập giải mã JWT (trong thực tế dùng thư viện jose hoặc jsonwebtoken)
// Ở đây ta giả sử logic đơn giản: nếu token hợp lệ thì mới tiếp tục
// Bạn cần thêm thư viện 'jose' để decode JWT thực tế
const payload = verifyJwt(token, env.JWT_SECRET);
if (!payload) {
return new Response('Unauthorized: Invalid Token', { status: 401 });
}
// 2. Kết nối Turso
const client = createClient({
url: `https://your-database-name.turso.io`, // Thay tên DB của bạn
authToken: env.TURSO_AUTH_TOKEN,
});
try {
const body = await request.json();
if (!body.sql) {
return new Response('Bad Request: Missing SQL', { status: 400 });
}
// 3. Thực thi query (Chặn các lệnh nguy hiểm như DROP, TRUNCATE nếu cần)
if (body.sql.toUpperCase().includes('DROP') || body.sql.toUpperCase().includes('TRUNCATE')) {
return new Response('Forbidden: Dangerous SQL command', { status: 403 });
}
const result = await client.execute({ sql: body.sql, args: body.params || [] });
return Response.json({ success: true, data: result.rows });
} catch (error) {
console.error('Turso Error:', error);
return new Response(JSON.stringify({ error: error.message }), {
status: 500,
headers: { 'Content-Type': 'application/json' }
});
}
},
};
// Hàm giả lập verify JWT (Thay thế bằng thư viện thực tế)
function verifyJwt(token: string, secret: string): any | null {
// Logic thật: const { payload } = jwtVerify(token, secret);
// Ở đây chỉ giả định: nếu token chứa 'valid' thì qua
return token.includes('valid') ? { userId: '123' } : null;
}
Kết quả mong đợi: File code đã sẵn sàng. Lưu ý bạn cần cài thêm thư viện npm install @libsql/client jose để code chạy thực tế.
Deploy và Verify Gateway
Đẩy code lên Cloudflare và test bằng curl với token giả lập hợp lệ:
npx wrangler deploy
Test kết quả:
# Test 1: Không có token -> 401
curl -X POST https://your-worker-name.your-subdomain.workers.dev/api/query \
-H "Content-Type: application/json" \
-d '{"sql": "SELECT * FROM users LIMIT 1"}'
# Test 2: Có token hợp lệ (giả định token chứa 'valid') -> 200
curl -X POST https://your-worker-name.your-subdomain.workers.dev/api/query \
-H "Content-Type: application/json" \
-H "Authorization: Bearer valid-token-here" \
-d '{"sql": "SELECT * FROM users LIMIT 1"}'
Kết quả mong đợi: Test 1 trả về lỗi Unauthorized. Test 2 trả về JSON chứa dữ liệu { "success": true, "data": [...] }. Token Turso thật không bao giờ lộ ra ngoài.
Mẹo quản lý chi phí và giới hạn quota của Turso
Turso tính phí dựa trên số lượng Read và Write operations, số lượng Replicas và dung lượng lưu trữ. Việc hiểu rõ các giới hạn giúp bạn tránh bị cắt dịch vụ đột ngột.
Giám sát Usage Dashboard
Luôn kiểm tra dashboard của Turso để xem xu hướng tiêu thụ. Bạn có thể xem thống kê này qua CLI hoặc Web Console.
turso usage
Kết quả mong đợi: CLI hiển thị bảng thống kê bao gồm: Reads, Writes, Storage và Replicas trong tháng hiện tại.
Tối ưu hóa để giảm chi phí (Cost Optimization)
Chi phí chủ yếu đến từ số lượng Write operations và số lượng Replicas. Dưới đây là các chiến lược cụ thể:
- Giảm Write Operations: Sử dụng
BATCH (cập nhật nhiều dòng cùng lúc) thay vì chèn từng dòng một. Một transaction chứa 100 dòng INSERT chỉ tính là 1 Write operation (tùy thuộc vào cách đếm của Turso, nhưng luôn rẻ hơn 100 lệnh riêng lẻ).
- Quản lý Replicas: Chỉ tạo Replica (Replication) khi cần thiết cho Read-heavy applications. Mỗi replica tăng chi phí. Nếu traffic thấp, hãy xóa replica không dùng.
- Sử dụng Branching: Dùng tính năng Branching của Turso cho môi trường Dev/Test. Các branch này thường rẻ hơn hoặc miễn phí so với production database chính.
Thiết lập Alert cho Quota
Turso có tính năng cảnh báo khi đạt đến 80% hoặc 90% quota. Bạn có thể cấu hình alert này qua dashboard web để nhận email khi sắp hết hạn.
Để tự động hóa việc này, bạn có thể viết script cron trên Ubuntu để check usage và gửi cảnh báo Slack nếu vượt ngưỡng:
cat > /opt/scripts/check-turso-quota.sh
Thêm vào crontab để chạy mỗi ngày lúc 8h sáng:
(crontab -l 2>/dev/null; echo "0 8 * * * /opt/scripts/check-turso-quota.sh") | crontab -
Kết quả mong đợi: Script được chạy tự động. Nếu usage vượt quá 80%, bạn sẽ nhận được thông báo cảnh báo trước khi bị giới hạn dịch vụ.
Điều hướng series:
Mục lục: Series: Triển khai Database Serverless với Turso và SQLite trên Ubuntu 24.04
« Phần 6: Tối ưu hóa hiệu năng và cân bằng tải (Replication)