benwilson-512
The CDC to Clickhouse flow has been very fast and reliable, and then for us at least setting up some incremental materialized views that set up what we need for reporting has been perfect. Clickhouse’s storage efficiency has been mind boggling.
Is that much processing power necessary? Not even a little, but hey now it hosts its own web UI (local network only).
Firmware (Elixir, nerves): https://github.com/benwilson512/coffee_time/blob/a49814e5d8a...
To elaborate a bit, while each LiveView client is indeed associated with a long lived process in the LiveView server, the Phoenix framework provides hooks and other lifecycle management tools to minimize the impact of disconnects from eg blue green server deploys. Most interestingly for example with forms, the "automatic form recovery" does a process where when the reconnect happens it delays re-rendering until the client side has had a chance to send the current form state back to the server and allow it to synchronize.
Bonus points if the meters can send data out of order.
Compression is seriously impressive, we see ~90% compression rate on our real world datasets. Having that data right next to our regular postgres tables and being able to operate on it all transactionally definitely simplifies our application logic.
Where I see a lot of folks run into issues with TimescaleDB is that it does require that your related data models hold on to relevant timestamps. If you want to query a hypertable efficiently, you always want to be able to specify the relevant time range so that it can ignore irrelevant chunks. This may mean that you need to put data_starts_at, data_ends_at columns on various other tables in your database to make sure you always know where you find your data. This is actually just fine though, because it also means you have an easy record of those min / max values on hand and don't need to hit the hypertable at all just to go "When did I last get data for this device".
If you're comfortable putting a hole in your house for the line and running power, it can really save a lot of money.
Maybe I'm missing something, but doesn't this have precisely the same latency characteristics as live view?
I do agree that some of the "you don't need redis" and similar are overblown (particularly claims about tasks replacing background jobs), but I think there is truth to these claims in the following sense: Particularly in Rails, Redis gets used not just as a tool, but also to manage shortcomings in the language / runtime. Eg: Hotwire. Ruby doesn't have a good way to concurrently manage stateful connections, so it has to fallback to Redis. Ruby doesn't have a good way to manage inter-process or inter-node communication, so once again Redis steps in to fill that gap.
There's nothing wrong with using Redis, it's a fantastic tool. Using it as a state holder for an event loop through ends up feeling extremely convoluted after you've written a few GenServers (Phoenix channels / liveview).
Synthetic benchmarks of the Elixir GraphQL implementation show a 64% improvement! Very excited to see how this plays out with real load, but this is very promising.
With TimescaleDB, would I use the recorded_at or inserted_at column for the hypertable?
Does this change if data for an individual sensor can sometimes arrive out of order? If the sensor malfunctions and the data contains timestamps in the far past or the far future does this cause issues with TimescaleDB?
What we've done in postgres so far is have the tables with data generally structured around the recorded_at column because most analysis wants to look at the data "in order" . to generate reports, graphs, etc. Each data row also contains a "payload_id" relating it to a "payloads" table which helps group data by when it actually hit the system. Data processing has generally been built around the payloads and then query any additional data in recorded_at order on the main data tables if we need to look back or forward in time.
We're using postgres presently for some IoT, B2B applications, and the timeseries tables are a half dozen orders of magnitude larger than the other tables in our application. Certain database operations, like updates, take a very long time because of this. I've wondered if by splitting the timeseries tables onto their own server I could handle updates independently, with the main app gracefully handling the timeseries DB being offline for some period of time.
It's more than just about downtime though. If through poor querying or other issues the timeseries db is overloaded the customer impact of the slow down would be limited.
We're in a similar boat these days so in fact our latest projects are back to simple server side rendering, but we're still making the data retrieval calls with GraphQL. It ensures that that the mobile app and reporting tools we're also developing will have parity, and we don't need to write ad hoc query logic for each use case. The built in docs and validations have simply proven too useful to pass up, and you really don't need a heavy weight client to make requests.
Definitely don't let this hold you back. As long as you're up front about where you are, and are willing to learn, lack of professional experience with a particular language is by no means a deal breaker.
A) Absinthe 1.4 provides first class support for GraphQL Subscriptions, which give you the ability to push data to clients based on subscription documents they submit, triggered by events within the system. GraphQL Mutations are the way you push changes at the server, and Absinthe provides tools when building your schema to indicate a mapping of mutation fields to interested subscription fields.
B) This significantly depends on your underlying datastore. GraphQL (and consequently Absinthe) is entirely agnostic about how, where, or in what you're storing and managing your data. This may seem like a non answer but the point really is that you're entirely free to pick whatever approach works best for your data and your means of storage.
C) A significant portion of this answer is the same as B, although there are some conventions within the GraphQL community that help here. The Relay connection pattern uses opaque cursor values alongside each item returned in a page, and you can encode in these cursors whatever information you need to provide a coherent pagination experience.
D) I'm a big fan. Phoenix has actually done some really amazing stuff with an ORSWOT CRDT in order to track channel presence within a cluster. We're in the middle of looking at how to integrate that with subscriptions. More generally, I think when you can work your problem into the feature set of a CRDT they can be immensely powerful.
All in all though I think you may be ascribing to GraphQL or Absinthe a stronger role in the management of the data than it has. If you want to use CRDTs then you just articulate one in your schema and push it to clients and let them push diffs at you. Same could be said for much fo B, C, D. Absinthe doesn't manage the _state_ of your application your clients. Rather, it provides a way for your to communicate that state in whatever way you choose.
The idea with GraphQL subscriptions is that a client can submit a document that is run in response to an event on the server, which pushes the result of that document to the client. So if you had a UI showing new orders, you might have a subscription that looked like:
subscription {
orderPlaced {
id
customer { id tableNumber }
}
}
When an order is placed you can publish an event to this subscription field either manually, or setup pubsub relationships in the schema itself.Here's the challenge: Suppose loading the customer of an order requires a DB lookup. If you have 100 people who all submit that subscription, and you run each of those subscriptions independently, you're going to end up with 100 individual database lookups for the same customer.
De-duping documents can help; if the documents are identical and the context you're executing them in is identical, you can just execute it once and push the values out. The problem is if you have authentication or authorization rules in the document then you can't really do that, each document needs to be executed individually within the context of whoever submitted it.
One of the main features of our subscription implementation however is that the batch dataloading mechanisms we have to avoid N+1 queries within a single document can also work with sets of documents. When an order is placed we take the full set of documents triggered by that event and run them as a batch, so any redundant DB lookups can get coalesced into a single query.
Elixir itself has been a huge help here. We can achieve this batched document storage and execution without any external dependencies like Redis and, because we integrate easily with Phoenix PubSub (other backends possible) we get cluster support for free also without any external dependencies. Add in linear scaling with the number of CPUs and it's really been a fantastic platform to build this on.
We've been super busy getting the book finished but we want to make sure that we've also got everything someone needs to get going available in the docs.
We’re hoping to get the 1.4 release out soon, it’s was delayed a bit to make sure some features were in place for the book. For now though the 1.4.0–rc is available and we’ve been using it in production.