---
title: "RisingWave, PHP, and the Streaming Database Idea"
url: https://www.exakat.io/risingwave-php-and-the-streaming-database-idea/
date: 2026-09-12
modified: 2026-09-12
lang: en
author: "dams"
description: "RisingWave, PHP, and the Streaming Database Idea A regular database answers the question you ask it, once, against whatever state happens to be sitting on disk at that moment. If..."
categories:
  - "Technology"
tags:
  - "database"
  - "php"
image: https://www.exakat.io/wp-content/uploads/2026/09/wave.320.jpg
word_count: 2146
---

# RisingWave, PHP, and the Streaming Database Idea

# RisingWave, PHP, and the Streaming Database Idea

A regular database answers the question you ask it, once, against whatever state happens to be sitting on disk at that moment. If the data changes a second later, your answer is stale until you ask again. This is why dashboards poll, and why "real-time analytics" has historically meant "batch job that runs every five minutes instead of every night." Streaming SQL engines, such as Storm, Kafka Streams, ksqlDB, or Flink SQL, grew up to close that gap, but they did it by putting a processing layer in front of a database, not by being one: you still needed somewhere to land the output.

A streaming database collapses that distinction. You write an ordinary `CREATE MATERIALIZED VIEW ... AS SELECT ...`, and instead of computing it once, the engine keeps it continuously correct: every insert that touches the view's inputs is incrementally folded into the result, and the query never has to run again from scratch. Materialize popularized the idea, and, tellingly, chose to speak Postgres's wire protocol rather than invent its own client ecosystem. [RisingWave](https://risingwave.com/), first released in 2022 and written in Rust, took the same premise of incremental view maintenance, not recomputation, and paired it with a Snowflake-style architecture: compute nodes that scale independently of storage, with S3 or a compatible object store as the actual source of truth underneath.

For this piece it's enough to know it as a database that happens to update its views for you. To make that concrete, imagine a coffee shop, BrewStream, whose orders you want to track as they arrive rather than re-query for. Now, let's use RisingWave to make this work and deliver delicious coffee.

## Setting up RisingWave

RisingWave ships a self-contained "playground" image for exactly this kind of exploration: one process, in-memory storage, gone after 30 minutes of inactivity. We'll use Docker for that.
`docker run -it --pull=always -p 4566:4566 -p 5691:5691 risingwavelabs/risingwave:latest playground`
Port 4566 is the SQL interface: this is the one PHP will talk to. Port 5691 is a dashboard, useful for watching a materialized view's fragments graph while you tinker, but nothing below depends on it.

Connect with `psql`, because RisingWave speaks the PostgreSQL wire protocol closely enough that the actual `psql` binary doesn't know the difference:
`psql -h localhost -p 4566 -U root`
PSA: we are not using password here. This is a playground, not a production environment.

## A table, same as any other

Nothing streaming-specific here yet. `coffee_orders` is a plain table you can `INSERT` into, exactly as PostgreSQL would let you. The streaming part will be added later, at the moment something else shows up and queries it continuously.

## The producer: pushing orders from PHP

There is no `risingwave/php-client` package published on Packagist, and there doesn't need to be one. RisingWave's front door is the Postgres wire protocol, and PHP has shipped a driver for that protocol since `pdo_pgsql` landed in PHP 5.1L a driver written years before RisingWave existed, which will connect to it without modification. The only requirement is the extension, which is part of the PHP core:
`php -m | grep pdo_pgsql`
Now, `producer.php` simulates BrewStream's influx of coffee orders, one at a time:

`php producer.php`
Ten coffee `INSERT`s over ten seconds. This is fine for a demo, and it represents a fair stream. A real BrewStream would front this with a CDC source or a Kafka topic rather than a PHP loop holding a PDO connection open; that distinction matters enough, so we'll come back to it at the end. For now, let's leave it aside.

## Reading the stream back, four ways

The interesting part isn't that PHP can read from RisingWave: any Postgres client can. It's that the same stored rows support four genuinely different query shapes without extra plumbing on RisingWave's side.

### Everything

This is basically a SQL database. Not impressive, but perfect to have some classic tools to start with a new technology.

### Filtered

This supports also the `WHERE` clause.

### Aggregated

And the `GROUP BY` clause.

Three ordinary SQL queries, indistinguishable from what you'd write against MySQL. The fourth one is where RisingWave stops pretending to be an ordinary database.

### Continuously aggregated

It looks a bit like SQL, but it has some important nuance. That `GROUP BY` doesn't get recomputed on read. RisingWave maintains it incrementally: every new row in `coffee_orders` nudges the count for its `coffee_type` rather than triggering a full rescan.

The naive way to read that back from PHP is what the previous three scripts already do: `SELECT * FROM coffee_orders_by_type` in a `while (true)` loop with a `sleep(2)`. It works, and it's also exactly the busy-polling this whole exercise was supposed to get away from: PHP asking "anything new?" on a timer, indistinguishable from hammering a plain table.

RisingWave has a better primitive for this, and PHP has one too: they just need introducing to each other.

On the RisingWave side, a subscription turns a table or materialized view into something you `FETCH`from rather than `SELECT` from, and the fetch can block on the server until a row actually shows up:

Each row that comes back carries an `op` column: `Insert`, `UpdateInsert`, `UpdateDelete`, or `Delete` — so a consumer sees not just the new count but what kind of change produced it.

That solves RisingWave's half of the problem: the server no longer has to be asked twice for the same unchanged answer. It doesn't yet solve PHP's half: calling that `FETCH` through an ordinary synchronous query still ties up the PHP process for up to five seconds, unable to do anything else, which is a strange way to treat "waiting for I/O" in a language whose sockets have supported non-blocking waits for decades. This is where `pg_socket()` earns its keep. It hands you the raw socket resource behind a PostgreSQL connection: it is a resource PDO's `pgsql` driver deliberately keeps hidden, which is why this one script switches to the lower-level `pgsql` extension instead. Once you have that socket, `stream_select()` can lock on it exactly the way it would block on a file, a pipe, or any other stream, which means "wait for RisingWave's next change" stops being a special case and becomes one more file descriptor in an ordinary I/O wait:

The shape looks similar to the polling version. After all, it is still a `while (true)`, still one iteration per change. But the wait inside it is now a real wait. `stream_select()` returns the moment data lands on the wire rather than on a fixed clock tick, and because it's a wait on a file descriptor rather than a `sleep()`, it composes: hand it a second socket and this loop watches two RisingWave subscriptions, or ten, without spinning a thread per connection. At ten orders a `sleep(2)` loop and this one are indistinguishable to the person watching the terminal. The difference only shows up under load, and under load is exactly when you can't afford a poll interval as your latency floor.

## Putting it together

- Start RisingWave (`playground` image).
- Create `coffee_orders` and `coffee_orders_by_type`.
- Run `producer.php` to generate orders.
- Run any consumer script against the same tables: plain reads and the continuously maintained aggregate coexist without conflict.

## The bigger picture: fewer moving parts, not just faster ones

The BrewStream example above is one PHP script `INSERT`-ing into a table it also owns: a fine way to learn the SQL, but not why anyone puts RisingWave in front of a real system. The actual case starts one layer up: a production Postgres or MySQL database that's the system of record for orders, and a reporting need that shouldn't be allowed anywhere near it.

The conventional answer is Change Data Capture: point Debezium at the database's write-ahead log or binlog, publish every row change to a Kafka topic, run a stream processor to reshape it, land the result somewhere queryable. That's four systems, namely connector, broker, processor, sink, to keep configured, monitored, and upgraded in lockstep, in service of one goal: let something read the data without touching the primary. RisingWave's CDC connectors fold all four into one `CREATE TABLE ... FROM cdc` statement, reading the source database's log directly and taking a consistent snapshot before it starts streaming, so the pipeline that used to be a small distributed system becomes a line of SQL.

What you get for that is the second half of the trade: heavy, bursty, or badly-written analytical queries, such as the `GROUP BY` a dashboard fires every ten seconds, the report someone runs at month-end, move onto RisingWave's copy of the data instead of the OLTP database that also has to process checkout. The primary stops being asked to be two things at once.

## PostgreSQL protocol everywhere!

And underneath both of those wins sits the detail that should, honestly, be a little startling: none of this required a new PHP driver. `pdo_pgsql` for the ordinary reads, the plain `pgsql` extension for the socket-level subscription — both predate RisingWave by well over a decade, and neither was written with the faintest idea it would one day be handed a `stream_select()`-driven subscription cursor to talk to. RisingWave's actual machinery, a distributed dataflow engine maintaining incremental views over object storage, has nothing in common with a single-node B-tree-and-WAL database. It doesn't matter. It answers on port 5432-shaped wire protocol like Postgres does, so every tool built assuming "the other end is Postgres": `psql`, `pdo_pgsql`, Grafana, dbt, whatever a PHP developer already had installed in 2007: it just works. Materialize made the same choice; so did CockroachDB, so did YugabyteDB. The wire protocol PostgreSQL happened to define has quietly become the thing every ambitious new database engine speaks at its front door, whatever it's doing in the basement. It is quite impressive to see the evolution from a protocol between a server and its client become an insdustry standard.

## Anoter tool for your database architecture

Databases are notoriously the slowest part of an online application. Data streaming is a modern tool to extract data that needs to be feed back in the application fast and in real time. With a materialized view, and some possible processing between ingestion and publication, RisingWave is a great tool to improve your application level of reactivity.