Структура данных для выбора групп машин

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

Проблема, которую я имею сейчас, заключается в том, что помимо некоторых базовых свойств (количество процессоров, памяти, ОС) существуют также эти странные свойства группировки (city, infiniband, network scratch).

Теперь проблема заключается в том, что когда пользователь запрашивает узлы с infiniband, я не могу просто дать ему какие-либо узлы, но я должен дать ему узлы, подключенные к одному коммутатору infiniband, поэтому узлы могут фактически обмениваться данными с помощью infiniband.

Это все еще нормально, когда пользователь запрашивает одно из таких свойств (я могу просто разбить массив на каждое из свойств, а затем попытаться выбрать узлы в каждом разделе отдельно).

Проблема заключается в объединении нескольких таких свойств, потому что тогда мне пришлось бы генерировать все комбинации подмножеств (разделов основного массива).

Хорошо, что большинство свойств находятся в отношении подмножества или эквивалентности (это похоже на то, что машины на одном бесконечном переключателе находятся в одном городе). Но это, к сожалению, не совсем верно.

Есть ли какая-то хорошая структура данных для хранения такого рода полуиерархической, преимущественно дерево-подобной вещи?

EDIT: пример

node1 : city=city1, infiniband=switch03, networkfs=server01
node2 : city=city1, infiniband=switch03, networkfs=server01
node3 : city=city1, infiniband=switch03
node4 : city=city1, infiniband=switch03
node5 : city=city2, infiniband=switch03, networkfs=server02
node6 : city=city2, infiniband=switch03, networkfs=server02
node7 : city=city2, infiniband=switch04, networkfs=server02
node8 : city=city2, infiniband=switch04, networkfs=server02

Пользователи запрашивают:

2x node with infiniband and networkfs

Желаемый результат: (node1, node2) или (node5,node6) или (node7,node8).

В хорошей ситуации этот пример не произойдет, но в некоторых случаях у нас есть эти странные межсайтовые соединения. Если бы узлы в city2 были бы все на infiniband switch04, это было бы легко. К сожалению, теперь мне приходится создавать группы узлов, которые имеют один и тот же переключатель infiniband и одну и ту же сетевую файловую систему.

В действительности проблема намного сложнее, поскольку пользователи не запрашивают целые узлы, а свойств много.

Изменить: добавлен желаемый результат для запроса.

Ответ 1

Предполагая, что у вас есть свойства группировки p и n машин, решение на основе корзин проще всего настроить и обеспечивает доступ и обновления O (2 p и middot; log (n)).

  • Вы создаете кучу-кучу для каждой группы свойств (так что у вас будет куча-кучка для "infiniband" , куча-кучка для "networkfs" и куча кучи для "infiniband & times; networkfs" ) — это означает 2 p ковш-кучи.
  • Каждая куча-куча содержит ведро для каждой комбинации значений (так что ведро "infiniband" будет содержать ведро для ключа "switch04" и одно для ключа "switch03" ) — это означает, что не более n & middot; 2 p ведра разбиты на все кучи-кучи.
  • Каждое ведро представляет собой список серверов (возможно, разделенных на доступные и недоступные). Куча-куча - стандартная куча (см. std::make_heap), где значение каждого ведра - это количество доступных серверов в этом ковше.
  • Каждый сервер хранит ссылки на все ведра, которые его содержат.
  • Когда вы ищете серверы, которые соответствуют определенной группе свойств, вы просто смотрите в соответствующее ведро для этой группы свойств и спускаетесь по куче, ища ведро, которое достаточно велико, чтобы разместить количество запрошенных серверов. Это принимает O (log (p) и middot; log (n)).
  • Если серверы отмечены как доступные или недоступные, вам необходимо обновить все ведра, содержащие эти серверы, а затем обновить кучи-кучи, содержащие эти ведра. Это операция O (2 p & middot; log (n)).

Если вы обнаружите, что у вас слишком много свойств (и 2 p выходит из-под контроля), алгоритм позволяет создавать кучи-кучи по требованию из других ковшовых куч: if пользователь запрашивает "infiniband & times; networkfs", но у вас есть только куча-кучка, доступная для "infiniband" или "networkfs", вы можете повернуть каждое ведро в кучу "бесконечно" в кучу кучи самостоятельно (используйте ленивый алгоритм, поэтому вам не нужно обрабатывать все ведра, если первый работает) и использовать ленивый алгоритм слияния кучи, чтобы найти подходящий ковш. Затем вы можете использовать кеш LRU для определения того, какие группы свойств хранятся и которые построены по требованию.

Ответ 2

Я предполагаю, что для решения этой проблемы не будет "простого, эффективного" алгоритма и структуры данных, потому что то, что вы делаете, сродни решению множества одновременных уравнений. Предположим, что всего 10 категорий (например, city, infiniband и network), и пользователь задает требуемые значения для 3 из них. Скажем, пользователь запрашивает 5 узлов. Затем ваша задача состоит в том, чтобы вывести значения для остальных 7 категорий, так что существует не менее 5 записей, которые имеют все 10 полей категории, равных этим значениям (указанные 3 и 7 выводятся). Может быть несколько решений.

Тем не менее, при условии, что в каждой категории не так много разных категорий и не слишком много разных возможностей, вы можете выполнить простой рекурсивный поиск грубой силы, чтобы найти возможные решения, где на каждом уровне рекурсии вы рассматриваете определенную категорию, и "попробуйте" каждую возможность для него. Предположим, что пользователь запрашивает записи k и может выбрать любое количество требований через required_city, required_infiniband и т.д.:

either(x, y) := if defined(x) then [x] else y

For each city c in either(required_city, [city1, city2]):
  For each infiniband i in either(required_infiniband, [switch03, switch04]):
    For each networkfs nfs in either(required_nfs, [undefined, server01, server02]):
      Do at least k records of type [c, i, nfs] exist?  If so, return them.

Функция either() - это просто способ ограничить поиск подпространством, содержащим точки, которые пользователь дал ограничениям для.

На основе этого вам понадобится способ быстрого поиска количества точек (строк) для любой данной комбинации [c, i, nfs] - вложенные хеш-таблицы будут отлично работать для этого.

Ответ 3

Шаг 1: Создайте индекс для каждого свойства. Например. для каждой пары свойств + значение создайте отсортированный список узлов с этим свойством. Поместите каждый такой список в некоторый ассоциативный массив. Это что-то вроде и stl map, по одному для каждого свойства, индексированное значениями. Таким образом, когда вы закончите, у вас есть функция с постоянным временем, которая может вернуть вам список узлов, которые соответствуют одной паре свойств+. Список просто сортируется по node номеру.

Шаг 2: если задан запрос, для каждой пары свойств + значение нужно получить список узлов.

Шаг 3. Начиная с кратчайшего списка, вызовите его в списке 0, сравните его с каждым из других списков, в свою очередь удалив элементы из списка 0, которые не входят в другие списки.

Теперь у вас должны быть только узлы, у которых есть все запрошенные свойства.

Другой вариант - использовать базу данных, она уже настроена для поддержки таких запросов. Это можно сделать в памяти с помощью чего-то вроде BerkeleyDB с расширениями SQL.

Ответ 4

Если сортировка списка по каждому критерию, указанному в запросе, жизнеспособна (или имеет список, предварительно отсортированный по каждому относительному критерию), это работает очень хорошо.

Под "относительными критериями" я имею в виду критерии не формы "x должно быть 5", которые тривиальны для фильтрации, но критерии формы "x должны быть одинаковыми для каждого элемента в наборе результатов". Если есть также критерии формы "x должно быть 5", затем сначала отфильтруйте их, затем выполните следующие действия.

Он использует устойчивую сортировку по нескольким столбцам для быстрого поиска совпадающих групп (без попыток комбинирования).

Сложность - количество узлов * количество критериев в запросе (для самого алгоритма) + количество узлов * log (количество узлов) * количество критериев (для сортировки, если не для предварительной сортировки). Итак, Nodes * Log (Nodes) * Criteria.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace bleh
{
    class Program
    {
        static void Main(string[] args)
        {
            List<Node> list = new List<Node>();

            // create a random input list
            Random r = new Random();
            for (int i = 1; i <= 10000; i++)
            {
                Node node = new Node();
                for (char c = 'a'; c <= 'z'; c++) node.Properties[c.ToString()] = (r.Next() % 10 + 1).ToString();
                list.Add(node);
            }

            // if you have any absolute criteria, filter the list first according to it, which is very easy
            // i am sure you know how to do that

            // only look at relative criteria after removing nodes which are eliminated by absolute criteria
            // example 
            List<string> criteria = new List<string> {"c", "h", "r", "x" };
            criteria = criteria.OrderBy(x => x).ToList();

            // order the list by each relative criteria, using a ***STABLE*** sort
            foreach (string s in criteria)
                list = list.OrderBy(x => x.Properties[s]).ToList();

            // size of sought group
            int n = 4;

            // this is the algorithm
            int sectionstart = 0;
            int sectionend = 0;
            for (int i = 1; i < list.Count; i++)
            {
                bool same = true;
                foreach (string s in criteria) if (list[i].Properties[s] != list[sectionstart].Properties[s]) same = false;
                if (same == true) sectionend = i;
                else sectionstart = i;
                if (sectionend - sectionstart == n - 1) break;
            }

            // print the results
            Console.WriteLine("\r\nResult:");
            for (int i = sectionstart; i <= sectionend; i++)
            {
                Console.Write("[" + i.ToString() + "]" + "\t");
                foreach (string s in criteria) Console.Write(list[i].Properties[s] + "\t");
                Console.WriteLine();
            }
            Console.ReadLine();
        }
    }
}

Ответ 5

Я бы сделал что-то вроде этого (очевидно, вместо строк вы должны сопоставить их с int и использовать int как коды)

struct structNode
{
    std::set<std::string> sMachines;
    std::map<std::string, int> mCodeToIndex;    
    std::vector<structNode> vChilds;        
};

void Fill(std::string strIdMachine, int iIndex, structNode* pNode, std::vector<std::string> &vCodes)
{
    if(iIndex < vCodes.size())
    {           
        // Add "Empty" if Needed
        if(pNode->vChilds.size() == 0)
        {
            pNode->mCodeToIndex.insert(pNode->mCodeToIndex.begin(), make_pair("empty", 0));
            pNode->vChilds.push_back(structNode());
        }

        // Add for "Empty"
        pNode->vChilds[0].sMachines.insert(strIdMachine);
        Fill(strIdMachine, (iIndex + 1), &pNode->vChilds[0], vCodes );

        if(vCodes[iIndex] == "empty")
            return;


        // Add for "Any"        
        std::map<std::string, int>::iterator mIte = pNode->mCodeToIndex.find("any");
        if(mIte == pNode->mCodeToIndex.end())
        {
            mIte = pNode->mCodeToIndex.insert(pNode->mCodeToIndex.begin(), make_pair("any", pNode->vChilds.size()));
            pNode->vChilds.push_back(structNode());     
        }

        pNode->vChilds[mIte->second].sMachines.insert(strIdMachine);
        Fill(strIdMachine, (iIndex + 1), &pNode->vChilds[mIte->second], vCodes );


        // Add for "Segment"
        mIte = pNode->mCodeToIndex.find(vCodes[iIndex]);
        if(mIte == pNode->mCodeToIndex.end())
        {
            mIte = pNode->mCodeToIndex.insert(pNode->mCodeToIndex.begin(), make_pair(vCodes[iIndex], pNode->vChilds.size()));
            pNode->vChilds.push_back(structNode());                 
        }

        pNode->vChilds[mIte->second].sMachines.insert(strIdMachine);
        Fill(strIdMachine, (iIndex + 1), &pNode->vChilds[mIte->second], vCodes );

    }
}

//////////////////////////////////////////////////////////////////////
// Get
//
// NULL on empty group
//////////////////////////////////////////////////////////////////////
set<std::string>* Get(structNode* pNode, int iIndex, vector<std::string> vCodes, int iMinValue)
{       
    if(iIndex < vCodes.size())
    {       
        std::map<std::string, int>::iterator mIte = pNode->mCodeToIndex.find(vCodes[iIndex]);       
        if(mIte != pNode->mCodeToIndex.end())
        {
            if(pNode->vChilds[mIte->second].sMachines.size() < iMinValue)
                return NULL;
            else
                return Get(&pNode->vChilds[mIte->second], (iIndex + 1), vCodes, iMinValue);
        }
        else
            return NULL;        
    }

    return &pNode->sMachines;   
}

Чтобы заполнить дерево вашим образцом

structNode stRoot;

    const char* dummy[] = { "city1", "switch03", "server01" };  
    const char* dummy2[] = { "city1", "switch03", "empty" };
    const char* dummy3[] = { "city2", "switch03", "server02" };
    const char* dummy4[] = { "city2", "switch04", "server02" };

    // Fill the tree with the sample    
    Fill("node1", 0, &stRoot, vector<std::string>(dummy, dummy + 3));
    Fill("node2", 0, &stRoot, vector<std::string>(dummy, dummy + 3));
    Fill("node3", 0, &stRoot, vector<std::string>(dummy2, dummy2 + 3));
    Fill("node4", 0, &stRoot, vector<std::string>(dummy2, dummy2 + 3));
    Fill("node5", 0, &stRoot, vector<std::string>(dummy3, dummy3 + 3));
    Fill("node6", 0, &stRoot, vector<std::string>(dummy3, dummy3 + 3));
    Fill("node7", 0, &stRoot, vector<std::string>(dummy4, dummy4 + 3));
    Fill("node8", 0, &stRoot, vector<std::string>(dummy4, dummy4 + 3));

Теперь вы можете легко получить все комбинации, которые вы хотите, например, запрос будет выглядеть примерно так:

vector<std::string> vCodes;
    vCodes.push_back("empty"); // Discard first property (cities)
    vCodes.push_back("any");   // Any value for infiniband
    vCodes.push_back("any");   // Any value for networkfs (except empty)

    set<std::string>* pMachines = Get(&stRoot, 0, vCodes, 2);

И, например, только City02 на switch03 с networfs не пуст.

vector<std::string> vCodes;
    vCodes.push_back("city2"); // Only city2
    vCodes.push_back("switch03");   // Only switch03
    vCodes.push_back("any");   // Any value for networkfs (except empy)

    set<std::string>* pMachines = Get(&stRoot, 0, vCodes, 2);