We needed an APNS package for use with a many-tenant MDM platform. Specifically, we needed the ability to quickly push many notifications to devices spanning across many push certificates.
Traditionally with APN, you establish a connection to Apple, send as many notifications as you desire, and then disconnect. The problem we have is that we need to create a separate connection for each account, and simply connecting and disconnecting for each notification will look like a DDoS attack from the perspective of Apple.
The particular project in question was written in Ruby, so we started looking at existing gem solutions.
We started here and eventually forked and added MDM support. apn_sender keeps a persistent connection to Apple, which is great. It doesn’t handle multiple certificates though, so that means we’d have to have a separate daemon process running for every single push certificate.
In fact, most APNS packages were eliminated for this reason: They weren’t built with multiple-certificate handling in mind, meaning something costly would have to be instantiated for each certificate.
This gem sets out to solve multiple-certificate handling, but it fell short in two ways:
- It requires the compilation and usage of a fork of ZMQMachine. We don’t want to have to manually compile the gem and we don’t want to depend on someone keeping a fork of a project maintained.
- It assumes the bottleneck is in the building of the APNS message, not in the SSL connection setup/teardown with Apple. The gem instantiates many workers for the activity of building the message to be pushed, but by default only runs a single firehose, which is responsible for connecting to Apple. We found the opposite to be true: the connection to Apple is the slowest part.
These are both outstanding, well used, active projects. They unfortunately have the same problems as most gems in that they were not designed for many, many different APNS accounts.
Alas, we finally found a solution.
In brief, AppleShove receives push requests from a Redis queue structure. These push requests include the APNS certificate and the payload to be pushed. A single thread called the demultiplexer reads from this Redis queue and also manages a pool of connection threads to Apple. When a request is received, the demultiplexer sends the request to the appropriate connection thread. If the connection thread doesn’t already exist, it’s created first. That’s it!
For you concurrency fans out there, we are using the Actor concurrency pattern via Celluloid.
This architecture accomplishes a few things:
- “Caches” connections to Apple. If we’ve sent a notification with a particular certificate recently, we get to reuse the connection instead of having to re-establish it.
- Allows notifications to be sent in parallel. We aren’t waiting for a series of connections and disconnections to take place before we can send notification #n.
- Simplifies our client implementation. Since each notification contains all of the information AppleShove needs to send it on it’s way, we can request notifications via a single static method.