---
title: 'Copycopter''s client: so fast'
teaser: Techniques for making Ruby client libraries fast and unobtrusive.
tags: web,copycopter,open source
author: Joe Ferris
published_on: 2011-02-24
---

[![Copycopter](http://images.thoughtbot.com/ui/speedcopter.jpg)](http://copycopter.com)

We [recently
launched](https://thoughtbot.com/blog/post/3201164937/introducing-copycopter)
our latest product to [great critical
acclaim](http://twitter.com/#!/obie/status/35842849090052096). We're happy to
see people climbing aboard the Copycopter, and feedback has been largely
positive.

However, one question we've had to field a few times is, **"how will Copycopter
affect the performance of my application?"** The answer is a good one: **it
won't**. However, that answer may seem too good to be true to some folks, so
let's find out how Copycopter manages to stay out of your application's way.

## Integration

One of Copycopter's nicer features is that the Ruby client is deeply integrated
into the Rails stack the second you install it. This is made possible by the
excellent [Rails I18n API](http://guides.rubyonrails.org/i18n.html). We hook in
our own I18n backend so that whenever Rails looks for a string, we use the text
you've set up on Copycopter. This all happens in copycopter_client's
[I18nBackend
class](https://github.com/copycopter/copycopter-ruby-client/blob/b48b4a6/lib/copycopter_client/i18n_backend.rb).
If you read quickly through that class, you'll see that fetching or storing copy
doesn't make a request to the Copycopter server; it looks for content in the
hash-like sync object.

```ruby
def lookup(locale, key, scope = [], options = {})
  parts = I18n.normalize_keys(locale, key, scope, options[:separator])
  key_with_locale = parts.join('.')
  content = sync[key_with_locale] || super
  sync[key_with_locale] = "" if content.nil?
  content
end
```

## Behind the scenes

The client's performance is acheived by using a background thread. When your
Rails application starts up, the client spins up a thread in the [Sync
class](https://github.com/copycopter/copycopter-ruby-client/blob/b48b4a6/lib/copycopter_client/sync.rb).

```ruby
    until @stop
      sync
      logger.flush if logger.respond_to?(:flush)
      sleep(polling_delay)
    end
```

Every five minutes, the background thread synchronizes with the Copycopter
server. It uses mutexes to make sure it isn't updating copy while the
application is using it. However, the mutex is only locked when already
downloaded copy is being swapped in, so the main thread won't be waiting for a
lock to release.

```ruby
def download
  client.download do |downloaded_blurbs|
    downloaded_blurbs.reject! { |key, value| value == "" }
    lock { @blurbs = downloaded_blurbs }
  end
rescue ConnectionError => error
  logger.error(error.message)
end
```

## HTTP friendly

We also don't want to waste any bandwidth or cycles by repeatedly downloading
unchanged copy. The [Client
class](https://github.com/copycopter/copycopter-ruby-client/blob/b48b4a6/lib/copycopter_client/client.rb)
is responsible for actually talking to the Copycopter server, and it speaks
fluent HTTP.

```ruby
def download
  connect do |http|
    request = Net::HTTP::Get.new(uri(download_resource))
    request['If-None-Match'] = @etag
    response = http.request(request)
    if check(response)
      log("Downloaded translations")
      yield JSON.parse(response.body)
    else
      log("No new translations")
    end
    @etag = response['ETag']
  end
end
```

Each running client tracks the latest ETag when it downloads copy, so most
requests simply return a 304 Not Modified response without sending any copy
data.

```ruby
when Net::HTTPNotModified
  false
```

## Developing

It should be mentioned that this passive behavior is only used in production
environments. During development and on a staging server, we found a five minute
delay between copy updates to be unacceptable. During development, we wrap each
request using a [little
piece](https://github.com/copycopter/copycopter-ruby-client/blob/b48b4a6/lib/copycopter_client/request_sync.rb)
of Rack middleware:

```ruby
def call(env)
  @sync.download
  response = @app.call(env)
  @sync.flush
  response
end
```

Although this could potentially add a slight delay to local requests, we found
that the faster feedback was worth the tradeoff, and the smart HTTP handling,
caching, and timeouts ensure that developing still feels snappy.

## Putting it all together

By integrating with I18n, using a background thread, and using the HTTP protocol
to our advantage, we achieve a number of performance and stability benefits:

* Slow copy downloads don't mean slow applications
* Server errors don't mean application errors
* Lots of application traffic doesn't mean many Copycopter requests
* Up-to-date copy doesn't cost much in terms of bandwidth
* Rails uses the I18n stack, so Rails engines and plugins support Copycopter by
  default

If you haven't given Copycopter a try yet, don't let apprehensions about
performance stop you. [Check out the code](https://github.com/copycopter/copycopter-ruby-client),
[install the client](https://rubygems.org/gems/copycopter_client), and see for yourself.

[Get to the choppa!](https://copycopter.com)
