eezhov-ooma opened a new pull request, #6102:
URL: https://github.com/apache/couchdb/pull/6102

   If the local node (coordinator) does not contain the shard, 
`group_by_proximity` returns empty list for SameZone shards list.
   
   If the `group_by_proximity` is called with list of shards without local 
node, the LocalNode variable is set to 'undefined'. As a result, the SameZone 
list is empty and all shards are set to the DifferentZone list.
   
   The solution is to use local node info to define LocalZone var.
   
   ----
   # Overview
   
   `mem3:group_by_proximity` does not return SameZone nodes if the coordinator 
node does not contain the shard.
   
   # Details
   
   The issue found in the CouchDB 3.5.2.
   When the request is to get view data like `GET 
/{{DB}}/_design/{{ddoc}}/_view/{{view_name}}`, the chain is (the issue ):
   
   
   ```
   chttpd_auth_request:authorize_request_int/1   :82   catch-all [_DbName | _]
     → db_authorization_check/1                  :108
       → fabric:get_security(DbName, [{user_ctx, Ctx}])
         → fabric.erl                            :230
           → fabric_util:get_db/2                :127
             → fabric_util:get_shard/4           :145
               → rexi:cast(Node, self(), {fabric_rpc, open_shard, [Name, …]}, 
[sync])
   ```
   
   The shard selection here is:
   
   ```
   fabric_util:get_db(DbName, Options) ->
       {Local, SameZone, DifferentZone} = 
mem3:group_by_proximity(mem3:shards(DbName)),
       Shards = Local ++ lists:keysort(#shard.name, SameZone) ++ 
lists:keysort(#shard.name, DifferentZone),
       Nodes = [node() | nodes()],
       Live = [S || #shard{node = N} = S <- Shards, lists:member(N, Nodes)],
       ...
       get_shard(Live, Options, Timeout, Factor).
   ```
   
   then it sends one request to the first selected shard:
   
   ```
   fabric_util:get_shard([#shard{node = Node, name = Name} | Rest], Opts, 
Timeout, Factor) ->
       Mon = rexi_monitor:start([rexi_utils:server_pid(Node)]),
       MFA = {fabric_rpc, open_shard, [Name, [{timeout, Timeout} | Opts]]},
       Ref = rexi:cast(Node, self(), MFA, [sync]),
   ```
   
   The issue is with shard selection part. It does not work as expected, and 
selects the first node in the alphabetical order from the DifferentZone list 
since the SameZone list is empty.
   
   Let's say the DB has the following shards:
   
   ```
   % curl -s 
admin:[email protected]:5984/account%2Fbd%2F6b%2F26370ff1b6aea7e6cfa5ddf27343/_shards
 | jq
   {
     "shards": {
       "00000000-ffffffff": [
         "[email protected]",
         "[email protected]",
         "[email protected]"
       ]
     }
   }
   ```
   
   If the request to get view is landed on the `couchdb2-zonea-1.com` then 
`mem3:group_by_proximity(mem3:shards(DbName))` returns:
   
   ```
   ([email protected])4>  
mem3:group_by_proximity(mem3:shards(<<"account/bd/6b/26370ff1b6aea7e6cfa5ddf27343">>)).
   {[],[],
    
[{shard,<<"shards/00000000-ffffffff/account/bd/6b/26370ff1b6aea7e6cfa5ddf27343.1787293722">>,
            '[email protected]',
            <<"account/bd/6b/26370ff1b6aea7e6cfa5ddf27343">>,
            [0,4294967295],
            undefined,[]},
     
{shard,<<"shards/00000000-ffffffff/account/bd/6b/26370ff1b6aea7e6cfa5ddf27343.1787293722">>,
            '[email protected]',
            <<"account/bd/6b/26370ff1b6aea7e6cfa5ddf27343">>,
            [0,4294967295],
            undefined,[]},
     
{shard,<<"shards/00000000-ffffffff/account/bd/6b/26370ff1b6aea7e6cfa5ddf27343.178729372"...>>,
            '[email protected]',
            <<"account/bd/6b/26370ff1b6aea7e6cfa5ddf27343">>,
            [0,4294967295],
            undefined,[]}]}
   
   ```
   
   where `Local = []`, `SameZone = []`, `DifferentZone = [... all shards ...]`.
   
   In opposite, `ushards` uses ZoneMap list to check shards correctly and it 
returns SameZone node as expected:
   
   ```
   ([email protected])1> 
mem3:ushards(<<"account/bd/6b/26370ff1b6aea7e6cfa5ddf27343">>).
   
[{shard,<<"shards/00000000-ffffffff/account/bd/6b/26370ff1b6aea7e6cfa5ddf27343.1787293722">>,
           '[email protected]',
           <<"account/bd/6b/26370ff1b6aea7e6cfa5ddf27343">>,
           [0,4294967295],
           undefined,[]}] 
   ```
   
   The issue is how `group_by_proximity/2` defines the LocalZone variable:
   
   From the example above, the nodes which contain the shards are the 
followings. In the code, it is variable "Node".
   ```
   ['[email protected]',
    '[email protected]',
    '[email protected]']
   ```
   
   Then, we should check what the ZoneMap is. Look the following two code 
sections:
   ```
   361 zone_map(Nodes) ->
   362     [{Node, node_info(Node, <<"zone">>)} || Node <- Nodes].
   ```
   
   and
   
   ```
   117 node_info(Node, Key) ->
   118     mem3_nodes:get_node_info(Node, Key).
   ```
   
   Based on the above code, I ran the following cmd:
   
   ```
   ([email protected])25> [{Node, mem3_nodes:get_node_info(Node, 
<<"zone">>)} || Node <- Nodes].
   [{'[email protected]',<<"dc3">>},
    {'[email protected]',<<"dc1">>},
    {'[email protected]',<<"dc2">>}]
   ```
   
   So, the ZoneMap here should be:
   
   ```
    [{'[email protected]',<<"dc3">>},
    {'[email protected]',<<"dc1">>},
    {'[email protected]',<<"dc2">>}]
   ```
   
   Finally, we need to look at the following code. The line 373 for LocalZone, 
it is just wrong. In line 373, the LocalZone is set to undefined. It should be 
the zone the local node(coordinator) is in.
   
   ```
   368 group_by_proximity(Shards, ZoneMap) ->
   369     {Local, Remote} = lists:partition(
   370         fun(S) -> mem3:node(S) =:= config:node_name() end,
   371         Shards
   372     ),
   373     LocalZone = proplists:get_value(config:node_name(), ZoneMap),
   374     Fun = fun(S) -> proplists:get_value(mem3:node(S), ZoneMap) =:= 
LocalZone end,
   375     {SameZone, DifferentZone} = lists:partition(Fun, Remote),
   376     {Local, SameZone, DifferentZone}.
   ```
   
   The following is an example output I ran from the `couchdb1-zonea-1.com`:
   ```
   ([email protected])40> 
mem3_nodes:get_node_info(config:node_name(),<<"zone">>).
   <<"dc1">>
   ```
   
   # Solution
   
   The solution is to use `node_info(config:node_name(),<<"zone">>)` instead of 
getting value from the ZoneMap, since it does not contain local 
node(coordinator).
   
   Example output after patch:
   
   ```
   ([email protected])1> 
mem3:group_by_proximity(mem3:shards(<<"account/bd/6b/26370ff1b6aea7e6cfa5ddf27343">>)).
   {[],
    
[{shard,<<"shards/00000000-ffffffff/account/bd/6b/26370ff1b6aea7e6cfa5ddf27343.1787293722">>,
            '[email protected]',
            <<"account/bd/6b/26370ff1b6aea7e6cfa5ddf27343">>,
            [0,4294967295],
            undefined,[]}],
    
[{shard,<<"shards/00000000-ffffffff/account/bd/6b/26370ff1b6aea7e6cfa5ddf27343.1787293722">>,
            '[email protected]',
            <<"account/bd/6b/26370ff1b6aea7e6cfa5ddf27343">>,
            [0,4294967295],
            undefined,[]},
     
{shard,<<"shards/00000000-ffffffff/account/bd/6b/26370ff1b6aea7e6cfa5ddf27343.1787293722">>,
            '[email protected]',
            <<"account/bd/6b/26370ff1b6aea7e6cfa5ddf27343">>,
            [0,4294967295],
            undefined,[]}]}
   ([email protected])2> 
   ```
   
   i.e. SameZone contains the shard from the local zone. 


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to