I was introduced to AWS's Quantum Ledger Database in 2021 at Lumeris as we looked for a way to manage data access logs in a simple, scalable, and cost-effective manner. This log datastore needed to be immutable, queryable, and able to handle hundreds of thousands of writes per day. We found an excellent solution in QLDB. After using it here, I gave it a shot as my database for a side-project. During these two experiences, I've found some huge wins and a couple of roadblocks, so I'd like to share some of my findings. Expect more to come - I am not done with QLDB! :)
My colleague Ben Main and I published a library nest-qldb which made working with QLDB insanely easy. With an out-of-the-box repository pattern and a simple Dapper-esque (for those familiar with the popular .NET ORM) query service, it only takes a couple of decorators to get up-and-running in our favorite NodeJS framework, NestJS. Below is an excerpt of our e2e tests that shows how to use the @QldbTable()
decorator:
@QldbTable({
tableName: 'app_users',
tableIndexes: ['dob', 'sex'],
})
class User {
dob: Date;
name: string;
gender: string;
sex: 'M' | 'F';
luckyNumber: number;
groups: { name: string }[] = [];
constructor(partial: Partial<User>) {
Object.assign(this, partial);
}
}
@Injectable()
class UserService {
constructor(
@InjectRepository(User) readonly usersRepository: Repository<User>, // auto-generated repository
private readonly queryService: QldbQueryService, // simple query service
) {}
async create(data: User): Promise<User & { id: string }> {
const result = await this.queryService.querySingle<{ documentId: string }>(
`INSERT INTO app_users ?`,
[data],
);
return {
...data,
id: result?.documentId,
};
}
/**
* Retrieves a record based on the QLDB id.
* @param id The QLDB ID of the object.
*/
async retrieve(id: string): Promise<User & { id: string }> {
return await this.queryService.querySingle<User & { id: string }>(
[
`SELECT id, u.*`,
`FROM app_users AS u`,
`BY id WHERE id = ?`,
].join(' '),
id,
);
}
}
@Module({
imports: [
NestQldbModule.forRoot({
qldbDriver: new QldbDriver('test-ledger'),
createTablesAndIndexes: false,
tables: [User],
}),
],
providers: [UserService],
exports: [UserService],
})
class TestRootModule {}
For the most part, QLDB is a document database - nested documents, complex data designs, and all that good stuff. However, your access layer is a SQL-like language, PartiQL. While it may not be as fully fleshed out as the query languages of the world, it can do all the typical things you'd be needing to do, including JOINs (be careful!) and nested document querying.
PartiQL felt very natural. Nested queries made sense, but the SQL-like language design made me feel right at home. I was very happy to work with PartiQL and didn't feel like I was fighting the language or digging through documentation to figure things out.
This is one of the core tenant of QLDB: data immutability out of the box with a historical context. For our access log purposes, this is exactly what we needed - along with the data integrity guarentees that QLDB provides.
QLDB is SO COST-EFFECTIVE! For context, we logged a million access logs and spent something like $0.50. It is also pay-as-you-go, which makes it wonderful for side-projects or apps that don't need to do a ton of complex querying. You could host your queryable data for under a dime for a year!
HOWEVER, you need to be aware of your document design and of your queries. Check out the roadblock section below on document design, but the synopsis is that you can shoot yourself in the foot if you're not being careful with your queries and indexes.
If you've worked with AWS DocumentDB or serverless Aurora, you know there's a bit of work to do as far as infrastructure setup and management. With QLDB, there isn't ANY work here. Literally. Zero. It's astounding. Here's the whole CloudFormation definition to set up your ledger:
Resources:
Qldb:
Type: AWS::QLDB::Ledger
Properties:
Name: !Ref QldbLedgerName
DeletionProtection: true
PermissionsMode: ALLOW_ALL
Five lines of YAML and you have a serverless, immutable, ledger database. Can't beat that.
One great feature we leveraged is having events streaming after writes to the ledger database. In our case, every time we log an access record, we trigger a Lambda event that sends the details to a third-party API. Very low-effort on our part, and very easy to integrate. Another great use of this would be to ship it to over to be surfaced through ElasticSearch for super-fast querying (see the roadblock section for more information).
The buzzword (buzzphrase?) makes it cool by default. Sorry, it is what it is.
There are a few core issues with indexes that made me change my mind about using QLDB for LoudChorus, but from what I hear, there are some maaajor changes coming in the future and I'm very excited to see the capabilities. Here's the main issues I found:
I played with a few designs for the LoudChorus database, particularly a relational approach and document design approach. The relational approach failed drastically - the performance was awful (as in, it couldn't complete the queries in 30 seconds), but the document design approach worked fantastically. While I expect the indexes in the relational approach to buy some performance gains, they definitely did not.
My qualm here is that, while you need to be smart with any data store, you need to be particularly careful with QLDB for a couple reasons:
In playing with some load tests of a few thousand records with some LIKE queries, I ended up racking 3 million read IOs at the cost of about $0.50. Really, this is nothing - but this would come to about $180/year, which becomes less interesting.
This happened because I was doing some pretty gnarly querying - LIKE queries against multiple columns in nested documents, and querying non-indexes. I don't blame QLDB because I was going off the rails, but it's something you really need to be conscious of when you're designing your data store and building your queries.
I really like QLDB and will definitely be using it on products now and in the future. For now, it won't be able to fully replace my Mongo Atlas data store because of the state of indexing, but I'm looking forward to the incoming features.
Give QLDB a shot. Try Nest and nest-qldb
to get a quick start!
Find out if MentorCruise is a good fit for you – fast, free, and no pressure.
Tell us about your goals
See how mentorship compares to other options
Preview your first month