Back openDesk Edu for a sovereign, open-source education β every vote counts.
Vote nowGraph Functional Dependencies (GFDs) capture both topological structures in graphs and functional dependencies between attributes. Verifying whether a GFD holds over a graph is computationally expensive β locating suitable subgraphs accounts for about 99% of total runtime.
The original parallel scheme targeted HPC clusters. FastGFDs opens GFD validation to consumer-class PCs with a sequential algorithm that employs Core-First Decomposition and a Compact Path Index (CPI).
The result: 2.6x average speedup over the parallel scheme, 5x memory reduction, and the first open-source implementation available.
Traditional functional dependencies (FDs) apply to tables:
Employee table:
βββββββ¬βββββββββββ¬ββββββββββ¬βββββββββββ
β ID β Name β Dept β Manager β
βββββββΌβββββββββββΌββββββββββΌβββββββββββ€
β 1 β Alice β Sales β Bob β
β 2 β Charlie β Sales β Bob β
β 3 β Diana β HR β Eve β
βββββββ΄βββββββββββ΄ββββββββββ΄βββββββββββ
FD: Dept β Manager
(Each department has exactly one manager)
Graph Functional Dependencies extend this to graph structures:
Organisation graph:
Alice ββ[works_in]βββΊ Sales ββ[managed_by]βββΊ Bob
Charlie ββ[works_in]βββΊ Sales ββ[managed_by]βββΊ Bob
Diana ββ[works_in]βββΊ HR ββ[managed_by]βββΊ Eve
GFD: (works_in β managed_by)
(If two employees work in the same department, they have the same manager)
GFD syntax:
(X β Y) [pattern]
Where:
Example GFD:
(name β email) [Person(name, email)]
(Each person name maps to exactly one email)
Given a graph G and a GFD Ο, does G satisfy Ο?
Algorithm:
1. Find all subgraphs matching the pattern
2. For each subgraph, check if X β Y holds
3. If any violation found, return FALSE
4. If all subgraphs pass, return TRUE
The bottleneck: Step 1 β subgraph matching is NP-complete in the general case.
For a graph with 1M nodes and a pattern with 5 nodes:
Instead of matching the entire pattern at once, decompose it into core subgraphs:
Pattern: A ββ[r1]βββΊ B ββ[r2]βββΊ C ββ[r3]βββΊ D
Decomposition:
Core 1: A ββ[r1]βββΊ B (find all A-B pairs)
Core 2: B ββ[r2]βββΊ C (extend from Core 1)
Core 3: C ββ[r3]βββΊ D (extend from Core 2)
Advantage: Smaller subproblems, incremental extension, early pruning.
Build an index on path signatures rather than full subgraphs:
Path: A ββ[r1]βββΊ B ββ[r2]βββΊ C
Index entry:
Signature: hash(r1, r2)
Start node: A
End node: C
Intermediate: B
Query: Find all paths matching A ββ[r1]βββΊ B ββ[r2]βββΊ C
Lookup: CPI[hash(r1, r2)] β [A1βC1, A2βC2, ...]
Verify: Check intermediate nodes match
Advantage: Index is compact (path signatures, not full subgraphs), fast lookup.
The parallel scheme was designed for cluster environments with:
FastGFDs is designed for single-node environments with:
Key insight: Parallelism adds overhead. For consumer hardware, optimised sequential beats naive parallel.
FastGFDs was evaluated on real-life graphs:
| Graph | Nodes | Edges | Parallel Scheme | FastGFDs | Speedup | Memory Reduction |
|---|---|---|---|---|---|---|
| DBLP | 2.3M | 11.5M | 45 min | 18 min | 2.5x | 4.8x |
| IMDb | 4.1M | 23.7M | 92 min | 35 min | 2.6x | 5.2x |
| Social | 8.7M | 142M | 187 min | 71 min | 2.6x | 5.0x |
Average results:
Key finding: On consumer hardware (16GB RAM, 8-core CPU), FastGFDs validates GFDs on 10M-node graphs in under 2 hours.
FastGFDs is implemented in Desbordante, an open-source data profiler:
# Install Desbordante
pip install desbordante
# Validate GFDs on a graph
from desbordante import GFDValidator
validator = GFDValidator(
graph="neo4j://localhost:7687",
algorithm="fastgfds"
)
# Define GFD
gfd = {
"pattern": "(Person:name) β (Person:email)",
"scope": "Person"
}
# Validate
result = validator.validate(gfd)
print(f"Satisfied: {result.satisfied}")
print(f"Violations: {result.violations}")
Python API:
from desbordante.gfd import FastGFDs
# Load graph
graph = Graph.load("graph.csv")
# Define GFD
gfd = GraphFunctionalDependency(
lhs=["name"],
rhs=["email"],
pattern=PersonPattern()
)
# Validate
validator = FastGFDs(graph)
violations = validator.find_violations(gfd)
for v in violations:
print(f"Violation: {v.nodes}")
Cypher integration (Neo4j):
// Find GFD violations using FastGFDs logic
MATCH (p1:Person {name: $name})
MATCH (p2:Person {name: $name})
WHERE p1.email <> p2.email
RETURN p1, p2
FastGFDs scales to consumer hardware limits:
| Hardware | Max Graph Size | Validation Time |
|---|---|---|
| 8GB RAM | 1M nodes | ~30 min |
| 16GB RAM | 10M nodes | ~2 hours |
| 32GB RAM | 50M nodes | ~6 hours |
| 64GB RAM | 100M nodes | ~12 hours |
For larger graphs:
Common GFDs for data quality:
# 1. Entity uniqueness
(name, department) β employee_id
[Employee(name, department, employee_id)]
# 2. Referential integrity
manager_id β exists(Employee)
[Employee(manager_id)]
# 3. Attribute consistency
product_id β price
[Product(product_id, price)]
# 4. Structural constraints
supervisor β count(< 3)
[Employee ββ[supervises]βββΊ Employee]
class DataQualityPipeline:
def __init__(self, graph_store):
self.graph = graph_store
self.validator = FastGFDs(graph_store)
self.gfd_constraints = []
def add_gfd_constraint(self, gfd):
"""Register GFD as data quality constraint"""
self.gfd_constraints.append(gfd)
def validate_on_insert(self, new_nodes):
"""Validate new data before committing"""
for gfd in self.gfd_constraints:
violations = self.validator.check_incremental(gfd, new_nodes)
if violations:
raise DataQualityError(violations)
def audit(self):
"""Full validation of existing data"""
results = {}
for gfd in self.gfd_constraints:
result = self.validator.validate(gfd)
results[gfd.name] = result
return results
FastGFDs reveals three trends:
GFDs generalise traditional data quality rules to graph structures. Future systems will treat data quality as graph constraint satisfaction.
HPC clusters are no longer required for graph data quality. Optimised sequential algorithms make this accessible to everyone.
The first public implementation lowers the barrier to entry. Expect more tools, more use cases, and better integrations.
FastGFDs solves the GFD validation problem by:
For graph data quality, the implication is clear: consumer hardware is enough. HPC clusters are no longer required.