Pipeline vẫn chạy, log không có exception, nhưng số job trễ hạn tuần này gấp đôi tuần trước. Dashboard "ngay lúc này" không trả lời được, vì vấn đề nằm ở xu hướng.
Cách xử lý: ghi tỷ lệ giải thành công, thời gian giải và chi phí vào một database chuỗi thời gian, rồi cảnh báo theo trung bình trượt.
Chọn database chuỗi thời gian trước
Quyết định lưu ở đâu chi phối cách instrument solver.
| Tiêu chí | Prometheus | InfluxDB | TimescaleDB |
|---|---|---|---|
| Hợp với | Giám sát vận hành | Cardinality cao | Phân tích bằng SQL |
| Truy vấn | PromQL | Flux | SQL |
| Độ khó | Thấp | Trung bình | Thấp nếu quen SQL |
Đã có Prometheus cho hạ tầng thì thêm metric CAPTCHA vào registry sẵn có; InfluxDB hợp khi cần kho riêng.
Bảy chỉ số hiệu suất giải CAPTCHA cần ghi
| Chỉ số | Kiểu | Vì sao cần |
|---|---|---|
| Tỷ lệ giải thành công (%) | Gauge | Chất lượng đổi theo ngày |
| Thời gian giải (ms) | Histogram | Bắt hiện tượng chậm dần |
| Tỷ lệ lỗi theo mã lỗi | Counter | Mã lỗi mới xuất hiện |
| Chi phí quy đổi mỗi lần giải ($) | Gauge | Đối chiếu ngân sách tháng |
| Độ sâu hàng đợi | Gauge | Khi nào cần thêm thread |
| Token hết hạn trước khi dùng | Counter | Tín hiệu chỉnh TTL |
| Số dư tài khoản API | Gauge | Kích hoạt nạp tiền |
Lưu ý dòng chi phí: CaptchaAI tính theo thread (luồng giải đồng thời), không theo từng lần giải, nên "$/lần giải" là số bạn tự quy đổi từ giá gói — BASIC ($15/tháng, 5 thread).
Đo trong solver Python, đẩy qua Pushgateway
Job giải CAPTCHA sống ngắn, Prometheus không kịp scrape — để solver tự đẩy số liệu.
Instrument solver bằng prometheus_client
import os
import time
import requests
from prometheus_client import CollectorRegistry, Counter, Histogram, Gauge, push_to_gateway
registry = CollectorRegistry()
SOLVE_TOTAL = Counter(
"captcha_solve_total", "Total CAPTCHA solve attempts",
["type", "status"], registry=registry
)
SOLVE_LATENCY = Histogram(
"captcha_solve_latency_seconds", "CAPTCHA solve latency",
["type"], buckets=[5, 10, 15, 20, 30, 45, 60, 90, 120],
registry=registry
)
SOLVE_COST = Counter(
"captcha_solve_cost_dollars", "Total cost of CAPTCHA solves",
["type"], registry=registry
)
API_BALANCE = Gauge(
"captcha_api_balance_dollars", "CaptchaAI account balance",
registry=registry
)
API_KEY = os.environ["CAPTCHAAI_API_KEY"]
PUSHGATEWAY = os.environ.get("PUSHGATEWAY_URL", "localhost:9091")
def solve_with_metrics(sitekey, pageurl, captcha_type="recaptcha_v2"):
start = time.time()
resp = requests.post("https://ocr.captchaai.com/in.php", data={
"key": API_KEY,
"method": "userrecaptcha",
"googlekey": sitekey,
"pageurl": pageurl,
"json": 1
})
data = resp.json()
if data.get("status") != 1:
SOLVE_TOTAL.labels(type=captcha_type, status="submit_error").inc()
push_metrics()
return {"error": data.get("request")}
captcha_id = data["request"]
for _ in range(60):
time.sleep(5)
result = requests.get("https://ocr.captchaai.com/res.php", params={
"key": API_KEY, "action": "get",
"id": captcha_id, "json": 1
}).json()
if result.get("status") == 1:
elapsed = time.time() - start
SOLVE_TOTAL.labels(type=captcha_type, status="solved").inc()
SOLVE_LATENCY.labels(type=captcha_type).observe(elapsed)
SOLVE_COST.labels(type=captcha_type).inc(0.00299)
push_metrics()
return {"solution": result["request"]}
if result.get("request") != "CAPCHA_NOT_READY":
SOLVE_TOTAL.labels(type=captcha_type, status="error").inc()
push_metrics()
return {"error": result.get("request")}
SOLVE_TOTAL.labels(type=captcha_type, status="timeout").inc()
push_metrics()
return {"error": "TIMEOUT"}
def push_metrics():
try:
push_to_gateway(PUSHGATEWAY, job="captcha_solver", registry=registry)
except Exception:
pass # Don't fail solving because metrics push failed
def update_balance():
resp = requests.get("https://ocr.captchaai.com/res.php", params={
"key": API_KEY, "action": "getbalance"
})
try:
balance = float(resp.text)
API_BALANCE.set(balance)
push_metrics()
except ValueError:
pass
push_metrics() nằm trong try/except: Pushgateway chết thì việc giải vẫn chạy tiếp.
Truy vấn PromQL cho tỷ lệ giải và P95
# Success rate over last hour
rate(captcha_solve_total{status="solved"}[1h])
/ rate(captcha_solve_total[1h]) * 100
# P95 solve latency
histogram_quantile(0.95, rate(captcha_solve_latency_seconds_bucket[1h]))
# Error rate by type
rate(captcha_solve_total{status="error"}[1h])
# Hourly cost
increase(captcha_solve_cost_dollars_total[1h])
Bốn truy vấn này đủ cho một dashboard Grafana và cho cảnh báo đầu tiên.
Đặt ngưỡng cảnh báo theo xu hướng, không theo một điểm dữ liệu
Cảnh báo theo từng lần giải lỗi sẽ kêu suốt đêm. So cửa sổ hiện tại với cùng cửa sổ hôm trước: tỷ lệ giải 1 giờ thấp hơn 10 điểm, P95 sáu giờ cao hơn 1,5 lần đường nền tuần, hoặc hàng đợi tăng liên tục ba mẫu đo. Chốt ngưỡng bằng dữ liệu của chính bạn sau một tuần chạy thật.
Ghi từng lần giải vào InfluxDB
Muốn tách số liệu CAPTCHA khỏi Prometheus hạ tầng, InfluxDB gọn hơn.
Viết point cho mỗi lần giải
from influxdb_client import InfluxDBClient, Point
from influxdb_client.client.write_api import SYNCHRONOUS
INFLUX_URL = os.environ.get("INFLUX_URL", "http://localhost:8086")
INFLUX_TOKEN = os.environ.get("INFLUX_TOKEN", "")
INFLUX_ORG = os.environ.get("INFLUX_ORG", "captcha")
INFLUX_BUCKET = os.environ.get("INFLUX_BUCKET", "captcha_metrics")
influx_client = InfluxDBClient(url=INFLUX_URL, token=INFLUX_TOKEN, org=INFLUX_ORG)
write_api = influx_client.write_api(write_options=SYNCHRONOUS)
def record_solve_metric(captcha_type, status, elapsed_ms, cost=0.0, error=None):
point = (
Point("captcha_solve")
.tag("type", captcha_type)
.tag("status", status)
.field("elapsed_ms", elapsed_ms)
.field("cost", cost)
.field("success", 1 if status == "solved" else 0)
)
if error:
point = point.tag("error_code", error)
write_api.write(bucket=INFLUX_BUCKET, record=point)
def record_balance(balance):
point = Point("captcha_balance").field("balance", balance)
write_api.write(bucket=INFLUX_BUCKET, record=point)
Trường success dạng 0/1 cho phép tính tỷ lệ bằng đúng hàm mean.
Truy vấn Flux theo cửa sổ 1 giờ
// Success rate over last 24 hours (1-hour windows)
from(bucket: "captcha_metrics")
|> range(start: -24h)
|> filter(fn: (r) => r._measurement == "captcha_solve" and r._field == "success")
|> aggregateWindow(every: 1h, fn: mean)
|> map(fn: (r) => ({r with _value: r._value * 100.0}))
|> yield(name: "success_rate")
// Average solve time by type
from(bucket: "captcha_metrics")
|> range(start: -24h)
|> filter(fn: (r) => r._measurement == "captcha_solve" and r._field == "elapsed_ms" and r.status == "solved")
|> group(columns: ["type"])
|> aggregateWindow(every: 1h, fn: mean)
|> yield(name: "avg_latency")
// Cumulative cost
from(bucket: "captcha_metrics")
|> range(start: -24h)
|> filter(fn: (r) => r._measurement == "captcha_solve" and r._field == "cost")
|> cumulativeSum()
|> yield(name: "cumulative_cost")
Ba đường: tỷ lệ thành công, thời gian giải và chi phí cộng dồn.
Bản Node.js với endpoint /metrics
Worker Node.js chạy dài không cần Pushgateway: mở sẵn /metrics cho Prometheus scrape.
const client = require("prom-client");
const axios = require("axios");
const register = new client.Registry();
const API_KEY = process.env.CAPTCHAAI_API_KEY;
const solveTotal = new client.Counter({
name: "captcha_solve_total",
help: "Total CAPTCHA solve attempts",
labelNames: ["type", "status"],
registers: [register],
});
const solveLatency = new client.Histogram({
name: "captcha_solve_latency_seconds",
help: "CAPTCHA solve latency",
labelNames: ["type"],
buckets: [5, 10, 15, 20, 30, 45, 60, 90, 120],
registers: [register],
});
async function solveWithMetrics(sitekey, pageurl, type = "recaptcha_v2") {
const start = Date.now();
const submit = await axios.post("https://ocr.captchaai.com/in.php", null, {
params: { key: API_KEY, method: "userrecaptcha", googlekey: sitekey, pageurl, json: 1 },
});
if (submit.data.status !== 1) {
solveTotal.inc({ type, status: "submit_error" });
return { error: submit.data.request };
}
const captchaId = submit.data.request;
for (let i = 0; i < 60; i++) {
await new Promise((r) => setTimeout(r, 5000));
const poll = await axios.get("https://ocr.captchaai.com/res.php", {
params: { key: API_KEY, action: "get", id: captchaId, json: 1 },
});
if (poll.data.status === 1) {
const elapsed = (Date.now() - start) / 1000;
solveTotal.inc({ type, status: "solved" });
solveLatency.observe({ type }, elapsed);
return { solution: poll.data.request };
}
if (poll.data.request !== "CAPCHA_NOT_READY") {
solveTotal.inc({ type, status: "error" });
return { error: poll.data.request };
}
}
solveTotal.inc({ type, status: "timeout" });
return { error: "TIMEOUT" };
}
// Expose metrics endpoint
const express = require("express");
const app = express();
app.get("/metrics", async (req, res) => {
res.set("Content-Type", register.contentType);
res.end(await register.metrics());
});
app.listen(9090);
Ví dụ: đội scraping TP.HCM đọc biểu đồ 24 giờ
Một agency ở TP.HCM giám sát giá trên dữ liệu công khai của Shopee và Tiki mỗi đêm. Sau khi bật metric: P95 thời gian giải tăng rõ trong khung 21–23h, tỷ lệ giải thành công không đổi, độ sâu hàng đợi tăng vọt. Solver không chậm đi — toàn bộ thread đã bận. Cách xử lý: giãn lịch batch, hoặc nâng từ STANDARD ($30/tháng, 15 thread) lên ADVANCE ($90/tháng, 50 thread).
Nhãn metric chỉ nên gồm loại, trạng thái và mã lỗi — đừng đưa URL hay dữ liệu người dùng vào: cardinality thấp hơn và hợp nguyên tắc tối thiểu hóa dữ liệu của Nghị định 13/2023/NĐ-CP.
Sự cố thường gặp khi đo hiệu suất giải CAPTCHA
| Triệu chứng | Nguyên nhân | Cách xử lý |
|---|---|---|
| Biểu đồ đứt quãng | Pushgateway không nhận dữ liệu | Push trước khi job thoát |
| P95 lệch xa thực tế | Bucket không khớp workload | Chỉnh bucket theo thời gian giải thật |
| Chi phí lệch hóa đơn | Một hằng số giá cho mọi loại | Quy đổi từ giá gói tháng |
| Prometheus ngốn RAM | Cardinality nhãn quá cao | Giới hạn nhãn ở type, status, error_code |
Câu hỏi thường gặp
Chi phí mỗi lần giải tính thế nào khi CaptchaAI bán theo thread?
Bạn tự quy đổi: lấy giá gói tháng chia cho tổng số lần giải thực chạy. CaptchaAI bán theo thread, không tính tiền theo từng lần giải.
Nên giữ số liệu CAPTCHA trong bao lâu?
Độ phân giải cao 7 ngày, tổng hợp theo giờ 90 ngày, tóm tắt theo ngày giữ lâu dài.
Bao nhiêu thread là đủ để hàng đợi không dồn?
Để biểu đồ trả lời: hàng đợi thường xuyên lớn hơn 0 trong khi tỷ lệ giải vẫn ổn định là dấu hiệu chạm trần thread.
Ghi metric có làm chậm solver không?
Không đáng kể. Phần chờ nằm ở polling res.php, không ở vài mili giây ghi metric.
Bước tiếp theo
Lấy API key CaptchaAI và đẩy điểm dữ liệu đầu tiên vào Prometheus hôm nay.
Hướng dẫn liên quan: