---
title: Amazon Neptune Analytics: Graph and Vector Analytics for Fraud Detection and Recommendations
description: Rules engines miss fraud rings that mutate weekly. Graph + vector queries don't. A production guide to Neptune Analytics for fraud detection, recommendation engines, and supply-chain risk — query patterns, cost gotchas, and where the architecture breaks down.
url: https://www.factualminds.com/blog/amazon-neptune-analytics-graph-vector/
datePublished: 2026-01-12T00:00:00.000Z
dateModified: 2026-01-12T00:00:00.000Z
author: palaniappan-p
category: analytics
tags: neptune-analytics, graph-database, vector-search, fraud-detection, aws-analytics
---

# Amazon Neptune Analytics: Graph and Vector Analytics for Fraud Detection and Recommendations

> Rules engines miss fraud rings that mutate weekly. Graph + vector queries don't. A production guide to Neptune Analytics for fraud detection, recommendation engines, and supply-chain risk — query patterns, cost gotchas, and where the architecture breaks down.

import { Image } from 'astro:assets';

Neptune Database handles real-time graph traversals well. Finding all accounts connected to a suspicious transaction two hops away, looking up a customer's product graph neighborhood, checking if a new merchant shares attributes with known fraudulent entities — these are OLTP graph queries and Neptune Database is the right tool for them.

The problem arises when you need to run analytics across the _entire_ graph. Computing PageRank on 500 million nodes. Identifying all fraud rings in a payment network by finding weakly connected components across 2 billion edges. Finding product clusters via community detection for a recommendation system. These are OLAP workloads, and Neptune Database's architecture — optimized for low-latency traversals with transactional consistency — is not designed for them. Running a full-graph PageRank against Neptune Database will lock the cluster, time out, and produce an expensive lesson.

Amazon Neptune Analytics, which reached GA in November 2023 and has expanded through 2024–2025, solves this by loading a snapshot of your graph into an in-memory analytics engine that is purpose-built for graph algorithm execution and vector similarity search. This post covers the OLTP/OLAP split, fraud ring detection walkthrough, combined graph+vector queries, data loading patterns, and cost analysis.

## Neptune Database vs. Neptune Analytics: The OLTP/OLAP Split

Understanding the architectural boundary between the two products prevents misuse of both.

| Dimension                        | Neptune Database                                   | Neptune Analytics                                                                    |
| -------------------------------- | -------------------------------------------------- | ------------------------------------------------------------------------------------ |
| **Architecture**                 | Distributed storage-compute separation, persistent | In-memory, loaded from snapshots                                                     |
| **Query languages**              | Gremlin, openCypher, SPARQL                        | openCypher only                                                                      |
| **Write support**                | Yes — full transactional writes                    | No — read-only analytics                                                             |
| **Data freshness**               | Real-time (as transactions commit)                 | Snapshot-based (load time)                                                           |
| **Graph algorithm support**      | No built-in algorithms                             | Yes — PageRank, WCC, betweenness centrality, shortest path, community detection, KNN |
| **Vector similarity search**     | No                                                 | Yes — cosine, Euclidean, dot product similarity on node embeddings                   |
| **Query latency (traversal)**    | Milliseconds for point queries                     | Milliseconds for algorithms on the full in-memory graph                              |
| **Full-graph analytics latency** | Minutes-to-timeout (storage I/O bound)             | Sub-second for most algorithms on millions of nodes                                  |
| **Billing model**                | Instance hours (r5/r6g instance families)          | Graph memory capacity (GB) + query hours                                             |
| **When to use**                  | Transactional writes, real-time traversals, CRUD   | Batch graph analytics, fraud ring detection, recommendations, embeddings             |

The canonical deployment pattern is read-write against Neptune Database for transactional operations, periodic (nightly or hourly) export to S3, load into Neptune Analytics for batch analytics, and write results back to an operational store (DynamoDB or RDS) for application consumption.

```
Application Layer
    │
    ├── Real-time traversals → Neptune Database (R/W)
    │                               │
    │                               └── Nightly export → S3
    │                                                     │
    └── Read enrichment scores ← DynamoDB ← Neptune Analytics ← S3 snapshot
```

## Fraud Ring Detection with Graph Analytics

Payment fraud rings are difficult to detect with row-level transaction analysis because the signal is in the _connections_ between entities, not in individual transaction properties. A card used for three transactions, each below the fraud threshold — suspicious in isolation only if you know those three transactions share a device fingerprint with 40 other cards linked to the same merchant, all controlled by the same shell entity.

Graph analytics surfaces this pattern through Weakly Connected Components (WCC). In graph theory, a connected component is a subgraph where every node can reach every other node. In a payment network, an unusually large connected component where nodes share multiple attribute paths is a strong fraud ring signal.

**Step 1: Build the payment network graph**

Model the payment network as a tripartite graph with four node types:

```
Nodes: Card, Merchant, Device, IP Address
Edges:
  (Card) -[USED_AT]-> (Merchant): transaction occurred
  (Card) -[ACCESSED_FROM]-> (Device): card used on device
  (Card) -[ACCESSED_FROM]-> (IP): card connected from IP
  (Merchant) -[REGISTERED_FROM]-> (IP): merchant signup IP
```

Export this graph from your transactional database as Neptune bulk load format:

```python
import boto3
import pandas as pd

# Generate node file (cards)
cards_df = pd.DataFrame({
    'id': card_ids,
    ':label': 'Card',
    'card_last4': card_last4_list,
    'issuer_bank': issuer_bank_list,
    'account_age_days': account_age_list
})
cards_df.to_csv('s3://fraud-data/graph-export/nodes-cards.csv', index=False)

# Generate edge file (card-to-merchant transactions)
transactions_df = pd.DataFrame({
    ':ID': edge_ids,
    ':START_ID': source_card_ids,
    ':END_ID': destination_merchant_ids,
    ':TYPE': 'USED_AT',
    'amount_usd': amounts,
    'txn_date': dates,
    'status': statuses
})
transactions_df.to_csv('s3://fraud-data/graph-export/edges-transactions.csv', index=False)
```

**Step 2: Load into Neptune Analytics**

```python
import boto3

neptune_analytics = boto3.client('neptune-graph', region_name='us-east-1')

# Create a Neptune Analytics graph with sufficient memory for the payment graph
response = neptune_analytics.create_graph(
    graphName='payment-fraud-analytics',
    provisionedMemory=64,  # GB — size based on your graph: ~1 GB per 10M edges
    replicaCount=0,        # 0 replicas for batch-only use cases
    deletionProtection=False,
    vectorSearchConfiguration={
        'dimension': 256  # Include if you're storing embedding vectors on nodes
    }
)

graph_id = response['id']
print(f"Graph ID: {graph_id}")

# Import from S3
import_response = neptune_analytics.start_import_task(
    graphIdentifier=graph_id,
    source='s3://fraud-data/graph-export/',
    format='CSV',
    roleArn='arn:aws:iam::123456789012:role/NeptuneAnalyticsImportRole',
    importOptions={
        'neptune': {
            'preserveDefaultVertexLabels': True,
            'preserveEdgeIds': True
        }
    }
)

task_id = import_response['taskId']
print(f"Import task: {task_id}")

# Wait for import to complete
import time
while True:
    status = neptune_analytics.get_import_task(taskId=task_id)
    if status['status'] in ['SUCCEEDED', 'FAILED']:
        print(f"Import completed: {status['status']}")
        break
    time.sleep(30)
```

**Step 3: Run Weakly Connected Components**

```python
# Execute WCC algorithm via openCypher
# Neptune Analytics graph algorithm invocations use openCypher with neptune.algo functions

query = """
CALL neptune.algo.wcc({
    nodeLabels: ['Card', 'Merchant', 'Device', 'IP'],
    relationshipTypes: ['USED_AT', 'ACCESSED_FROM', 'REGISTERED_FROM'],
    writeProperty: 'component_id'
})
YIELD componentId, nodeCount
WHERE nodeCount > 50  // Components with > 50 members warrant investigation
RETURN componentId, nodeCount
ORDER BY nodeCount DESC
LIMIT 100
"""

response = neptune_analytics.execute_query(
    graphIdentifier=graph_id,
    queryString=query,
    language='OPEN_CYPHER'
)

# Parse results
results = json.loads(response['payload'].read())
fraud_ring_components = results['results']

print(f"Suspicious components found: {len(fraud_ring_components)}")
for component in fraud_ring_components[:10]:
    print(f"Component {component['componentId']}: {component['nodeCount']} connected entities")
```

**Step 4: Enrich and export for investigators**

```python
# For each suspicious component, pull entity details for case management
enrichment_query = """
MATCH (n)
WHERE n.component_id = $componentId
RETURN
    labels(n)[0] as entity_type,
    n.id as entity_id,
    n.component_id as component_id,
    CASE labels(n)[0]
        WHEN 'Card' THEN n.card_last4
        WHEN 'Merchant' THEN n.merchant_name
        WHEN 'Device' THEN n.device_fingerprint
        WHEN 'IP' THEN n.ip_address
    END as identifier
"""

fraud_entities = []
for component in fraud_ring_components:
    entities = neptune_analytics.execute_query(
        graphIdentifier=graph_id,
        queryString=enrichment_query,
        language='OPEN_CYPHER',
        parameters={'componentId': component['componentId']}
    )
    fraud_entities.extend(json.loads(entities['payload'].read())['results'])

# Write to DynamoDB for case management system
dynamodb = boto3.resource('dynamodb')
fraud_cases_table = dynamodb.Table('fraud-cases')

for entity in fraud_entities:
    fraud_cases_table.put_item(Item={
        'component_id': str(entity['component_id']),
        'entity_type': entity['entity_type'],
        'entity_id': entity['entity_id'],
        'identifier': entity['identifier'],
        'detected_date': datetime.now().isoformat(),
        'status': 'PENDING_REVIEW'
    })
```

## Graph + Vector Combined Queries

Neptune Analytics uniquely supports combining graph structure with vector similarity in a single query. This unlocks recommendation patterns that neither pure graph traversal nor pure vector search can achieve alone.

**Use case: product recommendations combining semantic similarity and purchase graph**

Store product embedding vectors (from a text embedding model like Amazon Titan Embeddings) as properties on product nodes in Neptune Database. When you load the graph into Neptune Analytics, the embedding vectors load with the node properties.

```python
# When building your graph: add embedding vectors to product nodes
# These are stored as array properties on nodes in Neptune Database

import boto3
import json

bedrock = boto3.client('bedrock-runtime')
neptune_db = boto3.client('neptunedata', endpoint_url='https://YOUR-CLUSTER.neptune.amazonaws.com:8182')

def embed_product(description: str) -> list[float]:
    response = bedrock.invoke_model(
        modelId='amazon.titan-embed-text-v2:0',
        body=json.dumps({'inputText': description})
    )
    return json.loads(response['body'].read())['embedding']

# Store as a node property via openCypher
for product in products:
    embedding = embed_product(product['description'])
    neptune_db.execute_open_cypher_query(
        openCypherQuery="""
        MATCH (p:Product {id: $productId})
        SET p.embedding = $embedding
        """,
        parameters=json.dumps({
            'productId': product['id'],
            'embedding': embedding
        })
    )
```

After loading this graph into Neptune Analytics with vector search configured, run combined queries:

```python
# Combined query: find products that are semantically similar
# AND frequently co-purchased by users in target customer's neighborhood

combined_query = """
// Step 1: Find the query product's embedding and customer's purchase graph
MATCH (queryProduct:Product {id: $productId})
MATCH (customer:Customer {id: $customerId})-[:PURCHASED]->(ownedProduct:Product)

// Step 2: Find semantically similar products using vector search
CALL neptune.algo.vectors.topKByNode(queryProduct, {
    topK: 50,
    concurrency: 4
}) YIELD node AS similarProduct, score AS similarityScore

// Step 3: Intersect with products bought by similar customers (graph traversal)
MATCH (similarCustomer:Customer)-[:PURCHASED]->(candidateProduct:Product)
WHERE similarCustomer <> customer
  AND (similarCustomer)-[:PURCHASED]->(ownedProduct)  // Similar customers share purchases
  AND candidateProduct IN similarProduct              // Candidate is semantically similar

// Step 4: Score by combined signal
RETURN candidateProduct.id AS productId,
       candidateProduct.name AS productName,
       similarityScore,
       count(DISTINCT similarCustomer) AS graphSignalStrength,
       (similarityScore * 0.4 + toFloat(count(DISTINCT similarCustomer)) / 100.0 * 0.6) AS combinedScore
ORDER BY combinedScore DESC
LIMIT 10
"""

recommendations = neptune_analytics.execute_query(
    graphIdentifier=graph_id,
    queryString=combined_query,
    language='OPEN_CYPHER',
    parameters={'productId': 'prod-abc123', 'customerId': 'cust-xyz789'}
)
```

The power here is that neither approach alone delivers the full signal:

- Pure vector similarity finds semantically similar products but ignores social purchase context
- Pure graph traversal finds what similar customers bought but can recommend contextually unrelated items
- Combined: semantically similar products that also have strong purchase graph signal — highest quality recommendations

## Loading and Querying Neptune Analytics

**Load from Neptune Database export:**

```python
# Export Neptune Database to S3 using the Neptune export service
neptune_export = boto3.client('s3', region_name='us-east-1')

# Trigger Neptune export (via Neptune Export API endpoint)
import requests

export_response = requests.post(
    'https://YOUR-EXPORT-ENDPOINT.neptune-export.amazonaws.com/v1/neptune-export/export',
    json={
        'command': 'export-pg',  # Property graph (Gremlin/openCypher)
        'params': {
            'endpoint': 'YOUR-NEPTUNE-CLUSTER.neptune.amazonaws.com',
            'profile': 'neptune_export_default',
            'outputS3Path': 's3://neptune-exports/fraud-graph/',
            'jobSize': 'medium'
        }
    },
    auth=('user', 'password')  # Use SigV4 signing in production
)

export_job_id = export_response.json()['jobId']
```

**Load from S3 CSV directly (no Neptune Database required):**

Neptune Analytics accepts S3 CSV files in the Neptune bulk load format — the same format used for Neptune Database bulk loading. Node files need `~id` and `~label` columns; edge files need `~id`, `~from`, `~to`, and `~label` columns:

```csv
# nodes-cards.csv
~id,~label,card_last4:String,account_age_days:Int,risk_tier:String
card-001,Card,4242,365,standard
card-002,Card,1234,12,new

# edges-transactions.csv
~id,~from,~to,~label,amount_usd:Double,txn_date:Date
txn-001,card-001,merchant-001,USED_AT,149.99,2026-06-01
txn-002,card-002,merchant-001,USED_AT,89.50,2026-06-01
```

**Error handling for large graph loads:**

```python
def wait_for_import_with_retry(neptune_analytics_client, task_id: str, max_wait_minutes: int = 120):
    import time
    start_time = time.time()

    while (time.time() - start_time) < (max_wait_minutes * 60):
        status = neptune_analytics_client.get_import_task(taskId=task_id)

        if status['status'] == 'SUCCEEDED':
            stats = status.get('importTaskDetails', {})
            print(f"Import succeeded. Nodes: {stats.get('progressPercentage', 'N/A')}%")
            return True

        if status['status'] == 'FAILED':
            error = status.get('statusReason', 'Unknown error')
            print(f"Import failed: {error}")
            # Common failures: insufficient memory, malformed CSV, S3 permissions
            if 'OutOfMemory' in error:
                print("Increase provisionedMemory parameter and retry")
            elif 'AccessDenied' in error:
                print("Check Neptune Analytics IAM role has s3:GetObject on export bucket")
            return False

        elapsed = int(time.time() - start_time)
        print(f"Import in progress... ({elapsed}s elapsed, status: {status['status']})")
        time.sleep(30)

    print(f"Import timed out after {max_wait_minutes} minutes")
    return False
```

## Cost Model

Neptune Analytics pricing has two dimensions: provisioned memory capacity (GB) and query execution hours.

| Dimension                        | Neptune Analytics                   | Neptune Database (r5.2xlarge equivalent) |
| -------------------------------- | ----------------------------------- | ---------------------------------------- |
| **Memory-based cost**            | ~$0.118/GB-hour (varies by region)  | Included in instance pricing             |
| **Instance cost**                | N/A                                 | ~$0.52/hour (r5.2xlarge)                 |
| **Full-graph algorithm**         | Included in query hours             | Typically impractical (timeout/cost)     |
| **Daily 1-hour batch analytics** | 64 GB × $0.118 × 1 hour = $7.55/day | N/A (would degrade cluster)              |
| **Monthly (1-hour daily batch)** | ~$227/month                         | ~$374/month (always-on r5.2xlarge)       |

For fraud ring detection running as a nightly batch job (1-2 hours), Neptune Analytics is _cheaper_ than keeping a large Neptune Database instance sized for analytics workloads, because you only pay for the time the analytics graph is loaded and queried.

**Self-hosted comparison:**

An alternative is running Apache Spark GraphX with GraphFrames on an EMR cluster for graph algorithms, plus a separate vector similarity solution. The engineering overhead (cluster management, algorithm implementation, result persistence) and operational complexity make this path significantly more expensive in practice than Neptune Analytics for teams without existing Spark expertise.

**Cost optimization tips:**

- Delete the Neptune Analytics graph after batch analytics completes — you only pay for the loaded, running graph
- Reload from S3 for the next batch run (import from S3 takes minutes for graphs under 10 GB)
- For very large graphs that take 30+ minutes to load, keep the graph running overnight rather than deleting and reloading every run
- Use `replicaCount: 0` for batch-only use cases (no read replicas needed)

---

Neptune Analytics fills a genuine gap in the AWS analytics portfolio. Graph algorithms against large graphs were previously either impractical (running on Neptune Database) or expensive engineering projects (Spark GraphX on EMR). Neptune Analytics makes PageRank, WCC, and community detection against billion-edge graphs a straightforward API call, with the added capability of combining graph algorithms with vector similarity search in a single query.

For fraud detection specifically, the WCC-based fraud ring detection pattern has better recall than rule-based systems for novel fraud rings — the graph structure reveals connections that row-level rules miss. For recommendation systems, the combined graph+vector query pattern produces recommendations that are both contextually relevant and informed by real purchase behavior.

For organizations building vector-enabled search infrastructure, compare Neptune Analytics' graph+vector approach with [Amazon S3 Vectors for native vector storage](/blog/amazon-s3-vectors-native-vector-storage/) and [Amazon OpenSearch Service's vector and analytics patterns](/blog/amazon-opensearch-service-architecture-patterns-cost-optimization/) to choose the right architecture for your specific use case. See also the [AWS AI services landscape for 2026](/blog/top-20-aws-ai-modern-services-2026/) for the full picture of where graph analytics fits in modern AWS data architectures.

Need help designing a Neptune Analytics architecture for fraud detection or recommendation systems? [FactualMinds](/contact-us/) works with data engineering teams on graph data modeling, Neptune Database to Neptune Analytics pipeline design, and combined graph+vector query patterns for production workloads.

## FAQ

### How fresh is the data in Neptune Analytics vs. Neptune Database?
Neptune Analytics operates on a snapshot of your graph — it is not a live view of Neptune Database transactions. When you create a Neptune Analytics graph, you load a point-in-time snapshot from Neptune Database (exported to S3) or directly from S3-hosted graph files. The graph in Neptune Analytics reflects the state of the data at load time. For fraud detection use cases that run daily batch analysis, this is typically acceptable — you load a fresh snapshot nightly and run algorithms against it. For use cases requiring near-real-time graph analytics (seconds-to-minutes latency), Neptune Analytics is not the right tool; you would use Neptune Database with real-time traversal queries instead.

### Does Neptune Analytics support Gremlin queries?
Neptune Analytics supports openCypher as its primary query language for graph traversals and algorithm invocations. It does not support Gremlin. If your application currently uses Gremlin against Neptune Database, you will need to translate those queries to openCypher for Neptune Analytics. The openCypher query model covers the same graph patterns (node/edge traversals, pattern matching, path queries) as Gremlin, but the syntax is different. Neptune Database continues to support Gremlin for transactional queries; Neptune Analytics is purely openCypher.

### What is the maximum graph size Neptune Analytics supports?
Neptune Analytics supports graphs up to 64 GB of graph memory capacity in a single analytics graph. For the largest graphs (billions of edges), you partition the graph into multiple Neptune Analytics graphs and run algorithms on each partition, then aggregate results. The in-memory model is what enables sub-second algorithm execution — the constraint is that your graph must fit in the allocated memory capacity. As of 2025–2026, this covers most enterprise graph analytics use cases; very large social networks or global transaction graphs may require partitioning strategies.

### Can I run Neptune Analytics without a Neptune Database (from S3 directly)?
Yes — Neptune Analytics can load graphs directly from S3-hosted CSV or Parquet files in the Neptune bulk load format, without a Neptune Database instance as an intermediary. This is particularly useful for organizations that generate graph data from relational databases (using Glue ETL to produce edge/node CSV files) or from streaming pipelines (writing graph snapshots to S3). The S3 import path also supports loading graphs from AWS data lakes without running a persistent Neptune Database cluster, which reduces cost for use cases that only need periodic graph analytics.

### How do I integrate Neptune Analytics results back into my application?
Neptune Analytics returns query results as standard openCypher result sets — lists of nodes, edges, properties, and algorithm scores. For fraud detection, you typically write the WCC (Weakly Connected Components) component IDs and suspicion scores back to a relational database (RDS or DynamoDB) or to S3 as a Parquet file, then join these enrichment scores with your operational transaction data for case management. For recommendation engines, you write the KNN similarity scores to a low-latency key-value store (DynamoDB or ElastiCache) so that the application can look up recommendations in real time without querying Neptune Analytics on the critical path.

---

*Source: https://www.factualminds.com/blog/amazon-neptune-analytics-graph-vector/*
