Redis re-implemented with SQLite
github.com
github.com
You can get substantially better performance out of sqlite by using the lower level https://github.com/crawshaw/sqlite, turning on WAL etc, using a connection per goroutine for reads, and sending batches of writes over a buffered channel / queue to a dedicated writer thread. That way you can turn off SQLite’s built in per-connection mutex but still be thread safe since each connection is only used on a single thread at a time.
For this use-case you will also probably save a lot of time if you use some large arena-style buffers (probably N per conn?) and copy incoming parameter bytes from the network request/socket to the buffer, or copy straight from sqlite out to the socket, instead of allocating and passing around a bunch of individual strings. Boxing those strings in interface{} (as done by the high level sql stdlib) slows things down even more.
None of this is necessary to get usable perf, even decently good perf, just sharing some tips from my experience trying to get absolutely maximum write throughput from SQLite in Golang.
Would this protect against a row update in the middle of a read? e.g. would a row at least be internally consistent at the time it was read?
— Small memory footprint even for large datasets.
— ACID transactions.
— SQL interface for introspection and reporting.
That's sometimes even just for development work.
A lot of these use a common API to a more complex distributed store, as well as to something simple like files on disk, in memory, or SQLite.
I'm most cases, it's one user at a time, so performance doesn't matter, but simplicity does.
It can also be for the project which has a 1 percent chance of going viral.
Etc. But I find relatively few cases between truly small scale and large scale.
I’ve made several versions of this, and to be honest, it ended up being so straightforward that I assumed it was a trivial solution.
This is pretty well-planned. This is 100% the way to go.
Heh. I took a detour into making my idea of “streams” also solve event sourcing in native python; dumb idea, if interesting. Mission creep probably killed my effort!
Nice work
https://python-rq.org
This RQ stuff has been a pain with a recent project because only Python seems to use it, so once an RQ job has been submitted only Python based things can do anything with it. :(If Redka works as a backend replacement, we could potentially have non-Python things check the SQLite database instead.
And it's written in Go. :)
Yes, I can pay for a 400Gb RAM instance of Redis, but it's expensive.
I can also cache it on disk, but then I need to think about cache expiration myself.
Or I can use something appropriate like a document database, but then I need additional code & additional configuration because we otherwise don't need that piece of infrastructure in our stack.
It would be a lot easier if I could just store it in Redis with the other (more reasonably sized) things that I need to cache.
If you rather have something in-process and writes to disk, to avoid extra infrastructure, I would also recommend RocksDB with Compression and TTL.
Edit: looks like redis has some built-in support for data sharding when used as a cluster (https://redis.io/docs/latest/commands/cluster-shards/) - I haven't used that, so not sure how easy it is to apply, and exactly what you'd have to change.
https://docs.aws.amazon.com/AmazonS3/latest/userguide/s3-exp...
We were using it to manage a millions of images for machine learning as many tools support S3 and the ability to add custom metadata to objects is useful (harder with files). It is one SQLite database per bucket but at the bucket level it is transactional.
Redis Data Tiering - Redis Enterprise and AWS Elasticache for Redis support data tiering (using SSD for 80% of the dataset and moving things in and out). On AWS, a cache.r6gd.4xlarge with 100GB of memory can handle 500GB of data.
Local Files
> I can also cache it on disk, but then I need to think about cache expiration myself.
Is the challenge that you need it shared among many machines? On a single machine you can put 20 million files in a directory hierarchy and let the fs cache keep things hot in memory as needed. Or use SQLite which will only load the pages needed for each query and also rely on the fs cache.
S3 - An interesting solution is one of the SQLite S3 VFS's. Those will query S3 fairly efficiently for specific data in a large dataset.
If the reverse proxy thing doesn't work I think memcached has two level storage like that now iirc
--
If you use Redis for queue tasks (this is popular in Rails and Django/Python web services), that means that during an incident where your queue jobs are getting added faster than they’re removed, you’re going to lose jobs if the incident goes on long enough.
ScyllaDB for me is in the middle of being high performance key-value store, but not really supporting transactions. FoundationDB is another one that I would consider.
Maybe you like this answer more: At the end of the day you can embed a bunch of Turing-complete programming languages in Postgres, and Postgres can store binary blobs, so Postgres can do literally anything. Can it do it performantly, and for low cost? Probably not. But if you put in enough time and money I’m sure you can re-implement Redis on Postgres using BLOB column alone.
Here’s a simpler answer: cuckoo filter is available out of the box in Redis, 2 seconds of Googling I didn’t find one for Postgres: https://redis.io/docs/latest/develop/data-types/probabilisti...
https://www.postgresql.org/docs/current/bloom.html
Have a look
That being said, building a bloom/cuckoo filter as a data type would be quite trivial - a basic version might take an hour or two, the more advanced stuff (to support partial aggregates etc) might take a bit more. Ultimately, this is not that different from what postgresql-hll does.
They have done some incredible job. Implementing things I didnt know existed about software/hardware.
Want to do the same thing with Postgres as well.
The Redis API and data structures are really nice and have been tuned to be useful in a wide variety of situations. Implementing these APIs is different concurrent, compute and storage models is a nice way to learn them and get options open.
SQLite only allows one writer at a time, so concurrent writes will fail with a "database is locked" (SQLITE_BUSY) error.
There are two ways to enforce the single writer rule:
1. Use a mutex for write operations.
2. Set the maximum number of DB connections to 1.
Intuitively, the mutex approach seems better, because it does not limit the number of concurrent read operations. The benchmarks show the following results:
- GET: 2% better rps and 25% better p50 response time with mutex
- SET: 2% better rps and 60% worse p50 response time with mutex
Due to the significant p50 response time mutex penalty for SET, I've decided to use the max connections approach for now.
[1]: https://github.com/nalgeon/redka/blob/main/internal/sqlx/db....
You can check the documentation [1], only some rare edge cases return this error in WAL. We abuse our sqlite and I never saw it happen with a WAL db.
[1] https://www.sqlite.org/wal.html#sometimes_queries_return_sql...
Where are the benchmarks?
Not familiar with Redis specifically, but I doubt this idea. You can run anything on top of a ramdisk (granted, you can save a few pointer additions and get rid of some safety checks if you know you're working with memory)
:memory:
To parent: I think all datastores can boil down to a SQL interface eventually. Someday a SQL-interface may dynamically choose a bespoke backend for your schema (or even sub-schema!)
Seems to be missing streams, hyperloglog and pubsub though, so mostly just the kv part of the side protocol with a different backend?
With this you can run a full SQL query.
I did something similiar for my small baseball statistics website switchfielder.com/baseball. On server startup, I copy the relevant tables into memory.
For an OLAP (i.e., no writes), small databases with complicated queries(joins, grouping, partitioning, ordering, etc.) this works very well. Yes this is somewhat of an edge case, but I'd bet small shops could fit their entire DB into one of the bigger (several hundred GB) instances.
> Both in-process (Go API) and standalone (RESP) servers.
In-process means that the database is "embedded / clientside" in your terms.
Often repeated and certainly rooted in truth but there was a healthy discussion on here the other day[0] where tptacek shared a link in a comment[1] to a related blog post about getting more scale out of using SQLite serverside.
Although I don't really see anything in the license change that would prevent me from using it at both home and business, Redis seem "complete" functionality wise so using a pre-license-change version can't hurt even long-term I think.
It's much better to use RocksDB as the underlying storage engine for such a solution. RocksDB can provide the performance and scalability required.If you need a distributed solution, I would suggest looking at TiKV or FoundationDB. These are both excellent distributed storage systems that can handle production workloads much better than a SQLite-based approach.