DynamoDB, sunucusuz CAPTCHA iş akışlarına doğal olarak uyum sağlar; bağlantı havuzu sorunları yaşanmaz, otomatik temizleme için yerleşik TTL ve her ölçekte tutarlı performans. Bu kılavuz, Lambda tabanlı mimarilerde CAPTCHA çözümlerini izlemeye yönelik tablo tasarımını, öğe yapısını ve sorgu modellerini kapsar.
Masa Tasarımı
Tek Masa Modeli
Tek bir DynamoDB tablosu çözüm geçmişini, etkin görevleri ve toplu istatistikleri yönetir:
| Bölüm Anahtarı (PK) | Sıralama Anahtarı (SK) | Amaç |
|---|---|---|
SOLVE#{captcha_id} |
META |
Kaydı çöz |
SITE#{sitekey} |
SOLVE#{timestamp} |
Site başına çözüm geçmişi |
STATS#{date} |
TYPE#{captcha_type} |
Günlük toplu istatistikler |
ACTIVE#{captcha_id} |
TASK |
Uçuş sırasında görev takibi |
Tablo Tanımı
{
"TableName": "CaptchaSolves",
"KeySchema": [
{ "AttributeName": "PK", "KeyType": "HASH" },
{ "AttributeName": "SK", "KeyType": "RANGE" }
],
"AttributeDefinitions": [
{ "AttributeName": "PK", "KeyType": "S" },
{ "AttributeName": "SK", "KeyType": "S" },
{ "AttributeName": "GSI1PK", "KeyType": "S" },
{ "AttributeName": "GSI1SK", "KeyType": "S" }
],
"GlobalSecondaryIndexes": [
{
"IndexName": "GSI1",
"KeySchema": [
{ "AttributeName": "GSI1PK", "KeyType": "HASH" },
{ "AttributeName": "GSI1SK", "KeyType": "RANGE" }
],
"Projection": { "ProjectionType": "ALL" }
}
],
"BillingMode": "PAY_PER_REQUEST",
"TimeToLiveSpecification": {
"AttributeName": "ttl",
"Enabled": true
}
}
Python Uygulaması
Kurulum
import os
import time
from datetime import datetime, timezone
import boto3
import requests
dynamodb = boto3.resource("dynamodb")
table = dynamodb.Table(os.environ.get("DYNAMODB_TABLE", "CaptchaSolves"))
API_KEY = os.environ["CAPTCHAAI_API_KEY"]
Çöz ve Takip Et
def solve_and_track(sitekey, pageurl, captcha_type="recaptcha_v2", project=None):
now = datetime.now(timezone.utc)
timestamp = now.isoformat()
ttl_90_days = int(now.timestamp()) + (90 * 24 * 3600)
# Submit to CaptchaAI
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:
# Store error record
table.put_item(Item={
"PK": f"SITE#{sitekey}",
"SK": f"SOLVE#{timestamp}",
"captcha_type": captcha_type,
"pageurl": pageurl,
"status": "error",
"error": data.get("request"),
"submitted_at": timestamp,
"project": project or "default",
"ttl": ttl_90_days,
"GSI1PK": f"STATUS#error",
"GSI1SK": timestamp
})
return {"error": data.get("request")}
captcha_id = data["request"]
# Track active task
table.put_item(Item={
"PK": f"ACTIVE#{captcha_id}",
"SK": "TASK",
"sitekey": sitekey,
"pageurl": pageurl,
"captcha_type": captcha_type,
"submitted_at": timestamp,
"ttl": int(now.timestamp()) + 600 # Auto-clean in 10 min
})
# Poll for result
polls = 0
for _ in range(60):
time.sleep(5)
polls += 1
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:
solved_at = datetime.now(timezone.utc).isoformat()
elapsed_ms = int(
(datetime.now(timezone.utc) - now).total_seconds() * 1000
)
# Store success record
table.put_item(Item={
"PK": f"SOLVE#{captcha_id}",
"SK": "META",
"captcha_type": captcha_type,
"sitekey": sitekey,
"pageurl": pageurl,
"status": "solved",
"submitted_at": timestamp,
"solved_at": solved_at,
"elapsed_ms": elapsed_ms,
"polls": polls,
"project": project or "default",
"ttl": ttl_90_days,
"GSI1PK": f"STATUS#solved",
"GSI1SK": timestamp
})
# Also store in site history
table.put_item(Item={
"PK": f"SITE#{sitekey}",
"SK": f"SOLVE#{timestamp}",
"captcha_id": captcha_id,
"status": "solved",
"elapsed_ms": elapsed_ms,
"ttl": ttl_90_days
})
# Remove active task
table.delete_item(Key={
"PK": f"ACTIVE#{captcha_id}", "SK": "TASK"
})
# Update daily stats
update_daily_stats(captcha_type, True, elapsed_ms)
return {"solution": result["request"]}
if result.get("request") != "CAPCHA_NOT_READY":
table.put_item(Item={
"PK": f"SITE#{sitekey}",
"SK": f"SOLVE#{timestamp}",
"captcha_id": captcha_id,
"status": "error",
"error": result.get("request"),
"ttl": ttl_90_days
})
table.delete_item(Key={
"PK": f"ACTIVE#{captcha_id}", "SK": "TASK"
})
update_daily_stats(captcha_type, False, 0)
return {"error": result.get("request")}
table.delete_item(Key={"PK": f"ACTIVE#{captcha_id}", "SK": "TASK"})
update_daily_stats(captcha_type, False, 0)
return {"error": "TIMEOUT"}
def update_daily_stats(captcha_type, success, elapsed_ms):
date_str = datetime.now(timezone.utc).strftime("%Y-%m-%d")
update_expr = "SET total_solves = if_not_exists(total_solves, :zero) + :one"
expr_values = {":zero": 0, ":one": 1}
if success:
update_expr += ", successful = if_not_exists(successful, :zero) + :one"
update_expr += ", total_elapsed = if_not_exists(total_elapsed, :zero) + :elapsed"
expr_values[":elapsed"] = elapsed_ms
else:
update_expr += ", failed = if_not_exists(failed, :zero) + :one"
table.update_item(
Key={"PK": f"STATS#{date_str}", "SK": f"TYPE#{captcha_type}"},
UpdateExpression=update_expr,
ExpressionAttributeValues=expr_values
)
Sorgu Kalıpları
def get_site_history(sitekey, limit=50):
"""Get recent solves for a specific site key."""
response = table.query(
KeyConditionExpression="PK = :pk",
ExpressionAttributeValues={":pk": f"SITE#{sitekey}"},
ScanIndexForward=False,
Limit=limit
)
return response["Items"]
def get_daily_stats(date_str=None):
"""Get stats for a specific date (default: today)."""
if not date_str:
date_str = datetime.now(timezone.utc).strftime("%Y-%m-%d")
response = table.query(
KeyConditionExpression="PK = :pk",
ExpressionAttributeValues={":pk": f"STATS#{date_str}"}
)
return response["Items"]
def get_active_tasks():
"""List all currently active CAPTCHA tasks."""
response = table.query(
IndexName="GSI1",
KeyConditionExpression="GSI1PK = :pk",
ExpressionAttributeValues={":pk": "STATUS#polling"}
)
return response["Items"]
JavaScript Uygulaması
const { DynamoDBClient } = require("@aws-sdk/client-dynamodb");
const { DynamoDBDocumentClient, PutCommand, QueryCommand, UpdateCommand } = require("@aws-sdk/lib-dynamodb");
const axios = require("axios");
const client = DynamoDBDocumentClient.from(new DynamoDBClient({}));
const TABLE = process.env.DYNAMODB_TABLE || "CaptchaSolves";
const API_KEY = process.env.CAPTCHAAI_API_KEY;
async function solveAndTrack(sitekey, pageurl, type = "recaptcha_v2") {
const now = new Date();
const timestamp = now.toISOString();
const ttl = Math.floor(now.getTime() / 1000) + 90 * 24 * 3600;
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) {
await client.send(new PutCommand({
TableName: TABLE,
Item: { PK: `SITE#${sitekey}`, SK: `SOLVE#${timestamp}`, status: "error", error: submit.data.request, ttl },
}));
return { error: submit.data.request };
}
const captchaId = submit.data.request;
let polls = 0;
for (let i = 0; i < 60; i++) {
await new Promise((r) => setTimeout(r, 5000));
polls++;
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() - now.getTime();
await client.send(new PutCommand({
TableName: TABLE,
Item: {
PK: `SOLVE#${captchaId}`, SK: "META", captcha_type: type,
sitekey, pageurl, status: "solved", submitted_at: timestamp,
solved_at: new Date().toISOString(), elapsed_ms: elapsed, polls, ttl,
},
}));
return { solution: poll.data.request };
}
if (poll.data.request !== "CAPCHA_NOT_READY") {
return { error: poll.data.request };
}
}
return { error: "TIMEOUT" };
}
async function getSiteHistory(sitekey, limit = 50) {
const result = await client.send(new QueryCommand({
TableName: TABLE,
KeyConditionExpression: "PK = :pk",
ExpressionAttributeValues: { ":pk": `SITE#${sitekey}` },
ScanIndexForward: false,
Limit: limit,
}));
return result.Items;
}
Maliyet Optimizasyonu
| Strateji | Etki |
|---|---|
| Değişken iş yükleri için isteğe bağlı faturalandırmayı kullanın | Aşırı provizyon yok |
| Otomatik kayıt temizliği için TTL'yi etkinleştirin | Depolama maliyetlerini azaltır |
| Projenin yalnızca sorgularda ihtiyaç duyduğu nitelikler | Daha düşük okuma ünitesi tüketimi |
BatchWriteItem ile toplu yazma |
Daha az API çağrısı |
| Analiz için DynamoDB Streams'i kullanın | Toplamanın yükünü Lambda'ya aktarın |
Sorun giderme
| Sorun | Sebep | Düzeltme |
|---|---|---|
ProvisionedThroughputExceededException |
Saniyede çok fazla yazma | İsteğe bağlı faturalandırmaya geçin veya WCU'yu artırın |
| TTL öğeleri hemen silinmez | DynamoDB TTL'nin silinmesi nihaidir (~48 saat) | Gerçek zamanlı temizlik için TTL'ye güvenmeyin; sorgulardaki süresi dolmuş öğeleri filtreleme |
STATS#{date}'de sıcak bölüm |
Tüm çalışanlar aynı bölüme yazıyor | Rastgeleleştirilmiş son ek kullanın: STATS#{date}#shard{0-9} |
| Sorgu çok fazla öğe döndürüyor | Geniş bölüm anahtarı | Sonuçları daraltmak için SK koşullarını ekleyin |
SSS
Sunucusuz CAPTCHA takibi için neden RDS yerine DynamoDB?
DynamoDB'nin bağlantı sınırı yoktur; her çağrının yeni bir bağlantı açtığı Lambda için idealdir. RDS, maliyet ve karmaşıklığı artıran bağlantı havuzu oluşturmayı (RDS Proxy) gerektirir.
DynamoDB'nin CAPTCHA takibinin maliyeti ne kadardır?
İsteğe bağlı faturalandırmayla: Milyon yazma başına ~1,25 ABD doları ve milyon okuma başına ~0,25 ABD doları. 10.000 çözüm/day'de, depolama ve erişim için 1/month dolarının altında bir değer bekliyoruz.
Tüm CAPTCHA türlerinde sorgulama yapabilir miyim?
Türler arasında duruma göre sorgulama yapmak için GSI1 dizinini kullanın. Türler arası analizler için DynamoDB Streams'i ve STATS# bölümüne yazan bir Lambda işlevini kullanarak toplama yapın.
Sonraki Adımlar
Otomatik olarak ölçeklenen sunucusuz CAPTCHA takibi oluşturun —CaptchaAI API anahtarınızı alın.
İlgili kılavuzlar: