AML Transaction Monitoring with a Streaming Database

AML Transaction Monitoring with a Streaming Database

AML transaction monitoring with a streaming database means evaluating every transaction against structuring rules, velocity thresholds, and network patterns the moment it hits the wire — enabling financial institutions to generate Suspicious Activity Reports faster and with far fewer false positives than legacy batch systems.

The Compliance Cost of Slow Detection

Anti-money laundering compliance is one of the most resource-intensive obligations in banking. U.S. banks paid over $2.7 billion in AML-related fines in a recent five-year period, and the cost of running compliance operations — analysts, case management, and system infrastructure — routinely exceeds $1 billion annually for large institutions.

A core problem: most AML systems are batch-based. They collect transactions throughout the day, run rule engines overnight, and surface alerts the next morning. By then, funds have often moved through multiple hops. Structuring activity — breaking large deposits into smaller amounts to avoid reporting thresholds — can complete across multiple branches before any alert fires.

Streaming databases eliminate the batch window. Rules run continuously over live transaction streams, and alerts fire within seconds.

Key AML Detection Patterns

Effective AML monitoring targets several behavioral patterns:

  • Structuring (smurfing): Multiple transactions just below reporting thresholds within a short time window
  • Velocity anomalies: Unusual transaction frequency for a given account or customer
  • Dormant account activation: Accounts with no activity suddenly moving large volumes
  • Round-trip flows: Funds sent out and returned within a short period
  • Network clustering: Multiple accounts transacting with the same counterparties

Streaming SQL handles all of these through windowed aggregations and temporal joins.

Ingesting Transaction Data

Connect RisingWave to your core banking transaction stream:

CREATE SOURCE bank_transactions (
    transaction_id   VARCHAR,
    account_id       VARCHAR,
    counterparty_id  VARCHAR,
    txn_type         VARCHAR,  -- 'deposit', 'withdrawal', 'transfer', 'wire'
    amount           NUMERIC,
    currency         VARCHAR,
    branch_id        VARCHAR,
    txn_time         TIMESTAMPTZ,
    channel          VARCHAR
)
WITH (
    connector = 'kafka',
    topic = 'core.banking.transactions',
    properties.bootstrap.server = 'broker:9092',
    scan.startup.mode = 'latest'
)
FORMAT PLAIN ENCODE JSON;

CREATE TABLE aml_watchlist (
    entity_id     VARCHAR PRIMARY KEY,
    entity_type   VARCHAR,  -- 'account', 'customer', 'counterparty'
    watchlist_type VARCHAR, -- 'sanctions', 'pep', 'high_risk'
    added_date    DATE
);

Structuring Detection with Tumbling Windows

The most common AML pattern — structuring — involves making multiple deposits just below the $10,000 Currency Transaction Report threshold. Detect it using a tumbling window over 24-hour periods:

CREATE MATERIALIZED VIEW structuring_alerts AS
SELECT
    account_id,
    COUNT(*)                              AS txn_count,
    SUM(amount)                           AS total_amount,
    MAX(amount)                           AS max_single_txn,
    MIN(amount)                           AS min_single_txn,
    COUNT(DISTINCT branch_id)             AS distinct_branches,
    window_start,
    window_end
FROM TUMBLE(
    bank_transactions,
    txn_time,
    INTERVAL '24 HOURS'
)
WHERE
    txn_type IN ('deposit', 'cash_deposit')
    AND amount BETWEEN 8000 AND 9999
GROUP BY
    account_id,
    window_start,
    window_end
HAVING
    COUNT(*) >= 3
    OR (COUNT(*) >= 2 AND COUNT(DISTINCT branch_id) >= 2);

This query continuously maintains counts of sub-threshold cash deposits per account per day. Any account hitting the HAVING threshold immediately surfaces as a structuring candidate.

Velocity and Dormant Account Detection

Hop windows allow overlapping time periods, ideal for detecting velocity spikes:

CREATE MATERIALIZED VIEW velocity_and_dormancy_alerts AS
SELECT
    t.account_id,
    COUNT(*)                          AS txn_count_7d,
    SUM(t.amount)                     AS total_volume_7d,
    AVG(t.amount)                     AS avg_txn_amount,
    cp.last_activity_date,
    CASE
        WHEN cp.last_activity_date < NOW() - INTERVAL '180 DAYS'
        THEN 'DORMANT_REACTIVATION'
        ELSE 'VELOCITY_SPIKE'
    END                               AS alert_type,
    window_start,
    window_end
FROM HOP(
    bank_transactions,
    txn_time,
    INTERVAL '1 DAY',
    INTERVAL '7 DAYS'
) t
LEFT JOIN customer_risk_profiles FOR SYSTEM_TIME AS OF t.txn_time AS cp
    ON t.account_id = cp.account_id
GROUP BY
    t.account_id,
    cp.last_activity_date,
    window_start,
    window_end
HAVING
    COUNT(*) > (cp.avg_weekly_txns * 5)
    OR (cp.last_activity_date < NOW() - INTERVAL '180 DAYS' AND SUM(t.amount) > 50000);

Routing Alerts to Case Management

Route confirmed alerts to your case management system via JDBC or Kafka:

CREATE SINK aml_case_management_sink
FROM (
    SELECT
        account_id,
        'STRUCTURING'     AS alert_type,
        txn_count         AS txn_count,
        total_amount,
        window_start      AS period_start,
        window_end        AS period_end,
        NOW()             AS generated_at
    FROM structuring_alerts
    UNION ALL
    SELECT
        account_id,
        alert_type,
        txn_count_7d,
        total_volume_7d,
        window_start,
        window_end,
        NOW()
    FROM velocity_and_dormancy_alerts
)
WITH (
    connector = 'jdbc',
    jdbc.url = 'jdbc:postgresql://case-mgmt-db:5432/aml',
    table.name = 'aml_alerts',
    user = 'rw_sink',
    password = 'secret'
)
FORMAT PLAIN ENCODE JSON;

Legacy Batch AML vs. Streaming AML

DimensionLegacy Batch AMLStreaming AML (RisingWave)
Alert latency12–24 hoursSeconds
Structuring detectionNext dayWithin transaction window
False positive rateHigh (stale context)Lower (fresh aggregates)
Analyst workloadHigh (manual triage)Reduced (pre-prioritized)
SAR filing timelinessRisk of deadline breachEasily within window
Infrastructure complexityHighSQL-only
Regulatory exam readinessDifficult to demonstrateReal-time audit trail

FAQ

Q: Does streaming AML replace existing rule engines like Actimize or Mantas? A: Not necessarily. RisingWave can feed pre-processed, enriched alert signals into existing case management platforms. Many institutions use it as a high-speed aggregation layer upstream of their existing AML platform, significantly reducing false positives before alerts reach analysts.

Q: How does RisingWave handle multi-currency transaction normalization? A: Currency conversion rates can be maintained in a RisingWave reference table and joined in real time using temporal joins, normalizing all amounts to a base currency within the materialized view.

Q: Can we perform network analysis (detecting money mule networks) in RisingWave? A: Graph-based network analysis is typically better handled by specialized graph databases. However, RisingWave can identify high-frequency counterparty relationships using aggregations over counterparty_id, which can then feed a graph analytics system.

Q: How do we ensure audit trail completeness for regulators? A: RisingWave sinks to Kafka or JDBC can be configured with exactly-once semantics. Combined with immutable storage targets like Iceberg, this creates a tamper-evident audit trail of all transactions and generated alerts.

Q: What is the typical false positive reduction when moving from batch to streaming? A: Institutions that have moved from nightly batch to streaming AML systems report 30–60% reductions in false positives, primarily because streaming systems use accurate, current behavioral context rather than stale prior-day snapshots.

Get Started

Bring your AML compliance into the real-time era with RisingWave's streaming SQL:

Best-in-Class Event Streaming
for Agents, Apps, and Analytics
GitHubXLinkedInSlackYouTube
Sign up for our to stay updated.