-
Notifications
You must be signed in to change notification settings - Fork 264
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix the issue of ignoring callback calls for removed keys. #789
Conversation
/azp run |
Azure Pipelines successfully started running 1 pipeline(s). |
@qiluo-msft kindly reminder to review |
common/configdb.h
Outdated
data = None | ||
else: | ||
client = self.get_redis_client(self.db_name) | ||
data = self.raw_to_typed(client.hgetall(key)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The return value of hgetall
is aligned with the Redis HGETALL
:
https://redis.io/commands/hgetall/
Return
Array reply: list of fields and their values stored in the hash, or an empty list when key does not exist.
From my POV it works as expected.
There is no reason to query data for the key if the key is removed. We can consider this also as a performance optimization.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@qiluo-msft, @liuh-80 kindly reminder to review
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree with you on HGETALL behavior.
common/configdb.h
Outdated
@@ -105,8 +105,11 @@ class ConfigDBConnector_Native : public SonicV2Connector_Native | |||
try: | |||
(table, row) = key.split(self.TABLE_NAME_SEPARATOR, 1) | |||
if table in self.handlers: | |||
client = self.get_redis_client(self.db_name) | |||
data = self.raw_to_typed(client.hgetall(key)) | |||
if item['data'] == 'del': |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can get raw_data == {}
in two cases:
- When the key is removed.
- If the key is created without attributes. An example is NTP configuration:
"NTP_SERVER": {
"10.210.25.32": {},
"10.75.202.2": {}
},
So, we can't rely only on the data itself but also need to take into account operation type.
If the operation type is 'del' we clearly understand that the key was removed and there is no need to query its data, so we can pass None
instead.
Otherwise, the key was added or changed, and raw_data == {}
is valid data in that case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for mistake. I update above comment hset
-> hdel
.
What I mean is that del
is not the only possible item['data']
for a deleted key. Please consider at least hdel
.
This is my experiment:
Session 1:
$ redis-cli -n 4
127.0.0.1:6379[4]> hset a b c
(integer) 1
127.0.0.1:6379[4]> hdel a b
(integer) 1
127.0.0.1:6379[4]>
Session 2:
$ redis-cli
127.0.0.1:6379> select 4
OK
127.0.0.1:6379[4]> psubscribe '__keyspace@4*__:*'
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "__keyspace@4*__:*"
3) (integer) 1
1) "pmessage"
2) "__keyspace@4*__:*"
3) "__keyspace@4__:a"
4) "hset"
1) "pmessage"
2) "__keyspace@4*__:*"
3) "__keyspace@4__:a"
4) "hdel"
1) "pmessage"
2) "__keyspace@4*__:*"
3) "__keyspace@4__:a"
4) "del"
1) "pmessage"
2) "__keyspace@4*__:*"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is correct, but we are interested in only del
messages in this particular case.
hdel
is generated when the field under the key is removed:
root@r-r740-08-bf3-sonic-01:/home/admin# redis-cli -n 4
127.0.0.1:6379[4]> HSET key_1 a 1
(integer) 1
127.0.0.1:6379[4]> HSET key_1 b 1
(integer) 1
127.0.0.1:6379[4]> HDEL key_1 b 1
(integer) 1
127.0.0.1:6379[4]>
127.0.0.1:6379[4]> psubscribe '__keyspace@4*__:*'
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "__keyspace@4*__:*"
3) (integer) 1
1) "pmessage"
2) "__keyspace@4*__:*"
3) "__keyspace@4__:key_1"
4) "hset"
1) "pmessage"
2) "__keyspace@4*__:*"
3) "__keyspace@4__:key_1"
4) "hset"
1) "pmessage"
2) "__keyspace@4*__:*"
3) "__keyspace@4__:key_1"
4) "hdel"
So, when 'hdel' is received we need to get key data to handle the removed field.
'del' message is generated when the key is removed, including all its data. In this case, there is no data to get from DB as they are already gone:
root@r-r740-08-bf3-sonic-01:/home/admin# redis-cli -n 4
127.0.0.1:6379[4]> HSET key_1 a 1
(integer) 1
127.0.0.1:6379[4]> HSET key_1 b 1
(integer) 1
127.0.0.1:6379[4]> DEL key_1
(integer) 1
127.0.0.1:6379[4]>
root@r-r740-08-bf3-sonic-01:/home/admin# redis-cli -n 4
127.0.0.1:6379[4]> psubscribe '__keyspace@4*__:*'
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "__keyspace@4*__:*"
3) (integer) 1
1) "pmessage"
2) "__keyspace@4*__:*"
3) "__keyspace@4__:key_1"
4) "hset"
1) "pmessage"
2) "__keyspace@4*__:*"
3) "__keyspace@4__:key_1"
4) "hset"
1) "pmessage"
2) "__keyspace@4*__:*"
3) "__keyspace@4__:key_1"
4) "del"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@qiluo-msft kindly reminder to review
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with you that hdel
does not always mean key deleted.
I have second thought on previous discussion
We can get raw_data == {} in two cases:
- ...
- If the key is created without attributes. An example is NTP configuration.
In this case, the underlying Redis data is actually
127.0.0.1:6379[4]> hgetall "NTP_SERVER|10.20.8.129"
1) "NULL"
2) "NULL"
So my suggestion is still applicable:
The root cause is in
raw_to_typed()
. Ifraw_data == {}
, the return value should beNone
. It is general fix.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@oleksandrivantsiv Please check my last comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@qiluo-msft the suggestion will work. The only concern I have is that it will consume more CPU resources and time to execute from the performance point of view. Because we will need to get the data for the not existing key. This operation is redundant as we already know that the key is removed.
@qiluo-msft @ganglyu any additional comments or we can move on and merge this one? |
@qiluo-msft kindly reminder to review |
/azp run |
Commenter does not have sufficient privileges for PR 789 in repo sonic-net/sonic-swss-common |
/azp run |
Azure Pipelines successfully started running 1 pipeline(s). |
@oleksandrivantsiv , this PR will break all sonic cli, because it changes the get_entry() behavior: before this change, if an entry does not exist, return {} for example, following command will break: admin@vlab-01:$ sudo config vlan add 1234 |
@oleksandrivantsiv Maybe we can change the implementation of |
@qiluo-msft I think that we need to close
ACK. I will check and provide feedback. |
@qiluo-msft I think that we need to return to the suggestion I proposed by checking the message type and returning |
Please go ahead to raise another PR. |
)" This reverts commit e0f394c.
)" (#804) This reverts commit e0f394c. The reason is unexpected behavior change of `get_entry`. ref: #789 (comment)
…#789) **What I did** Fix the issue of ignoring callback calls for removed keys. **Why I did it** ConfigDBConnector.listen method has a caching mechanism (added in sonic-net#587 PR) that preloads the DB state before starting. When the notification about the changed key is received the listen method gets key data from the DB (in all cases when the key was added, updated, or removed) and compares the data with the cache. It fires the callback only if data differ from the cache. Otherwise, the callback is ignored. If the `client.hgetall(key)` is called for the removed key it returns an empty dictionary (`{}`). This can be confused with the data of the key with no attributes. For example: `{"TABLE": {"KEY": {}}}`. So if preloaded data contains a key with no attributes and the listener method receives a notification about the removal of such key the callback is not called. The listener will simply remove the key from the cache without calling the callback. This leads to the situation when the removal of the key is not handled. The solution is to get data for the added or updated keys, and for the removed keys use `None` instead. This will ensure that the comparison with the cache will work as expected. **How I verified it** Compile the package and run the unit test. Unit tests are extended to cover the expected behavior.
…onic-net#789)" (sonic-net#804) This reverts commit e0f394c. The reason is unexpected behavior change of `get_entry`. ref: sonic-net#789 (comment)
What I did
Fix the issue of ignoring callback calls for removed keys.
Why I did it
ConfigDBConnector.listen method has a caching mechanism (added in #587 PR) that preloads the DB state before starting. When the notification about the changed key is received the listen method gets key data from the DB (in all cases when the key was added, updated, or removed) and compares the data with the cache. It fires the callback only if data differ from the cache. Otherwise, the callback is ignored.
If the
client.hgetall(key)
is called for the removed key it returns an empty dictionary ({}
). This can be confused with the data of the key with no attributes. For example:{"TABLE": {"KEY": {}}}
.So if preloaded data contains a key with no attributes and the listener method receives a notification about the removal of such key the callback is not called. The listener will simply remove the key from the cache without calling the callback. This leads to the situation when the removal of the key is not handled.
The solution is to get data for the added or updated keys, and for the removed keys use
None
instead. This will ensure that the comparison with the cache will work as expected.How I verified it
Compile the package and run the unit test. Unit tests are extended to cover the expected behavior.
Details if related