Hacker News new | past | comments | ask | show | jobs | submit login

Wouldn't this system have a bunch of drawbacks:

- Long startup times as the entire image needs to be loaded and prepared.

- It would be hard to distribute the state across multiple nodes

- What happens in case of a crash? How fault tolerant would this be?

- Does this architecture essentially amount to building in a sort-of-kind-of datastore into your already complex application? Without a well-defined well-tested existing code base, is this just re-inventing the wheel for each new project?

- How do you enforce constraints on the data?

- How do transactions work (debit one account, [crash], credit another account?

- How do you allow different components (say web user interface, admin system, reporting system, external data sources) to share this state?

Just curious.

EDIT:

- Isn't this going to lead to you writing code that almost always has side-effects, causing it to be really hard to test? How would you implement this system in Haskell?




- The startup times can be a problem if you have a lot of data. Modern disks are pretty fast for streaming reads, though, and you can split the deserialization load across multiple processors.

- Mirroring state is easy; you just pipe the serialized commands to multiple boxes.

- It's very fault tolerant. Because every change is logged before being applied, you just load the last snapshot and replay the log.

- It didn't seem that way to me.

- In code. In the system I built, each mutation was packaged as a command, and the commands enforced integrity.

- Each command is a transaction. As with DB transactions, you do have to be careful about where you draw your transaction boundaries.

- Via API. Which I like better, as it allows you to enforce more integrity than you can with DB constraints.


Thanks for the informative response. Just a couple more questions:

> - The startup times can be a problem if you have a lot of data. Modern disks are pretty fast for streaming reads, though, and you can split the deserialization load across multiple processors.

Reading data, at even a GB/second from disk (which is currently not possible) is going to mean a second spent of a GB of data, just to read, let alone deserialize. That's with reading a snapshot, not replaying old transactions.

> - Mirroring state is easy; you just pipe the serialized commands to multiple boxes.

That's not distributing the load. I'm talking about having more data than fits in an reasonable amount of RAM (say 1TB). Also mirroring is nice for when you want read-only access to your data. You'll have the same problem as any other data store when you want multiple writers. Also, is replication synchronous or asynchronous (which end of CAP do you fall on)?

>- It's very fault tolerant. Because every change is logged before being applied, you just load the last snapshot and replay the log.

So it's going to at the speed of the disk then (http://smackerelofopinion.blogspot.com/2009/07/l1-l2-ram-and...). Don't get me wrong, this is still faster than writing to the network, but then writes are way slower than reads.

My other question is how much of a pain in the ass is it to debug such a system? I suppose if you have a nice offline API to look at your data, change something, revert back, etc, it would work well, but if it's deep within your normal application, it could become nightmarish.


> Reading data, at even a GB/second from disk (which is currently not possible) is going to mean a second spent of a GB of data, just to read, let alone deserialize.

If that's just saying that startup time can be an issue, I agree. There are a variety of techniques to mitigate that, though. The simplest is to compress snapshots and/or put them on RAID, boosting read speed. The most complicated is just to have mirrored servers and only restart the one not in use right now.

> I'm talking about having more data than fits in an reasonable amount of RAM (say 1TB).

For something where you need transactions across all of that? This architecture's probably not a reasonable approach, then. The basic precondition is that everything fits in RAM. However, sharding is certainly possible if you can break your data into domains across which you don't require consistent transactions.

> So it's going to at the speed of the disk.

Sort of.

Because it's just writing to a log, mutations go at the speed of streaming writes, which is very fast on modern disks. And there are a variety of techniques for speeding that up, so I'm not aware of a NoDB system for which write speed is the major problem.

Regardless, it's a lot better for writes than the performance of an SQL database on the same hardware.

> My other question is how much of a pain in the ass is it to debug such a system?

It seemed fine. A big upside is that you have a full log of every change, so there's no more "how did X get like Y"; if you want to know you just replay the log until you see it change.

Last I did this we used BeanShell to let us rummage through the running system. It was basically like the Rails console.


- Mirroring state is easy; you just pipe the serialized commands to multiple boxes.

What? No, that's ridiculous. That's how inconsistencies crop up. Unless you plan on locking the entire system during each command.


One box is the master; the others are slaves. And yes, the easy way to do this is system having single write lock.

That seems ridiculous if you are thinking like databases do, in terms of taking away the pain of all those disk seeks needed to write something. But if everything is hot in RAM, executing a command is extremely fast. Much faster than a database.

If that still isn't fast enough, you can split your data graph into chunks that don't require simultaneous locking and have one lock per. For example, if you are making a stock exchange like the LMAX people were, you can have one set of data (and one lock) per stock.


The lock doesn't need to just cover one write. It needs to cover the whole transaction. The canonical example is that of incrementing a counter. Replica synchronization aside, without a transaction lock (or some other guarantee of transaction consistency), at some point you will read a counter value which another client is in the middle of updating.

The first "fix" to this that comes to mind is timestamping data, rolling back transactions which try to write having read outdated data. Do extant NoDB systems do this?


Yes, these approaches provide proper transactions, but the approach is much simpler than you imagine.

Imagine you have a graph of objects in RAM. Say, a Set of Counters. Imagine you want to change the graph by incrementing a counter. To do this create an object, the UpdateCounterCommand, with one method: execute. You hand the command to the system, and it puts it in a queue. When it gets to the top, it serializes the command to disk and then executes it. Exactly one write command runs at a time.

For a real-world example, check out Prevayler. It provides all the ACID guarantees that a database does, but in a very small amount of code.


Mostly agree, but to this point:

> - Isn't this going to lead to you writing code that almost always has side-effects, causing it to be really hard to test? How would you implement this system in Haskell?

You'd have to figure out how to isolate the IO monad as much as possible, but this is no different than interacting with a database in Haskell. And Haskell would give you nice features like STM to address other concerns as well.


Re: Haskell it would probably look a lot like (exactly like?) Happs-State: http://happs.org/




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: