Blog Post

Azure Database for PostgreSQL Blog
5 MIN READ

General Availability of Graph Database Support in Azure Database for PostgreSQL

JoshMSFT's avatar
JoshMSFT
Icon for Microsoft rankMicrosoft
May 19, 2025

We are excited to announce the general availability of the Apache AGE extension for Azure Database for PostgreSQL! This marks a significant milestone in empowering developers and businesses to harness the potential of graph data directly within their PostgreSQL environments, offering fully managed graph database service. 

Unlocking Graph Data Capabilities

Apache AGE (A Graph Extension) is a powerful PostgreSQL extension. It allows users to store and query graph data within Postgres seamlessly, enabling advanced insights through intuitive graph database queries via the openCypher query language.

Graph data is instrumental in applications such as social networks, recommendation systems, fraud detection, network analysis, and knowledge graphs. By integrating Apache AGE into Azure Database for PostgreSQL, developers can now benefit from a unified platform that supports both relational and graph data models, unlocking deeper insights and streamlining data workflows.

Benefits of Using Apache AGE in Azure Database for PostgreSQL

The integration of Apache AGE (AGE) in Azure Database for PostgreSQL brings numerous benefits to developers and businesses looking to leverage graph processing capabilities:

  • Enterprise-grade Managed Graph Database Service: AGE in Azure Database for PostgreSQL provides a fully managed graph database solution, eliminating infrastructure management while delivering built-in security, updates, and high availability.
  • Simplified Data Management: AGE's ability to integrate graph and relational data simplifies data management tasks, reducing the need for separate graph database solutions.
  • Enhanced Data Analysis: With AGE, you can perform complex graph analyses directly within your PostgreSQL database, gaining deeper insights into relationships and patterns in your data.
  • Cost Efficiency: By utilizing AGE within Azure Database for PostgreSQL, you can consolidate your database infrastructure, lowering overall costs and reducing the complexity of your data architecture.
  • Security and Compliance: Leverage Azure's industry-leading security and compliance features, ensuring your graph data is protected and meets regulatory requirements.
  • Index Support: Index graph properties with BTREE and GIN indexes.

Real-World Applications

Apache AGE opens up a range of possibilities for graph-powered applications. Here are just a few examples:

  • Social Networks: Model and analyze complex relationships, such as user connections and interactions.
  • Fraud Detection: Identify suspicious patterns and connections in financial transactions.
  • Recommendation Systems: Leverage graph data to deliver personalized product or content recommendations.
  • Knowledge Graphs: Structure facts and concepts as nodes and relationships, enabling AI-driven search and data discovery.

In the following example, we need to provide Procurement with an updated status of all statements of work (SOW) by vendor, including their invoice status. With AGE and Postgres, this once complex task becomes quite simple.  

We’ll start by creating the empty graph.

SELECT ag_catalog.create_graph('vendor_graph');

 

Then, we’ll create all the ‘vendor’ nodes from the vendors table.

SELECT *
FROM ag_catalog.cypher(
  'vendor_graph',
  $$
    UNWIND $rows AS v
    CREATE (:vendor { id: v.id, name: v.name })
  $$,
  ARRAY(
    SELECT jsonb_build_object('id', id, 'name', name)
    FROM vendors
  )
);

Next, we’ll create all the ‘sow’ nodes.

SELECT *
FROM ag_catalog.cypher(
  'vendor_graph',
  $$
    UNWIND $rows AS s
    CREATE (:sow { id: s.id, number: s.number })
  $$,
  ARRAY(
    SELECT jsonb_build_object('id', id, 'number', number)
    FROM sows
  )
);

 

Then, we’ll create the ‘has_invoices’ relationships (edges).

SELECT *
FROM ag_catalog.cypher(
  'vendor_graph',
  $$
    UNWIND $rows AS r
    MATCH (v:vendor { id: r.vendor_id })
    MATCH (s:sow    { id: r.sow_id    })
    CREATE (v)-[:has_invoices { payment_status: r.payment_status,
                                amount:         r.invoice_amount }]->(s)
  $$,
  ARRAY(
    SELECT jsonb_build_object(
      'vendor_id',      vendor_id,
      'sow_id',         sow_id,
      'payment_status', payment_status,
      'invoice_amount', amount
    )
    FROM invoices
  )
);

 

Now that we’ve completed these steps, we have a fully populated vendor_graph with vendor nodes, sow nodes, and has_invoices edges with the invoice attributes. We’re ready to query the graph to start our report for Procurement.

 

 

 

SELECT *
FROM ag_catalog.cypher('vendor_graph' , $$
MATCH (v:vendor)-[rel:has_invoices]->(s:sow)
RETURN v.id AS vendor_id, v.name AS vendor_name, s.id AS sow_id, s.number AS sow_number, rel.payment_status AS payment_status, rel.amount AS invoice_amount $$) AS graph_query(vendor_id BIGINT, vendor_name TEXT, sow_id BIGINT, sow_number TEXT, payment_status TEXT, invoice_amount FLOAT);

 

This statement invokes Apache AGE’s Cypher engine that treats our graph as a relational table:

 

  ag_catalog.cypher('vendor_graph', $$ … $$)  

 

  executes the Cypher query against the graph named “vendor_graph.”

 

  • The inner Cypher fragment,
MATCH (v:vendor)-[rel:has_invoices]->(s:sow)  
  RETURN  
    v.id      AS vendor_id,  
    v.name    AS vendor_name,  
    s.id      AS sow_id,  
    s.number  AS sow_number,  
    rel.payment_status AS payment_status,  
    rel.amount         AS invoice_amount 

 

  • finds every vendor node with outgoing has_invoices edges to SOW nodes
  • projects each vendor’s ID/name, the target sow’s ID/number, and invoice attributes.

 

  • Wrapping that in

  

 … ) AS graph_query(  
      vendor_id BIGINT,  
      vendor_name TEXT,  
      sow_id    BIGINT,  
      sow_number TEXT,  
      payment_status TEXT,  
      invoice_amount FLOAT  
    );  

 

  tells PostgreSQL how to map each returned column into a regular SQL result set with proper types.

 

The result? You get a standard table of rows—one per invoice edge—with those six columns populated and ready for further SQL joins, filters, aggregates, etc.

 

Performance notes for this example: 

  • AGE will scan all “vendor–has_invoices–sow” paths in the graph.
  • If the graph is large, consider an index on the vendor or sow label properties or filter by additional predicates.
  • You can also push WHERE clauses into the Cypher fragment for more selective matching.

Scaling to Large Graphs with AGE

The Apache AGE extension in Azure Database for PostgreSQL enables seamless scaling to large graphs. Indexing plays a pivotal role in enhancing query performance, particularly for complex graph analyses.

Effective Indexing Strategies

To optimize graph queries, particularly those involving joins or range queries, implementing the following indexes is recommended:

  • BTREE Index: Ideal for exact matches and range queries. For vertex tables, create an index on the unique identifier column (e.g., id).
    • CREATE INDEX ON graph_name."VLABEL" USING BTREE (id);

       

  • GIN Index: Designed for efficient searches within JSON fields, such as the properties column in vertex tables.
    • CREATE INDEX ON graph_name."VLABEL" USING GIN (properties);

       

  • Edge Table Indexes: For relationship traversal, use BTREE indexes on start_id and end_id columns.
    • CREATE INDEX ON graph_name."ELABEL" USING BTREE (start_id);
    • CREATE INDEX ON graph_name."ELABEL" USING BTREE (end_id);

Example: Targeted Key-Value Indexing

For targeted queries that focus on specific attributes within the JSON field, a smaller BTREE index can be created for precise filtering.

CREATE INDEX ON graph_name.label_name USING BTREE (agtype_access_operator(VARIADIC ARRAY[properties, '"KeyName"'::agtype]));

 

Using these indexing strategies ensures efficient query execution, even when scaling large graphs. Additionally, leveraging the EXPLAIN command helps validate index utilization and optimize query plans for production workloads.

How to Get Started

Enabling Apache AGE in Azure Database for PostgreSQL is simple:

1. Update Server Parameters

Within the Azure Portal, navigate to the PostgreSQL Flexible Server instance and select the Server Parameters option. Adjust the following settings:

  • azure.extensions: In the parameter filter, search for and enable AGE among the available extensions.

 

 

  • shared_preload_libraries: In the parameter filter, search for and enable AGE.

 

 

Click Save to apply these changes. The server will restart automatically to activate the AGE extension.

Note: Failure to enable the shared_preload_libraries will result in the following error when you first attempt to use the AGE schema in a query.

“ERROR: unhandled cypher(cstring) function call error on first cypher query”

2. Enable AGE Within PostgreSQL

Once the server restart is complete, connect to the PostgreSQL instance using the psql interpreter. Execute the following command to enable AGE:

CREATE EXTENSION IF NOT EXISTS AGE CASCADE;
3. Configure Schema Paths

AGE adds a schema called ag_catalog, which is essential for handling graph data. Ensure this schema is included in the search path by executing:

SET search_path=ag_catalog,"$user",public;

 

That’s it! You’re ready to create your first graph within PostgreSQL on Azure.

Ready to dive in?

Experience the power of graph data with Apache AGE on Azure Database for PostgreSQL. Visit AGE on Azure Database for PostgreSQL Overview for more details, and explore how this extension can transform your data analysis and application development.

Get started for free with an Azure free account

 

 

 

 

Updated May 18, 2025
Version 1.0

4 Comments

  • Can you confirm which regions this extension is available in?  I'm not seeing AGE as an extension option, nor shared lib option for an NZ North postgres 

    • JoshMSFT's avatar
      JoshMSFT
      Icon for Microsoft rankMicrosoft

      Thanks for your comment. Engineering took a look at the region and it should now be available. I deployed a test instance in New Zealand North just now and I see the extension available in the Server Parameters > extensions. 

      • DevranUenalBM's avatar
        DevranUenalBM
        Copper Contributor

        JoshMSFT​ is there an ETA when it will become available in Sweden Central?
        I just created a new server and the extension is not listed :(