aja
aja

Reputation: 139

Tornado websockets with mysql and redis

I have a tornado server which will create web-socket connection with clients. Whenever a client requests for some data I need to get it from either Redis or MySQL-DB. In addition to this I need to listen to broadcast from the tornado server, receive the network packets and send them to the clients if they are subscribed for the data. This sending of broadcast packets to client depends on the token which will be there in the packet. If clients are subscribed to the token we should send the packet to him.

Request rate:

  1. 5000 active web socket connections(Which can increase)
  2. 1-DB request per socket-connection per second so total 5000 DB-requests/second
  3. 1-Redis request per socket-connection per second so total 5000 Redis-requests/second.
  4. In broadcast I should listen to 1000 packets/second and check if any of the users are subscribed for the token.

I know redis is single threaded and work asynchronously(correct me if I am wrong). For MySQL-DB asynchronous driver which I am using is `tormysql`(Most of my calls to DB are select queries and no complex DB operations.).

My Question:

  1. Will the call to MySQL-DB is blocking? If they are blocking call can I run different thread/process just for DB queries?
  2. Is there a chance that tornado drop packets on the broadcast?
  3. I can have a load-balancer in front of my resources and server them but is it possible that I can use a single CPU with 2-cores 8-GB RAM?

Update 1:
I have written a code for MySQL:
Server.py

import logging
import tornado.escape
import tornado.ioloop
import tornado.options
import tornado.web
import tornado.websocket
import os.path
import uuid
import sys
from tornado import gen

from tornado.options import define, options

import tormysql ## MySQL async driver
pool = tormysql.ConnectionPool(
    max_connections = 20, #max open connections
    idle_seconds = 7200, #conntion idle timeout time, 0 is not timeout
    wait_connection_timeout = 3, #wait connection timeout
    host = "127.0.0.1",
    user = "test",
    passwd = "",
    db = "testdb",
    charset = "utf8"
)


define("port", default=8000, help="run on the given port", type=int)

class Application(tornado.web.Application):
    def __init__(self):
        handlers = [
            (r"/", MainHandler),
            (r"/dataSock", ChatSocketHandler),
        ]
        settings = dict(
            cookie_secret="__TODO:_GENERATE_YOUR_OWN_RANDOM_VALUE_HERE__",
            template_path=os.path.join(os.path.dirname(__file__), "templates"),
            static_path=os.path.join(os.path.dirname(__file__), "static"),
            xsrf_cookies=True,
        )
        super(Application, self).__init__(handlers, **settings)

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.render("indexWS.html")

class ChatSocketHandler(tornado.websocket.WebSocketHandler):
    openConnCount = 0
    def get_compression_options(self):
        # Non-None enables compression with default options.
        return {}

    def open(self):
        # print("Socket open:%s"%(self))
        ChatSocketHandler.openConnCount += 1
        None
    def on_close(self):
        # print("Socket closed:%s"%(self))
        ChatSocketHandler.openConnCount -= 1
        None

    async def on_message(self, message):
        logging.info("open conn count %r", ChatSocketHandler.openConnCount)
        returnDB = await getDataFromDB()
        self.write_message("server got this from you:%s"%(str(returnDB)))

@gen.coroutine
def getDataFromDB():
    with (yield pool.Connection()) as conn:
        try:
            with conn.cursor() as cursor:
                yield cursor.execute("SHOW TABLES;")
                # print(cursor.fetchall())
        except:
            yield conn.rollback()
        else:
            yield conn.commit()

        with conn.cursor() as cursor:
            yield cursor.execute("SELECT * FROM theme;")
            datas = cursor.fetchall()

    return datas

    # yield pool.close()
def main():
    tornado.options.parse_command_line()
    app = Application()
    # print "options:", options
    # sys.exit()
    app.listen(options.port)
    print("PORT:%s"%(options.port))
    tornado.ioloop.IOLoop.current().start()

if __name__ == "__main__":
    main()

Now when I test this code with this :
Client.py

import asyncio
import websockets

async def hello(i):
    async with websockets.connect('ws://localhost:8000/dataSock') as websocket:
        name = 'A'#input("What's your name? ")
        print("******************************%s******************************"%(i))
        for j in range(100):
            await websocket.send(name)
            # print("> {}".format(name))

            greeting = await websocket.recv()
            print("{}: {}".format(i, len(greeting)))
            asyncio.sleep(10)

async def start():
    for i in range(10):
        await hello(i)
    print("end")
    asyncio.sleep(20)
asyncio.get_event_loop().run_until_complete(start())
# asyncio.get_event_loop().run_forever()

If I run a single instance of the code every thing is working good. When I increase the number of clients to 70 (70 instance of the client) there is a delay in the response that I get.

2nd question explanation:
Tornado server has to listen on some port in which I will be receiving the network packets the one which I have to send it to clients if they are subscribed. So will there be a possibility that these packets will be dropped ?

Upvotes: 0

Views: 500

Answers (1)

xyres
xyres

Reputation: 21744

  1. Will the call to MySQL-DB is blocking? If they are blocking call can I run different thread/process just for DB queries?

As you mentioned you're using tormysql as the driver, so no, the calls won't block since tormysql is asynchronous.

  1. Is there a chance that tornado drop packets on the broadcast?

I don't quite understand what you mean by that. But since the websocket protocol is built on TCP, so delivery of all the packets is guaranteed. TCP takes care of that.

  1. I can have a load-balancer in front of my resources and server them but is it possible that I can use a single CPU with 2-cores 8-GB RAM?

Yes.


I think you're overthinking your application at this point. Premature optimization is evil. You haven't written any code yet and you're thinking about performance. It's just wasting your time.

Write the code first, then stress test it to see how much load can your application handle. Then do profiling to see where the things are slowing down. Then you optimize the code, or change your setup and maybe upgrade your hardware.

But just thinking about performance without writing and stress testing the code is just a waste of time.

Upvotes: 1

Related Questions