'Order of users.messages.list observed to be not descending

In order to sync mailboxes my application follows the sync recommendations by attempting to find the history ID of the latest message in the users mailbox. We then use this for partial syncs going forward.

Recently we noticed behavior that suggested an issue with these syncs. One explanation was that we were receiving a much older message and history ID. I've tested our functionality and it appears to work correctly. Still, in an attempt to rule out a potential root cause, I added some checks to detect if the users.messages.list API return results out of descending order. These checks ended up being hit suggesting that this is an issue.

Here is my function, in Go, for finding the latest history ID. This includes the additional checks I added to validate the ordering -- essentially instead of using messages.get for the first entry in the list, it also gets the last entry in the list and then compares dates/history IDs: the first entry in the list should have the greatest history ID and date.

func getLatestHistoryID(ctx context.Context, gmailService *gmail.Service) (uint64, time.Time, error) {
    messagesResponse, err := gmailService.Users.Messages.List("me").IncludeSpamTrash(true).Context(ctx).Do()
    if err != nil {
        return 0, time.Time{}, err
    }

    messagesList := messagesResponse.Messages
    if messagesList == nil || len(messagesList) == 0 {
        return 0, time.Time{}, nil
    }

    latestMessage, err := gmailService.Users.Messages.Get("me", messagesList[0].Id).Context(ctx).Do()
    if err != nil {
        return 0, time.Time{}, err
    } else if latestMessage == nil {
        return 0, time.Time{}, nil
    }

    earliestMessage, err := gmailService.Users.Messages.Get("me", messagesList[len(messagesList)-1].Id).Context(ctx).Do()
    if err != nil {
        log.Errorf("error doing optional check to validate ordering of message list. %v", err)
    } else if earliestMessage == nil {
        log.Errorf("unexpected earliest message not retrieved")
    } else {
        if latestMessage.HistoryId < earliestMessage.HistoryId {
            return 0, time.Time{}, fmt.Errorf("message list was not in the expected order by history id! first in list %d (%s), last %d (%s)",
                latestMessage.HistoryId, latestMessage.Id,
                earliestMessage.HistoryId, earliestMessage.Id)
        }

        // This could probably fail in rare clock skew cases, but right now we're observing this being a several hour difference between dates.
        if latestMessage.InternalDate < earliestMessage.InternalDate {
            return 0, time.Time{}, fmt.Errorf("message list was not in the expected order by date! first in list %s (%s), last %s (%s)",
                time.UnixMilli(latestMessage.InternalDate).String(), latestMessage.Id,
                time.UnixMilli(earliestMessage.InternalDate).String(), earliestMessage.Id)
        }
    }

    return latestMessage.HistoryId, time.UnixMilli(latestMessage.InternalDate), nil
}

I've found several resources that confirm that users.messages.list is expected to be descending by date/history ID:

When I test the function above locally it works as expected, and the return statement on the last line is hit. Yet I've observed the out of order detection errors hundred of times. Of the failures, ~9/10 times I'm seeing the HistoryId check fail. I believe this is largely failing on a small set of mailboxes, and I am currently not sure what proportion usages this occurs (working on gathering this).

Is there any reason the API may return results out of order? Is there anything wrong with the assumptions made by my checks?



Solution 1:[1]

API return results out of descending order.

If you check the documentation for users.messages.list you will find that there is no order by parameter. Which means that there is no way for you to guarantee the order the data arrives in.

It could arrive sometimes in descending order and other times not in descending order. There is no way to guarantee it if there was it would state the order in the docs.

#limitations does not mention anything about order it only mentions that it may or may not be alliable.

History records are typically available for at least one week and often longer.

you should always sort this locally.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 DaImTo