Uploaded image for project: 'Python Driver'
  1. Python Driver
  2. PYTHON-515

MongoClient ignores ReadPreference.SECONDARY?

    • Type: Icon: Bug Bug
    • Resolution: Done
    • Priority: Icon: Major - P3 Major - P3
    • None
    • Affects Version/s: 2.4, 2.5, 2.5.1
    • Component/s: None
    • Labels:
      None
    • Environment:
      Server 2.4.1, Python 2.7.3, pymongo 2.4 from pip, pymongo 2.5+ from git master

      We rely on reading secondaries to balance our working set load and we recently switched to MongoClient. It looks like the MongoClient class doesn't support reading from secondaries properly. Quick summary of the call stack:

      MongoClient().collection.find()
      --> Cursor().__send_message()
      ----> MongoClient._send_message_with_response()
      ------> MongoClient.__socket() # ALWAYS RETURNS MASTER
      

      This appears to be a problem on pymongo 2.4 in pip and the latest git master as well.

      Here's a sample session to help demonstrate what's going on (names have been changed to protect the innocent servers):

      # Our server names are m4,m5,m6. During this example, m5 is master.
      
      >>> from pymongo import *
      >>> version
      '2.5+'
      >>> m = MongoClient(['m4'], replicaSet='example', tz_aware=True, read_preference=ReadPreference.SECONDARY)
      >>> m.server_info()
      {u'ok': 1.0, u'bits': 64, u'javascriptEngine': u'V8', u'version': u'2.4.1', u'gitVersion': u'1560959e9ce11a693be8b4d0d160d633eee75110', u'versionArray': [2, 4, 1, 0], u'debug': False, u'compilerFlags': u'-Wnon-virtual-dtor -Woverloaded-virtual -fPIC -fno-strict-aliasing -ggdb -pthread -Wall -Wsign-compare -Wno-unknown-pragmas -Winvalid-pch -Werror -pipe -fno-builtin-memcmp -O3', u'maxBsonObjectSize': 16777216, u'sysInfo': u'Linux ip-10-2-29-40 2.6.21.7-2.ec2.v1.2.fc8xen #1 SMP Fri Nov 20 17:48:28 EST 2009 x86_64 BOOST_LIB_VERSION=1_49', u'loaderFlags': u'-fPIC -pthread -rdynamic', u'allocator': u'tcmalloc'}
      
      # How does the driver see our replicaset?
      
      >>> m._MongoClient__nodes
      set([(u'm5', 27017), (u'm6', 27017), (u'm4', 27017)]) # all good
      
      >>> m._MongoClient__simple_command(m._MongoClient__socket(), 'admin', {'ismaster':1})
      ({u'me': u'm5:27017', u'ismaster': True, u'ok': 1.0, u'setName': u'example', u'arbiters': [u'a1:27017'], u'primary': u'm5:27017', u'hosts': [u'm5:27017', u'm6:27017', u'm4:27017'], u'maxMessageSizeBytes': 48000000, u'localTime': datetime.datetime(2013, 5, 9, 1, 20, 9, 267000), u'maxBsonObjectSize': 16777216, u'passives': [u'm7:27017'], u'secondary': False}, 0.12795710563659668) # just confirming that m5 is, indeed, the master
      
      # Now MongoClient.colleciton.find() eventually calls MongoClient.__socket(), so let's see who it sends to:
      
      >>> m._MongoClient__socket().sock.getpeername()
      ('XXX.YYY.ZZZ.WWW', 27017) # this the ip of m5, so it's sending to the master
      
      # Also, MongoClient.__socket() relies on MongoClient.__find_node() which relies on  MongoClient.__try_node(node) to decide if the connection is suitable. Let's try it on all nodes like __find_node() does:
      
      >>> [m._MongoClient__try_node(node) for node in m._MongoClient__nodes]
      [((u'm5', 27017), True, False, 0.011200189590454102), ((u'm5', 27017), True, False, 0.012281179428100586), ((u'm5', 27017), True, False, 0.016232967376708984)]
      
      # You can see from the above that __try_node() always returns the master. It parses the return from the sever, looking for 'primary'. It finds this in every server response, and ends up returning __try_node(primary) for all of them.
      

            Assignee:
            bernie@mongodb.com Bernie Hackett
            Reporter:
            ryanwitt Ryan Witt
            Votes:
            0 Vote for this issue
            Watchers:
            2 Start watching this issue

              Created:
              Updated:
              Resolved: