First, break the problem into "requests" and "replies". A request (from server to sub-server) should begin with some sort of request type identifier, and all of the other data in the request should depend on the request type identifier (e.g. if you have a "set name" request, then you don't include irrelevant data like "number of cats" in that request). The type of request also determines the type/s of reply the sub-server might send back (e.g. if you send a "set name" request then you might get a "name set" reply or an "error reason" reply; but won't get a "number of cats" reply).
Second; avoid having modes/contexts if at all possible. For example, rather than having a "set mode to foo" request and a "get name" request, just have a "get name using mode foo" request. This reduces the amount of state in the sub-servers and makes code cleaner; and it can also be important for things like fault tolerance and scalability later on.
Third; allow groups of requests (and groups of replies) to be sent across the network in the same packet. The only reason for this is that small packets waste bandwidth which can become a bottleneck under load. Basically you'd have a "send_request()" function that tries to add the request to the current packet (and sends the packet and starts a new one if the request won't fit). You will also need timing to avoid latency problems under light load. For example, when you add the first request to a new packet, set a timer for about 2 ms, and when that timer expires send the packet regardless of whether it's been filled or not.
Fourth; I personally dislike sending data that isn't text as text. Some people think it makes it easier for debugging (and it can a little bit if you're unable to work with hex dumps or write a simple decoder); but this doesn't justify the unnecessary increase in bloat. For a simple example, sending a signed 32-bit integer would mean converting to ASCII, sending up to 11 bytes of data, then converting from ASCII back into an integer; rather than sending 4 bytes of data with no conversions (other than serialisation for endianness that would be almost always optimised to nothing). Sadly, there's a lot of people using scripting languages for more than just scripting now, so "doing it right(tm)" might be impractical and avoiding bloat may be impossible (e.g. a lot of scripting languages store integers as strings internally).
Fifth; document the protocol well. You should write a "formal-ish" specification that describes each individual request (what its intended for, its "request ID", which fields in which format, which replies are possible, etc) and describes each individual reply. It should also mention if there's any special requirements (e.g. a "lock entry" request needs to be followed by one or more "modify entry" requests and then an "unlock entry request").
Sixth; examine how the data is used. Typically (for databases) finding the right row/s takes most of the time and it doesn't matter how many columns are in that row; and typically you need multiple pieces of data from a row at the same time. It's likely that having one sub-server for "name", one for "address", one for "age", etc is going to give bad performance, because if you want the name, address and age at the same time then you end up with 3 sub-servers trying to find the right rows (with triple the overhead). Normally there's some sort of unique key involved (e.g. a user ID) and it's far better for the server to use this to determine which sub-server is responsible (e.g. for 9 sub-servers, the server would do "sub_server_number = user_ID % 9" to determine which sub-server to talk to for all transactions involving that user ID). Note that this may also be important for fault tolerance - e.g. if the sub-server for "name" explodes then the entire system will probably fail; but if the sub-server for every nth unique key explodes then all transactions involving other unique keys can continue uninterrupted.