views:

346

answers:

1

We are currently considering a change from Postgres to CouchDB for a usage monitoring application. Some numbers:

Approximately 2000 connections, polled every 5 minutes, for approximately 600,000 new rows per day. In Postgres, we store this data, partitioned by day:

t_usage {service_id, timestamp, data_in, data_out}
t_usage_20100101 inherits t_usage.
t_usage_20100102 inherits t_usage. etc.

We write data with an optimistic stored proc that presumes the partition exists and creates it if necessary. We can insert very quickly.

For reading of the data, our use cases, in order of importance and current performance are:
* Single Service, Single Day Usage : Good Performance
* Multiple Services, Month Usage : Poor Performance
* Single Service, Month Usage : Poor Performance
* Multiple Services, Multiple Months : Very Poor Performance
* Multiple Services, Single Day : Good Performance

This makes sense because the partitions are optimised for days, which is by far our most important use case. However, we are looking at methods of improving the secondary requirements.

We often need to parameterise the query by hours as well, for example, only giving results between 8am and 6pm, so summary tables are of limited use. (These parameters change with enough frequency that creating multiple summary tables of data is prohibitive).

With that background, the first question is: Is CouchDB appropriate for this data? If it is, given the above use cases, how would you best model the data in CouchDB documents? Some options I've put together so far, which we are in the process of benchmarking are (_id, _rev excluded):

One Document Per Connection Per Day

{
  service_id:555
  day:20100101
  usage: {1265248762: {in:584,out:11342}, 1265249062: {in:94,out:1242}}
}

Approximately 60,000 new documents a month. Most new data would be updates to existing documents, rather than new documents.

(Here, the objects in usage are keyed on the timestamp of the poll, and the values the bytes in and byes out).

One Document Per Connection Per Month

{
  service_id:555
  month:201001
  usage: {1265248762: {in:584,out:11342}, 1265249062: {in:94,out:1242}}
}

Approximately 2,000 new documents a month. Moderate updates to existing documents required.

One Document Per Row of Data Collected

{
  service_id:555
  timestamp:1265248762
  in:584
  out:11342
}
{
  service_id:555
  timestamp:1265249062
  in:94
  out:1242
}

Approximately 15,000,000 new documents a month. All data would be an insert to a new document. Faster inserts, but I have questions about how efficient it's going to be after a year or 2 years with hundreds of millions of documents. The file IO would seem prohibitive (though I'm the first to admit I don't fully understand the mechanics of it).

I'm trying to approach this in a document-oriented way, though breaking the RDMS habit is difficult :) The fact you can only minimally parameterise views as well has me a bit concerned. That said, which of the above would be the most appropriate? Are there other formats that I haven't considered which will perform better?

Thanks in advance,

Jamie.

+1  A: 

I don't think it's a horrible idea.

Let's consider your Connection/Month scenario.

Given that an entry is ~40 (that's generous) characters long, and you get ~8,200 entries per month, your final document size will be ~350K long at the end of the month.

That means, going full bore, you're be reading and writing 2000 350K documents every 5 minutes.

I/O wise, this is less than 6 MB/s, considering read and write, averaged for the 5m window of time. That's well within even low end hardware today.

However, there is another issue. When you store that document, Couch is going to evaluate its contents in order to build its view, so Couch will be parsing 350K documents. My fear is that (at last check, but it's been some time) I don't believe Couch scaled well across CPU cores, so this could easily pin the single CPU core that Couch will be using. I would like to hope that Couch can read, parse, and process 2 MB/s, but I frankly don't know. With all it's benefits, erlang isn't the best haul ass in a straight line computer language.

The final concern is keeping up with the database. This will be writing 700 MB every 5 minutes at the end of the month. With Couchs architecture (append only), you will be writing 700MB of data every 5 min, which is 8.1GB per hour, and 201GB after 24 hrs.

After DB compression, it crushes down to 700MB (for a single month), but during that process, that file will be getting big, and quite quickly.

On the retrieve side, these large documents don't scare me. Loading up a 350K JSON document, yes it's big, but it's not that big, not on modern hardware. There are avatars on bulletin boards bigger than that. So, anything you want to do regarding the activity of a connection over a month will be pretty fast I think. Across connections, obviously the more you grab, the more expensive it will get (700MB for all 2000 connections). 700MB is a real number that has real impact. Plus your process will need to be aggressive in throwing out the data you don't care about so it can throw away the chaff (unless you want to load up 700MB of heap in your report process).

Given these numbers, Connection/Day may be a better bet, as you can control the granularity a bit better. However, frankly, I would go for the coarsest document you can, because I think that gives you the best value from the database, solely because today all the head seeks and platter rotations are what kill a lot of I/O performance, many disk stream data very well. Larger documents (assuming well located data, since Couch is constantly compacted, this shouldn't be a problem) stream more than seek. Seeking in memory is "free" compared to a disk.

By all means run your own tests on our hardware, but take all these considerations to heart.

EDIT:

After more experiments...

Couple of interesting observations.

During import of large documents CPU is equally important to I/O speed. This is because of the amount of marshalling and CPU consumed by converting the JSON in to the internal model for use by the views. By using the large (350k) documents, my CPUs were pretty much maxed out (350%). In contrast, with the smaller documents, they were humming along at 200%, even though, overall, it was the same information, just chunked up differently.

For I/O, during the 350K docs, I was charting 11MB/sec, but with the smaller docs, it was only 8MB/sec.

Compaction appeared to be almost I/O bound. It's hard for me to get good numbers on my I/O potential. A copy of a cached file pushes 40+MB/sec. Compaction ran at about 8MB/sec. But that's consistent with the raw load (assuming couch is moving stuff message by message). The CPU is lower, as it's doing less processing (it's not interpreting the JSON payloads, or rebuilding the views), plus it was a single CPU doing the work.

Finally, for reading, I tried to dump out the entire database. A single CPU was pegged for this, and my I/O pretty low. I made it a point to ensure that the CouchDB file wasn't actually cached, my machine has a lot of memory, so a lot of stuff is cached. The raw dump through the _all_docs was only about 1 MB/sec. That's almost all seek and rotational delay than anything else. When I did that with the large documents, the I/O was hitting 3 MB/sec, that just shows the streaming affect I mentioned a benefit for larger documents.

And it should be noted that there are techniques on the Couch website about improving performance that I was not following. Notably I was using random IDs. Finally, this wasn't done as a gauge of what Couch's performance is, rather where the load appears to end up. The large vs small document differences I thought were interesting.

Finally, ultimate performance isn't as important as simply performing well enough for you application with your hardware. As you mentioned, you're doing you're own testing, and that's all that really matters.

Will Hartung
CouchDB will launch multiple system processes for the view server to process a view, so it does scale just fine across multiple cores. The rest of CouchDB is in Erlang and is great at using multiple cores as well.
mikeal
You're right. I ran a test, and I insert 2000 of these large documents (20 processes inserting 100 each, simultaneously) in to a v0.9 Couch instance. On a 4 core 2.66G Mac Pro, these were inserted in basically 3m30s. Couch took 350% of the CPU. In the end the disk file was ~2G. Even after compaction, it hardly changed at all. In contrast, inserting 2000 "single day" documents took ~18s. Much quicker, of course. 3m30s is too close to the 5m window that they have. 18s is much much better. Compacting took almost 3m though.
Will Hartung
Thanks very much for this, it's a great place to start. We've run some benchmarks and found much the same as you have. The main issue we're going to have is the constant updates to the data - it seems like it will get prohibitively slow for the "entire month" documents. As long as we can regularly compact, hopefully we'll be ok.It's a shame we can't go for a document per data point, but as you suspected the file IO seems prohibitive. Unfortunately to update any other kind of document, we need to read before we can write, in order to get the _rev...
majelbstoat