Этим летом абитуриентам было жарко, МГУ по проходному баллу почти превратился в ПТУ, а кто-то успешно поступил в вооруженные силы РФ, сам того не желая. Отсутствие автоматизации в 2021 году, способной обработать распределение абитуриентов по учебным заведениям, а так же необходимость написать продолжение предыдущей статьи, описывающей основы теории о Распределенной Авторизации (РА) побудило решить эту детскую задачу автоматизации.

Вербальное описание и анализ задачи

Процесс поступления в РФ на бюджетные отделения ВУЗ должен позволять не только упростить процедуру подачи документов, но так же позволить абитуриенту получить настолько полный объем информации для принятия решения, насколько это возможно.

Главной проблемой является поддержание актуальности состояния списка абитуриентов поступающих на конкретную специальность. Разумеется можно создать по серверу на каждый ВУЗ, что бы все равно столкнуться с теми же проблемами.

Второй проблемой является хаотичность процесса поступления, ввиду того, что в каждом субъекте РФ существует свой часовой пояс (дискриминация по часовому поясу, как и по ФИО - недопустимы), а может даже свои "особенности" поступления. Данная проблема разрешима только через создание такого процесса поступления, при котором пользователю будет не выгодно принимать решения, но принять его он будет обязан.

Третья проблема поступления в ВУЗ связана с особенностями поступления на бюджетные места, а именно целевые программы и другие варианты авто-поступления. Необходимо организовать процесс таким образом, что бы в момент выбора ВУЗ для поступления абитуриент уже знал, сколько бюджетных мест занято целевиками.

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

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

  1. Регистрация ВУЗ в приеме абитуриентов на обучение - составление списка специальностей, верстка страницы для привлечения абитуриентов и предоставление документов о количестве бюджетных и платных мест, другая информация, информация о поступающих по целевой программе на данном этапе не нужна;

  2. Регистрация Абитуриентов в системе, а так же подача заявлений на поступление, возможность сразу подтвердить желание поступить в конкретный ВУЗ. На данном этапе возможно сразу производить автоматическое или ручное оценивание и ранжирование студентов, так что абитуриент Иван, нажимая на кнопку "Подать документы в МИРЭА на специальность "Вычислительные машины, системы, комплексы и сети"" смог через какое время увидеть себя в списке на поступление, например на пятидесятом месте;

  3. Ранжирование Абитуриентов и отправка согласия на поступление - данная фаза нужна, что бы гарантировать, что все документы обработаны, у всех абитуриентов расставлены оценки, согласно их результатам экзаменов, собеседования или иной формы оценки. Поступающие по целевой программе обучения переведены из статуса "поступающий" в "поступивший по целевой программе". На данном этапе каждый абитуриент должен потратить одну из своих возможностей подтверждения поступления в ВУЗ. Абитуриенты, не сделавшие это в любой из выбранных ими ВУЗ, исключаяются из процесса приема по окончании этого этапа;

  4. Финальное принятие решения о поступлении в ВУЗ - так как на данном этапе у всех абитуриентов уже есть подтверждение на поступление, то каждому можно вывести список, где каждый абитуриент сможет увидеть свое место в списке кандидатов, а так же заметить, что те 10 человек, что были выше него - теперь выделены серым цветом, потому что поступить в тот же ВУЗ не захотели. Если кто-то поступает по целевой программе, то это так же будет отображено в списке поступающих, потому что эти записи будут в самом верху списка с соответствующим комментарием. По окончании этого этапа изменить свое подтверждение на поступление в ВУЗ уже будет нельзя;

  5. Отказ от поступления в ВУЗ - если по каким-то причинам абитуриент не желает поступать в ВУЗ, то будет лучше предоставить возможность, глядя на финальный список поступающих, предоставить абитуриенту право отказаться от поступления. Если не сделать этот этап, то ВУЗ опять придется выполнять работу по выяснению, тратить время на бессмысленную работу.

За эти 5 этапов каждому абитуриенту гарантирует максимально удобные условия для поступления. На каждом этапе ему предоставление максимально возможное количество информации для приема решения. На этапе 4 гарантируется, что в последний момент перед Алексеем не появится 100 человек сразу подтвердивших свое поступление. Это гарантирует, что списки на поступление будут более стабильны и не будут хаотично изменяться, большинству абитуриентов вообще не потребуется расходовать дополнительные "согласия на поступления". Так же это позволит обеспечить ВУЗ возможностью привлекать всегда лучших абитуриентов, которых они в принципе могут привлечь.

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

Такая формализация процесса поступления в ВУЗ позволит избежать коррупции, связанной с доп набором, а так же полностью исключить текущую проблему, когда абитуриент отправляет согласие на поступление в 2 и более ВУЗов и потом какое-то время еще решается, куда же он поступит на самом деле. Если после такого процесса набора по каким-то причинам в какие-то ВУЗ не будет набрано полное число студентов, то это уже вопрос к самому ВУЗ, почему никто не хочет туда поступать.

И последнее, только при таком подходе можно одним потоком обрабатывать всех абитуриентов каждый год с минимальным расходованием средств.

Пользователи системы

Пользователей системы можно минимально разбить на 3 группы. Разберем каждую группу пользователей и определим их функции в системе.

Администратор необходим для работы с представителями ВУЗ, выдачи им необходимых прав на работу с данными ВУЗ. Блокировку и разблокировку пользователей.

  1. Выдать/забрать права представителя ВУЗ пользователю;

  2. Блокировка/разблокировка пользователей.

Абитуриенты - самая массовая часть всех пользователей, их функциями являются:

  1. Участвовать в поступлении (необходимо для регистрации пользователя в работе и учете приемной комиссии);

  2. Подать документы на поступление на выбранную специальность в ВУЗ;

  3. Отправить согласие на поступление;

  4. Отказаться от поступления в ВУЗ.

Представители ВУЗ являются пользователями, которые позволяют сотрудникам ВУЗ выполнять их работу по обеспечению поступления в ВУЗ абитуриентов. Перечислим основные функции:

  1. Получение отчета о поступающих на момент выполнения генерации отчета;

  2. Перевод абитуриента из статуса "поступающий" в статус "поступивший по целевой программе";

  3. Работа с информацией о ВУЗ;

  4. Настройка автоматизации с системами ВУЗ (если системы ВУЗ могут интегрироваться с API);

  5. Загрузка различных документов для подтверждения (например факта зачисления студента по целевой программе).

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

Модель данных - всему голова

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

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

Для описания ВУЗ и его специальностей заведем две таблицы:

CREATE TABLE apasc.eduinst
(
    eduinst_id UUID NOT NULL,
    CONSTRAINT eduinst_pk PRIMARY KEY (eduinst_id)
);

CREATE TABLE apasc.spec
(
    spec_id    UUID NOT NULL,
    eduinst_id UUID NOT NULL,
    CONSTRAINT spec_pk PRIMARY KEY (spec_id),
    CONSTRAINT spec_eduinst_fk FOREIGN KEY (eduinst_id) REFERENCES apasc.eduinst (eduinst_id)
);

Так же необходима таблица, в которой хранятся данные о текущем наборе:

CREATE SEQUENCE apasc.selection_session_seq START 1 INCREMENT BY 1;
CREATE TABLE apasc.selection_session
(
    selection_session_id INT       NOT NULL DEFAULT nextval('apasc.selection_session_seq')::regclass,
    created              TIMESTAMP NOT NULL DEFAULT current_timestamp,
    CONSTRAINT selection_session_pk PRIMARY KEY (selection_session_id)
);
CREATE INDEX selection_session_created_idx ON apasc.selection_session (created);

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

Для того, что бы хранить информацию о конкретном наборе (например у ВУЗ может быть добавлена или удалена специальность) для каждой приемной комиссии необходимо обеспечить уполномоченного ВУЗ возможностью создавать список специальностей, которые доступны абитуриентам в этом году, там же он может указать сколько бюджетных и платных мест (год от года они могут меняться в соответствии с изменениями бюджета). Для хранения этих данных заведем таблицу:

CREATE TABLE apasc.ses_spec
(
    spec_id              UUID NOT NULL,
    selection_session_id INT  NOT NULL,
    CONSTRAINT ses_spec_pk PRIMARY KEY (spec_id, selection_session_id),
    CONSTRAINT ses_spec_spec_id_fk FOREIGN KEY (spec_id) REFERENCES apasc.spec (spec_id),
    CONSTRAINT ses_spec_selection_session_id_fk FOREIGN KEY (selection_session_id) REFERENCES apasc.selection_session (selection_session_id)
);

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

CREATE TABLE apasc.enrollee
(
    user_id              UUID     NOT NULL,
    selection_session_id INT      NOT NULL,
    disabled             BOOLEAN  NOT NULL DEFAULT FALSE,
    selected_target_id   UUID              DEFAULT NULL,
    selection_limit      SMALLINT NOT NULL DEFAULT 3,
    CONSTRAINT enrollee_pk PRIMARY KEY (user_id, selection_session_id),
    CONSTRAINT enrollee_selection_session_id_fk FOREIGN KEY (selection_session_id) REFERENCES apasc.selection_session (selection_session_id),
    CONSTRAINT enrollee_selection_limit_more_than_zero CHECK ( selection_limit >= 0 ),
    CONSTRAINT enrollee_selected_target_id_fk FOREIGN KEY (selected_target_id, selection_session_id) REFERENCES apasc.ses_spec (spec_id, selection_session_id)
);
CREATE INDEX enrollee_selection_idx ON apasc.enrollee (selected_target_id, disabled);

Если по каким-то причинам пользователь был заблокирован, то для учета данного события есть поле disabled, которое в запросах необходимо проверять на не равенство true.

Когда пользователь пожелает отправить документы в ВУЗ на поступление на выбранную специальность, то этот факт необходимо зарегистрировать отдельной строкой в таблице:

CREATE TABLE apasc.enrollee_select
(
    user_id              UUID      NOT NULL,
    selection_session_id INT       NOT NULL,
    spec_id              UUID      NOT NULL,
    status               INT       NOT NULL DEFAULT 0,
    score                INT       NOT NULL DEFAULT 0,
    created              TIMESTAMP NOT NULL DEFAULT current_timestamp,
    CONSTRAINT enrollee_select_pk PRIMARY KEY (user_id, selection_session_id, spec_id),
    CONSTRAINT enrollee_select_user_fk FOREIGN KEY (user_id, selection_session_id) REFERENCES apasc.enrollee (user_id, selection_session_id),
    CONSTRAINT enrollee_select_spec_fk FOREIGN KEY (spec_id, selection_session_id) REFERENCES apasc.ses_spec (spec_id, selection_session_id)
);
CREATE INDEX enrollee_select_enrollee_idx ON apasc.enrollee_select (user_id, selection_session_id);
CREATE INDEX enrollee_select_spec_id_idx ON apasc.enrollee_select (spec_id, selection_session_id);
CREATE INDEX enrollee_select_status_idx ON apasc.enrollee_select (status);
CREATE INDEX enrollee_select_score_idx ON apasc.enrollee_select (score);
CREATE INDEX enrollee_select_created_idx ON apasc.enrollee_select (created);

При такой организации данных невозможно подать документы на одну специальность дважды в рамках набора, а так же не получится выполнить запрос, если пользователь не стал абитуриентом (не был добавлен в apasc.enrollee, попасть в которую можно только пройдя полноценную авторизацию). В сценарии тестирования будет использоваться генератор случайных чисел для получение оценки абитуриента, однако для реального сервиса не желательно вычислять эту оценку сразу, тем более что у каждого ВУЗ алгоритм может быть свой, бонусы свои и т.д. Что бы поддерживать фоновое вычисление нужно добавить поле, в котором будет хранится статус оценки абитуриента. Подробнее об этом будет рассказано далее.

Профилируем часто используемые запросы

Для начала сгенерируем крошечную БД на 1 000 000 человек, 100 ВУЗ в каждом по 100 специальностей, так же добавим 10 приемных комиссий, что бы убедится, что между ними все данные разграничены:

Генерация данных для тестирования
DO
$$
    DECLARE
        i    bigint;
        j    BIGINT;
        eiid UUID;
        sid  UUID;
        ssid INT;
    BEGIN
        FOR i IN 1..100
            LOOP
                eiid := CAST(LPAD(TO_HEX(i), 32, '0') AS UUID);
                INSERT INTO apasc.eduinst(eduinst_id) VALUES (eiid);
                FOR j IN 1..100
                    LOOP
                        sid := CAST(LPAD(TO_HEX((i - 1) * 100 + j), 32, '0') AS UUID);
                        INSERT INTO apasc.spec(spec_id, eduinst_id) VALUES (sid, eiid);
                    END LOOP;
            END LOOP;
        FOR ssid IN 1..10
            LOOP
                INSERT INTO apasc.selection_session(selection_session_id, created) VALUES (ssid, current_timestamp);
                FOR i IN 1..100
                    LOOP
                        eiid := CAST(LPAD(TO_HEX(i), 32, '0') AS UUID);
                        FOR j IN 1..100
                            LOOP
                                sid := CAST(LPAD(TO_HEX((i - 1) * 100 + j), 32, '0') AS UUID);
                                INSERT INTO apasc.ses_spec(spec_id, selection_session_id)
                                VALUES (sid, ssid);
                            END LOOP;
                    END LOOP;
            END LOOP;
    END;
$$;

DO
$$
    DECLARE
        i   bigint;
        j   BIGINT;
        uid UUID;
        sid UUID;
    BEGIN
        FOR i IN 1..1000000
            LOOP
                uid := CAST(LPAD(TO_HEX(i), 32, '0') AS UUID);
                INSERT INTO apasc.enrollee(user_id, selection_session_id)
                VALUES (uid, 1);
                FOR j IN 1..5
                    LOOP
                        sid = CAST(LPAD(TO_HEX((i - 1 + j) % 10000 + 1), 32, '0') AS UUID);
                        INSERT INTO apasc.enrollee_select(user_id, selection_session_id, spec_id, score)
                        VALUES (uid, 1, sid, floor(random() * 1000)::int);
                    END LOOP;
            END LOOP;
    END;
$$;

Так как мы уже создали 5 000 000 записей о том, что абитурент желает поступить в ВУЗ, то можно сразу приступить к тестированию запроса для "отправить документы":

EXPLAIN ANALYSE
INSERT INTO apasc.enrollee_select(user_id, selection_session_id, spec_id)
VALUES (CAST(LPAD(TO_HEX(1), 32, '0') AS UUID), 1, CAST(LPAD(TO_HEX(201), 32, '0') AS UUID));
Insert on enrollee_select  (cost=0.00..0.01 rows=1 width=52) (actual time=0.095..0.095 rows=0 loops=1)
  ->  Result  (cost=0.00..0.01 rows=1 width=52) (actual time=0.002..0.002 rows=1 loops=1)
Planning Time: 0.023 ms
Trigger for constraint enrollee_select_user_fk: time=0.107 calls=1
Trigger for constraint enrollee_select_spec_fk: time=0.026 calls=1
Execution Time: 0.244 ms

"Согласие на поступление" выполняется следующим запросом:

EXPLAIN ANALYSE
UPDATE apasc.enrollee e
SET selected_target_id = CAST(LPAD(TO_HEX(2), 32, '0') AS UUID),
    selection_limit    = selection_limit - 1
WHERE user_id = CAST(LPAD(TO_HEX(1), 32, '0') AS UUID);
Update on enrollee e  (cost=0.42..8.45 rows=1 width=45) (actual time=0.037..0.037 rows=0 loops=1)
  ->  Index Scan using enrollee_pk on enrollee e  (cost=0.42..8.45 rows=1 width=45) (actual time=0.019..0.020 rows=1 loops=1)
        Index Cond: (user_id = '00000000-0000-0000-0000-000000000001'::uuid)
Planning Time: 0.049 ms
Execution Time: 0.051 ms

Из анализа вывода для этих двух операций можно точно сказать, что 1000 рпс достичь более чем реально. На самом деле теоретический потолок, которого можно достичь с 2 ядрами и 2 гб ОЗУ - это примерно 20 000 рпс, больше не получится.

Однако перечисленные запросы - просты и не показывают самого главного. Есть два запроса, которые пользователю будут одинаково нужны - это список поступающих на специальность и список позиций абитуриента. Из-за того, что данные разделены на 2 таблицы, то без джойна уже не обойтись, а с ним всегда происходят какие-то проблемы. Получить 200 первых поступающих можно следующим запросом:

EXPLAIN ANALYSE
SELECT es.user_id
     , es.score
     , es.selection_session_id
     , es.spec_id
     , e.disabled
     , e.selection_limit
     , e.selected_target_id
FROM apasc.enrollee_select es
         JOIN apasc.enrollee e on e.user_id = es.user_id and e.selection_session_id = es.selection_session_id
WHERE es.spec_id = CAST(LPAD(TO_HEX(1), 32, '0') AS UUID) 
	and es.selection_session_id = 1
ORDER BY es.score DESC, es.created
LIMIT 200;
Limit  (cost=5970.46..5970.96 rows=200 width=67) (actual time=0.996..1.009 rows=200 loops=1)
  ->  Sort  (cost=5970.46..5971.72 rows=502 width=67) (actual time=0.995..1.001 rows=200 loops=1)
"        Sort Key: es.score DESC, es.created"
        Sort Method: top-N heapsort  Memory: 72kB
        ->  Nested Loop  (cost=22.00..5948.77 rows=502 width=67) (actual time=0.036..0.875 rows=500 loops=1)
              ->  Bitmap Heap Scan on enrollee_select es  (cost=21.58..1881.38 rows=502 width=48) (actual time=0.030..0.145 rows=500 loops=1)
                    Recheck Cond: ((spec_id = '00000000-0000-0000-0000-000000000001'::uuid) AND (selection_session_id = 1))
                    Heap Blocks: exact=116
                    ->  Bitmap Index Scan on enrollee_select_spec_id_idx  (cost=0.00..21.45 rows=502 width=0) (actual time=0.021..0.021 rows=500 loops=1)
                          Index Cond: ((spec_id = '00000000-0000-0000-0000-000000000001'::uuid) AND (selection_session_id = 1))
              ->  Index Scan using enrollee_pk on enrollee e  (cost=0.42..8.10 rows=1 width=39) (actual time=0.001..0.001 rows=1 loops=500)
                    Index Cond: ((user_id = es.user_id) AND (selection_session_id = 1))
Planning Time: 0.165 ms
Execution Time: 1.035 ms

Результат конечно посредственный, учитывая что spring съест еще порядка 100-500 мкс минимум, то запрос будет выполняться за ~2-3 мс, что для нашего сервиса довольно много. Однако следует учесть, что даже если у абитуриента не будет возможности посмотреть текущую ситуацию, модифицирующие запросы все равно будут работать намного быстрее и учитываться сразу. Это означает, что по окончании соответствующего этапа он может быть уверен, что через сутки не появится сотня новых абитуриентов выше него уже отправивших согласие.

Список позиций пользователя необходим, чтобы избежать необходимости выводить всю БД для каждого пользователя (например из-за того что он находится на последнем месте). Вычислять и хранить позицию пользователя в реальном времени, каждый раз меняя значения в БД - это неприемлимо (в данный момент не существует технической возможность вычислять состояние для 1 000 000 человек по 5 позициям для каждого за разумное время на дешевых серверах). Поэтому в ЛК абитуриента желательно выводить результат следующего запроса:

EXPLAIN ANALYSE
SELECT es.spec_id
     , es.score
     , (SELECT count(*)
        FROM apasc.enrollee_select es2
        WHERE es.selection_session_id = es2.selection_session_id
          AND es.spec_id = es2.spec_id
          AND es.user_id <> es2.user_id
          AND (es.score < es2.score OR (es.score = es2.score AND es.created > es2.created))) + 1 AS place
FROM apasc.enrollee_select es
WHERE es.user_id = CAST(LPAD(TO_HEX(20), 32, '0') AS UUID)
  AND es.selection_session_id = 1;
Index Scan using enrollee_select_enrollee_idx on enrollee_select es  (cost=0.43..15143.54 rows=8 width=28) (actual time=0.200..0.937 rows=5 loops=1)
  Index Cond: ((user_id = '00000000-0000-0000-0000-000000000014'::uuid) AND (selection_session_id = 1))
  SubPlan 1
    ->  Aggregate  (cost=1890.32..1890.33 rows=1 width=8) (actual time=0.184..0.184 rows=1 loops=5)
          ->  Bitmap Heap Scan on enrollee_select es2  (cost=21.50..1889.90 rows=168 width=0) (actual time=0.045..0.172 rows=226 loops=5)
                Recheck Cond: ((es.spec_id = spec_id) AND (es.selection_session_id = selection_session_id))
                Filter: ((es.user_id <> user_id) AND ((es.score < score) OR ((es.score = score) AND (es.created > created))))
                Rows Removed by Filter: 274
                Heap Blocks: exact=586
                ->  Bitmap Index Scan on enrollee_select_spec_id_idx  (cost=0.00..21.46 rows=503 width=0) (actual time=0.033..0.033 rows=500 loops=5)
                      Index Cond: ((spec_id = es.spec_id) AND (selection_session_id = es.selection_session_id))
Planning Time: 0.141 ms
Execution Time: 0.956 ms

Запрос конечно тяжелый, почти в 3 раза тяжелее запроса на получение страницы из 200 элементов, но работать с ним можно. Если все так же использовать spring, то время выполнения http-запроса, исключая задержки сети до клиента, не должно превышать 2-3 мс. Опять же посредственный результат, но получить 1000 рпс вполне реально при должных оптимизациях.

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

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

Наброски микросервисов

Основной микросервис ответственнен за регистрацию абитурентов, прием их решений о поступлении, согласии или отзыве, а так же выдачу информации. Работа администраторов и уполномоченных ВУЗ так же реализуема на основном микросервисе. В других будут реализовываться дополнительные функции, такие как фоновый рассчет оценки абитуриента, отправка событий в систему, а так же любые другие интеграционные взаимодействия, например отправка отчета о поступивших в ВУЗ для дальнейшего документооборота, если таковой вообще потребуется, скорее всего все остановится на "скачать pdf".

БД уже спроектирована так, что абитуриент не сможет выполнить то, что ему нельзя сделать, за исключением особенностей этапов. Не смотря на то, что в описании модели данных они не представлены, в БД они должны быть, при старте микросервиса эти данные загружаются в память и обновляются, например раз в час/день. Приемная комисиия работает только в одном экземпляре каждый год, так что хранить информацию о том, какие фазы она претерпевает - не сложно. Все прошлые приемные комиссии будут итак в статусе "только для чтения", а то и вовсе выгружены из системы, согласитесь, не сложно определить факт режима для чтения. Это означает, что все необходимые данные для авторизации доступа абитуриента к системе (за исключением регистрации пользователя как абитуриента) находятся локально и не нужно делать вызовы в другие микросервисы - именно за счет этого можно достичь 1000 рпс, даже для постраничного получения поступающих на специальность.

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

  1. Валидация токена;

  2. Сверка с таблицей, что операция разрешена в текущем наборе;

  3. Отправка запроса в БД.

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

В модели данных было описано, что оценку абитуриента желательно вычислять в фоновом режиме, для этого нужен дополнительный микросервис (буквально 0.5 ЦПУ и 256 мб ОЗУ хватит). В случае большого объема работ всегда можно добавить инстансов. Работать этот микросервис должен следующим образом:

  1. Найти специальность, в которой есть необработанные записи абитуриентов;

  2. Заблокировать специальность, что бы другие микросервисы ее не трогали;

  3. В цикле пройтись по всем записям абитуриентов и произвести рассчеты в соответствии с конфигурацией, которую задал уполномоченный ВУЗ. На каждую запись создается своя транзакция. Так как блокировка специальности возможна только в рамках транзакции (если автор не ошибается), то желательно для этого использовать поле, которое через update будет изменяться, а по количеству измененных записей можно судить об успехе операции.

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

Имея на руках уже две статьи, автор рекомендует читателю попробовать реализовать самому эту задачу по имеющемуся описанию и попробовать произвести нагрузочное тестирование. Хороший разработчик на Java в состоянии выжать 10 000 рпс из такой архитектуры в легкую (речь про подачу документов и согласие/отказ на поступления). Даже если у вас не очень большой опыт работы, скачайте репозитории 1, 2, 3, изучите их, благо кода там совсем немного. По образу и подобию имплементируйте эту задачу. Создание тестовой БД для 1 000 000 пользователей по представленному скрипту займет примерно 2 минуты на intel i7 9700k, 10 000 000 - уже почти 30 минут. Ощутимой разницы в работе запросов на таких выборках замечено не было, так что смысла тратить время на генерацию болей БД - нет. Однако следует заметить, что брать меньше 500 000 не следует, так как планировщик PostgreSQL может изменить план работы для малых БД и работать иначе, например использовать последовательное сканирование.

Полный скрипт для БД тут.

Заключение

В работе предложено решение проблемы для организации работы приемной комиссии в рамках государства. Предложены наброски для микросервисов, которые бы выполняли требуемые функции. Система в теории должна на довольно слабом железе (2 ядра и 2 гб ОЗУ для БД и столько же для ноды) дать ~10 000 рпс, что вполне должно хватить для обработки запросов абитуриентов.

Комментарии (11)


  1. Infinitiv
    21.09.2021 10:53
    +2

    стартовый вопрос - участвовали ли вы в каком-либо качестве в работе приемной комиссии этого года?

    относительно "этапов".

    1 - каждый вуз обязан сделать это минимум четырежды - официальный сайт вуза, федеральный портал Поступай правильно, федеральная информационная система ЕГЭ и приема, единый портал гос услуг. плюс добровольно возможно внесение в различные коммерческие системы.

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

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

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

    5 - невозможно лишить поступающего права отказаться от зачисления, не армия все-таки, а образовательная услуга.

    В целом, честно говоря, не очень понял, в чем новизна идеи на организационном уровне.

    Попытка создания единого федерального сервиса уже есть (в двух итерациях - ФИС ГИА и приема и суперсервис Поступай онлайн), но текущий год показал, что система (обе) архитектурно очень кривая и нагрузку не выдержала даже в пилотной эксплуатации с относительно небольшой нагрузкой.

    А еще есть очень много юридического легаси и гос политики в процедуре приема, поэтому одним микросервисом проблему, увы, не решить...

    Но вам было интересно решать эту задачу, что безусловно хорошо )


    1. kozlyuk
      21.09.2021 11:55

      Почему бы не подавать сведения о количестве мест один раз в единую систему для вузов, из которой три федеральных системы для абитуриентов (да и коммерческие системы) забирали бы данные? Для публикации на сайте тоже глупо каждому вузу что-то придумывать. Авторы системы сбора данных могли бы дать некое общее решение: шаблон HTML со ссылками на те же файлы, которые загружаются в систему, или скрипт генерации статических страниц из них.


      1. Infinitiv
        21.09.2021 12:35

        вы смотрите на ситуацию с позиции технического здравого смысла, но государство так не работает )

        ФИС ГИА - проект рособрнадзора, Поступай правильно - минобра, Суперсервис - минцифры. Обязательства по внесению информации накладываются на вузы либо приказами ведомств, каждое из которых не указ другим, либо постановлениями правительства, либо аж федеральными законами. поэтому сводить все это в одну кучу, чтобы информация не дублировалась и всем было удобно нужно кому-то, кто стоит выше всех этих текущих участников, а у этих людей и без приема на в вузы полно забот.

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


        1. lastrix Автор
          21.09.2021 13:07
          +1

          Государство так не работает, потому что кому-то нужно именно так, как оно сейчас есть - свисток для жопы.

          Человек вам пытался объяснить, что есть решения грамотные, а есть не грамотные.

          А что касается статьи, она для разработчиков, а не чиновников. Цель - обучение разработчиков писать нормальный софт, исправлять бедлам в головах "блатных" чиновников я не собираюсь.

          Скорее КНР для своих 100 млн абитуриентов сделает подобную платформу, чем в РФ что-то сделают хотя бы на троечку, потому что за последние 30 лет как-то в среднем даже неуд ставить сложно.

          А законы, они меняются. Иногда сразу все. Учите историю. Все дальнейшие комментарии по поводу законодательства и нытья по поводу особенностей национальной юриспруденции буду удалять.
          Статья техническая, если у вас есть вопросы технического характера задавайте, иначе валите в свои... кабинеты.


          1. kozlyuk
            21.09.2021 13:53

            Зря вы ругаетесь. Спасибо Infinitiv'у, что делится фактами из практики и сообщает то, что иначе пришлось бы выуживать. Автоматизацию этой области невозможно спроектировать, не погрузившись в законы, и ни в какой --- без общения со специалистами. Написание нормального софта начинается с анализа.


            1. lastrix Автор
              21.09.2021 14:10

              Просто наболело от всяких собак на сене, у которых "все же хорошо, чего ты дергаешься?".

              И повторюсь, это статья техническая и цель - обучение разработчиков писать нормальный софт.

              Не поймите меня не правильно, но позицию этого человека я не поддерживаю, потому что позиция заключается в "не я такой, жизнь такая". Любой закон - это только бумажка, которую изменить можно, была бы воля это делать. Любая организация - это всего лишь договоренность, которую можно изменить, была бы воля это делать. А ее судя по всему нет, поэтому нам и предлагают постоянно одно и то же блюдо "Ешьчёдали".

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

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


              1. Infinitiv
                21.09.2021 18:23
                +1

                если у человека что-то наболело, то это не проблемы других человеков, которые работают внутри системы, которую вы пытаетесь улучшить.

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

                техническую реализацию нет смысла смотреть, если она оторвана от реальности. ваша оторвана.

                ждете комментариев по технической части - ну ждите, я против что ли )


                1. lastrix Автор
                  21.09.2021 20:50

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

                  Как-то быстро вы переобуваетесь.


    1. lastrix Автор
      21.09.2021 12:54
      -1

      Чиновник не проблемы решает, он бюджет осваивает!


  1. Sdolgov
    21.09.2021 14:45

    Столкнулся с этой темой в прошлом году - дочь поступала. Честно говоря вообще непонятен смысл этого бардака с подачей согласий в последний момент и возможностью перекладывать. За год до этого в институт поступала родственница в Украине. Поэтому ознакомился с их системой поступления. Делается так - сразу же подаешь заявление на пять мест (разные вузы/специальности и т.п.) и расставляешь их в порядке приоритета. И все. Это и есть согласие на поступление. Куда в итоге по баллам пройдешь туда и обязан идти. Если проходишь по конкурсу в первый по приоритету вуз - поступишь туда, если нет - смотрится конкурс в следующем и так далее. И никаких перекладываний согласий.


    1. lastrix Автор
      21.09.2021 14:58
      +1

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

      Так же, мое лично мнение, что такой вариант не является оптимальным.
      Например вы желаете поступить в вуз - А, Б и В, при этом если вы не проходите на бюджет во всех трех, то желаете поступить на платное в вуз Б. С описанной вами системой это сделать можно конечно, только придется еще и приоритеты для платных мест заводить каждому абитуриенту.

      Основной фишкой предложенного мой варианта в том, что в этапе 4 у абитуриента есть максимальная полнота информации, а главное, что у всех абитуриентов уже отправлено согласие куда именно он желает поступить, а так как это означает лишь изменение собственного выбора, то маловероятно, что таких людей будет много. Вы правда думаете, что из топового вуз стобальник заберет документы, что бы идти куда-то еще? Если списки публичные, то особо активные могут и проверить куда поступает еще абитуриент №123134, а то и вовсе ссылку ставить, что он выбрал институт такой-то, перейти к списку поступающих. Сам процесс будет более предсказуемым. Я уверен, что 90% абитуриентов даже не будут пользоваться своим правом изменить согласие на поступление, потому что уже на момент дачи согласия (3 этап) можно будет увидеть кто поступает и с какими баллами, потому что новых людей в список поступающих добавить нельзя.