Hasura GraphQL Engine and SQL Server
github.com
github.com
Overall the experience has been fantastic. The performance and authorization scheme is very good. It has allowed us to wash our hands clean of bespoke endpoint writing for our enterprise customers with complex integration requirements (for the most part... waiting on mutations!).
One thing I wish was handled differently would be Same Schema, Different Database support.
We have multiple multi-tenant databases as well as many single tenant databases. All share the exact same table structure. As it stands, we have to maintain a separate Hasura instance for each of these databases as the table names conflict and there is no way to rename or reference them differently. That leaves us with the minor annoyance of needing to instruct users on their appropriate server prefix (server1.fast-weigh.dev/graphql vs server2.fast-weigh.dev/graphql... etc). Yes, we could proxy in front of these and route accordingly. But that's just one more layer to deal with and maintain.
It sure would be nice to have a single instance to maintain that could handle database availability based on the role of the incoming request.
Even with the minor inconvenience of multiple instances, I 10/10 would recommend. It's a huge timesaver assuming you've got the data access problems it seeks to make easy.
Multi-tenancy seems like their main 2.0 push
We have the exact same scenario and solved in with the exact same workaround. As things stand, spinning up the Hasura instance is currently the last piece of the process we need to automate before we are fully able to onboard new clients without manual ops action.
Hasura V2, currently in alpha, is supposed to support multitenancy as its flagship new feature. However, the "same object name in different database" issue is still open and on the roadmap, so presumably it's a _very_ early alpha.
If you are inserting non-sequential data into a clustered index, every insert results in a non-trivial rearrangement of the rows. UUIDs are not sequential, so at scale you will experience performance issues if you are using UUID primary keys and the PK index is clustered.
You won't notice this until significant scale, however. You can still use a unique identifier alongside an incrementing primary key, and you could choose to use a more compact format than the UUID. 8 base32 characters have over a trillion combinations, and are nowhere near as unsightly in a URL.
AFAIK, only MySQL (with InnoDB engine) and SQL Server, AFAIK, do it by default (always for MySQL/InnoDB, and by default unless you create a different clustered index before adding the PK constraint for SQL Server, but even then you can specify a nonclustered PK index.)
PG doesn't have clustered indexes at all, DB2 has a thing called clustered indexes which aren’t quite the same thing, Oracle calls having a clustered index on the PK an “index organized table” and its an non-default table option, and SQLite has what seems equivalent to a clustered index ONLY for INTEGER PRIMARY KEY tables not declared as WITHOUT ROWID.
> You can still use a unique identifier alongside an incrementing primary key, and you could choose to use a more compact format than the UUID.
A key point of using a UUID is distributed generation avoiding lock contention on a sequence generator, which is defeated by using both. Just “don’t use a clustered index where distributed key generation is important” seems a better rule, even if it precludes MySQL/InnoDB use.
Also, most DB’s that explicitly handle UUIDs store them compactly as 128-bit values. If you want to transform them to something other than the standard format for UI reasons [0], that doesn’t preclude using UUIDs in the DB.
[0] seems like bikeshedding, but, whatever.
You get closer to a clustered index (at the cost of more storage, but the benefit that you can have more than one on a table) with an index using INCLUDE to add all non-key columns in the index.
UUID v4 keys don't give away information about the number of rows in a relation. You can directly use them in api responses.
In recent events, iirc, parler was so easy to scrape precisely because they were using int keys exposed in their api get endpoints.
It also makes it too easy to paginate through relations for certain use cases where you may want obfuscation.
There are generally two types of information in applications, "public" and "privileged". The former has the IDs and such discoverable by an index or explore page, the latter requires authentication and has user-specific permissions.
For both cases, if hiding IDs is the only access control on the backend, it's fundamentally flawed. For both cases, if the access control is implemented well (in addition to rate limiting), integer vs UUIDs don't make a difference.
What do you think?
In many cases it's useful for a page to be publicly accessible yet, not indexable.
This is why sites like YouTube have an "unlisted" level of permission, UUID keys are a convenient way to implement that level of access control.
UUID keys are very useful for distributed systems where a local machine wants to generate a unique key locally, and then later upload it to a centralized store. It's especially helpful in third normal form databases where often you'll need to create objects that reference each other via the primary key.
This _should_ be part of a multi-layer security plan though. You don't depend on it as a primary source of security, but why would you expose more internal information than needed? If something does go wrong with another layer that obscurity _helps mitigate the damage_.
we use UUID's exclusively and it maps to a scalar uuid type without any issues
do you mind explaining a bit more?
I could be mistaken, but I think it also creates issues with client libraries that want to cache around an object ID, as the type is not a GraphQL ID.
We are using typescript and graphql-codegen to generate our types and haven't run into that issue.
We've been working on SQL server native support and we're happy to announce support for read-only GraphQL cases with new or existing SQL Servers.
Next up is adding support for stored procedures, mutations, Hasura event triggers [1] and more!
[1]: https://hasura.io/docs/latest/graphql/core/event-triggers/in...
Sorry, I know it took a while, but we had to make sure we could make the upgrade path non-breaking given it was a massive release and this took longer than we expected!
Please do feel free to reach out if you need any help with the upgrade :)
Was evaluating it against cognito and auth0 last week.
Prisma when combined with Apollo on the other hand makes it easy to build GQL handler, which can handle strange requirements but also makes it easy to avoid Hasura induced awkwardness.
The Hasura team seems very component however and I hope they will work out these issues.
This becomes super useful for folks building new applications or new features against existing SQL Server systems (which is a rather large ecosystem beyond just the database, since so many products use SQL Server underneath too!)
https://github.com/hasura/graphql-engine/
https://www.graphile.org/postgraphile/pricing/
https://hasura.io/pricing/ (see: self-hosted)
https://hasura.io/docs/2.0/graphql/core/databases/postgres/i...
You can get started with Hasura for free which is great, and you can run it on your own servers (if you want to manage a Haskell service) which is also great, but in practice choosing Hasura means you're relying on a company for your backend and choosing postgraphile means you're relying on an express plugin which you can get a support plan for.
I'm not saying one is the better choice than the other for this reason, many would prefer the funded company.
Nowadays most of these self-hosted apps run on Docker containers as a wonderful abstraction.
For example, in my company we have self hosted Metabase on App Engine Flex. It is written with Clojure and runs on the JVM. I know nothing about these things, yet I was able to make it run with high availability.
You could also run it on Kubernetes or other similar options elsewhere.
Edit: I forgot that I was trying a "learning repository" pattern where I put longform comments throughout the code. A little more difficult to discover than markdown, as I've learned.
PostgreSQL's JSON tooling makes it much easier to build SQL queries that return different shaped data from different tables in a single query.
The row_to_json() function for example turns an entire PostgreSQL row into a JSON object. Here's a query I built that uses that to return results from two different tables, via some CTEs and a UNION ALL: https://simonwillison.net/dashboard/row-to-json/
with quotations as (
select 'quotation' as type, created,
row_to_json(blog_quotation) as row
from blog_quotation
),
blogmarks as (
select 'blogmark' as type, created,
row_to_json(blog_blogmark) as row
from blog_blogmark
),
combined as (
select * from quotations
union all
select * from blogmarks
)
select * from combined order by created desc limit 100
Even more interesting is what you can do with json_agg - it lets you combine results from other tables. Here's a demo that solves the classic problem of needing to include data from a table at the end of a many-to-many relationship (in this case the tags on entries on my blog): https://simonwillison.net/dashboard/json-agg-demo/ select
blog_entry.id, title, slug, created,
json_agg(json_build_object(blog_tag.id, blog_tag.tag))
from
blog_entry
join blog_entry_tags on blog_entry.id = blog_entry_tags.entry_id
join blog_tag on blog_entry_tags.tag_id = blog_tag.id
group by
blog_entry.id
order by
blog_entry.created desc
limit 10
(Both these demos use https://django-sql-dashboard.datasette.io/ )Imagine having a table with large rows, and another table to join with small data but many rows.
Normally you'd do a inner join of some sort, and the data from "large rows" would be duplicated many many times - json_agg simply fixes this.
you can actually do the full table without json_build_object, too you can do something like
select json_agg(t.) from ( select from table ) as t
this makes joining multiple tables together very very easy, and performant.
Love the product and the team, keep up the great work.
Out of curiosity is support for multiple roles in the works?
https://github.com/hasura/graphql-engine/issues/6991
General support for inherited roles is one of the things I'm most excited about because it makes a bunch of hard things around reusing and composition so easy.
This improvement plays really well along with things like "role-based schemas" so that GraphQL clients have access to just the exact GraphQL schema they should be able to access - which is in turn composed by putting multiple scopes together into one role.
Also interesting is how well this could play with other innovations on the GraphQL client ecosystem like gqless[1] and graphql-zeus[2] because now there's a typesafe and secure SDK for really smooth developer experience on the client side.
[1]: https://github.com/gqless/gqless [2]: https://github.com/graphql-editor/graphql-zeus
Those client libraries are interesting. We are using introspection queries and graphql-codegen to generate react hooks and typescript types for our schema and its working really well.
Could you point us to the docs you were looking at for the auth0 integration?
You can even implement refresh token / auth token with rotation relatively simply. I felt Auth0 makes these things complicated to implement, whereas implementing them took only a few days and the docs / help online on how to do so are very good these days.
the mobile app it generates is react-native with auth0 already included. It uses hasura as a backend, the pulumi config will deploy it to ECS for you if want.
tbh though I don't see how this is a problem with hasura and just a auth0/RN issue.
We have different queries with dramatically different latency requirements (e.g. 1 second vs 5 minutes vs 1 hour). Currently we are only using Hasura for the low-latency queries, and are falling back to polling for other things. But it would simplify our development model if we could just subscribe to these changes with a lower frequency.
If we could additionally have some kind of ETAG-style if-not-modified support when initialising connections, that would be extra amazing.
Could you open a github issue with a rough proposal of how you'd like to specify this information?
For example: at a subscription level (with a directive or header), or via metadata built-on query collections [1] (what Hasura uses underneath for managing allow-lists).
[1]: https://hasura.io/docs/latest/graphql/core/api-reference/sch...
https://github.com/wundergraph/polyglot-persistence-postgres...
https://github.com/wundergraph/wundergraph-demo/blob/906f72c...
It also needs its own PG db to function in order to support SQL Server.
PG usage was pretty good. Auth sucked.
Usage in CI pipelines is hot garbage. Command line tooling does not work well with it at all.
I'd probably take the risk again for a toy...maybe.
Their are downsides (it has proven frustrating for us to implement the authentication part for a role that is not user or admin) but I would definitely recommend Hasura to experienced developpers.
We've been building out a pretty complex RBAC system and i might be able to help.
https://www.prisma.io/docs/concepts/overview/should-you-use-...
Hasura is great if you want to get a CRUD GraphQL API. Prisma on the other hand in an ORM that you import into your application code giving you more flexibility into the potential use-cases for it.
Postgres was the first database we added support for!
https://hasura.io/docs/2.0/graphql/core/databases/postgres/i...
https://hasura.io/docs/latest/graphql/cloud/response-caching...
In terms of query-plan caching - which provides a lot of benefits for speeding up pre-query execution - that's already enabled by default as part of the Hasura engine.
Response caching is a little more complicated and requires a separate service outside of the main GraphQL server to keep the solution generalized (ex. redis, memcached, lots of other options).
We're definitely looking at ways to have some more examples as to how someone could go about rolling their own caching solution for self-hosted instances.
In the case of hosted solutions to caching, there's Hasura Cloud which pairs the cache with monitoring and some other usability and security niceties - but you could also use a service like GraphCDN (there are a couple other as well) in front of your Hasura instance which helps setup response caching.