Give Codeship’s CI/CD Platform a Try

Want to learn more?

This article was originally published by Cheyne Wallace on his blog, and with his permission, we are sharing it here for Codeship readers.

Storing and retrieving large chunks of data from your database can be tricky if it’s not done correctly. What happens when you want to store a relatively large document or body of text in your database but that large chunk of text is likely to be identical for thousands of new records? You’re faced with the possibility of storing gigabytes of duplicated data that really doesn’t need to be there.

Here’s the scenario: say you have a piece of software that generates a log file on installation with some key information you would like to track and archive. You push this log file back to your server via an API endpoint and store it in the database. The log file is roughly 100KB in file size; for every 100,000 installs, that’s coming close to 10GB in storage.

The problem is 80 percent of these log files are identical — they contain the same information but we treat and store them as though they were unique, wasting disk space and blowing out our table size and tuple count with an unnecessary number of rows.

We all know disk space is cheap, but processing power isn’t. Large tables cause large indexes, which in turn cause slower queries, require more CPU cycles, and mean you need to be more careful about writing your future SQL queries. If you query on a non-indexed field, you can trigger a full table scan, and your app takes a big performance hit in the process.

People love to say, “Storage is cheap! store everything!” But moving, loading and managing 100GB backup files is a lot more difficult and time-consuming than 20GB backup files, so it’s worth optimizing where possible.

So how do we optimize this? We’re going to create three tables, The user table, one for the log files, and one that acts as a join table between the users and logs. Instead of having a direct relation between the Log and the User, there will be a relation from User to the UserLog and from the UserLog to the Log.

Instead of just saving the log file when we receive it, we will instead hash the contents and perform a look-up on this hash in the database to see if we have already seen this exact block of data before. If we get a match on the hash, we will instead use the id of that record and throw away the data. This means that if 1000 people generate the exact same log output, we will still only store it once.

Okay, so we have the base tables we need to deduplicate our data. Now let’s see how we would store the log files. (For the sake of a cleaner read, I’m going to omit all the usual boilerplate code you would find in a normal application, like validation and authentication and get to the point.)

!Sign up for a free Codeship Account

Assuming that we are receiving the logs from an API endpoint somewhere and that we just want to store them for later inspection and return a status code, here is a simple way we could implement it:

It’s as simple as that. The Log.store method simply uses Digest::MD5.hexdigest to calculate an MD5 checksum of the log data to which on the next line we then attempt to look up if the record exists. If it does not, we create a new log file and save it, being sure to wrap the save operation in a transaction just in case another log file with the same content is being stored at the same time.

The User model has a through: :user_logs relation to the Log model, which means when you call User.logs, it performs log look-up via the smaller join table, which just consists of ids in order to find relevant logs. This essentially resolves to a query that looks something like:

Subscribe via Email

Over 60,000 people from companies like Netflix, Apple, Spotify and O'Reilly are reading our articles. Subscribe to receive a weekly newsletter with articles around Continuous Integration, Docker, and software development best practices.

We promise that we won't spam you. You can unsubscribe any time.

Join the Discussion

Leave us some comments on what you think about this topic or if you like to add something.