Monday, January 11, 2016

Implementing a chat. It is not easy as it sounds...


Chat.  One of the most basic modules in any social app/site, but it still costs you. alot.

Requirements
Let`s go over the requirements of my typical 1on1 chat:
  • The user can have multiple 1x1 chats in parallel. He can see a list of the open chats, and see which chats have unread messages.
  • If it`s an app, a push notification to tell me someone is trying to reach me can be nice.
  • Every chat has history, I want to see, at least, the last 100 messages.
  • Low latency for new chat
  • Presence is optional (mark user as online/offline)
Client-Server protocol
There are two technologies to discuss here.
First, should we use custom chat messages and custom server code for message-format, presence keep-alive, user unique-id, or should we use the well known XMPP protocol (used by lots of IM, like google talk).  Using this known protocol, we can find well tested servers (like ejabberd) which implement it.
Second. what push/poll technology to use?
  • Regular Polling - to achieve low latency we will need to poll each 10 seconds, even if there is no update. This is a bit of a waste.
    Long Polling (also called Comet/Hanging Get) - the client calls a Get and the server will not respond until an update is ready, even if it will "wait on it" for an hour. This trick allows to have a one directional simulated "push" from the server to the client.
    WebSockets - open a bi-directional, always open, socket between the server and the client.  Not all browsers support it yet. The major unsupported version is Android prior to 4.4, this is still quite big market. And (TBD:verify) I heard it may work not as great on mobile, when switching network.

Implementation 

There are many ways to solve this. Let me discuss my solution, which is good for small apps/prototypes. In the appendix, I will describe few more, which can scale better.As my current back-end is based on Parse.com, we will use for the chat-history DB.  As Parse does not have realtime support (no long-polling or web-sockets), we will use a different back-end provider, quickblox, for realtime chat.

The data model:
The trivial one, is storing each message in a log-like table (from, to , time, message,opened-flag) and on query, read the last X(=100) message, and on the client-code combine them into Y(=15) chat conversations and mark those still not-opened.
This is the standard "SQL" way, let`s think about the "Non-SQL" / "Document" way. Another problem here is that X might need to be very large.

One option:

Using quickblox:
Integrate with quickblox users (you can use external users, as long as you saved the user id outside).
On signup/login, login with the same user&password to all platforms. Save in each platform, the user-id in the other platform ,for example use Parse id in Quickblox user.externalUserID, and vice versa.  If you already have some users, change the login code to sing-up quickblox and then login.


Use the chat api 2.0 , when current user P1 wants to communicate to P2 (parse-users-id), retrieve the curent user quickblox id (B1) on the current-user, and query P2 for it`s external B2 id.

Online/Offline - if you want to see the online state of users, you can use the roster chat option. You register for all the users you are intrested in the client (for example current 10 friends) and will get updates in realtime.
Note that this is a very resource intensive action. If a user roster contains 20 friends, although no chat messages is done, you still get updates. A less real-time option will be to use your server to see the states, and let the user query this once in few minutes. 


Other options...

Implement it using Google App Engine Channels (Comet/hanging GET).  Pricing

WebSockets - there are node.js open source implementations, 
or use (Still in alpha) Google App Engine has two option,  the  WebSocket-server on ManagedVM 

You can use a BaaS which provide chat or realtime-data services. This is usually free at first, but costly in large-scale. Generally a good option for rapid-development, for example: Firebase, Pusher (no history), Layer (no js.. coming soon)


No comments: