{"schemaVersion":1,"id":"https://lucioduran.com/es/blog/graph-databases-neo4j-surrealdb-age-comparison","url":"https://lucioduran.com/es/blog/graph-databases-neo4j-surrealdb-age-comparison","title":"Bases de Datos de Grafos: Neo4j vs SurrealDB vs Apache AGE, Cypher, GQL y Optimización Real de Queries de Red Social","description":"Una comparación práctica de tres enfoques de bases de datos de grafos, Neo4j (grafo nativo), SurrealDB (multi-modelo) y Apache AGE (extensión PostgreSQL). Cubre Cypher vs el estándar GQL emergente, modelos de property graph y optimización real de queries de un proyecto de red social.","language":"es","datePublished":"2025-11-24","dateModified":null,"contentFormat":"text/markdown","content":"Databases\n\n# Bases de Datos de Grafos: Neo4j vs SurrealDB vs Apache AGE, Cypher, GQL y Optimización Real de Queries de Red Social\n\n2025-11-248 min lectura\n\nLucio Durán\nEngineering Manager & AI Solutions Architect\n\nTambién disponible en: [English](<https://lucioduran.com/blog/graph-databases-neo4j-surrealdb-age-comparison>)\n\n## El Modelo de Datos\n\nUna feature de red social requiere: usuarios, follows, posts, likes, comentarios e \"intereses\" (temas con los que los usuarios se etiquetan). El modelo de grafo:\n\n```\n(:User)-[:FOLLOWS]->(:User)\n(:User)-[:POSTED]->(:Post)\n(:User)-[:LIKED]->(:Post)\n(:User)-[:COMMENTED {text: \"...\"}]->(:Post)\n(:User)-[:INTERESTED_IN]->(:Topic)\n(:Post)-[:TAGGED_WITH]->(:Topic)\n```\n\nUnos 500K usuarios, 2M relaciones de follow, 1.5M posts, 8M likes. No es masivo de ninguna manera, pero suficiente para exponer diferencias de performance.\n\n## Neo4j: El Grafo Nativo\n\n[Neo4j](<https://neo4j.com/>) almacena datos como nodos y relaciones en un formato de grafo nativo, index-free adjacency significa que cada nodo apunta físicamente a sus vecinos. Esto hace que las operaciones de traversal sean constant-time por hop, independientemente del tamaño total del grafo.\n\n### Setup e Ingesta\n\n```cypher\n// Crear constraints e índices\nCREATE CONSTRAINT user_email IF NOT EXISTS\nFOR (u:User) REQUIRE u.email IS UNIQUE;\n\nCREATE INDEX user_name IF NOT EXISTS\nFOR (u:User) ON (u.name);\n\nCREATE INDEX post_created IF NOT EXISTS\nFOR (p:Post) ON (p.createdAt);\n\n// Import batch con LOAD CSV\nLOAD CSV WITH HEADERS FROM 'file:///users.csv' AS row\nCREATE (u:User {\n id: row.id,\n name: row.name,\n email: row.email,\n joinedAt: datetime(row.joinedAt)\n});\n```\n\nLa ingesta de 500K usuarios + relaciones tomó unos 90 segundos usando ` neo4j-admin import ` (bulk load offline). El approach con LOAD CSV tardó 12 minutos. Para migraciones de producción, siempre utilizar la herramienta admin.\n\n### Ejemplos de Queries\n\nEl pan y la manteca, \"¿a quién debería seguir este usuario?\" basado en conexiones mutuas e intereses compartidos:\n\n```cypher\n// Recomendación amigo-de-amigo excluyendo follows existentes\nMATCH (me:User {id: $userId})-[:FOLLOWS]->(friend)-[:FOLLOWS]->(suggestion)\nWHERE NOT (me)-[:FOLLOWS]->(suggestion)\n AND suggestion <> me\nWITH suggestion, count(friend) AS mutualFriends\nORDER BY mutualFriends DESC\nLIMIT 20\nRETURN suggestion.name, suggestion.id, mutualFriends\n```\n\nEsto corrió en 15ms en nuestro dataset. El query planner usa el índice en ` User.id `, después traversa dos hops a través de la relación FOLLOWS. La agregación ` count(friend) ` es donde pasa el ranking.\n\nUna más compleja, \"encontrá el camino más corto entre dos usuarios a través de su grafo de follows\":\n\n```cypher\nMATCH path = shortestPath(\n (a:User {id: $userA})-[:FOLLOWS*..6]-(b:User {id: $userB})\n)\nRETURN [node IN nodes(path) | node.name] AS names,\n length(path) AS degrees\n```\n\nEse ` *..6 ` es crítico. Sin un límite de profundidad, esta query va a explorar todo el componente conectado, que en un grafo social significa potencialmente millones de nodos. Se establece en 6 por la teoría de los seis grados de separación, si no hay camino dentro de 6 hops, los usuarios no están significativamente conectados para propósitos típicos de UX.\n\n### Optimización de Performance\n\nLa optimización de mayor impacto en Neo4j es **profilear tus queries**:\n\n```cypher\nPROFILE MATCH (me:User {id: $userId})-[:FOLLOWS]->(friend)-[:FOLLOWS]->(suggestion)\nWHERE NOT (me)-[:FOLLOWS]->(suggestion)\nRETURN suggestion.name, count(friend) AS mutual\nORDER BY mutual DESC LIMIT 20\n```\n\nEl prefijo ` PROFILE ` te muestra el plan de ejecución de la query con conteos de filas reales. El análisis reveló que una query de \"posts trending en tu red\" estaba haciendo un ` AllNodesScan ` en Post en vez de usar el índice de fecha, agregar un filtro ` WHERE p.createdAt > datetime() - duration('P7D') ` antes del traversal de relaciones cortó la query de 2.3s a 80ms.\n\n## SurrealDB: El Enfoque Multi-Modelo\n\n[SurrealDB](<https://surrealdb.com/>) maneja grafos a través de su sistema de record links, cualquier campo puede referenciar otro registro usando sintaxis ` record_id `, y es posible traversar estos links con notación de flecha en SurrealQL.\n\n```sql\n-- Definir esquema\nDEFINE TABLE user SCHEMAFULL;\nDEFINE FIELD name ON user TYPE string;\nDEFINE FIELD email ON user TYPE string;\nDEFINE INDEX email_idx ON user FIELDS email UNIQUE;\n\nDEFINE TABLE follows SCHEMAFULL TYPE RELATION\n FROM user TO user;\nDEFINE FIELD since ON follows TYPE datetime DEFAULT time::now();\n\nDEFINE TABLE post SCHEMAFULL;\nDEFINE FIELD content ON post TYPE string;\nDEFINE FIELD author ON post TYPE record<user>;\nDEFINE FIELD createdAt ON post TYPE datetime DEFAULT time::now();\n```\n\n### Traversal de Grafo en SurrealQL\n\nLa sintaxis de flechas es sorprendentemente limpia:\n\n```sql\n-- Follows directos\nSELECT name FROM user:alice->follows->user;\n\n-- Amigo-de-amigo (2 hops)\nSELECT name, count() AS mutual FROM\n user:$userId->follows->user->follows->user\nWHERE id NOT IN (SELECT VALUE out FROM follows WHERE in = user:$userId)\n AND id != user:$userId\nGROUP BY id\nORDER BY mutual DESC\nLIMIT 20;\n```\n\nLa misma query de recomendación que tardó 15ms en Neo4j corrió en 45ms en SurrealDB con hardware equivalente. No está mal para una base de datos que también maneja queries de documentos y relacionales, pero la brecha se ensancha en traversals más profundos.\n\nDonde SurrealDB brilla es en workloads mixtos. Considérese una query de feed que necesita tanto traversal de grafo (¿a quién sigo?) como agregación estilo documento (¿cuáles son sus posts recientes, rankeados por engagement?):\n\n```sql\n-- Feed: posts de usuarios seguidos, rankeados por engagement\nSELECT\n content,\n author.name AS authorName,\n createdAt,\n count(->liked_by) AS likes,\n count(->comments) AS commentCount\nFROM post\nWHERE author IN (SELECT VALUE out FROM follows WHERE in = user:$userId)\n AND createdAt > time::now() - 7d\nORDER BY likes DESC, createdAt DESC\nLIMIT 50;\n```\n\nEn Neo4j, esto requeriría combinar traversal Cypher con búsqueda full-text y agregación, se puede pero es menos natural.\n\n## Apache AGE: Grafo sobre PostgreSQL\n\nApache AGE (A Graph Extension) agrega soporte de openCypher a PostgreSQL. Tus datos de grafo viven en tablas de PostgreSQL con un formato interno específico, pero los se consulta con sintaxis Cypher envuelta en SQL.\n\n```sql\n-- Habilitar la extensión\nCREATE EXTENSION IF NOT EXISTS age;\nLOAD 'age';\nSET search_path = ag_catalog, \"$user\", public;\n\n-- Crear un grafo\nSELECT create_graph('social');\n\n-- Crear nodos\nSELECT * FROM cypher('social', $$\n CREATE (u:User {id: 'alice', name: 'Alice', email: 'alice@example.com'})\n RETURN u\n$$) AS (u agtype);\n\n-- Crear relaciones\nSELECT * FROM cypher('social', $$\n MATCH (a:User {id: 'alice'}), (b:User {id: 'bob'})\n CREATE (a)-[:FOLLOWS {since: '2025-01-15'}]->(b)\n$$) AS (result agtype);\n```\n\n### El Puente SQL-Cypher\n\nLa feature asesina es mezclar SQL y Cypher en la misma query:\n\n```sql\n-- Usar tablas regulares de PostgreSQL junto con queries de grafo\nSELECT\n u.display_name,\n u.avatar_url,\n graph_data.mutual_count\nFROM users u\nJOIN (\n SELECT * FROM cypher('social', $$\n MATCH (me:User {id: $userId})-[:FOLLOWS]->(friend)-[:FOLLOWS]->(suggestion)\n WHERE NOT (me)-[:FOLLOWS]->(suggestion) AND suggestion <> me\n RETURN suggestion.id AS user_id, count(friend) AS mutual_count\n ORDER BY mutual_count DESC\n LIMIT 20\n $$, $1) AS (user_id agtype, mutual_count agtype)\n) AS graph_data ON u.id = graph_data.user_id::text;\n```\n\nEste es el superpoder de AGE, es posible agregar queries de grafo a una aplicación PostgreSQL existente sin migrar datos a una base de datos separada. La tabla ` users ` con datos de perfil se queda en PostgreSQL regular; el grafo social vive en AGE.\n\n### Realidad de Performance\n\nLa misma recomendación amigo-de-amigo: 85ms en AGE vs 15ms en Neo4j. El overhead viene del executor de PostgreSQL planeando la query Cypher como una subquery, cada hop pasa por el planner regular en vez de traversal nativo de grafo.\n\nPara queries de hasta 3 hops, AGE es absolutamente competitivo. Más allá de eso, el overhead se compone:\n\n```\n1 hop: AGE 8ms vs Neo4j 3ms (2.7x)\n2 hops: AGE 35ms vs Neo4j 15ms (2.3x)\n3 hops: AGE 85ms vs Neo4j 25ms (3.4x)\n4 hops: AGE 340ms vs Neo4j 55ms (6.2x)\n5 hops: AGE 1.8s vs Neo4j 120ms (15x)\n```\n\nEl ratio empeora porque el index-free adjacency de Neo4j es O(1) por hop mientras AGE hace lookups de índice a través del B-tree de PostgreSQL para cada nivel.\n\n## GQL: El Estándar del Futuro\n\nISO publicó GQL (ISO/IEC 39075) en 2024 como el lenguaje de query de grafos estándar. Toma mucho de Cypher pero agrega features como tipos de grafo, patrones de camino y un sistema de tipos más riguroso.\n\n```gql\n-- Sintaxis GQL (en evolución, esto es aproximado)\nGRAPH social\nMATCH (me:User WHERE me.id = $userId)\n -/:FOLLOWS/-> (friend)\n -/:FOLLOWS/-> (suggestion)\nWHERE NOT EXISTS (me -/:FOLLOWS/-> suggestion)\n AND suggestion <> me\nRETURN suggestion.name, COUNT(friend) AS mutual\nORDER BY mutual DESC\nLIMIT 20;\n```\n\nLa diferencia clave con Cypher: GQL usa sintaxis de patrones de camino (` -/:FOLLOWS/-> `) y selección explícita de grafo (` GRAPH social `). Neo4j se comprometió con soporte GQL, y bases de datos más nuevas como Memgraph y Kuzu lo están implementando.\n\nPor ahora, Cypher sigue siendo la opción práctica. La adopción de GQL está donde estaba SQL a principios de los 90, el estándar existe, pero las implementaciones están poniéndose al día.\n\n## Recomendaciones por Caso de Uso\n\nPara una feature de grafo social greenfield: **Neo4j** si las queries de grafo son el workload principal y es posible justificar el costo operacional de una base de datos separada. La performance de queries y el tooling (Neo4j Browser, biblioteca APOC) no tienen rival.\n\nPara agregar queries de grafo a una app PostgreSQL existente: **Apache AGE** sin dudar. La capacidad de hacer JOIN de resultados de grafo con tablas regulares es una ventaja masiva, y la performance es suficientemente buena para queries de 3 hops que cubre la mayoría de case es de uso reales.\n\nPara un proyecto nuevo con requisitos mixtos (grafo + documento + real-time): **SurrealDB** vale la pena evaluarlo. El enfoque multi-modelo reduce la complejidad operacional. Solo entrá con los ojos abiertos sobre la madurez del optimizador de queries.\n\nEl modelo de grafo no es la parte difícil. La parte difícil es saber cuándo tus datos son realmente un grafo, y cuándo se está forzando un problema relacional en una forma de grafo porque el lenguaje de queries se ve más lindo.\n\n## Preguntas frecuentes\n\n### ¿Cuándo debería usar una base de datos de grafos en vez de una relacional?\n\nCuando tus queries son relationship-centric, 'encontrá todos los caminos entre A y B', 'mostrá N grados de conexión', 'detectar ciclos en un grafo de dependencias'. Si mayormente se hace lookups key-value o agregaciones, una base relacional con JOIN está bien. El punto de equilibrio suele estar alrededor de 3+ JOINs en tablas auto-referenciales, donde el traversal de grafo se vuelve más rápido y más legible.\n\n### ¿SurrealDB está listo para producción con workloads de grafo?\n\nA principios de 2026, SurrealDB es usable para workloads de grafo moderados pero le falta la madurez del optimizador de queries de Neo4j. Su fortaleza es el enfoque multi-modelo, si se necesita queries de documentos, grafos y relacionales en el mismo sistema, es atractivo. Para workloads puros de grafo a escala, Neo4j sigue siendo la apuesta más segura.\n\n### ¿Qué es GQL y cómo se relaciona con Cypher?\n\nGQL (Graph Query Language) es el estándar ISO (ISO/IEC 39075) para bases de datos de grafos, publicado en 2024. Está fuertemente influenciado por Cypher (el lenguaje de queries de Neo4j) y openCypher. Considerar en GQL como lo que SQL es para bases de datos relacionales, un estándar vendor-neutral. Neo4j se está moviendo hacia compatibilidad con GQL, y nuevas bases de datos lo están implementando nativamente.\n\n## Tips\n\n1. **Tip:** Nunca traverses más de 4 hops en una query de grafo social sin límite de profundidad. Una lección común: una query de 'amigos de amigos de amigos' en 500K usuarios tardó 47 segundos en Neo4j, agregar ` *1..3 ` la bajó a 200ms.\n2. **Tip:** Si tus queries de grafo siempre arrancan desde un nodo conocido, indexá la propiedad de lookup de ese nodo. Un ` MATCH (u:User {email: $email}) ` sin índice hace un full node scan, clusters de Neo4j pueden llegar a 100% CPU por un índice faltante en un campo usado en cada query.\n3. **Tip:** Apache AGE es brillante para agregar queries de grafo a una app PostgreSQL existente, pero no intentes migrar un workload de grafo completo a él. El overhead del executor de Postgres en traversals profundos es real, realizar pruebas de rendimiento antes de comprometerte.\n\n- graph-databases\n- neo4j\n- surrealdb\n- apache-age\n- cypher\n- gql\n- property-graphs\n- optimizacion-de-queries\n- red-social\n- bases-de-datos\n\n[Volver al Blog](<https://lucioduran.com/es/blog>)","headings":[{"level":1,"text":"Bases de Datos de Grafos: Neo4j vs SurrealDB vs Apache AGE, Cypher, GQL y Optimización Real de Queries de Red Social","url":"https://lucioduran.com/es/blog/graph-databases-neo4j-surrealdb-age-comparison#title"},{"level":2,"text":"El Modelo de Datos","url":"https://lucioduran.com/es/blog/graph-databases-neo4j-surrealdb-age-comparison#el-modelo-de-datos"},{"level":2,"text":"Neo4j: El Grafo Nativo","url":"https://lucioduran.com/es/blog/graph-databases-neo4j-surrealdb-age-comparison#neo4j-el-grafo-nativo"},{"level":3,"text":"Setup e Ingesta","url":"https://lucioduran.com/es/blog/graph-databases-neo4j-surrealdb-age-comparison#setup-e-ingesta"},{"level":3,"text":"Ejemplos de Queries","url":"https://lucioduran.com/es/blog/graph-databases-neo4j-surrealdb-age-comparison#ejemplos-de-queries"},{"level":3,"text":"Optimización de Performance","url":"https://lucioduran.com/es/blog/graph-databases-neo4j-surrealdb-age-comparison#optimizacin-de-performance"},{"level":2,"text":"SurrealDB: El Enfoque Multi-Modelo","url":"https://lucioduran.com/es/blog/graph-databases-neo4j-surrealdb-age-comparison#surrealdb-el-enfoque-multi-modelo"},{"level":3,"text":"Traversal de Grafo en SurrealQL","url":"https://lucioduran.com/es/blog/graph-databases-neo4j-surrealdb-age-comparison#traversal-de-grafo-en-surrealql"},{"level":2,"text":"Apache AGE: Grafo sobre PostgreSQL","url":"https://lucioduran.com/es/blog/graph-databases-neo4j-surrealdb-age-comparison#apache-age-grafo-sobre-postgresql"},{"level":3,"text":"El Puente SQL-Cypher","url":"https://lucioduran.com/es/blog/graph-databases-neo4j-surrealdb-age-comparison#el-puente-sql-cypher"},{"level":3,"text":"Realidad de Performance","url":"https://lucioduran.com/es/blog/graph-databases-neo4j-surrealdb-age-comparison#realidad-de-performance"},{"level":2,"text":"GQL: El Estándar del Futuro","url":"https://lucioduran.com/es/blog/graph-databases-neo4j-surrealdb-age-comparison#gql-el-estndar-del-futuro"},{"level":2,"text":"Recomendaciones por Caso de Uso","url":"https://lucioduran.com/es/blog/graph-databases-neo4j-surrealdb-age-comparison#recomendaciones-por-caso-de-uso"},{"level":2,"text":"Preguntas frecuentes","url":"https://lucioduran.com/es/blog/graph-databases-neo4j-surrealdb-age-comparison#questions"},{"level":3,"text":"¿Cuándo debería usar una base de datos de grafos en vez de una relacional?","url":"https://lucioduran.com/es/blog/graph-databases-neo4j-surrealdb-age-comparison#question-1"},{"level":3,"text":"¿SurrealDB está listo para producción con workloads de grafo?","url":"https://lucioduran.com/es/blog/graph-databases-neo4j-surrealdb-age-comparison#question-2"},{"level":3,"text":"¿Qué es GQL y cómo se relaciona con Cypher?","url":"https://lucioduran.com/es/blog/graph-databases-neo4j-surrealdb-age-comparison#question-3"},{"level":2,"text":"Tips","url":"https://lucioduran.com/es/blog/graph-databases-neo4j-surrealdb-age-comparison#article-tips"}],"sections":[{"heading":"Bases de Datos de Grafos: Neo4j vs SurrealDB vs Apache AGE, Cypher, GQL y Optimización Real de Queries de Red Social","level":1,"url":"https://lucioduran.com/es/blog/graph-databases-neo4j-surrealdb-age-comparison","content":"Databases"},{"heading":"Bases de Datos de Grafos: Neo4j vs SurrealDB vs Apache AGE, Cypher, GQL y Optimización Real de Queries de Red Social","level":1,"url":"https://lucioduran.com/es/blog/graph-databases-neo4j-surrealdb-age-comparison#title","content":"2025-11-248 min lectura\n\nLucio Durán\nEngineering Manager & AI Solutions Architect\n\nTambién disponible en: [English](<https://lucioduran.com/blog/graph-databases-neo4j-surrealdb-age-comparison>)"},{"heading":"El Modelo de Datos","level":2,"url":"https://lucioduran.com/es/blog/graph-databases-neo4j-surrealdb-age-comparison#el-modelo-de-datos","content":"Una feature de red social requiere: usuarios, follows, posts, likes, comentarios e \"intereses\" (temas con los que los usuarios se etiquetan). El modelo de grafo:\n\n```\n(:User)-[:FOLLOWS]->(:User)\n(:User)-[:POSTED]->(:Post)\n(:User)-[:LIKED]->(:Post)\n(:User)-[:COMMENTED {text: \"...\"}]->(:Post)\n(:User)-[:INTERESTED_IN]->(:Topic)\n(:Post)-[:TAGGED_WITH]->(:Topic)\n```\n\nUnos 500K usuarios, 2M relaciones de follow, 1.5M posts, 8M likes. No es masivo de ninguna manera, pero suficiente para exponer diferencias de performance."},{"heading":"Neo4j: El Grafo Nativo","level":2,"url":"https://lucioduran.com/es/blog/graph-databases-neo4j-surrealdb-age-comparison#neo4j-el-grafo-nativo","content":"[Neo4j](<https://neo4j.com/>) almacena datos como nodos y relaciones en un formato de grafo nativo, index-free adjacency significa que cada nodo apunta físicamente a sus vecinos. Esto hace que las operaciones de traversal sean constant-time por hop, independientemente del tamaño total del grafo."},{"heading":"Setup e Ingesta","level":3,"url":"https://lucioduran.com/es/blog/graph-databases-neo4j-surrealdb-age-comparison#setup-e-ingesta","content":"```cypher\n// Crear constraints e índices\nCREATE CONSTRAINT user_email IF NOT EXISTS\nFOR (u:User) REQUIRE u.email IS UNIQUE;\n\nCREATE INDEX user_name IF NOT EXISTS\nFOR (u:User) ON (u.name);\n\nCREATE INDEX post_created IF NOT EXISTS\nFOR (p:Post) ON (p.createdAt);\n\n// Import batch con LOAD CSV\nLOAD CSV WITH HEADERS FROM 'file:///users.csv' AS row\nCREATE (u:User {\n id: row.id,\n name: row.name,\n email: row.email,\n joinedAt: datetime(row.joinedAt)\n});\n```\n\nLa ingesta de 500K usuarios + relaciones tomó unos 90 segundos usando ` neo4j-admin import ` (bulk load offline). El approach con LOAD CSV tardó 12 minutos. Para migraciones de producción, siempre utilizar la herramienta admin."},{"heading":"Ejemplos de Queries","level":3,"url":"https://lucioduran.com/es/blog/graph-databases-neo4j-surrealdb-age-comparison#ejemplos-de-queries","content":"El pan y la manteca, \"¿a quién debería seguir este usuario?\" basado en conexiones mutuas e intereses compartidos:\n\n```cypher\n// Recomendación amigo-de-amigo excluyendo follows existentes\nMATCH (me:User {id: $userId})-[:FOLLOWS]->(friend)-[:FOLLOWS]->(suggestion)\nWHERE NOT (me)-[:FOLLOWS]->(suggestion)\n AND suggestion <> me\nWITH suggestion, count(friend) AS mutualFriends\nORDER BY mutualFriends DESC\nLIMIT 20\nRETURN suggestion.name, suggestion.id, mutualFriends\n```\n\nEsto corrió en 15ms en nuestro dataset. El query planner usa el índice en ` User.id `, después traversa dos hops a través de la relación FOLLOWS. La agregación ` count(friend) ` es donde pasa el ranking.\n\nUna más compleja, \"encontrá el camino más corto entre dos usuarios a través de su grafo de follows\":\n\n```cypher\nMATCH path = shortestPath(\n (a:User {id: $userA})-[:FOLLOWS*..6]-(b:User {id: $userB})\n)\nRETURN [node IN nodes(path) | node.name] AS names,\n length(path) AS degrees\n```\n\nEse ` *..6 ` es crítico. Sin un límite de profundidad, esta query va a explorar todo el componente conectado, que en un grafo social significa potencialmente millones de nodos. Se establece en 6 por la teoría de los seis grados de separación, si no hay camino dentro de 6 hops, los usuarios no están significativamente conectados para propósitos típicos de UX."},{"heading":"Optimización de Performance","level":3,"url":"https://lucioduran.com/es/blog/graph-databases-neo4j-surrealdb-age-comparison#optimizacin-de-performance","content":"La optimización de mayor impacto en Neo4j es **profilear tus queries**:\n\n```cypher\nPROFILE MATCH (me:User {id: $userId})-[:FOLLOWS]->(friend)-[:FOLLOWS]->(suggestion)\nWHERE NOT (me)-[:FOLLOWS]->(suggestion)\nRETURN suggestion.name, count(friend) AS mutual\nORDER BY mutual DESC LIMIT 20\n```\n\nEl prefijo ` PROFILE ` te muestra el plan de ejecución de la query con conteos de filas reales. El análisis reveló que una query de \"posts trending en tu red\" estaba haciendo un ` AllNodesScan ` en Post en vez de usar el índice de fecha, agregar un filtro ` WHERE p.createdAt > datetime() - duration('P7D') ` antes del traversal de relaciones cortó la query de 2.3s a 80ms."},{"heading":"SurrealDB: El Enfoque Multi-Modelo","level":2,"url":"https://lucioduran.com/es/blog/graph-databases-neo4j-surrealdb-age-comparison#surrealdb-el-enfoque-multi-modelo","content":"[SurrealDB](<https://surrealdb.com/>) maneja grafos a través de su sistema de record links, cualquier campo puede referenciar otro registro usando sintaxis ` record_id `, y es posible traversar estos links con notación de flecha en SurrealQL.\n\n```sql\n-- Definir esquema\nDEFINE TABLE user SCHEMAFULL;\nDEFINE FIELD name ON user TYPE string;\nDEFINE FIELD email ON user TYPE string;\nDEFINE INDEX email_idx ON user FIELDS email UNIQUE;\n\nDEFINE TABLE follows SCHEMAFULL TYPE RELATION\n FROM user TO user;\nDEFINE FIELD since ON follows TYPE datetime DEFAULT time::now();\n\nDEFINE TABLE post SCHEMAFULL;\nDEFINE FIELD content ON post TYPE string;\nDEFINE FIELD author ON post TYPE record<user>;\nDEFINE FIELD createdAt ON post TYPE datetime DEFAULT time::now();\n```"},{"heading":"Traversal de Grafo en SurrealQL","level":3,"url":"https://lucioduran.com/es/blog/graph-databases-neo4j-surrealdb-age-comparison#traversal-de-grafo-en-surrealql","content":"La sintaxis de flechas es sorprendentemente limpia:\n\n```sql\n-- Follows directos\nSELECT name FROM user:alice->follows->user;\n\n-- Amigo-de-amigo (2 hops)\nSELECT name, count() AS mutual FROM\n user:$userId->follows->user->follows->user\nWHERE id NOT IN (SELECT VALUE out FROM follows WHERE in = user:$userId)\n AND id != user:$userId\nGROUP BY id\nORDER BY mutual DESC\nLIMIT 20;\n```\n\nLa misma query de recomendación que tardó 15ms en Neo4j corrió en 45ms en SurrealDB con hardware equivalente. No está mal para una base de datos que también maneja queries de documentos y relacionales, pero la brecha se ensancha en traversals más profundos.\n\nDonde SurrealDB brilla es en workloads mixtos. Considérese una query de feed que necesita tanto traversal de grafo (¿a quién sigo?) como agregación estilo documento (¿cuáles son sus posts recientes, rankeados por engagement?):\n\n```sql\n-- Feed: posts de usuarios seguidos, rankeados por engagement\nSELECT\n content,\n author.name AS authorName,\n createdAt,\n count(->liked_by) AS likes,\n count(->comments) AS commentCount\nFROM post\nWHERE author IN (SELECT VALUE out FROM follows WHERE in = user:$userId)\n AND createdAt > time::now() - 7d\nORDER BY likes DESC, createdAt DESC\nLIMIT 50;\n```\n\nEn Neo4j, esto requeriría combinar traversal Cypher con búsqueda full-text y agregación, se puede pero es menos natural."},{"heading":"Apache AGE: Grafo sobre PostgreSQL","level":2,"url":"https://lucioduran.com/es/blog/graph-databases-neo4j-surrealdb-age-comparison#apache-age-grafo-sobre-postgresql","content":"Apache AGE (A Graph Extension) agrega soporte de openCypher a PostgreSQL. Tus datos de grafo viven en tablas de PostgreSQL con un formato interno específico, pero los se consulta con sintaxis Cypher envuelta en SQL.\n\n```sql\n-- Habilitar la extensión\nCREATE EXTENSION IF NOT EXISTS age;\nLOAD 'age';\nSET search_path = ag_catalog, \"$user\", public;\n\n-- Crear un grafo\nSELECT create_graph('social');\n\n-- Crear nodos\nSELECT * FROM cypher('social', $$\n CREATE (u:User {id: 'alice', name: 'Alice', email: 'alice@example.com'})\n RETURN u\n$$) AS (u agtype);\n\n-- Crear relaciones\nSELECT * FROM cypher('social', $$\n MATCH (a:User {id: 'alice'}), (b:User {id: 'bob'})\n CREATE (a)-[:FOLLOWS {since: '2025-01-15'}]->(b)\n$$) AS (result agtype);\n```"},{"heading":"El Puente SQL-Cypher","level":3,"url":"https://lucioduran.com/es/blog/graph-databases-neo4j-surrealdb-age-comparison#el-puente-sql-cypher","content":"La feature asesina es mezclar SQL y Cypher en la misma query:\n\n```sql\n-- Usar tablas regulares de PostgreSQL junto con queries de grafo\nSELECT\n u.display_name,\n u.avatar_url,\n graph_data.mutual_count\nFROM users u\nJOIN (\n SELECT * FROM cypher('social', $$\n MATCH (me:User {id: $userId})-[:FOLLOWS]->(friend)-[:FOLLOWS]->(suggestion)\n WHERE NOT (me)-[:FOLLOWS]->(suggestion) AND suggestion <> me\n RETURN suggestion.id AS user_id, count(friend) AS mutual_count\n ORDER BY mutual_count DESC\n LIMIT 20\n $$, $1) AS (user_id agtype, mutual_count agtype)\n) AS graph_data ON u.id = graph_data.user_id::text;\n```\n\nEste es el superpoder de AGE, es posible agregar queries de grafo a una aplicación PostgreSQL existente sin migrar datos a una base de datos separada. La tabla ` users ` con datos de perfil se queda en PostgreSQL regular; el grafo social vive en AGE."},{"heading":"Realidad de Performance","level":3,"url":"https://lucioduran.com/es/blog/graph-databases-neo4j-surrealdb-age-comparison#realidad-de-performance","content":"La misma recomendación amigo-de-amigo: 85ms en AGE vs 15ms en Neo4j. El overhead viene del executor de PostgreSQL planeando la query Cypher como una subquery, cada hop pasa por el planner regular en vez de traversal nativo de grafo.\n\nPara queries de hasta 3 hops, AGE es absolutamente competitivo. Más allá de eso, el overhead se compone:\n\n```\n1 hop: AGE 8ms vs Neo4j 3ms (2.7x)\n2 hops: AGE 35ms vs Neo4j 15ms (2.3x)\n3 hops: AGE 85ms vs Neo4j 25ms (3.4x)\n4 hops: AGE 340ms vs Neo4j 55ms (6.2x)\n5 hops: AGE 1.8s vs Neo4j 120ms (15x)\n```\n\nEl ratio empeora porque el index-free adjacency de Neo4j es O(1) por hop mientras AGE hace lookups de índice a través del B-tree de PostgreSQL para cada nivel."},{"heading":"GQL: El Estándar del Futuro","level":2,"url":"https://lucioduran.com/es/blog/graph-databases-neo4j-surrealdb-age-comparison#gql-el-estndar-del-futuro","content":"ISO publicó GQL (ISO/IEC 39075) en 2024 como el lenguaje de query de grafos estándar. Toma mucho de Cypher pero agrega features como tipos de grafo, patrones de camino y un sistema de tipos más riguroso.\n\n```gql\n-- Sintaxis GQL (en evolución, esto es aproximado)\nGRAPH social\nMATCH (me:User WHERE me.id = $userId)\n -/:FOLLOWS/-> (friend)\n -/:FOLLOWS/-> (suggestion)\nWHERE NOT EXISTS (me -/:FOLLOWS/-> suggestion)\n AND suggestion <> me\nRETURN suggestion.name, COUNT(friend) AS mutual\nORDER BY mutual DESC\nLIMIT 20;\n```\n\nLa diferencia clave con Cypher: GQL usa sintaxis de patrones de camino (` -/:FOLLOWS/-> `) y selección explícita de grafo (` GRAPH social `). Neo4j se comprometió con soporte GQL, y bases de datos más nuevas como Memgraph y Kuzu lo están implementando.\n\nPor ahora, Cypher sigue siendo la opción práctica. La adopción de GQL está donde estaba SQL a principios de los 90, el estándar existe, pero las implementaciones están poniéndose al día."},{"heading":"Recomendaciones por Caso de Uso","level":2,"url":"https://lucioduran.com/es/blog/graph-databases-neo4j-surrealdb-age-comparison#recomendaciones-por-caso-de-uso","content":"Para una feature de grafo social greenfield: **Neo4j** si las queries de grafo son el workload principal y es posible justificar el costo operacional de una base de datos separada. La performance de queries y el tooling (Neo4j Browser, biblioteca APOC) no tienen rival.\n\nPara agregar queries de grafo a una app PostgreSQL existente: **Apache AGE** sin dudar. La capacidad de hacer JOIN de resultados de grafo con tablas regulares es una ventaja masiva, y la performance es suficientemente buena para queries de 3 hops que cubre la mayoría de case es de uso reales.\n\nPara un proyecto nuevo con requisitos mixtos (grafo + documento + real-time): **SurrealDB** vale la pena evaluarlo. El enfoque multi-modelo reduce la complejidad operacional. Solo entrá con los ojos abiertos sobre la madurez del optimizador de queries.\n\nEl modelo de grafo no es la parte difícil. La parte difícil es saber cuándo tus datos son realmente un grafo, y cuándo se está forzando un problema relacional en una forma de grafo porque el lenguaje de queries se ve más lindo."},{"heading":"Preguntas frecuentes","level":2,"url":"https://lucioduran.com/es/blog/graph-databases-neo4j-surrealdb-age-comparison#questions","content":""},{"heading":"¿Cuándo debería usar una base de datos de grafos en vez de una relacional?","level":3,"url":"https://lucioduran.com/es/blog/graph-databases-neo4j-surrealdb-age-comparison#question-1","content":"Cuando tus queries son relationship-centric, 'encontrá todos los caminos entre A y B', 'mostrá N grados de conexión', 'detectar ciclos en un grafo de dependencias'. Si mayormente se hace lookups key-value o agregaciones, una base relacional con JOIN está bien. El punto de equilibrio suele estar alrededor de 3+ JOINs en tablas auto-referenciales, donde el traversal de grafo se vuelve más rápido y más legible."},{"heading":"¿SurrealDB está listo para producción con workloads de grafo?","level":3,"url":"https://lucioduran.com/es/blog/graph-databases-neo4j-surrealdb-age-comparison#question-2","content":"A principios de 2026, SurrealDB es usable para workloads de grafo moderados pero le falta la madurez del optimizador de queries de Neo4j. Su fortaleza es el enfoque multi-modelo, si se necesita queries de documentos, grafos y relacionales en el mismo sistema, es atractivo. Para workloads puros de grafo a escala, Neo4j sigue siendo la apuesta más segura."},{"heading":"¿Qué es GQL y cómo se relaciona con Cypher?","level":3,"url":"https://lucioduran.com/es/blog/graph-databases-neo4j-surrealdb-age-comparison#question-3","content":"GQL (Graph Query Language) es el estándar ISO (ISO/IEC 39075) para bases de datos de grafos, publicado en 2024. Está fuertemente influenciado por Cypher (el lenguaje de queries de Neo4j) y openCypher. Considerar en GQL como lo que SQL es para bases de datos relacionales, un estándar vendor-neutral. Neo4j se está moviendo hacia compatibilidad con GQL, y nuevas bases de datos lo están implementando nativamente."},{"heading":"Tips","level":2,"url":"https://lucioduran.com/es/blog/graph-databases-neo4j-surrealdb-age-comparison#article-tips","content":"1. **Tip:** Nunca traverses más de 4 hops en una query de grafo social sin límite de profundidad. Una lección común: una query de 'amigos de amigos de amigos' en 500K usuarios tardó 47 segundos en Neo4j, agregar ` *1..3 ` la bajó a 200ms.\n2. **Tip:** Si tus queries de grafo siempre arrancan desde un nodo conocido, indexá la propiedad de lookup de ese nodo. Un ` MATCH (u:User {email: $email}) ` sin índice hace un full node scan, clusters de Neo4j pueden llegar a 100% CPU por un índice faltante en un campo usado en cada query.\n3. **Tip:** Apache AGE es brillante para agregar queries de grafo a una app PostgreSQL existente, pero no intentes migrar un workload de grafo completo a él. El overhead del executor de Postgres en traversals profundos es real, realizar pruebas de rendimiento antes de comprometerte.\n\n- graph-databases\n- neo4j\n- surrealdb\n- apache-age\n- cypher\n- gql\n- property-graphs\n- optimizacion-de-queries\n- red-social\n- bases-de-datos\n\n[Volver al Blog](<https://lucioduran.com/es/blog>)"}],"links":[{"text":"English","url":"https://lucioduran.com/blog/graph-databases-neo4j-surrealdb-age-comparison"},{"text":"Neo4j","url":"https://neo4j.com/"},{"text":"SurrealDB","url":"https://surrealdb.com/"},{"text":"Volver al Blog","url":"https://lucioduran.com/es/blog"}],"structuredData":[{"@context":"https://schema.org","@graph":[{"@type":"BlogPosting","@id":"https://lucioduran.com/es/blog/graph-databases-neo4j-surrealdb-age-comparison#article","mainEntityOfPage":{"@type":"WebPage","@id":"https://lucioduran.com/es/blog/graph-databases-neo4j-surrealdb-age-comparison"},"headline":"Bases de Datos de Grafos: Neo4j vs SurrealDB vs Apache AGE, Cypher, GQL y Optimización Real de Queries de Red Social","description":"Una comparación práctica de tres enfoques de bases de datos de grafos, Neo4j (grafo nativo), SurrealDB (multi-modelo) y Apache AGE (extensión PostgreSQL). Cubre Cypher vs el estándar GQL emergente, modelos de property graph y optimización real de queries de un proyecto de red social.","datePublished":"2025-11-24","author":{"@type":"Person","@id":"https://lucioduran.com/#person","name":"Lucio Durán","url":"https://lucioduran.com"},"publisher":{"@type":"Person","@id":"https://lucioduran.com/#person","name":"Lucio Durán","url":"https://lucioduran.com"},"image":"https://statics.lucioduran.com/images/lucioduran-social-card.jpg","url":"https://lucioduran.com/es/blog/graph-databases-neo4j-surrealdb-age-comparison","inLanguage":"es","isPartOf":{"@id":"https://lucioduran.com/blog#blog"},"articleSection":"Databases","keywords":"graph-databases, neo4j, surrealdb, apache-age, cypher, gql, property-graphs, optimizacion-de-queries, red-social, bases-de-datos","wordCount":1490,"timeRequired":"PT8M","workTranslation":[{"@type":"BlogPosting","inLanguage":"en","url":"https://lucioduran.com/blog/graph-databases-neo4j-surrealdb-age-comparison"}]},{"@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Inicio","item":"https://lucioduran.com/"},{"@type":"ListItem","position":2,"name":"Blog","item":"https://lucioduran.com/es/blog"},{"@type":"ListItem","position":3,"name":"Bases de Datos de Grafos: Neo4j vs SurrealDB vs Apache AGE, Cypher, GQL y Optimización Real de Queries de Red Social","item":"https://lucioduran.com/es/blog/graph-databases-neo4j-surrealdb-age-comparison"}]},{"@type":"FAQPage","mainEntity":[{"@type":"Question","name":"¿Cuándo debería usar una base de datos de grafos en vez de una relacional?","acceptedAnswer":{"@type":"Answer","text":"Cuando tus queries son relationship-centric, 'encontrá todos los caminos entre A y B', 'mostrá N grados de conexión', 'detectar ciclos en un grafo de dependencias'. Si mayormente se hace lookups key-value o agregaciones, una base relacional con JOIN está bien. El punto de equilibrio suele estar alrededor de 3+ JOINs en tablas auto-referenciales, donde el traversal de grafo se vuelve más rápido y más legible."}},{"@type":"Question","name":"¿SurrealDB está listo para producción con workloads de grafo?","acceptedAnswer":{"@type":"Answer","text":"A principios de 2026, SurrealDB es usable para workloads de grafo moderados pero le falta la madurez del optimizador de queries de Neo4j. Su fortaleza es el enfoque multi-modelo, si se necesita queries de documentos, grafos y relacionales en el mismo sistema, es atractivo. Para workloads puros de grafo a escala, Neo4j sigue siendo la apuesta más segura."}},{"@type":"Question","name":"¿Qué es GQL y cómo se relaciona con Cypher?","acceptedAnswer":{"@type":"Answer","text":"GQL (Graph Query Language) es el estándar ISO (ISO/IEC 39075) para bases de datos de grafos, publicado en 2024. Está fuertemente influenciado por Cypher (el lenguaje de queries de Neo4j) y openCypher. Considerar en GQL como lo que SQL es para bases de datos relacionales, un estándar vendor-neutral. Neo4j se está moviendo hacia compatibilidad con GQL, y nuevas bases de datos lo están implementando nativamente."}}]}]}],"contentHash":"55f78f73a701b43a42531f789f662500fca4a7596a3a69d5a6815773ef1689e1","provenance":{"method":"Deterministic serialization of the server-rendered main element","source":"https://lucioduran.com/es/blog/graph-databases-neo4j-surrealdb-age-comparison","hashAlgorithm":"sha256","hashScope":"JSON document fields before contentHash and provenance","sourceHTMLBytes":68551,"contentBytes":13111}}
