Real-Time Fraud Detection¶
Fraud detection requires analyzing complex relationships (graph) and patterns in real-time, often while ingesting high volumes of transaction data. Uni's single-writer architecture with L0 memory buffering makes it ideal for high-velocity ingest combined with instant "read-your-writes" queries.
Why Uni for Fraud Detection?¶
| Challenge | Traditional Approach | Uni Approach |
|---|---|---|
| Ingest Rate | Graph DBs struggle with high write loads; Relational DBs can't do hops. | L0 Buffer: Writes hit memory first (WAL-backed), amortized flush to disk. |
| Freshness | "Eventual consistency" or ETL lag (minutes/hours). | Snapshot Isolation: Readers see writes immediately (if configured). |
| Deep Links | Detecting "Payment Rings" requires 3+ hops. SQL joins choke. | CSR Cache: O(1) adjacency lookups make multi-hop checks fast. |
Scenario: Payment Ring Detection¶
We want to detect a circular payment pattern: User A -> User B -> User C -> User A happening within a short time window.
1. Schema Definition¶
We model Users, Devices, and IPs as nodes to detect shared infrastructure usage. Transactions are edges with timestamps.
Conceptual schema (illustrative):
{
"labels": {
"User": { "id": 1 },
"Device": { "id": 2 },
"IP": { "id": 3 }
},
"edge_types": {
"SENT_MONEY": { "id": 1, "src_labels": ["User"], "dst_labels": ["User"] },
"USED_DEVICE": { "id": 2, "src_labels": ["User"], "dst_labels": ["Device"] },
"USED_IP": { "id": 3, "src_labels": ["User"], "dst_labels": ["IP"] }
},
"properties": {
"SENT_MONEY": {
"amount": { "type": "Float64", "nullable": false },
"ts": { "type": "Int64", "nullable": false }
},
"User": {
"risk_score": { "type": "Float32", "nullable": true }
}
},
"indexes": []
}
Schema (Rust example):
use uni_db::DataType;
db.schema()
.label("User")
.property_nullable("risk_score", DataType::Float32)
.done()
.label("Device")
.done()
.label("IP")
.done()
.edge_type("SENT_MONEY", &["User"], &["User"])
.property("amount", DataType::Float64)
.property("ts", DataType::Int64)
.done()
.edge_type("USED_DEVICE", &["User"], &["Device"])
.done()
.edge_type("USED_IP", &["User"], &["IP"])
.done()
.apply()
.await?;
2. Configuration¶
For high write throughput, we tune the WAL and L0 Buffer.
Rust config example:
use std::time::Duration;
use uni_db::UniConfig;
let mut config = UniConfig::default();
config.auto_flush_threshold = 100_000; // Larger in-memory buffer before flushing
config.auto_flush_interval = Some(Duration::from_millis(50));
config.auto_flush_min_mutations = 1;
let db = Uni::open("./fraud_graph")
.config(config)
.build()
.await?;
3. Streaming Ingestion¶
Use the embedded Rust API to ingest transactions as they happen.
// Rust API Example (parameterized Cypher)
db.query_with(
"MATCH (a:User {id: $src}), (b:User {id: $dst})
CREATE (a)-[:SENT_MONEY {amount: $amount, ts: $ts}]->(b)"
)
.param("src", user_a_id)
.param("dst", user_b_id)
.param("amount", 5000.00)
.param("ts", current_time)
.fetch_all()
.await?;
4. Real-time Detection Query¶
Run this query synchronously when a transaction is attempted. It checks for a cycle of length 3-4 involving the sender.
// Check for cycles starting from User A
MATCH (a:User)-[t1:SENT_MONEY]->(b:User)-[t2:SENT_MONEY]->(c:User)-[t3:SENT_MONEY]->(a)
// Filter recent transactions only (e.g., last 1 hour)
WHERE t1.ts > $threshold AND t2.ts > $threshold AND t3.ts > $threshold
// Return the cycle details
RETURN a.id, b.id, c.id, t1.amount, t2.amount, t3.amount
5. Identity Resolution Query¶
Check if the sender shares an IP or Device with known fraudsters.
MATCH (sender:User)-[:USED_DEVICE]->(shared_resource)<-[:USED_DEVICE]-(other:User)
WHERE other.risk_score > 0.8
RETURN count(other) as suspicious_links
Key Advantages¶
- Ingest Speed: Uni's L0 buffer acts like a Write-Optimized Store (WOS), handling spikes in transaction volume without blocking readers.
- Latency: Checking a 3-hop cycle takes milliseconds due to the in-memory CSR cache.
- Data Locality: The graph structure and properties are co-located; no need to query a Redis cache for "risk scores" separately.