Как использовать целочисленные RowKeys в хранилище таблиц Azure? - PullRequest
5 голосов
/ 07 марта 2011

У меня есть последовательно пронумерованные объекты, которые я хочу сохранить с помощью службы таблиц Azure, однако тип столбца RowKey проблематичен.Номер объекта должен храниться в столбце RowKey, чтобы я мог быстро запрашивать объекты (PK = '..' && RowKey = 5), получать новейшие объекты (RowKey > 10) и запрашивать определенный набор объектов (RowKey > 5 && RowKey < 10).

Так как RowKey должен быть строкой, сравнения ниже, чем проблематично ("100" < "11").Я думал о добавлении нулей к числам (так что "100" > "011"), но я не могу предсказать количество сущностей (и, следовательно, количество нулей).

Я знаю, что мог бы просто создать целочисленный столбец, но я бы потерял преимущество в производительности индексированного столбца RowKey (плюс у меня нет другой информации, подходящей для RowKey).У кого-нибудь была эта проблема раньше?

Ответы [ 5 ]

5 голосов
/ 18 октября 2013

У меня была похожая проблема с добавленной оговоркой, которую я также хотел поддержать, отсортировав RowKey в порядке убывания. В моем случае я не заботился о поддержке триллионов возможных значений, потому что я правильно использовал PartitionKey, а также использовал префиксы областей действия, когда это было необходимо для дальнейшего сегментирования RowKey (например, "scope-id" -> "12-8374").

В итоге я остановился на конкретной реализации общего подхода, предложенного enzi. Я использовал модифицированную версию кодировки Base64, создавая четырехсимвольную строку, которая поддерживает более 16 миллионов значений и может быть отсортирована в порядке возрастания или убывания. Вот код, который был протестирован модулем, но в нем отсутствует проверка / проверка диапазона.

/// <summary>
/// Gets the four character string representation of the specified integer id.
/// </summary>
/// <param name="number">The number to convert</param>
/// <param name="ascending">Indicates whether the encoded number will be sorted ascending or descending</param>
/// <returns>The encoded string representation of the number</returns>
public static string NumberToId(int number, bool ascending = true)
{
    if (!ascending)
        number = 16777215 - number;

    return new string(new[] { 
        SixBitToChar((byte)((number & 16515072) >> 18)), 
        SixBitToChar((byte)((number & 258048) >> 12)), 
        SixBitToChar((byte)((number & 4032) >> 6)), 
        SixBitToChar((byte)(number & 63)) });
}

/// <summary>
/// Gets the numeric identifier represented by the encoded string.
/// </summary>
/// <param name="id">The encoded string to convert</param>
/// <param name="ascending">Indicates whether the encoded number is sorted ascending or descending</param>
/// <returns>The decoded integer id</returns>
public static int IdToNumber(string id, bool ascending = true)
{
    var number = ((int)CharToSixBit(id[0]) << 18) | ((int)CharToSixBit(id[1]) << 12) | ((int)CharToSixBit(id[2]) << 6) | (int)CharToSixBit(id[3]);

    return ascending ? number : -1 * (number - 16777215);
}

/// <summary>
/// Converts the specified byte (representing 6 bits) to the correct character representation.
/// </summary>
/// <param name="b">The bits to convert</param>
/// <returns>The encoded character value</returns>
[MethodImplAttribute(MethodImplOptions.AggressiveInlining)] 
static char SixBitToChar(byte b)
{
    if (b == 0)
        return '!';
    if (b == 1)
        return '$';
    if (b < 12)
        return (char)((int)b - 2 + (int)'0');
    if (b < 38)
        return (char)((int)b - 12 + (int)'A');
    return (char)((int)b - 38 + (int)'a');
}

/// <summary>
/// Coverts the specified encoded character into the corresponding bit representation.
/// </summary>
/// <param name="c">The encoded character to convert</param>
/// <returns>The bit representation of the character</returns>
[MethodImplAttribute(MethodImplOptions.AggressiveInlining)] 
static byte CharToSixBit(char c)
{
    if (c == '!')
        return 0;
    if (c == '$')
        return 1;
    if (c <= '9')
        return (byte)((int)c - (int)'0' + 2);
    if (c <= 'Z')
        return (byte)((int)c - (int)'A' + 12);
    return (byte)((int)c - (int)'a' + 38);
}

Вы можете просто передать false восходящему параметру, чтобы обеспечить сортировку закодированного значения в противоположном направлении. Я выбрал ! и $ для завершения набора Base64, поскольку они действительны для значений RowKey. Этот алгоритм может быть легко изменен для поддержки дополнительных символов, хотя я твердо убежден, что большие значения не имеют смысла для значений RowKey, поскольку ключи хранения таблиц должны быть эффективно сегментированы. Вот несколько примеров вывода:

0 -> !!!! asc & zzzz desc

1000 -> !! Dc asc & zzkL desc

2000 -> !! TE asc & zzUj desc

3000 -> !! isc & zzF5 desc

4000 -> !! yU asc & zz $ T desc

5000 ->! $ C6 asc & zylr desc

6000 ->! $ Rk asc & zyWD desc

7000 ->! $ HM asc & zyGb desc

8000 ->! $ X! asc & zy0z desc

9000 ->! 0Ac asc & zxnL desc

3 голосов
/ 26 сентября 2013

Я нашел простой способ, но предыдущее решение более эффективно (в отношении длины ключа). Вместо использования всех алфавитов мы можем использовать только цифры, и ключ должен сделать длину фиксированной (0000,0001,0002, .....):

public class ReadingEntity : TableEntity
{
    public static string KeyLength = "000000000000000000000";
    public ReadingEntity(string partitionId, int keyId)
    {
        this.PartitionKey = partitionId;
        this.RowKey = keyId.ToString(KeyLength); ;


    }
    public ReadingEntity()
    {
    }
}


public IList<ReadingEntity> Get(string partitionName,int date,int enddate)
{
        CloudTableClient tableClient = storageAccount.CreateCloudTableClient();

        // Create the CloudTable object that represents the "people" table.
        CloudTable table = tableClient.GetTableReference("Record");

        // Construct the query operation for all customer entities where PartitionKey="Smith".
        TableQuery<ReadingEntity> query = new TableQuery<ReadingEntity>().Where(TableQuery.CombineFilters(
    TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, partitionName),
    TableOperators.And,TableQuery.CombineFilters(
    TableQuery.GenerateFilterCondition("RowKey", QueryComparisons.LessThan, enddate.ToString(ReadingEntity.KeyLength)), TableOperators.And,
    TableQuery.GenerateFilterCondition("RowKey", QueryComparisons.GreaterThanOrEqual, date.ToString(ReadingEntity.KeyLength)))));
        return table.ExecuteQuery(query).ToList();
}

Надеюсь, это поможет.

1 голос
/ 04 апреля 2011

Я решил эту проблему, создав собственный класс RowKey, который оборачивает строку и предоставляет метод Increment.

Теперь я могу определить диапазон допустимых символов (например, 0-9 + a-z + A-Z) и «количество» в этом диапазоне (например, az9 + 1 = aza, azZ + 1 = aA0).Преимущество этого по сравнению с использованием только чисел состоит в том, что у меня гораздо больший диапазон возможных ключей (62^n вместо 10^n).

Мне все еще нужно определить длину строки заранее и mustnне могу изменить его, но теперь я могу хранить практически любое количество объектов, сохраняя при этом саму строку намного короче.Например, с 10 цифрами я могу хранить ключи ~8*10^17 и с 20 цифрами ~7*10^35.

Количество допустимых символов, конечно, можно увеличить, чтобы использовать количество цифр еще эффективнее, но в моем случае вышеуказанный диапазон был достаточным и все еще достаточно читаемым для целей отладки.

Я надеюсь, что этот ответ поможет другим, кто сталкивается с той же проблемой.

РЕДАКТИРОВАТЬ: просто как примечание на случай, если кто-то захочет реализовать что-то подобное: вам придется создавать собственные диапазоны символовне может просто считать от 0 и выше, потому что между числами (0-9) и строчными буквами есть недопустимые символы (например, /, \).

0 голосов
/ 11 февраля 2012

Я нашел потенциальное решение, если вы используете Linq для запроса к хранилищу таблиц Azure .

Вы добавляете что-то подобное в свою модель для стола ...

public int ID 
{
    get
    {
        return int.Parse(RowKey);
    }
}

И тогда вы можете сделать это в вашем запросе Linq ...

.Where(e => e.ID > 1 && e.ID < 10);

С помощью этой техники вы фактически не добавляете столбец «ID» в таблицу, поскольку в нем нет операции «set».

В одном я не уверен, что именно происходит за кулисами. Я хочу знать, как выглядит запрос к хранилищу таблиц Azure в окончательной форме, но я не уверен, как это выяснить. Мне не удалось найти эту информацию при отладке и использовании быстрых часов.

UPDATE

Я до сих пор не понял, что происходит, но у меня есть сильное чувство, что это не очень эффективно. Я думаю, что для этого нужно создать сортируемую строку , как это сделал ОП. Затем вы можете использовать функцию RowKey.CompareTo() в предложении Linq where для фильтрации по диапазону.

0 голосов
/ 24 марта 2011

вы можете добавить гид к целому числу.это должно помочь с родами.http://blogs.southworks.net/fboerr/2010/04/22/compsition-in-windows-azure-table-storage-choosing-the-row-key-and-simulating-startswith/

...