An HN style social news site written in Ruby/Sinatra/Redis/JQuery by Antirez
github.com
github.com
Speaking of best practices, putting serialized objects into the database (as you're doing with the comment objects) is usually considered an anti-pattern in the SQL world. Could you briefly explain (beyond what you said in the Readme) why you've used this approach for comments, but not for, say, news objects? Was this decision solely made to work around anticipated performance/memory bottlenecks and if so, what are the trade-offs, and are there any rules of thumb for making the same decision for object types in other applications?
the difference between comments and news is that a thread (a collection of comments for a given news) is an hash made of sub-hashes, the hash is ID -> comment_hash. The comment hash just contains the different fields.
When there are this two levels, storing the first level as a Redis hash, and the second as JSON leads to very good memory usage performances, it is internally stored as a linear array. We can do that because we know most news will have just a few tens of comments. If there are more, the hash will turn into a real hash table transparently, more space, but worth it for the rare cases when this is needed.
The news instead is just a collection of fields. There is no outer object that is reasonably sized, like "all the news obeject" (it is too big), so there is no gain in using this approach.
What is good about storing sub-objects of hashes as JSON objects is that Redis unstable just got JSON support in Lua scripts, so it will also be able to manipulate this objects sending Redis small Lua scripts.
I hope this clarifies the issue.
many thanks for the reply, that does clarify the issue.
So when sub-objects are only a few dozen per collection (per parent object) on average, storing them as JSON blobs allows Redis to "inline" them, yielding good memory usage. But for large numbers of items per parent, Redis can't inline them so you might as well represent them as a hash, which can never be inlined. Is this a fair summary?
I guess that ideally, all objects would always be represented in the same way from a client perspective, and the database engine would decide which internal format is best suited for storage, maybe using hints provided by the client, and handle any necessary (de)serialization as an implementation detail. That said, I know Redis is still a young project and I suppose this is something you guys are thinking to improve long-term anyway.
Cheers!
In both cases, Lamernews still stores comment IDs as the hash keys and the JSON-serialized structures as the hash values, antirez was just commenting on a memory-saving feature of Redis for small hashes.
When there are this two levels, storing the first level as a Redis hash, and the second as JSON leads to very good memory usage performances, it is internally stored as a linear array.
I'm reading this to say that memory usage wouldn't be as good if the second level was stored as a hash instead of a JSON blob.My specific question was: why store the comment as a JSON blob as opposed to a hash? I think Antirez answer was, in a nutshell: because a hash of hashes can't be stored as an array. For news items it doesn't matter because the hash couldn't be stored as an array anyway due to the (anticipated) large number of news items.
Are you saying this interpretation is wrong?
While the time complexity is worse, absolute performance might be superior, due to both skipping the hashing arithmetic, and (due to that lower memory usage) fitting more data in the CPU cache.
I'm curious, why do templates suck?
I've been working on a 'for fun' application with Redis, and had been counting on some sort of on disk memory.
(Because storing everything in ram is comparatively expensive, I had figured I could use Diskstore or VM with a small server to start).
Now I'm considering porting over to a mongodb or sql backend because the disk based storage options won't be supported in Redis in the future.
What should I do? Simply Use Redis as a cache? Buy RAM? Doesn't that seem to limit its utility and complicate things?
You do the same with memory, either you buy more RAM, or you distribute the data across multiple hosts (that is what we are trying to do with Redis Cluster).
The whole point is, what order of magnitude is my data? Is memory a big enough storage for my needs?
For instance for Lamer News you can store many millions of news and comments in 1 GB. So it makes perfectly sense for this application. For other applications you want a mixed solution where fast meta-data is stored on Redis and larger documents on disk in some other database. For other apps you need Cassandra and nothing else, and for other apps a good SQL database. It depends, as usually :)
In the long term (i.e. after cluster is finished), there are plans to manipulate data structures directly on disk (an incredibly elegant solution, and also really good for SSDs). Though in the short term, you are right in that RAM is far more expensive than disk, and this does limit the utility of Redis as a primary datastore.
You have to either buy more RAM, start using the VM option, or start using OS virtual memory. The latter two will slow it down a bit.
Another possibility is to figure out which sets of data are taking up the most space, and offload them to a separate disk-backed DB, and just cache the most frequently accessed subset of that data through Redis (this could be easily done through key expiration). I find this option kind of painful, mostly because I really like the way that Redis approaches data, and using another database is comparatively inconvenient.
Having worked with it a bit, I think Redis is a completely awesome piece of technology, and in practice it is perfectly good to use for something like this, at least in the short term. I don't want to bring down Antirez's hard work in any way.
Being the type of developer that hems and haws over corner cases and unlikely what-ifs, however, I felt the need to ask, as it has been in the back of my mind for a while.
I am encouraged that there will be a version that manipulates the data-structures on disk, as that was the best solution that I could think of as well. (Basically you could then run two instances of Redis, a RAM based one and a disk based one, echoing commands to both, but allowing data on the disk backed Redis to expire and taking responses from whichever returns an answer first).
My only hope is that clustering won't take to long and that it won't be abandoned in favor of a different castle in the sky if it doesn't turn out to be perfect.
The code base got just a few days of after-work hacking, so probably this will take a few more days to be ready.
Thanks for the hint.
SHA1(SHA1(SHA1(0|pass)|1)|2) and so forth.
This way there is no requirement for an additional library.
(The difference between this and PBKDF1 is that PBKDF1 requires using either MD2 or SHA1 as the hash function, and hasn't been updated to reflect the availability of SHA-256 and SHA-512.)
I guess that the xor approach used in PBKDF2 is useful since you want to compute T1, T2, T3 ..., Tc that are multiple output blocks all starting from the same input, so this gives more information to the attacker and the schema is designed to avoid showing some "state" that is possible to more easily analyze. Not sure, but the point is, I don't think PBKDF1 is unsafe either, and it is just chaining.
Your code is neat. I don't care what hash you use. I'm just saying.
SHA256(SHA256(... SHA256(password + salt)))
With perhaps five thousand iterations of SHA256, if you really don't want to depend on bcrypt. Or just use bcrypt; it's a good library, and removes the temptation to get creative with password hashing.
However, it should be noted that I'm thankful that the author decided to share this.
But actually... I can just run it easily in a server of mine just to show it to you. Let's try to install it... just a moment.
I'll leave it running for a few hours assuming it will not crash. It's very new code ;)
Looking at the source, you use side-effects to return user objects and such. I've always been taught side-effects are a bad thing that create complex code, which seems to go against one of the goals listed in your README.md.
# Try to authenticate the user, if the credentials are ok we populate the
# $user global with the user information.
# Otherwise $user is set to nil, so you can test for authenticated user
# just with: if $user ...
#
# Return value: none, the function works by side effect.
def auth_user(auth)
return if !auth
id = $r.get("auth:#{auth}")
return if !id
user = $r.hgetall("user:#{id}")
$user = user if user.length > 0
end
Can you explain why you used side-effects?It is very helpful for me to vest the clothes of the user, it makes me understanding a lot more about Redis. If you are always in the other side of the table you miss a lot.
This is surprising. Why not make minimal use of ERB or Mustache?
Can you post some benchmarks on that? Would like to see how many (average sized) news/comments can be stored on the 512mb VPS.