Skip to main content

Book 4 · The AI Solutions Architect · 8 min read

How to Build a GDPR-Compliant RAG Deletion Pipeline

A GDPR deletion request covers five stores: Qdrant, Redis, LangGraph state, the RAGAS dataset, and an immutable audit log. SHA-256 hashing resolves the audit log conflict — no PII to delete. The automated pipeline ran in 3 minutes 14 seconds and issued a signed deletion receipt.

How to Build a GDPR-Compliant RAG Deletion Pipeline

A GDPR deletion request is not a single database delete. A RAG system stores customer data in at least five places. Missing one means the deletion is incomplete — and a 72-hour SLA breach is a regulatory event.

The Valid Failure

Before Book 4, ShopBot's deletion process was manual. When a tenant customer filed a GDPR deletion request, a developer received an email, looked through five systems, deleted what they could find, and sent a confirmation.

The first compliance audit found that the process took an average of 4 hours and 12 minutes. The GDPR requirement is 72 hours — so the timeline was technically met. But the audit also found that two previous deletions had missed the RAGAS evaluation dataset: a labeled CSV file that contained customer session transcripts. The system had been logging real queries to build the evaluation set, and the CSV had not been included in the deletion inventory.

No deletion process is reliable unless it is automated and covers a complete, written-down inventory of every store that holds customer data.

The 5-Store Deletion Inventory

For ShopBot's six-tenant architecture, a customer's data lived in five places:

Store What it holds Deletion mechanism
Qdrant (per-tenant collection) Chunk payloads with session metadata delete by filter on customer_id hash
Redis Session memory (active conversations) delete by session key
LangGraph state In-flight pipeline state Flush on session close
RAGAS evaluation dataset Labeled query-answer pairs Row deletion from CSV/database
Audit log (DynamoDB) Immutable query events Cannot delete — SHA-256 hash resolves this

The audit log conflict — how do you delete from an immutable log? — is the architecturally interesting problem.

Resolving the Audit Log Conflict

The audit log must be immutable (tamper-evident, regulatory requirement). It also must not contain PII that is subject to deletion (GDPR requirement).

The resolution: store a SHA-256 hash of the customer identifier, not the identifier itself.

import hashlib

def tenant_scoped_hash(customer_id: str, tenant_id: str) -> str:
    """One-way hash that is unique per (customer, tenant) pair."""
    raw = f"{tenant_id}:{customer_id}"
    return hashlib.sha256(raw.encode()).hexdigest()

# Usage
customer_hash = tenant_scoped_hash("user@example.com", "vastralaya")
# → "a3f8d2c1..." (irreversible)

# Store in audit log — no email address, no PII
audit_event = {
    "customer_hash": customer_hash,  # not the email
    "tenant_id": "vastralaya",
    "query_fingerprint": hashlib.sha256(query.encode()).hexdigest(),
    "timestamp": datetime.utcnow().isoformat(),
    "event_type": "QUERY",
}

The SHA-256 hash is not reversible. Deleting the email address from other systems destroys the link between the hash and the person. The audit log entry remains — but it no longer identifies anyone. The deletion is GDPR-compliant.

The Automated Deletion Pipeline

import boto3
import redis
import hashlib
from datetime import datetime, timezone

r = redis.Redis()
dynamodb = boto3.resource("dynamodb", region_name="ap-south-1")

def delete_customer_data(customer_id: str, tenant_id: str) -> dict:
    customer_hash = tenant_scoped_hash(customer_id, tenant_id)
    collection = f"shopbot_{tenant_id}"
    results = {}
    started_at = datetime.now(timezone.utc)

    # 1. Qdrant — delete chunks tagged with this customer's session IDs
    from qdrant_client.models import Filter, FieldCondition, MatchValue
    qdrant_client.delete(
        collection_name=collection,
        points_selector=Filter(must=[
            FieldCondition(key="customer_hash", match=MatchValue(value=customer_hash))
        ]),
    )
    results["qdrant"] = "deleted"

    # 2. Redis — delete all session keys for this customer
    session_pattern = f"session:{tenant_id}:{customer_hash}:*"
    for key in r.scan_iter(session_pattern):
        r.delete(key)
    results["redis"] = "deleted"

    # 3. LangGraph state — already gone (TTL on session close)
    results["langgraph"] = "ttl_expired"

    # 4. RAGAS evaluation dataset
    remove_from_ragas_dataset(customer_hash, tenant_id)
    results["ragas_dataset"] = "deleted"

    # 5. Audit log — no PII stored, hash only, nothing to delete
    results["audit_log"] = "no_pii_stored_sha256_only"

    completed_at = datetime.now(timezone.utc)
    elapsed = (completed_at - started_at).total_seconds()

    # Issue deletion receipt
    receipt = {
        "customer_hash": customer_hash,
        "tenant_id": tenant_id,
        "requested_at": started_at.isoformat(),
        "completed_at": completed_at.isoformat(),
        "elapsed_seconds": elapsed,
        "stores_processed": results,
        "status": "COMPLETE",
    }

    # Write receipt to audit table (non-PII)
    audit_table = dynamodb.Table("shopbot-audit")
    audit_table.put_item(Item={
        **receipt,
        "event_type": "GDPR_DELETION_RECEIPT",
    })

    return receipt

The Deletion Receipt

The deletion receipt is the evidence that the process ran and completed. It is stored in the audit table (non-PII — it contains only the customer hash, not the customer identifier).

{
  "customer_hash": "a3f8d2c1...",
  "tenant_id": "vastralaya",
  "requested_at": "2026-07-28T09:14:22Z",
  "completed_at": "2026-07-28T09:17:36Z",
  "elapsed_seconds": 194,
  "stores_processed": {
    "qdrant": "deleted",
    "redis": "deleted",
    "langgraph": "ttl_expired",
    "ragas_dataset": "deleted",
    "audit_log": "no_pii_stored_sha256_only"
  },
  "status": "COMPLETE"
}

In Book 4, the deletion pipeline ran in 3 minutes and 14 seconds. The 72-hour GDPR window was met with 71 hours and 57 minutes to spare.

The Valid Knowledge

  • Write the inventory first: before building the pipeline, list every system that stores any customer data. The deletion pipeline is only as complete as the inventory.
  • SHA-256 + tenant scoping resolves the immutable log conflict: the hash is irreversible. When the email is deleted from other systems, the hash becomes an orphan — it no longer identifies anyone.
  • Tenant scoping prevents hash collision across tenants: sha256("vastralaya:user@example.com")sha256("jaipur_threads:user@example.com"). The same customer using two tenants has two different hashes — their cross-tenant identity is not linkable from the hash alone.
  • The receipt is regulatory evidence: when an audit asks "prove you deleted this customer's data within 72 hours," the receipt is the answer. Store it.
  • 72 hours is the maximum, not the target: the pipeline should run in minutes. The 72-hour window is for edge cases (manual investigation, disputed requests). The automated path should complete in under 5 minutes.

This is the concept. The book is the system.

Book 4: The AI Solutions Architect

Articles give you understanding. The book gives you a working system — full production code, RAGAS evaluation scores, and the patterns that hold up at 11 PM when something breaks.