3 Required Behaviors for Global Consistency
To assure consistency when modifying a local collection, the following behaviors will be defined:
- Creation
- Update/Deletion
- Conflict Resolution
To assure consistency when processing incoming (subscribed) collections, the following behaviors will be defined:
Although the precise format of the data is a decision left to each implementer, the algorithms described in this section assume that conceptually the data is stored in some structure that is capable of expressing order and relationships.
Depending on the particular implementation, the data could be stored in nested tables in a database, or in an object graph in memory, or as a series of entries in a flat file. The important thing is that when the sync algorithms are performed, the implementer must preserve parent/child container relationships between the different types of data.
3.1 Creation Behavior
When creating a new item as the result of a local operation, implementations MUST:
- Create an sx:sync structure as follows:
- Set the id value (in conformance with section 2.3)
- Set the updates value to 1
- Optionally set the noconflicts value
- Optionally set the deleted value if the item is tombstoned from birth
- Create a sx:history structure as follows:
- Set the sequence value (in conformance with section 2.5). It is RECOMMENDED that the sequence value be set to 1.
- Do one or both of the following:
- Set the when value to the current date-time. Note that this value is optional if the by value is set, but it is RECOMMENDED that this value be set whenever possible.
- Set the by value to the current endpoint's identifier. Note that this value is optional if the when value is set, but it is RECOMMENDED that this value be set whenever possible.
- Add the sx:history structure as the topmost child of the sx:sync structure.
- Add the sx:sync structure as a child of the item
- Append the item as a child of the collection.
- Optionally update the until value for the sx:sharing structure
Here is what an item could look like after being created:
POX Example
<item>
<subject>Buy groceries</subject>
<body>Get milk and eggs</body>
<sx:sync id="item_1_myapp_2005-05-21T11:43:33Z" updates="1">
<sx:history sequence="1" when="2005-05-21T09:43:33Z" by="REO1750"/>
</sx:sync>
</item>
JSON Example
{
“title” : “Buy groceries”,
“description”: “Get milk and eggs”,
“sync”: {
“id”: “item_1_myapp_2005-05-21T11:43:33Z”,
“updates”: “1”,
“history”: [
{“sequence”: “1”, “when”: “2005-05-21T09:43:33Z”, “by”: “REO1750”}
]
}
}
Atom Example (for comparison purposes only)
<entry>
<title>Buy groceries</title>
<content>Get milk and eggs</content>
<updated>2005-05-21T09:43:33Z </updated>
<id>urn:uuid:60a76c80-d399-11d9-b93C-0003939e0aa0</id>
<author>
<name>Ray Ozzie</name>
</author>
<sx:sync id="item_1_myapp_2005-05-21T11:43:33Z" updates="1">
<sx:history sequence="1" when="2005-05-21T09:43:33Z" by="REO1750"/>
</sx:sync>
</entry>
3.2 Update/Deletion Behavior
When updating or deleting an item via a local operation, implementations MUST:
- Update the sx:sync structure as follows:
- Increment the updates value by 1
- Create a sx:history structure as follows:
- Do one or both of the following:
- Set the when value to the current date-time. Note that this value is optional if the by value is set, but it is RECOMMENDED that this value be set whenever possible.
- Set the by value to the current endpoint's identifier. Note that this value is optional if the when value is set, but it is RECOMMENDED that this value be set whenever possible.
- If the by value has not been set in Step 2a, set S1 to be the same value as the updates value set in Step 1.
- If the by value has been set in Step 2a:
- Set S1 to any valid value. It is RECOMMENDED that implementations set S1 to the same value as the updates value set in Step 1.
- Set S2 to be the greatest sequence value of all sx:history children with the same by value as the value set in Step 2a. Implementations MAY set S2 to be the greatest sequence value of all sx:history sub-elements across all items in the feed with the same by attribute value.
- If S1 <= S2, then set S1 to any valid value that is greater than S2. It is RECOMMENDED that implementations set S1 to (S2 + 1).
- Set the sequence value to S1.
- Add the sx:history structure as the topmost child of the sx:sync structure.
- If the by attribute has been set in step 2a, and the sx:conflicts structure is present and non-empty:
- For each child structure X of the sx:conflicts structure:
- Set H to be the topmost sx:history child structure of the sx:sync structure for X
- If the by attribute of H is set and has the same value as the by attribute set in step 2a, then follow the steps for Merging Conflict Items (see Section 3.4) for item X
- If this is a deletion, set the deleted value on the sx:sync structure to true.
- If this is an un-deletion, set the deleted value on the sx:sync structure to false.
Additionally implementations MAY:
- Clean up sx:history children with a by attribute value so that only the sx:history child with the highest sequence attribute value for a given by is retained, and all other sx:history children with the same by attribute value are discarded.
- If this is a deletion, remove the item’s data as long as the item’s sx:sync and topmost sx:history children are preserved. Note that truncation or removal of data might cause unexpected results if a subsequent conflict occurs for the item, or if the item is subsequently undeleted. Also note that publishers SHOULD rely on the since, until, and expires values (if present) of the sx:sharing structure to determine when to stop publishing items.
- Update the until value for the sx:sharing structure.
Here is what the same item in the above example would look like after having been updated in sequence by the same endpoint:
POX Example #1
<item>
<subject>Buy groceries</subject>
<body>Get milk, eggs and butter</body>
<sx:sync id="item_1_myapp_2005-05-21T11:43:33Z" updates="2">
<sx:history sequence="2" when="2005-05-21T10:43:33Z" by="REO1750"/>
<sx:history sequence="1" when="2005-05-21T09:43:33Z" by="REO1750"/>
</sx:sync>
</item>
RSS Example #1 (for comparison purposes only)
<item>
<title>Buy groceries</title>
<description>Get milk, eggs and butter</description>
<sx:sync id="item_1_myapp_2005-05-21T11:43:33Z" updates="2">
<sx:history sequence="2" when="2005-05-21T10:43:33Z" by="REO1750"/>
<sx:history sequence="1" when="2005-05-21T09:43:33Z" by="REO1750"/>
</sx:sync>
</item>
Here is what the same item in the above example would look like after having been updated in sequence by another endpoint:
POX Example #2
<item>
<subject>Buy groceries</subject>
<body>Get milk, eggs, butter and bread</body>
<sx:sync id="item_1_myapp_2005-05-21T11:43:33Z" updates="3">
<sx:history sequence="3" when="2005-05-21T09:43:33Z" by="JEO2000"/>
<sx:history sequence="2" when="2005-05-21T10:43:33Z" by="REO1750"/>
<sx:history sequence="1" when="2005-05-21T09:43:33Z" by="REO1750"/>
</sx:sync>
</item>
RSS Example #2 (for comparison purposes only)
<item>
<title>Buy groceries</title>
<description>Get milk, eggs, butter and bread</description>
<sx:sync id="item_1_myapp_2005-05-21T11:43:33Z" updates="3">
<sx:history sequence="3" when="2005-05-21T09:43:33Z" by="JEO2000"/>
<sx:history sequence="2" when="2005-05-21T10:43:33Z" by="REO1750"/>
<sx:history sequence="1" when="2005-05-21T09:43:33Z" by="REO1750"/>
</sx:sync>
</item>
3.3 Merge Behavior
When a subscribing endpoint incorporates items from a publishing endpoint’s collection, these items must be merged with the existing local items. The act of merging items from an incoming collection detects new items, item updates and item conflicts and produces a merged result collection. The merging of two items with the same id value will result in a ‘winning’ item that might have conflict items. In order to merge items, implementations MUST follow this algorithm for the two items:
- If no local item exists with the same id value as the incoming item, add the incoming item to the merge result collection; we are done processing the incoming item.
- Create a collection L and populate it with the local item and the local item’s conflicts (if any exist) by using the following steps:
- For each item child of the sx:conflicts structure for the local item:
- Add the item child to L
- If the local item has a sx:conflicts child, remove it
- Add the local item to L
- Create a collection I and populate it with the incoming item and the incoming item’s conflicts (if any exist) by using the following steps:
- For each item child of the sx:conflicts structure for the incoming item:
- Add the item child to I
- If the incoming item has a sx:conflicts child, remove it
- Add the incoming item to I
- Create a collection M that will be used to contain items that will appear in the merged result collection
- Create a reference W for the current ‘winning’ item and set it to an unassigned value
- Using L as the outer collection and I as the inner collection, perform the following step
- For each item X in outer collection:
- For each item Y in inner collection:
- Determine if X is subsumed1 by Y – if so then remove X from the outer collection; process the next item in the outer collection
- Add X to M
- If W has not been assigned a value, set W to X; process the next item in the outer collection
- Determine if X should be declared as the new ‘winning’ item3 – if so set W to X.
- Using I as the outer collection and L as the inner collection, perform step 7 again
- Add W to the merge result collection
- If the noconflicts value is set to true, then we are done processing
- If M contains more than one item:
- Create a sx:conflicts structure and add it as a child of the sx:sync structure for W
- For each item Z in M:
- If Z equals W (i.e. they are the same item), then process the next item in M
- Add Z as a child of the sx:conflicts structure created in step 11a.
Note that children of a sx:conflicts structure are always preserved as a flat, unordered list.
1 Item Subsumption
An item X is subsumed by an item Y when the topmost sx:history child of X is subsumed2 by one of the sx:history children of Y, where X and Y are items with the same id value. In order to determine if X is subsumed by Y, implementations MUST perform the following:
- Set HX as the topmost sx:history child of X
- For each sx:history child HY of item Y:
- Compare HX with HY to see if HX is subsumed2 by HY – if so then X is subsumed by Y
If no determination has been made as to whether X is or is not subsumed by Y, then X is not subsumed by Y
2 History Subsumption
To determine if a sx:history structure HX is subsumed by a sx:history structure HY, implementations MUST perform the following comparisons, in order, for HX and HY:
- If a by value exists for HX:
- If HY has the same by value as HX, and HY has an equal or greater sequence value than HX, then HX is subsumed by HY
- If no by value exists for HX:
- If no by value exists for HY, and the when and sequence values for HX respectively matches the when and sequence values for HY, then HX is subsumed by HY
- If no determination has been made as to whether HX is or is not subsumed by HY, then HX is not subsumed by HY
3 Winner Picking
The ‘winning’ item between an item X and an item Y is the item with most recent update , where X and Y are items with the same id value. In order to determine the ‘winning’ item, implementations MUST perform the following comparisons, in order, for X and Y:
- If X has a greater updates value for the sx:sync child than Y’s, then X is the ‘winning’ item
- If X has the same updates value for the sx:sync child as Y:
- If X has a when value for the topmost sx:history child and Y does not, then X is the ‘winning’ item
- If X has a when value for the topmost sx:history child and that is chronologically later than Y’s, then X is the ‘winning’ item
- If X has the same when value for the topmost sx:history child as Y:
- If X has a by value for the topmost sx:history child and Y does not, then X is the ‘winning’ item
- If X has a by value for the topmost sx:history child that is collates greater (see Section 2.1.4 for collation rules) than Y’s, then X is the ‘winning’ item
- 3. Y is the ‘winning’ item
Here is what the same item in the above example would look like after having been independently updated by two endpoints, causing a conflict:
POX Example
<item>
<subject>Buy groceries - DONE</subject>
<body>Get milk, eggs, butter and bread</body>
<sx:sync id="item_1_myapp_2005-05-21T11:43:33Z" updates="4">
<sx:history sequence="4" when="2005-05-21T12:43:33Z" by="GPM7383"/>
<sx:history sequence="3" when="2005-05-21T11:43:33Z" by="JEO2000"/>
<sx:history sequence="2" when="2005-05-21T10:43:33Z" by="REO1750"/>
<sx:history sequence="1" when="2005-05-21T09:43:33Z" by="REO1750"/>
<sx:conflicts>
<item>
<subject>Buy groceries</subject>
<body>Get milk, eggs, butter and rolls</body>
<sx:sync id="item_1_myapp_2005-05-21T11:43:33Z" updates="4">
<sx:history sequence="4" when="2005-05-21T12:03:33Z" by="JEO2000"/>
<sx:history sequence="3" when="2005-05-21T11:43:33Z" by="JEO2000"/>
<sx:history sequence="2" when="2005-05-21T10:43:33Z" by="REO1750"/>
<sx:history sequence="1" when="2005-05-21T09:43:33Z" by="REO1750"/>
</sx:sync>
</item>
</sx:conflicts>
</sx:sync>
</item>
RSS Example
<item>
<title>Buy groceries - DONE</title>
<description>Get milk, eggs, butter and bread</description>
<sx:sync id="item_1_myapp_2005-05-21T11:43:33Z" updates="4">
<sx:history sequence="4" when="2005-05-21T12:43:33Z" by="GPM7383"/>
<sx:history sequence="3" when="2005-05-21T11:43:33Z" by="JEO2000"/>
<sx:history sequence="2" when="2005-05-21T10:43:33Z" by="REO1750"/>
<sx:history sequence="1" when="2005-05-21T09:43:33Z" by="REO1750"/>
<sx:conflicts>
<item>
<title>Buy groceries</title>
<description>Get milk, eggs, butter and rolls</description>
<sx:sync id="item_1_myapp_2005-05-21T11:43:33Z" updates="4">
<sx:history sequence="4" when="2005-05-21T12:03:33Z" by="JEO2000"/>
<sx:history sequence="3" when="2005-05-21T11:43:33Z" by="JEO2000"/>
<sx:history sequence="2" when="2005-05-21T10:43:33Z" by="REO1750"/>
<sx:history sequence="1" when="2005-05-21T09:43:33Z" by="REO1750"/>
</sx:sync>
</item>
</sx:conflicts>
</sx:sync>
</item>
3.4 Conflict Resolution Behavior
Applications can allow users to "resolve" conflicts for an item, where "resolve" means to perform an update to the item that takes the conflicting item data into account in some meaningful way. For items that have multiple conflicts, implementations MAY selectively resolve conflicts, keeping some conflicts unresolved and intact for subsequent resolution. When a particular conflict is resolved, it is removed from the list of the item’s conflicts and merged into the item’s history. In order to resolve conflicts, the following MUST occur:
- The user is presented, within some user interface, with the ‘winning’3 item data along with the data for each of the conflicting versions of that same item (i.e. retrieved from the sx:conflicts children of the item). The user will then select one of the following actions:
- To keep the most recent data as the “resolved” state (i.e. confirming that the latest state is the desired state)
- To select one of the conflicting version’s data as the “resolved” state
- To combine or otherwise create new data as the “resolved” state (i.e. creating their own desired state)
- The appropriate “resolved” state MUST be used to perform an update operation (section Section 3.2) to the item
- The conflict items the user factored into their decision MUST then be merged4 into the item’s history so the user isn’t asked to resolve the same conflict more than once.
Note that while the steps above focus on user based conflict resolution, it does not obviate the possibility of programmatic conflict resolution. Programmatic conflict resolution would require that all endpoints execute the same conflict resolution logic for deterministic results.
4 Merging Conflict Items
In order to merge conflict items into the resolved item’s history, implementations MUST:
- Set Y as a reference the resolved item
- Set Sy as a reference to the sx:sync child for Y
- For each item child X of the sx:conflicts structure that has been resolved:
- Set SX as a reference to the sx:sync child for X
- Remove X from the sx:conflicts structure.
- For each sx:history child HX of SX:
- For each sx:history sub-element Hy of SY:
- Compare HX with Hy to see if HX can be subsumed2 by HY – if not then add HX as a sub-element of SY, immediately after the topmost sx:history sub-element of SY.
-
If the sx:conflicts structure contains no children, the sx:conflicts structure SHOULD be removed.
Additionally implementations MAY:
- Clean up sx:history children with a by attribute value so that only the sx:history child with the highest sequence attribute value for a given by is retained, and all other sx:history children with the same by attribute value are discarded.
Here is what the same item in the above example would look like after conflict resolution has occurred, where one of the endpoint’s changes are picked as the resolved state:
POX Example
<item>
<subject>Buy groceries - DONE</subject>
<body>Get milk, eggs, butter and bread</body>
<sx:sync id="item_1_myapp_2005-05-21T11:43:33Z" updates="5">
<sx:history sequence="5" when="2005-05-21T12:53:33Z" by="GPM7383"/>
<sx:history sequence="4" when="2005-05-21T12:03:33Z" by="JEO2000"/>
<sx:history sequence="4" when="2005-05-21T12:43:33Z" by="GPM7383"/>
<sx:history sequence="3" when="2005-05-21T11:43:33Z" by="JEO2000"/>
<sx:history sequence="2" when="2005-05-21T10:43:33Z" by="REO1750"/>
<sx:history sequence="1" when="2005-05-21T09:43:33Z" by="REO1750"/>
</sx:sync>
</item>
RSS Example (for comparison purposes only)
<item>
<title>Buy groceries - DONE</title>
<description>Get milk, eggs, butter and bread</description>
<sx:sync id="item_1_myapp_2005-05-21T11:43:33Z" updates="5">
<sx:history sequence="5" when="2005-05-21T12:53:33Z" by="GPM7383"/>
<sx:history sequence="4" when="2005-05-21T12:03:33Z" by="JEO2000"/>
<sx:history sequence="4" when="2005-05-21T12:43:33Z" by="GPM7383"/>
<sx:history sequence="3" when="2005-05-21T11:43:33Z" by="JEO2000"/>
<sx:history sequence="2" when="2005-05-21T10:43:33Z" by="REO1750"/>
<sx:history sequence="1" when="2005-05-21T09:43:33Z" by="REO1750"/>
</sx:sync>
</item>
4 Partial Collections and Complete Collections
Publishers will generally include, in a collection, only the most recent modifications, additions, and deletions within some reasonable time window. These collections are referred to herein as partial collections, whereas collections containing the complete set of items are referred to as complete collections.
In the collection sharing context new subscribers, or existing subscribers failing to subscribe within the published collection window, will need to initially copy a complete set of items from a publisher before being in a position to process incremental updates. As such, this specification provides for the ability for the latter collection to reference the complete collection. By placing the link to this collection in the collection descriptor, only the partial collection URL need to be distributed to potential subscribers.
The guidelines below explain how publishers and subscribers MAY optionally use since and until values for the sx:sharing structure to ensure that all item updates are synchronized, even if the publisher periodically purges items from its collection. Note that these guidelines are only applicable when the subscriber maintains a local store for the collection’s items.
4.1 Subscriber guidelines
4.1.1 The following steps SHOULD be followed by the subscriber for initial collection consumption
- Read the contents of the published collection
- See if a sx:related child exists for the sx:sharing structure, with a type value of ‘complete’ – then repeat step 1 above with the collection URL specified by the link value
- Perform a merge operation (see Section 3.3)
- If the subscribed collection contains the since and until values for the sx:sharing structure:
- The subscriber SHOULD cache the value of the until value for the sx:sharing structure for subsequent collection consumption
4.1.2 The following steps SHOULD be followed by the subscriber for subsequent collection consumption
- Read the contents of the published collection
- If the collection contains the since and until values for the sx:sharing structure and the subscriber cached the value of the until value from a previous read, then determine if the subscriber is out of sync with the collection:
- If the value of the since value for the sx:sharing structure is greater than the cached value of the until value:
- If a sx:related child exists for the sx:sharing structure, with a type value value of ‘complete’ – if so then repeat step 1 above with the collection URL specified by the link value
- The subscriber is out of sync with collection, so all items that were not created, or most recently updated, by the subscribing endpoint should be discarded from the subscriber’s local store.
- Subsequent collection consumption is complete for the out of sync subscriber. Continue with the steps outlined for initial collection consumption in section 5.1.1
- The subscriber SHOULD cache the value of the until value for the sx:sharing structure for subsequent collection consumption
- Perform a merge operation for the collection items in the local store and the collection (see Section 3.3)
4.2 Publisher guidelines
4.2.1 The following steps SHOULD be followed by the publisher when publishing a collection for the first time (for either a complete or partial collection)
- If items will ever be purged from the collection:
- Set the value1 of the since value of the sx:sharing structure to correspond to the incorporation of least recently updated item in the collection.
- Set the value1 of the until value of the sx:sharing structure to correspond to the incorporation of most recently updated item in the collection.
- Optionally set the value of the expires value for the sx:sharing structure to a date-time value before which subscribers SHOULD read the collection in order to avoid missing item updates due to items being purged from collection.
4.2.2 The following steps MAY be followed by the publisher when subsequently publishing a collection
- If the expires value for the sx:sharing structure exists, update its value to a date-time before which subscribers SHOULD read the collection in order to avoid missing item updates.
4.2.3 The following steps SHOULD be followed by the publisher when incorporating an item update into the collection
- Increment the value of the until value for the sx:sharing structure by an appropriate amount
- Associate and store the new value of the until value with the item
- If the expires value for the sx:sharing structure exists, optionally keep track of the date-time when incorporating the item’s changes. This value MAY be used in relation to the expires value for the sx:sharing structure by the publisher when determining which items to remove from a collection.
4.2.4 The following steps SHOULD be followed by the publisher when removing an item from a collection
- If the stored value associated with the item from (step 2 in section 5.2.3) is greater than the value for the since value for the sx:sharing structure, set the value for the since value for the sx:sharing structure to the stored value
1since and until values
These values SHOULD be thought of as ever-increasing values that are represented in a manner that allows string collation by subscribers to yield the appropriate results. For example, if the publisher uses a date-time value to represent an incorporation timestamp for since and until, the value MUST be normalized for string comparison (e.g. “2006-05-02T09:10:33Z” instead of “2 May 2006 09:10:33”).
5 References
RFC 2119: http://www.ietf.org/rfc/rfc2119.txt
RFC 2141: http://www.ietf.org/rfc/rfc2141.txt
RFC 3339: http://www.ietf.org/rfc/rfc3339.txt
RFC 3986: http://www.ietf.org/rfc/rfc3986.txt
RFC 4287: http://www.ietf.org/rfc/rfc4287.txt
6 Licensing Information
Microsoft's copyrights in this specification are licensed under the Creative Commons Attribution-ShareAlike License (version 3.0). To view a copy of this license, please visit http://creativecommons.org/licenses/by-sa/3.0/. As to software implementations, Microsoft is not aware of any patent claims it owns or controls that would be necessarily infringed by a software implementation that conforms to the specification's extensions. If Microsoft later becomes aware of any such necessary patent claims, Microsoft also agrees to offer a royalty-free patent license on reasonable and non-discriminatory terms and conditions to any such patent claims for the purpose of publishing and consuming the extensions set out in the specification.