Changes between Initial Version and Version 1 of OtherTopics


Ignore:
Timestamp:
09/23/26 13:54:28 (5 days ago)
Author:
233149
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • OtherTopics

    v1 v1  
     1== Други теми - перформанси и безбедност
     2
     3== Мерна поставеност
     4
     5Анализата на перформанси не може да се направи врз официјалните тест податоци од
     6data_load.sql, бидејќи тие се премногу мали - 8 корисници и 20 предмети. Врз
     7табела со 20 редови PostgreSQL речиси секогаш избира секвенцијално читање дури и
     8кога постои совршен индекс, затоа што читањето на 20 редови е поевтино од
     9пребарување низ индекс. Мерењето врз такви податоци не би покажало ништо.
     10
     11Затоа е креирана посебна шема '''perf''' со иста структура како project, но со
     12генерирани податоци од реален обем:
     13
     14|| '''Табела''' || '''Редови''' ||
     15|| users || 4.120 (4.000 студенти, 120 професори) ||
     16|| subjects || 300 ||
     17|| active_semesters || 20 ||
     18|| major_subjects || 600 ||
     19|| professour_subjects || 6.000 ||
     20|| enrolled_semesters || 16.000 ||
     21|| semesters_subjects || 80.000 ||
     22|| passed_subjects || 56.000 ||
     23|| payment || 16.000 ||
     24
     25Упатството за Фаза 2 дозволува креирање дополнителни шеми за експерименти и
     26тестови, па официјалната шема project останува недопрена.
     27
     28Сите мерења се направени со {{{EXPLAIN (ANALYZE, BUFFERS)}}} и со {{{ANALYZE}}}
     29извршен пред секое мерење, за статистиките на планерот да бидат свежи.
     30
     31'''Забелешка за околината:''' мерењата се извршени врз локална инстанца
     32PostgreSQL 18, а не врз доделената проектна база (PostgreSQL 17.11). Причината е
     33што проектната база е достапна само преку SSH тунел кој во текот на работата
     34постојано се прекинуваше, а мерење на времиња низ тунел и онака би ја мерело
     35мрежата наместо базата. Плановите за извршување се споредливи, бидејќи истата
     36шема, истите барања и истите податоци се употребени во двата случаја.
     37
     38== SQL перформанси
     39
     40=== Предложени индекси
     41
     42PostgreSQL автоматски креира индекс за примарен клуч и за секое UNIQUE
     43ограничување, но '''никогаш''' не креира индекс за колоната која е надворешен
     44клуч. Освен тоа, сложен индекс помага само на барања кои ја користат неговата
     45'''прва''' колона.
     46
     47Според тоа, во шемата недостасуваат индекси на следните места:
     48
     49{{{#!sql
     50CREATE INDEX ix_semesters_subjects_subject   ON semesters_subjects (subjects_id);
     51CREATE INDEX ix_semesters_subjects_professor ON semesters_subjects (professor_id);
     52CREATE INDEX ix_enrolled_semesters_semester  ON enrolled_semesters (semester_id);
     53CREATE INDEX ix_enrolled_semesters_major     ON enrolled_semesters (major_id);
     54CREATE INDEX ix_payment_enrollment           ON payment (enrollment_id);
     55CREATE INDEX ix_major_subjects_subject       ON major_subjects (subject_id);
     56CREATE INDEX ix_professour_subjects_semester ON professour_subjects (active_semester_id, subject_id);
     57CREATE INDEX ix_user_documents_document      ON user_documents (document_id);
     58CREATE INDEX ix_token_user                   ON token (user_id);
     59CREATE INDEX ix_passed_subjects_enrolled_grade ON passed_subjects (enrolled_id, grade);
     60}}}
     61
     62Колоните semesters_subjects.enrolled_semesters_id, enrolled_semesters.user_id,
     63passed_subjects.enrolled_id, high_school.user_id и contact.user_id '''не''' се на
     64списокот, бидејќи веќе се покриени - тие се првата колона на постоечко UNIQUE
     65ограничување.
     66
     67=== Резултати пред и по индексите
     68
     69|| '''Барање''' || '''Пред''' || '''По''' || '''Промена''' ||
     70|| Извештај 1 - проодност по предмет || 146,0 ms || 156,0 ms || нема ||
     71|| Извештај 2 - досие на сите студенти || 188,5 ms || 179,4 ms || нема ||
     72|| Извештај 3 - најуспешен студент по програма || 107,3 ms || 94,0 ms || нема ||
     73|| Извештај 4 - најоптоварен професор || 121,9 ms || 124,6 ms || нема ||
     74|| Извештај 5 - промена на активноста || 124,0 ms || 138,1 ms || нема ||
     75|| Досие на '''еден''' студент || 2,19 ms || '''1,07 ms''' || '''2,0 пати побрзо''' ||
     76|| Дневник на професор за еден професор || 16,3 ms || '''7,6 ms''' || '''2,1 пати побрзо''' ||
     77
     78=== Зошто петте извештаи не станаа побрзи
     79
     80Ова не е неуспех на индексите, туку очекувано однесување. Петте извештаи од Фаза
     816 се агрегатни - тие читаат '''сè''' од semesters_subjects, passed_subjects и
     82enrolled_semesters, за да пресметаат проодност, просек или ранг. Кога барањето и
     83онака мора да ги прочита сите редови, секвенцијалното читање е побрзо од
     84пребарување низ индекс, бидејќи чита последователни блокови од дискот наместо да
     85скока по индексот и потоа по табелата.
     86
     87Дека разликите во табелата се шум, а не забавување, се гледа од повторените
     88мерења на истото барање со исти индекси:
     89
     90{{{
     91run 1: 164,0 ms
     92run 2: 151,5 ms
     93run 3: 162,7 ms
     94run 4: 179,9 ms
     95}}}
     96
     97Распонот од околу 28 ms меѓу четири последователни извршувања е поголем од сите
     98разлики „пред/по“ во табелата. Значи индексите за овие барања не менуваат ништо -
     99ниту на добро, ниту на лошо.
     100
     101=== Каде индексите навистина помогнаа
     102
     103Кај двете '''селективни''' барања - оние кои бараат податоци за еден студент или
     104за еден професор - разликата е јасна и се гледа во самиот план.
     105
     106'''Досие на еден студент''' (2,19 ms → 1,07 ms). Планот пред индексите содржеше:
     107
     108{{{
     109Seq Scan on payment
     110}}}
     111
     112а по индексите:
     113
     114{{{
     115Index Scan using ix_payment_enrollment
     116}}}
     117
     118'''Дневник на професор''' (16,3 ms → 7,6 ms). Пред индексот:
     119
     120{{{
     121Seq Scan on semesters_subjects   (80.000 реда прочитани, филтрирани на ~660)
     122}}}
     123
     124По индексот:
     125
     126{{{
     127Bitmap Heap Scan on semesters_subjects
     128  -> Bitmap Index Scan using ix_semesters_subjects_professor
     129}}}
     130
     131Наместо да ги прочита сите 80.000 реда и да ги отфрли 99%, базата сега оди
     132директно до редовите на тој професор.
     133
     134=== Дали индексите навистина се употребени
     135
     136Бројачите од pg_stat_user_indexes по сите мерења:
     137
     138|| '''Индекс''' || '''Големина''' || '''Пати употребен''' ||
     139|| ix_passed_subjects_enrolled_grade || 1248 kB || 1410 ||
     140|| ix_payment_enrollment || 368 kB || 8 ||
     141|| ix_semesters_subjects_subject || 584 kB || 8 ||
     142|| ix_semesters_subjects_professor || 576 kB || 1 ||
     143|| ix_enrolled_semesters_major || 128 kB || '''0''' ||
     144|| ix_enrolled_semesters_semester || 136 kB || '''0''' ||
     145|| ix_major_subjects_subject || 32 kB || '''0''' ||
     146|| ix_professour_subjects_semester || 152 kB || '''0''' ||
     147|| ix_token_user || 8 kB || '''0''' ||
     148|| ix_user_documents_document || 48 kB || '''0''' ||
     149
     150Од десет предложени индекси, '''четири''' се навистина употребени, а '''шест'''
     151ниту еднаш. Тоа е важен резултат: индекс кој не се употребува не е бесплатен -
     152зазема простор и го забавува секој INSERT, UPDATE и DELETE врз таа табела,
     153бидејќи и индексот мора да се ажурира.
     154
     155=== Заклучок
     156
     157 * Индексите врз надворешни клучеви '''не ги забрзуваат''' агрегатните извештаи од Фаза 6, бидејќи тие и онака ја читаат целата табела. Секвенцијалното читање таму е правилниот избор на планерот.
     158 * Истите индекси ги забрзуваат '''селективните''' барања - оние што ги користи апликацијата - околу '''два пати''', со видлива промена на планот од Seq Scan во Index Scan.
     159 * Бидејќи апликацијата работи со селективни барања (еден студент, еден професор), а извештаите се пуштаат ретко, индексите се исплатливи.
     160 * Се задржуваат само четирите употребени индекси. Останатите шест се отстрануваат, со една резерва: индекс врз надворешен клуч помага и при бришење во родителската табела, па ix_enrolled_semesters_semester и ix_major_subjects_subject може да се задржат ако се предвидуваат такви операции.
     161 * Мерењето врз 20 реда не докажува ништо. Секое тврдење за перформанси мора да се прави врз податоци со реален обем.
     162
     163== Безбедносни мерки
     164
     165=== Во апликацијата
     166
     167'''Спречување на SQL injection.''' Апликацијата '''воопшто не составува SQL од
     168текст'''. Проверено врз целиот изворен код - нема ниту еден повик на !FromSqlRaw,
     169!ExecuteSqlRaw, !NpgsqlCommand ниту рачно поставен !CommandText. Целиот пристап оди
     170преку Entity Framework Core, кој секоја вредност ја испраќа како параметар. Единственото
     171место со напишан SQL е проверката на врската:
     172
     173{{{#!sql
     174.SqlQuery<string>($"SELECT version() AS \"Value\"")
     175}}}
     176
     177Тоа е интерполиран стринг во C#, но EF Core таквите изрази ги претвора во
     178параметризирани наредби, а не во спојување на текст - вредностите никогаш не
     179влегуваат во телото на барањето.
     180
     181'''Лозинки.''' Не се чуваат во читлива форма. При регистрација се хешираат со
     182BCrypt, а при најава се споредуваат со Verify:
     183
     184{{{
     185PasswordHash = BCrypt.Net.BCrypt.HashPassword(registerDto.Password)
     186BCrypt.Net.BCrypt.Verify(loginDto.Password, user.PasswordHash)
     187}}}
     188
     189BCrypt носи сопствена сол и намерно е бавен, што го отежнува пробувањето лозинки.
     190
     191'''Автентикација и авторизација.''' Пристапот е со JWT токен со краток рок;
     192подолгата сесија се одржува со refresh токен кој се чува во табелата token и се
     193поништува при одјава (is_valid = FALSE) наместо да се брише, за да остане трага.
     194Улогата се чита '''од потпишаниот токен''', не од барањето, па повикувачот не
     195може да си ја додели.
     196
     197'''Авторизацијата е во самото барање, не покрај него.''' Наместо прво да се
     198провери правото, па потоа да се изврши барањето, условот е дел од наредбата:
     199
     200{{{#!sql
     201INSERT INTO passed_subjects (enrolled_id, grade, date_passed)
     202SELECT ss.id, '9', now()
     203FROM semesters_subjects ss
     204JOIN enrolled_semesters es ON es.id = ss.enrolled_semesters_id
     205WHERE ss.professor_id = 4 AND ss.subjects_id = 2 AND es.user_id = 1;
     206}}}
     207
     208Ако професорот не го предава тој предмет на тој студент, наредбата не менува
     209ниту еден ред. Нема временски прозорец меѓу проверката и дејството.
     210
     211Проверено со барања од туѓа улога: студент кон професорски краен точки враќа
     212'''403''', професор кон административни '''403''', барање без токен '''401''', а
     213професор кој се обидува да оцени предмет што не го предава добива одбивање без
     214никаква измена во базата.
     215
     216=== Во базата
     217
     218'''Правила кои важат и при директен пристап.''' Проверките од Фаза 7 се тригери
     219во базата, а не само во апликацијата: предметот мора да е во студиската програма,
     220професорот мора да го предава тој семестар, предусловите мора да се положени,
     221оценка не смее да има датум во иднина. Кој и да пишува во базата - апликацијата,
     222скрипта или човек преку DBeaver - правилата важат.
     223
     224'''Домени.''' Форматите се дел од типот на колоната, па невалидна е-пошта или ЕМБГ
     225не може да влезе ниту преку директен INSERT.
     226
     227'''Изолација на податоци меѓу факултети.''' Со политиките за безбедност на ниво
     228на редица (Фаза 7), барањата автоматски се ограничуваат на тековниот факултет.
     229
     230'''Отворено прашање.''' Апликацијата се поврзува со корисникот кој е сопственик
     231на шемата, а сопственикот ги '''заобиколува''' политиките за безбедност на ниво
     232на редица, освен ако не се употреби FORCE ROW LEVEL SECURITY. Значи политиките
     233во моментов се напишани и точни, но не се активни за апликацијата. Правилното
     234решение е посебна улога со ограничени права:
     235
     236{{{#!sql
     237CREATE ROLE iknow_app LOGIN PASSWORD '...';
     238GRANT USAGE ON SCHEMA project TO iknow_app;
     239GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA project TO iknow_app;
     240-- без DROP, без ALTER, без права врз други шеми
     241}}}
     242
     243Ова не е спроведено, бидејќи доделениот кориснички налог на проектната база нема
     244право да креира нови улоги.
     245
     246'''Лозинки во конфигурација.''' Лозинката за базата не е во складиштето на кодот
     247- таа е во appsettings.Development.json, кој е во .gitignore, а во складиштето
     248постои само appsettings.Development.json.example со празно место.
     249
     250== Други развојни теми
     251
     252'''Материјализиран преглед.''' Статистиката по предмети (Фаза 7) е пресметка врз
     253целата база - точно оној вид агрегатно барање кое, како што покажа мерењето
     254погоре, не може да се забрза со индекс. Затоа е материјализирана и се освежува
     255еднаш дневно, со уникатен индекс кој овозможува освежување со CONCURRENTLY,
     256односно без заклучување за читање.
     257
     258'''Pool на конекции.''' Документиран во Фаза 8; тука е релевантен затоа што
     259базата е зад SSH тунел, па секоја нова конекција е нов канал низ тунелот и
     260отворањето е поскапо од вообичаеното.
     261
     262== Користење на вештачка интелигенција
     263
     264 [wiki:OtherTopicsAIUsage OtherTopicsAIUsage]
     265
     266== Историјат
     267
     268 '''Верзија 1''' - Прва верзија: мерење на перформансите на петте извештаи од Фаза
     269 6 пред и по воведување индекси врз шема со 80.000 запишани предмети, анализа на
     270 плановите за извршување, проверка кои индекси навистина се употребуваат, и
     271 документирање на безбедносните мерки во апликацијата и во базата.
     272
     273== Статус
     274
     275 ''' [[span(style=color: #FF8000, Во тек )]] '''