The Semantic Layer, From ScratchChapter 7
Chapter 7

Where the data lives: materialized vs virtual

Two ways to give your ontology real data, why we leave ours in Aurora and Redshift, and the plumbing that lets Stardog reach them.

Try a quick experiment in your head. Open Studio, pick kit-c360, and ask for Carolyn Inworth: SELECT * { ?c :name "Carolyn Inworth" }. You'd get nothing. After two chapters of modeling, your database knows exactly what a :Customer is, but not a single actual customer. You built the table definitions, and the rows are sitting in Aurora and Redshift. This chapter is about how Stardog gets at them, and the choice you make here (copy them in, or reach out to them) shapes cost, freshness, security and speed for the rest of the book.

7.1The schema is loaded; the rows are not

Recall Chapter 5's split. The T-Box is the vocabulary (161 triples in <urn:stardog:training-c360:1.0:model>). The A-Box is facts about individual things: "customer 225 is named Carolyn Inworth". In Postgres the DDL and the rows live in one engine; in Stardog the A-Box can live somewhere else entirely. Ours does:

EngineDatabase / schemaTables (rows)
Aurora PostgreSQL 16 (Serverless v2)c360 / c360customer (500), address (500), credit_card (500), rewards_account (500)
Redshift Serverlessdev / c360purchase (2,500), product (580), category (31), vendor (418)

7.2Option one: materialize (copy the triples in)

A materialized graph is instance data that has been converted to triples and stored inside Stardog's own storage. Your INSERT DATA triples about Carolyn in Chapters 2 to 4 were a tiny version. At scale you'd bulk-load: export tables to CSV or Turtle, or run a mapping once with the admin command virtual import, which writes the resulting triples into a database. The original c360 knowledge kit works this way: its loader turns US_Customers.csv, Purchases.csv and friends into triples stored in kit-c360, and queries never leave Stardog.

7.3Option two: virtualize (leave the data where it is)

A virtual graph is a named graph whose triples are never stored in Stardog. Instead, Stardog holds three things: a connection to the source database, a mapping that says how rows turn into triples (Chapter 8), and a name such as virtual://aurora_c360_safe. When a query touches that graph, Stardog translates that part into SQL, sends it to the source, and turns the returned rows into triples on the fly. Only query results cross the wire.

The technique that makes this workable is pushdown: instead of dragging whole tables into Stardog and filtering there, Stardog pushes as much work as it can (filters, joins within one source, aggregates, limits) into the SQL it sends, because the database has the indexes (the Stardog docs call it moving the compute to the data). For example: ask for customers in Texas and Aurora receives … WHERE state = 'Texas', returning 53 rows instead of 500. Chapter 9 takes pushdown apart on a real federated query.

Materialized is a copy; virtual is a view that runs fresh SQL every time.

Example: like CREATE MATERIALIZED VIEW vs CREATE VIEW.

Materialized Virtual Aurora customers Redshift purchases nightly load job (ETL) Stardog storage a full copy of every triple query reads the copy: fast, but as old as the last load Aurora rows stay here Redshift rows stay here Stardog mappings + data sources only SQL at query time SQL at query time query is translated and sent out: always current, as fast as the sources
Figure 7.1: Two ways to bind an A-Box. Left: copy the triples into Stardog. Right: keep the rows in the source and translate each query into SQL.

7.4Choosing between them

Neither option is "the right one". The trade-offs are the ones between a federated query engine and a warehouse.

ConcernMaterializedVirtual
FreshnessAs old as the last load. Needs a refresh pipeline.Live. A row inserted a second ago shows up in the next query.
Query speedFastest. Everything is in Stardog's indexes.Bounded by the sources plus network. Our Aurora cold start adds 15 to 20 seconds after a pause.
Load on the sourcesOnly during the load.Every query hits your production databases.
Storage and costYou pay to store a second copy (and the Free tier has limits).No copy. You pay the sources' compute per query (Redshift's 60-second minimum, for example).
GovernanceSensitive data now lives in a second system needing its own controls, retention and audit.Data stays in the system of record. Stardog controls who can query which graph (Chapter 12); the source keeps its own grants.
AvailabilityQueries work even if a source is down.If Redshift is paused or unreachable, queries that touch it fail or wait.
Changing the mappingRe-run the load to see the change.Edit the mapping; the next query uses it.

For this book we go virtual, for three reasons that match the AWS blog's pitch: we want live answers ("no ETL" is the point of the demo); SSNs and card numbers should stay in Aurora, not be copied into another store; and a semantic layer over existing systems is the architecture the book teaches, which materializing would quietly turn back into a warehouse project.

Real deployments often mix the two: virtualize big, sensitive or fast-changing tables; materialize small, stable reference data. (Stardog also has virtual-graph caching as a middle ground; check the docs for your tier.)

7.5Data sources: the connection half of a virtual graph

Stardog splits a virtual graph into two separately managed objects. The first is the data source: a named, reusable connection to an external database, plus a cached copy of its metadata (schemas, tables, columns, types, keys, row-count estimates). The other half, the mapping, is Chapter 8. The split lets one connection serve many graphs: in Chapter 8 you'll build two virtual graphs over the one data source aurora_c360, with the password stored in one place.

The connection itself is JDBC (Java Database Connectivity): the standard Java API for SQL databases, with a vendor-supplied driver per database. DBeaver uses it too, which is why the settings will look familiar.

Your two data sources were created during the AWS setup; you won't change them. Their options, as Stardog reports them:

Optionaurora_c360 (PostgreSQL)redshift_c360 (Redshift)
jdbc.urljdbc:postgresql://<your-aurora-cluster>.us-west-2.rds.amazonaws.com:5432/c360?sslmode=requirejdbc:redshift://<your-workgroup>.us-west-2.redshift-serverless.amazonaws.com:5439/dev
jdbc.driverorg.postgresql.Drivercom.amazon.redshift.jdbc42.Driver
jdbc.username / jdbc.password(masked)(masked)
sql.schemasc360c360
initialSize00
minIdle / maxIdle0 / 00 / 0
testOnBorrowtruetrue
timeBetweenEvictionRunsMillis00

7.6The connection pool, and why minIdle=0 saves money

A connection pool keeps database connections open between queries so the next query doesn't pay for a new TCP connection, a TLS handshake and a login. Stardog's JDBC data sources use a standard Java pool (the Tomcat JDBC pool), and its defaults suit an always-on database: open 3 connections up front (initialSize=3), keep idle ones, and validate them periodically in the background.

Ours sleeps on purpose. Aurora Serverless v2 is configured with a minimum of 0 ACUs and auto-pause after 5 minutes of inactivity; while paused, compute costs $0. But Aurora only pauses when there are no open user connections, so a pool holding three idle connections means it bills $0.12 per ACU-hour around the clock. Nothing breaks; the bill just never drops to zero.

So both data sources use the settings the Stardog docs list for disabling pooling:

SettingValueEffect
initialSize0Don't open connections when the data source loads.
minIdle0Never keep a minimum number of idle connections around.
maxIdle0When a query finishes, close its connection instead of parking it.
testOnBorrowtrueCheck a connection is alive before using it (useful when the database may have just woken up).
timeBetweenEvictionRunsMillis0Turn off the background thread that would otherwise touch idle connections.

The price: every query opens a fresh connection (a few hundred milliseconds), and the first query after a pause waits 15 to 20 seconds while Aurora resumes. A fine trade for a sandbox; in production you'd keep a pool and a nonzero minimum capacity.

Redshift Serverless bills differently: $0.375 per RPU-hour with a 60-second minimum per wake-up, $0 when idle. With pooling off it runs only when a query needs it, and our cap of 1 RPU-hour per day (about 15 minutes at 4 RPUs) is plenty for the book.

7.7The network path: Stardog Cloud's egress IP and your security group

Stardog Cloud runs in Stardog's AWS account, not yours. Its SQL reaches Aurora over the public internet, made safe enough for a sandbox by two things.

First, Stardog Cloud sends all virtual-graph traffic from a single, published egress IP per region: for us-west-2, where our endpoint lives, 44.238.183.206 (DNS name produswest2-vg-egress.stardog.cloud).

Second, the Aurora cluster and the Redshift workgroup sit behind a security group (AWS's stateful firewall) that allows inbound traffic on 5432 (Postgres) and 5439 (Redshift) from exactly two /32 addresses: Stardog's egress IP and your laptop's IP (so DBeaver works). Everything else is dropped. Both databases need a public IP (two of the $0.005/hour IPv4 charges in the cost sheet) so Stardog Cloud can reach them.

Stardog Cloud Stardog's AWS account, us-west-2 data sources (JDBC) egress 44.238.183.206 Your laptop DBeaver, Studio in the browser internet (TLS) security group inbound 5432, 5439 from 44.238.183.206/32 and your-ip/32 only Aurora PostgreSQL :5432 db c360, schema c360 0 to 1 ACU, pauses after 5 min idle customer, address, card, rewards Redshift Serverless :5439 db dev, schema c360 4 RPU, 60 s minimum, 1 RPU-hr/day cap purchase, product, category, vendor
Figure 7.2: Who can reach the databases. Only two addresses get through the security group: Stardog Cloud's published egress IP and your laptop.

Know the limit: every Stardog Cloud tenant in us-west-2 shares that egress IP, so the allowlist means "Stardog Cloud", not "your endpoint"; the password and TLS do the real work. Production setups use private networking, such as the blog's self-managed Stardog on EKS inside your VPC.

7.8Try it: look, don't touch

This part is read-only: look at the two data sources in Studio and the rows in DBeaver, so you know exactly what Chapter 8 turns into triples.

Check yourself

In one sentence each, what is a materialized graph and what is a virtual graph?

A materialized graph is triples copied into Stardog's own storage (like loading a warehouse). A virtual graph's triples are produced on demand: Stardog translates the query into SQL, runs it on the source, and turns the rows into triples.

Name two reasons this book virtualizes the c360 data instead of loading it.

Any two of: answers must be live (no ETL, which is the point of the demo); sensitive columns such as SSNs should stay in the system of record rather than be copied; the book teaches a semantic layer over existing systems; changing a mapping takes effect immediately without a reload.

What is the difference between a data source and a virtual graph?

A data source is the connection (JDBC URL, driver, credentials, options such as sql.schemas and pool settings) plus cached metadata. A virtual graph is a name plus a mapping that uses a data source. One shared data source can back several virtual graphs, as aurora_c360 will back both aurora_c360_safe and aurora_c360_pii.

Why would Stardog's default connection pool keep Aurora from ever auto-pausing, and which settings prevent that?

The default pool opens connections up front and keeps idle ones open, and Aurora only pauses with no connections, so it would bill around the clock. initialSize=0, minIdle=0, maxIdle=0, timeBetweenEvictionRunsMillis=0 (plus testOnBorrow=true) close connections after each query and stop background activity.

A mapping fails with "Object 'customer' not found within 'c360'", yet DBeaver shows c360.customer. What do you check?

The data source's sql.schemas option. Without c360 listed, Stardog's metadata only covers the default schema (public). If the option is set and a recently added table is still missing, refresh the data source's metadata (schema drift).

Why does the security group allow 44.238.183.206, and what does that allowlist not protect against?

That's Stardog Cloud's published virtual-graph egress IP for us-west-2, so every SQL query from Stardog arrives from it. Every Stardog Cloud tenant in the region shares it, so the allowlist doesn't distinguish your endpoint from anyone else's; credentials and TLS do the real work. Private networking (for example Stardog on EKS in your VPC) removes the public exposure.

Chapter recap