ActionHeroJS Persistent bidirectional websocket connections with a million devices

273 views
Skip to first unread message

Danny Brody

unread,
Apr 15, 2014, 8:59:32 PM4/15/14
to action...@googlegroups.com
Hi Evan, I have been experimenting with ActionHeroJS and I love it! I am fighting my co-workers and trying to win them over to use ActionHeroJS in production.  With that said here is where I am stuck.
Pretty much I need thousands of clients (a little over 1 million during peak time) be able to maintain a persistent connection to my api to deliver information back and forth between the clients and the server. 
I want persistent the connections because the client will be sending and receiving data 1-30 times in a 5 min period as long as the devices are on, and low latency is key! the faster the response the better!
I was thinking of using sockets accomplish this, however, I am having trouble finding out how to handle/implement socket actions from the client with ActionHeroJS.
I see in the actionHeroClient.js file there are commands for say, file, detailsView, etc... I need to forget about the chatroom concept and create my own methods completely and find out how to react to them on the ActionHeroJS server.
Maybe I'm too used to the way socket.io works so I really do not know where to go from here :(

So I just need to be pointed in the right direction on how to persist a websocket connection with a client and handle various actions while maintaining a connection.
For example: 
1) client connects with some payload data
2) ActionHero server finds information about the client in my database (already set up the initializers and actions for the database with actiohero and are working nicely independently)
3) ActionHero sends information back to the client through the connection
4) client might request some sort of file
5) client will be sending payloads to the server at random times whenever an event on the client side triggers it
6) ActionHero might push an event to the client at anytime
7) ActionHero might push an event to MANY clients at anytime
I have a feeling I am really over thinking this, but just thought I would ask just in case!
Thank you for all your hard work Evan, I truly appreciate all the awesomeness in ActionHeroJS.

PS I know a million connections is a lot, and this will be deployed across tens to hundreds of pretty big EC2 instances in a huge cluster of NODEJS awesomeness

Evan Tahler

unread,
Apr 16, 2014, 11:57:57 AM4/16/14
to action...@googlegroups.com, Danny Brody
Hi!  Let me see if I can help you out:

Sending a message every 10 seconds (30 messages within 5 minutes) over a persistent connection is something that node (in general) should be very good at, regardless of framework.  That said, keeping 1M connections open on a single server is a *lot* for any OS to manage (sockets open, files open, file pointers, etc), even without the overhead of Node, a web server, etc.  It’s possible, but you probably will need a number of hosts to handle this load.  actionhero has support for this out-of-the-box, based on redis and faye.  We use a redis backend in actionhero to support both node-cluster and multi-server deployments.  Redis is a very fast DB backend, assuming your working set can fit in RAM.   1M records should be able to fit under 2GB, but you should do some testing with actionhero and simulating connections. 

When using the websocket clients, there are really only 2 ways to communicate with the server: actions and chat.  Actions (like http requests) send data to the server and expect a response back.  Chat is always listening for events.  From your examples, most things look like an action to me (send connection payload, send click events from client, etc).   The types of events where the server sends information to one or many clients are a great use-case for chat. Don’t think that the ‘chat’ system has to be used for client-to-client messaging, as in most cases, the chat system is used only for server-client messaging (you have a message, the time is x, you need to re-authenticate, etc).  There’s an authentication system around chat rooms.  You can prohibit client-client communication if you want.  Clients can only “be” in one chat room at a time, but they can listen to messages from may rooms at once.  

In general, I always advise against sending image/file data over web sockets.  It’s possible, but it is very easy to have encoding problems and the like.  While it adds a second step, I always suggest just sending the file’s URL via websocket and having the browser/phone load in the image over HTTP.  There’s already a robust ecosystem of image caching solutions, CDNs, etc which are optimized for sharing content with large numbers of clients. 

--
You received this message because you are subscribed to the Google Groups "actionHero.js" group.
To unsubscribe from this group and stop receiving emails from it, send an email to actionhero-j...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Danny Brody

unread,
Apr 16, 2014, 3:21:19 PM4/16/14
to action...@googlegroups.com
Thanks for your response Evan that is really appreciated.  I have a separate server setup that is only being used as the redis store to manage all the connections and it has 30GB of memory so that should be able to accomplish everything connection-wise.  My issue i guess is i do not know where to start implementing actions for use with websockets.  Do i just create standard actions and they will automatically be able to be used with any type of connection?  Do i need to set things up in the /servers/websocket.js file.  I think i just need to figure out where to start in order to get this done and I'm having trouble with doing that at this point. ill continue to use the "trial and error" method until i hear back.. thank you for all your help so far!

Evan Tahler

unread,
Apr 16, 2014, 4:04:15 PM4/16/14
to action...@googlegroups.com, Danny Brody
Actions created within your actionhero project work for all connection types and servers… that’s the point! 

You can scope an action to only respond to a certain server type with `action.blockedConnectionTypes: [“webSocket”]`, but the default is that they will respond to all connections.


From: Danny Brody thebro...@gmail.com
Reply: Danny Brody thebro...@gmail.com
Date: April 16, 2014 at 12:21:20 PM
To: action...@googlegroups.com action...@googlegroups.com
--

Danny Brody

unread,
Apr 16, 2014, 4:18:39 PM4/16/14
to action...@googlegroups.com, Danny Brody
AWESOME AS ALWAYS!!!! THANK YOU!!

Danny Brody

unread,
Apr 16, 2014, 4:40:11 PM4/16/14
to action...@googlegroups.com
So i am playing with the chat.html file that came with the basic installation.  So my current roadblock is how do i get the client to interact with an action through websockets?

Simple example, i am trying to get the client to access the "status" action.  the default chat.html is:

var client;
  var boot = function(){
    client = new actionheroClient;
    client.on('connected', function(){ 
    })
    client.on('disconnected', function(){ console.log('disconnected :(') })
    
    // client.on('message',      function(message){ console.log(message) })
    client.on('alert',        function(message){ alert(message) })
    client.on('api',          function(message){ alert(message) })
    client.on('welcome',      function(message){ appendMessage(message); })
    client.on('say',          function(message){ appendMessage(message); })
    client.connect(function(err, details){
      if(err != null){
        console.log(err);
      }else{
        client.roomChange("defaultRoom");
        document.getElementById("name").innerHTML = "<span style=\"color:#" + intToARGB(hashCode(client.id)) + "\">" + client.id + "</span>"
      }
    });
      
  }

With that said, how do i call the "status" action through websockets on the client side?  Once I do that I will be able to figure out everything else.  ive tried client.status() but that doesnt work.  Thank you for your help once again. I figure there is something missing on the client to be able to call the default status action.

On Tuesday, April 15, 2014 5:59:32 PM UTC-7, Danny Brody wrote:

Evan Tahler

unread,
Apr 16, 2014, 4:42:12 PM4/16/14
to action...@googlegroups.com, Danny Brody
var data = {key: value};
client.action(‘nameOfAction’, data, function(response){
console.log(response)
});


From: Danny Brody thebro...@gmail.com
Reply: Danny Brody thebro...@gmail.com
Date: April 16, 2014 at 1:40:12 PM
To: action...@googlegroups.com action...@googlegroups.com
Subject:  [actionHero] Re: ActionHeroJS Persistent bidirectional websocket connections with a million devices

var data = {key: value};
client.action(‘nameOfAction’, data, function(response){
console.log(response)
});

whizz...@gmail.com

unread,
Apr 16, 2014, 4:42:52 PM4/16/14
to action...@googlegroups.com
Take a look at http://actionherojs.com/wiki/servers/websocket.html#methods, specifically:

client.action(action, params, callback) where
action is a string, like “login”
params is an object
callback will be passed error, response

Evan Tahler

unread,
Apr 16, 2014, 4:45:56 PM4/16/14
to action...@googlegroups.com, whizz...@gmail.com
Ahh! There’s a slight error in the docs.  Thanks for pointing that out https://github.com/evantahler/actionhero/commit/77b2609b7672f398989b1b9897d7a8f9b55edf84


From: whizz...@gmail.com whizz...@gmail.com
Reply: whizz...@gmail.com whizz...@gmail.com
Date: April 16, 2014 at 1:42:54 PM
To: action...@googlegroups.com action...@googlegroups.com
Subject:  [actionHero] Re: ActionHeroJS Persistent bidirectional websocket connections with a million devices

Danny Brody

unread,
Apr 16, 2014, 5:22:33 PM4/16/14
to action...@googlegroups.com, whizz...@gmail.com
Great job Evan, you just made me WEEK!! here is some basic sample code just so anyone else can reference if they have a common problem:

client code:
        var data = {userName: 'danny'};
          client.action('status', data, function(response){
            console.log(response)
        });
console log result on client side response variable:
Object {id: "xx.xx.xxx.xxx", time: 1397683082816, data: "sendmethis", messageCount: 2, context: "response"}

status action code:
var action = {}; /////////////////////////////////////////////////////////////////////
// metadata
action.name = 'status';
action.description = 'I will return some basic information about the API';
action.inputs = {
'required' : ['userName'],
'optional' : []
};
action.blockedConnectionTypes = [];
action.outputExample = {
status: 'OK',
uptime: 1234,
stats: {}
} /////////////////////////////////////////////////////////////////////
// functional
action.run = function(api, connection, next){
console.log(connection.params)

connection.response = {};
connection.response.id = api.id;
connection.response.time = new Date().getTime();
connection.response.data = 'sendmethis';
next(connection, true);
}; /////////////////////////////////////////////////////////////////////
// exports
exports.action = action;

output from console on server side:
{ userName: 'danny',
  action: 'status',
  limit: 100,
  offset: 0,
  apiVersion: 1 }

Perfect! I am now confident I can get this ActionHeroJS usable for my project!!!! Evan you seriously are great, most people don't respond when people have problems with using frameworks, but you give me great confidence in using ActionHeroJS.  thank you for all of your time and effort!!!

stephane...@gmail.com

unread,
Apr 18, 2015, 4:49:50 AM4/18/15
to action...@googlegroups.com
Evan also made my day today; I was a little bit confused the way action hero was working, the documentation is not really clear about what "chat rooms" are, and the way the framework is handling different connection types; I can see clearer now.

Just a message to say thanks to Evan, good and clear explanations :)

Evan Tahler

unread,
Apr 18, 2015, 7:34:02 AM4/18/15
to action...@googlegroups.com, stephane...@gmail.com
Yay! 

And this is why a searchable message board is very important.  

If any of you have changes which you think would make the documentation better, just send in a Pull Request! https://github.com/evantahler/actionhero/tree/gh-pages
Reply all
Reply to author
Forward
0 new messages