Skip to content

Commit 0547f63

Browse files
author
ymqytw
committed
proposal of preserving order
1 parent 08f4fc7 commit 0547f63

File tree

1 file changed

+226
-0
lines changed

1 file changed

+226
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
# Preserve Order in Strategic Merge Patch
2+
3+
author: @mengqiy
4+
5+
## Motivation
6+
7+
The order of lists with a merge strategy is important, because an item in the list may depends on an item before it.
8+
An use case is the environment variables. We don't preserve the order which causes
9+
issue [40373](https://github.com/kubernetes/kubernetes/issues/40373)
10+
11+
## Proposed Change
12+
13+
Changes are all in strategic merge patch package.
14+
It will be similar to how we solve the problem of deleting items from a list of primitives.
15+
16+
Always send a parallel list along with the patch for list:
17+
18+
- When patching a list of maps, the parallel list contains a list of items. Each item contains only the merge key.
19+
- When patching a list of primitives, the parallel list contains a full list from user's config file.
20+
21+
All the items in the server's live list but not in the parallel list will be append to the end of the parallel list.
22+
The relative order between these appended items are kept.
23+
If the relative order of live config in the server is different from the order of the parallel list,
24+
user's patch will always override the order in the server.
25+
26+
The patched list will looks like:
27+
```
28+
mergingList:
29+
- parallelListItem1 \
30+
... |===> items from the parallel list
31+
- parallelListItemN /
32+
- otherItem1 \
33+
... |===> items in the server's list but not in the parallel list
34+
- otherItemN /
35+
```
36+
37+
## Version Skew
38+
39+
The new version patch is always a superset of the old version patch.
40+
The new patch has one additional parallel list which will be dropped by the old server.
41+
This change is fully backward compatible.
42+
43+
If an old client sends a old patch to a new server, the server will not preserve the order which behave as an old server.
44+
45+
If a new client sends a new patch to an old server, the server doesn't recognise the parallel list and will drop it.
46+
So it will behave the same as before.
47+
48+
## Example
49+
50+
We take environment variables as an example.
51+
Environment variables is a list of maps with merge patch strategy.
52+
53+
Suppose we define a list of environment variables and we call them
54+
the original environment variables:
55+
```yaml
56+
env:
57+
- name: ENV1
58+
value: foo
59+
- name: ENV2
60+
value: bar
61+
- name: ENV3
62+
value: baz
63+
```
64+
65+
Then the server appends two environment variables and reorder the list:
66+
```yaml
67+
env:
68+
- name: ENV2
69+
value: bar
70+
- name: ENV5
71+
value: server-added-2
72+
- name: ENV1
73+
value: foo
74+
- name: ENV3
75+
value: baz
76+
- name: ENV4
77+
value: server-added-1
78+
```
79+
80+
Then the user wants to change it from the original to the following using `kubectl apply`:
81+
```yaml
82+
env:
83+
- name: ENV1
84+
value: foo
85+
- name: ENV2
86+
value: bar
87+
- name: ENV6
88+
value: new-env
89+
```
90+
91+
The patch will looks like:
92+
```yaml
93+
$preserveOrderList/env:
94+
- name: ENV1
95+
- name: ENV2
96+
- name: ENV6
97+
env:
98+
- name: ENV3
99+
$patch: delete
100+
- name: ENV6
101+
value: new-env
102+
```
103+
104+
After server applying the patch:
105+
```yaml
106+
env:
107+
- name: ENV1
108+
value: foo
109+
- name: ENV2
110+
value: bar
111+
- name: ENV6
112+
value: new-env
113+
- name: ENV5
114+
value: server-added-2
115+
- name: ENV4
116+
value: server-added-1
117+
```
118+
119+
# Alternative Considered
120+
121+
## Proposed Change
122+
123+
Use an approach similar to [MongoDB](https://docs.mongodb.com/manual/reference/operator/update/position/).
124+
When patching a list of maps with merge patch strategy,
125+
use a new directive `$position` in each map in the list.
126+
127+
If the order in the user's config is different from the order of the live config,
128+
we will insert the `$position` directive in each map in the list.
129+
We guarantee that the order of the user's list will always override the order of live list.
130+
131+
All the items in the server's live list but not in the patch list will be append to the end of the patch list.
132+
The relative order between these appended items are kept.
133+
If the relative order of live config in the server is different from the order in the patch,
134+
user's patch will always override the order in the server.
135+
136+
When patching a list of primitives with merge patch strategy,
137+
we send a whole list from user's config.
138+
139+
## Version Skew
140+
141+
It is NOT backward compatible in terms of list of primitives.
142+
143+
When patching a list of maps:
144+
- An old client sends a old patch to a new server, the server just merges the change and no reordering.
145+
The server behaves the same as before.
146+
- An new client sends a new patch to an old server, the server doesn't understand the new directive.
147+
So it just simply does the merge.
148+
149+
When patching a list of primitives:
150+
- An old client sends a old patch to a new server, the server will reorder the patch list which is sublist of user's.
151+
The server has the WRONG behavior.
152+
- An new client sends a new patch to an old server, the server will deduplicate after merging.
153+
The server behaves the same as before.
154+
155+
## Example
156+
157+
For patching list of maps:
158+
159+
Suppose we define a list of environment variables and we call them
160+
the original environment variables:
161+
```yaml
162+
env:
163+
- name: ENV1
164+
value: foo
165+
- name: ENV2
166+
value: bar
167+
- name: ENV3
168+
value: baz
169+
```
170+
171+
Then the server appends two environment variables and reorder the list:
172+
```yaml
173+
env:
174+
- name: ENV2
175+
value: bar
176+
- name: ENV5
177+
value: server-added-2
178+
- name: ENV1
179+
value: foo
180+
- name: ENV3
181+
value: baz
182+
- name: ENV4
183+
value: server-added-1
184+
```
185+
186+
Then the user wants to change it from the original to the following using `kubectl apply`:
187+
```yaml
188+
env:
189+
- name: ENV1
190+
value: foo
191+
- name: ENV2
192+
value: bar
193+
- name: ENV6
194+
value: new-env
195+
```
196+
197+
The patch will looks like:
198+
```yaml
199+
env:
200+
- name: ENV1
201+
$position: 0
202+
- name: ENV2
203+
$position: 1
204+
- name: ENV6
205+
value: new-env
206+
$position: 2
207+
- name: ENV3
208+
$patch: delete
209+
```
210+
211+
After server applying the patch:
212+
```yaml
213+
env:
214+
- name: ENV1
215+
value: foo
216+
- name: ENV2
217+
value: bar
218+
- name: ENV6
219+
value: new-env
220+
- name: ENV5
221+
value: server-added-2
222+
- name: ENV4
223+
value: server-added-1
224+
```
225+
226+

0 commit comments

Comments
 (0)