| | 1 | = Phase P5: Normalization = |
| | 2 | |
| | 3 | == a) Initial De-normalized Relation and Functional Dependencies == |
| | 4 | |
| | 5 | === Global Attribute List (unified relation R) === |
| | 6 | |
| | 7 | Сите атрибути од целиот ER модел, обединети во една единствена релација R, без дупликати имиња: |
| | 8 | |
| | 9 | {{{ |
| | 10 | R( |
| | 11 | user_id, user_email, user_name, user_picture, user_created_at, |
| | 12 | tenant_id, tenant_name, owner_email, tenant_created_at, |
| | 13 | role, membership_created_at, |
| | 14 | env_id, env_name, env_created_at, |
| | 15 | env_token_id, token, et_created_at, expires_at, |
| | 16 | save_process_history, es_created_at, es_updated_at, |
| | 17 | computer_id, computer_name, computer_user, computer_ip, computer_os, |
| | 18 | first_seen, last_seen, sysmon_available, |
| | 19 | process_id, pid, process_name, cpu_percent, memory_mb, |
| | 20 | proc_username, cmdline, proc_timestamp, |
| | 21 | history_id, cpu_usage, ram_usage, disk_usage, |
| | 22 | network_sent_mb, network_recv_mb, hist_timestamp, |
| | 23 | net_conn_id, local_address, remote_address, status, |
| | 24 | nc_process_name, nc_timestamp, |
| | 25 | alert_id, alert_type, severity, description, alert_timestamp, resolved, |
| | 26 | sysmon_event_id, event_id, event_type, message, sysmon_timestamp, details |
| | 27 | ) |
| | 28 | }}} |
| | 29 | |
| | 30 | === Canonical Cover — Global Set of Functional Dependencies === |
| | 31 | |
| | 32 | {{{ |
| | 33 | FD1: user_id -> user_email, user_name, user_picture, user_created_at |
| | 34 | FD2: tenant_id -> tenant_name, owner_email, tenant_created_at |
| | 35 | FD3: (user_id, tenant_id) -> role, membership_created_at |
| | 36 | FD4: env_id -> tenant_id, env_name, env_created_at |
| | 37 | FD5: env_token_id -> tenant_id, env_name, token, et_created_at, expires_at |
| | 38 | FD6: (tenant_id, env_name) -> save_process_history, es_created_at, es_updated_at |
| | 39 | FD7: computer_id -> tenant_id, env_name, computer_name, computer_user, |
| | 40 | computer_ip, computer_os, first_seen, last_seen, sysmon_available |
| | 41 | FD8: process_id -> computer_id, pid, process_name, cpu_percent, memory_mb, |
| | 42 | proc_username, cmdline, proc_timestamp |
| | 43 | FD9: history_id -> computer_id, cpu_usage, ram_usage, disk_usage, |
| | 44 | network_sent_mb, network_recv_mb, hist_timestamp |
| | 45 | FD10: net_conn_id -> computer_id, pid, local_address, remote_address, |
| | 46 | status, nc_process_name, nc_timestamp |
| | 47 | FD11: alert_id -> computer_id, alert_type, severity, description, |
| | 48 | alert_timestamp, resolved |
| | 49 | FD12: sysmon_event_id -> computer_id, event_id, event_type, message, |
| | 50 | sysmon_timestamp, details |
| | 51 | }}} |
| | 52 | |
| | 53 | Ова е канонски cover (минимален сет): секоја FD има единствен атрибут / минимален сет на левата страна, нема вишок FD-и кои можат да се изведат од другите, и нема redundant атрибути на десните страни. |
| | 54 | |
| | 55 | ---- |
| | 56 | |
| | 57 | == b) Candidate Keys and Primary Key Selection == |
| | 58 | |
| | 59 | === Идентификување примитивни атрибути === |
| | 60 | |
| | 61 | Атрибут е '''примитивен''' ако никогаш не се појавува на десната страна (RHS) на ниту една FD. Таквите атрибути мора да бидат дел од секој кандидат клуч, бидејќи ниту една FD не може да ги "произведе" - единствениот начин да се дојде до нив е да се земат директно. |
| | 62 | |
| | 63 | Проверка на секој атрибут од R против сите RHS на FD1-FD12: |
| | 64 | |
| | 65 | {{{ |
| | 66 | Примитивни атрибути (никогаш на RHS): |
| | 67 | user_id, env_id, env_token_id, process_id, |
| | 68 | history_id, net_conn_id, alert_id, sysmon_event_id |
| | 69 | |
| | 70 | Забелешка: tenant_id и computer_id НЕ се примитивни - |
| | 71 | тие се појавуваат на RHS во FD4, FD5, FD7 (изведливи атрибути). |
| | 72 | }}} |
| | 73 | |
| | 74 | === Пробен клуч и Attribute Closure (K+) === |
| | 75 | |
| | 76 | {{{ |
| | 77 | K = { user_id, env_id, env_token_id, process_id, |
| | 78 | history_id, net_conn_id, alert_id, sysmon_event_id } |
| | 79 | }}} |
| | 80 | |
| | 81 | Пресметка на closure K+ чекор по чекор: |
| | 82 | |
| | 83 | {{{ |
| | 84 | K+ (почетно) = K |
| | 85 | |
| | 86 | Применуваме FD1 (user_id): |
| | 87 | K+ = K+ U { user_email, user_name, user_picture, user_created_at } |
| | 88 | |
| | 89 | Применуваме FD4 (env_id): |
| | 90 | K+ = K+ U { tenant_id, env_name, env_created_at } |
| | 91 | |
| | 92 | Применуваме FD5 (env_token_id): |
| | 93 | K+ = K+ U { token, et_created_at, expires_at } |
| | 94 | (tenant_id, env_name веќе се во K+) |
| | 95 | |
| | 96 | Применуваме FD8 (process_id): |
| | 97 | K+ = K+ U { computer_id, pid, process_name, cpu_percent, memory_mb, |
| | 98 | proc_username, cmdline, proc_timestamp } |
| | 99 | |
| | 100 | Применуваме FD9 (history_id): |
| | 101 | K+ = K+ U { cpu_usage, ram_usage, disk_usage, |
| | 102 | network_sent_mb, network_recv_mb, hist_timestamp } |
| | 103 | |
| | 104 | Применуваме FD10 (net_conn_id): |
| | 105 | K+ = K+ U { local_address, remote_address, status, |
| | 106 | nc_process_name, nc_timestamp } |
| | 107 | |
| | 108 | Применуваме FD11 (alert_id): |
| | 109 | K+ = K+ U { alert_type, severity, description, alert_timestamp, resolved } |
| | 110 | |
| | 111 | Применуваме FD12 (sysmon_event_id): |
| | 112 | K+ = K+ U { event_id, event_type, message, sysmon_timestamp, details } |
| | 113 | |
| | 114 | Сега computer_id е веќе во K+ -> применуваме FD7: |
| | 115 | K+ = K+ U { computer_name, computer_user, computer_ip, computer_os, |
| | 116 | first_seen, last_seen, sysmon_available } |
| | 117 | |
| | 118 | Сега (tenant_id, env_name) се двете во K+ -> применуваме FD6: |
| | 119 | K+ = K+ U { save_process_history, es_created_at, es_updated_at } |
| | 120 | |
| | 121 | Сега (user_id, tenant_id) се двете во K+ -> применуваме FD3: |
| | 122 | K+ = K+ U { role, membership_created_at } |
| | 123 | |
| | 124 | ------------------------------------------------------ |
| | 125 | K+ = сите атрибути на R => K е superkey |
| | 126 | ------------------------------------------------------ |
| | 127 | }}} |
| | 128 | |
| | 129 | === Минималност (доказ дека К е Candidate Key) === |
| | 130 | |
| | 131 | За да биде К candidate key, а не само superkey, мора да е '''минимален''' - тргнувањето на било кој атрибут од К мора да го "скрши" closure-от (да не стигне повеќе до сите атрибути). |
| | 132 | |
| | 133 | Бидејќи сите 8 атрибути во К се '''примитивни''' (никогаш не се на RHS на ниту една FD), секој од нив е задолжителен - ниту еден друг атрибут во целата релација не може да го "изведе". Значи тргнувањето на кој било од нив прави closure-от веднаш да падне под целосниот сет атрибути. |
| | 134 | |
| | 135 | {{{ |
| | 136 | => K е минимален superkey => K е Candidate Key |
| | 137 | |
| | 138 | Candidate Key = (user_id, env_id, env_token_id, process_id, |
| | 139 | history_id, net_conn_id, alert_id, sysmon_event_id) |
| | 140 | }}} |
| | 141 | |
| | 142 | === Единственост на кандидат клучот === |
| | 143 | |
| | 144 | Бидејќи сите 8 атрибути во К се примитивни (задолжителни во секој candidate key), а нивниот closure веќе покрива целосно R, не постои друга комбинација атрибути (со помалку или различни примитивни атрибути) која исто така би била минимален superkey. Следствено, ова е '''единствениот''' candidate key на релацијата R. |
| | 145 | |
| | 146 | {{{ |
| | 147 | PRIMARY KEY (R) = (user_id, env_id, env_token_id, process_id, |
| | 148 | history_id, net_conn_id, alert_id, sysmon_event_id) |
| | 149 | }}} |
| | 150 | |
| | 151 | '''Забелешка:''' tenant_id и computer_id намерно НЕ се дел од клучот, иако интуитивно "изгледаат" како да треба - тие се redundant атрибути (изводливи преку env_id/computer_id верижно преку FD4, FD7), и нивно вклучување во клучот би го нарушило условот за минималност. |
| | 152 | |
| | 153 | === Нормална форма на почетната релација === |
| | 154 | |
| | 155 | {{{ |
| | 156 | 1NF: ДА - сите вредности се атомски, нема повторувачки групи/низи |
| | 157 | |
| | 158 | 2NF: НЕ - клучот е composite (8 атрибути), но постојат атрибути кои |
| | 159 | зависат само од ДЕЛ од клучот: |
| | 160 | * user_email, user_name, ... зависат само од user_id |
| | 161 | * token, et_created_at, ... зависат само од env_token_id |
| | 162 | * process_name, cpu_percent, ... зависат само од process_id |
| | 163 | * ... (аналогно за сите останати FD-и) |
| | 164 | Ова е класична парцијална зависност -> нарушување на 2NF. |
| | 165 | |
| | 166 | Заклучок: почетната релација R е само во 1NF. |
| | 167 | Декомпозицијата започнува со отстранување на парцијалните зависности. |
| | 168 | }}} |
| | 169 | |
| | 170 | ---- |
| | 171 | |
| | 172 | == c) Step-by-Step Decomposition == |
| | 173 | |
| | 174 | === Чекор 1: 1NF -> 2NF (отстранување на парцијални зависности) === |
| | 175 | |
| | 176 | За секоја FD чија лева страна е '''подмножество''' (proper subset) на кандидат клучот, тој дел од клучот заедно со сите атрибути кои зависи од него се издвојува во нова релација. |
| | 177 | |
| | 178 | '''Анализирана релација:''' R (почетна, 1NF) |
| | 179 | '''FD-и кои предизвикуваат проблем:''' FD1, FD2, FD3, FD4, FD5, FD6, FD7, FD8, FD9, FD10, FD11, FD12 (сите, бидејќи левите страни се proper subsets на 8-атрибутниот клуч) |
| | 180 | '''Прв старт на декомпозиција:''' FD1 (user_id -> ...), потоа редоследно и останатите |
| | 181 | |
| | 182 | Резултат по декомпозицијата: |
| | 183 | |
| | 184 | {{{ |
| | 185 | R1: Users(user_id, user_email, user_name, user_picture, user_created_at) |
| | 186 | FD: user_id -> сите останати |
| | 187 | Candidate key: {user_id} PK: user_id |
| | 188 | |
| | 189 | R2: Tenants(tenant_id, tenant_name, owner_email, tenant_created_at) |
| | 190 | FD: tenant_id -> сите останати |
| | 191 | Candidate key: {tenant_id} PK: tenant_id |
| | 192 | |
| | 193 | R3: Memberships(user_id, tenant_id, role, membership_created_at) |
| | 194 | FD: (user_id, tenant_id) -> role, membership_created_at |
| | 195 | Candidate key: {user_id, tenant_id} PK: (user_id, tenant_id) |
| | 196 | |
| | 197 | R4: Environments(env_id, tenant_id, env_name, env_created_at) |
| | 198 | FD: env_id -> tenant_id, env_name, env_created_at |
| | 199 | Candidate key: {env_id} PK: env_id |
| | 200 | |
| | 201 | R5: EnvTokens(env_token_id, tenant_id, env_name, token, et_created_at, expires_at) |
| | 202 | FD: env_token_id -> сите останати |
| | 203 | Candidate key: {env_token_id} PK: env_token_id |
| | 204 | |
| | 205 | R6: EnvSettings(tenant_id, env_name, save_process_history, es_created_at, es_updated_at) |
| | 206 | FD: (tenant_id, env_name) -> save_process_history, es_created_at, es_updated_at |
| | 207 | Candidate key: {tenant_id, env_name} PK: (tenant_id, env_name) |
| | 208 | |
| | 209 | R7: Computers(computer_id, tenant_id, env_name, computer_name, computer_user, |
| | 210 | computer_ip, computer_os, first_seen, last_seen, sysmon_available) |
| | 211 | FD: computer_id -> сите останати |
| | 212 | Candidate key: {computer_id} PK: computer_id |
| | 213 | |
| | 214 | R8: Processes(process_id, computer_id, pid, process_name, cpu_percent, |
| | 215 | memory_mb, proc_username, cmdline, proc_timestamp) |
| | 216 | FD: process_id -> сите останати |
| | 217 | Candidate key: {process_id} PK: process_id |
| | 218 | |
| | 219 | R9: ComputerHistory(history_id, computer_id, cpu_usage, ram_usage, disk_usage, |
| | 220 | network_sent_mb, network_recv_mb, hist_timestamp) |
| | 221 | FD: history_id -> сите останати |
| | 222 | Candidate key: {history_id} PK: history_id |
| | 223 | |
| | 224 | R10: NetworkConnections(net_conn_id, computer_id, pid, local_address, |
| | 225 | remote_address, status, nc_process_name, nc_timestamp) |
| | 226 | FD: net_conn_id -> сите останати |
| | 227 | Candidate key: {net_conn_id} PK: net_conn_id |
| | 228 | |
| | 229 | R11: SecurityAlerts(alert_id, computer_id, alert_type, severity, |
| | 230 | description, alert_timestamp, resolved) |
| | 231 | FD: alert_id -> сите останати |
| | 232 | Candidate key: {alert_id} PK: alert_id |
| | 233 | |
| | 234 | R12: SysmonEvents(sysmon_event_id, computer_id, event_id, event_type, |
| | 235 | message, sysmon_timestamp, details) |
| | 236 | FD: sysmon_event_id -> сите останати |
| | 237 | Candidate key: {sysmon_event_id} PK: sysmon_event_id |
| | 238 | }}} |
| | 239 | |
| | 240 | '''FD Preservation:''' секоја од оригиналните FD1-FD12 директно се содржи во точно една од новите релации (R1<-FD1, R2<-FD2, R3<-FD3, R4<-FD4, R5<-FD5, R6<-FD6, R7<-FD7, R8<-FD8, R9<-FD9, R10<-FD10, R11<-FD11, R12<-FD12) => сите FD-и се зачувани. |
| | 241 | |
| | 242 | '''Lossless Join проверка''' (за секоја поделба, заедничкиот атрибут мора да е клуч на барем едната страна): |
| | 243 | |
| | 244 | {{{ |
| | 245 | R ∩ R1 = {user_id} -> клуч на R1 OK |
| | 246 | R ∩ R2 = {tenant_id} -> клуч на R2 OK |
| | 247 | R ∩ R3 = {user_id, tenant_id} -> клуч на R3 OK |
| | 248 | R ∩ R4 = {env_id} -> клуч на R4 OK |
| | 249 | R ∩ R5 = {env_token_id} -> клуч на R5 OK |
| | 250 | R ∩ R6 = {tenant_id, env_name} -> клуч на R6 OK |
| | 251 | R ∩ R7 = {computer_id} -> клуч на R7 OK |
| | 252 | R ∩ R8 = {process_id} -> клуч на R8 OK |
| | 253 | R ∩ R9 = {history_id} -> клуч на R9 OK |
| | 254 | R ∩ R10 = {net_conn_id} -> клуч на R10 OK |
| | 255 | R ∩ R11 = {alert_id} -> клуч на R11 OK |
| | 256 | R ∩ R12 = {sysmon_event_id} -> клуч на R12 OK |
| | 257 | }}} |
| | 258 | |
| | 259 | Сите поделби ја задоволуваат lossless join теоремата (заедничкиот атрибут е клуч на новодобиената релација) => декомпозицијата е lossless. |
| | 260 | |
| | 261 | === Чекор 2: 2NF -> 3NF (проверка на транзитивни зависности) === |
| | 262 | |
| | 263 | За секоја R1-R12, се проверува дали не-клучен атрибут зависи од друг не-клучен атрибут (наместо директно од клучот). |
| | 264 | |
| | 265 | {{{ |
| | 266 | R1 (Users): само еден детерминант (user_id) во FD1 -> нема транзитивност |
| | 267 | R2 (Tenants): само еден детерминант (tenant_id) во FD2 -> нема транзитивност |
| | 268 | R3 (Memberships): role, membership_created_at немаат меѓусебна |
| | 269 | зависност -> нема транзитивност |
| | 270 | R4 (Environments): env_name, env_created_at зависат само од |
| | 271 | env_id, не едно од друго -> нема транзитивност |
| | 272 | R5 (EnvTokens): token, expires_at зависат само од |
| | 273 | env_token_id -> нема транзитивност |
| | 274 | R6 (EnvSettings): save_process_history не зависи од друг |
| | 275 | не-клучен атрибут -> нема транзитивност |
| | 276 | R7 (Computers): computer_name, computer_os, ... зависат само |
| | 277 | од computer_id (env_name овде е FK, не |
| | 278 | детерминира ништо друго) -> нема транзитивност |
| | 279 | R8 (Processes): сите атрибути зависат директно од process_id -> нема транзитивност |
| | 280 | R9 (ComputerHistory): сите атрибути зависат директно од history_id -> нема транзитивност |
| | 281 | R10 (NetworkConnections): сите атрибути зависат директно од net_conn_id -> нема транзитивност |
| | 282 | R11 (SecurityAlerts): сите атрибути зависат директно од alert_id -> нема транзитивност |
| | 283 | R12 (SysmonEvents): сите атрибути зависат директно од |
| | 284 | sysmon_event_id -> нема транзитивност |
| | 285 | |
| | 286 | Заклучок: сите R1-R12 се веќе во 3NF по завршување на Чекор 1. |
| | 287 | }}} |
| | 288 | |
| | 289 | === Чекор 3: 3NF -> BCNF === |
| | 290 | |
| | 291 | За BCNF, за секоја нетривијална FD X -> Y во релацијата, X мора да е superkey. |
| | 292 | |
| | 293 | {{{ |
| | 294 | R1: единствена FD е user_id -> ... user_id е PK -> superkey BCNF OK |
| | 295 | R2: единствена FD е tenant_id -> ... tenant_id е PK -> superkey BCNF OK |
| | 296 | R3: единствена FD е (user_id,tenant_id)->... тоа е PK -> superkey BCNF OK |
| | 297 | R4: единствена FD е env_id -> ... env_id е PK -> superkey BCNF OK |
| | 298 | R5: единствена FD е env_token_id -> ... env_token_id е PK -> superkey BCNF OK |
| | 299 | R6: единствена FD е (tenant_id,env_name)->.. тоа е PK -> superkey BCNF OK |
| | 300 | R7: единствена FD е computer_id -> ... computer_id е PK -> superkey BCNF OK |
| | 301 | R8: единствена FD е process_id -> ... process_id е PK -> superkey BCNF OK |
| | 302 | R9: единствена FD е history_id -> ... history_id е PK -> superkey BCNF OK |
| | 303 | R10: единствена FD е net_conn_id -> ... net_conn_id е PK -> superkey BCNF OK |
| | 304 | R11: единствена FD е alert_id -> ... alert_id е PK -> superkey BCNF OK |
| | 305 | R12: единствена FD е sysmon_event_id -> ... sysmon_event_id е PK -> superkey BCNF OK |
| | 306 | |
| | 307 | Заклучок: сите R1-R12 се веќе во BCNF. |
| | 308 | Декомпозицијата завршува по еден единствен чекор (1NF -> 2NF), бидејќи |
| | 309 | истовремено ги отстранивме сите парцијални И транзитивни зависности. |
| | 310 | }}} |
| | 311 | |
| | 312 | ---- |
| | 313 | |
| | 314 | == d) Final Result and Discussion == |
| | 315 | |
| | 316 | === Финален нормализиран дизајн === |
| | 317 | |
| | 318 | {{{ |
| | 319 | Users(user_id, user_email, user_name, user_picture, user_created_at) |
| | 320 | Tenants(tenant_id, tenant_name, owner_email, tenant_created_at) |
| | 321 | Memberships(user_id, tenant_id, role, membership_created_at) |
| | 322 | Environments(env_id, tenant_id, env_name, env_created_at) |
| | 323 | EnvTokens(env_token_id, tenant_id, env_name, token, et_created_at, expires_at) |
| | 324 | EnvSettings(tenant_id, env_name, save_process_history, es_created_at, es_updated_at) |
| | 325 | Computers(computer_id, tenant_id, env_name, computer_name, computer_user, |
| | 326 | computer_ip, computer_os, first_seen, last_seen, sysmon_available) |
| | 327 | Processes(process_id, computer_id, pid, process_name, cpu_percent, memory_mb, |
| | 328 | proc_username, cmdline, proc_timestamp) |
| | 329 | ComputerHistory(history_id, computer_id, cpu_usage, ram_usage, disk_usage, |
| | 330 | network_sent_mb, network_recv_mb, hist_timestamp) |
| | 331 | NetworkConnections(net_conn_id, computer_id, pid, local_address, remote_address, |
| | 332 | status, nc_process_name, nc_timestamp) |
| | 333 | SecurityAlerts(alert_id, computer_id, alert_type, severity, description, |
| | 334 | alert_timestamp, resolved) |
| | 335 | SysmonEvents(sysmon_event_id, computer_id, event_id, event_type, message, |
| | 336 | sysmon_timestamp, details) |
| | 337 | }}} |
| | 338 | |
| | 339 | Сите 12 релации се во '''BCNF''', со зачувани функционални зависности (FD preservation) и без губење на податоци при join (lossless join). |
| | 340 | |
| | 341 | === Дискусија: споредба со дизајнот од Фаза 2 === |
| | 342 | |
| | 343 | Формалната normalization постапка (стартувајќи од единствена денормализирана универзална релација и attribute closure анализа) резултира со дизајн кој е '''речиси идентичен''' со реалната имплементирана шема од Фаза 2 на проектот. Ова е очекувано и добар знак - потврдува дека физичкиот дизајн од самиот почеток бил веќе близу оптимален (3NF/BCNF), без непотребна редундантност. |
| | 344 | |
| | 345 | Единствената разлика е физичка, не логичка: во реалната имплементација, логичкиот ентитет `Processes` е физички распослан на три табели - |
| | 346 | `computer_processes`, `computer_processes_current` и `computer_processes_history` - истата структура, но со различна намена (тековна снимка наспроти историски архив). Ова е инженерска одлука мотивирана од перформанси и стратегија за архивирање на податоци, а не грешка во нормализацијата - логички, и трите се инстанци на истата нормализирана релема `Processes(process_id, computer_id, pid, process_name, cpu_percent, memory_mb, proc_username, cmdline, proc_timestamp)`. |
| | 347 | |
| | 348 | '''Заклучок:''' дизајнот од Фаза 2 останува дизајнот кој ќе се користи во следните фази на проектот, бидејќи е веќе во BCNF и оваа анализа само формално го потврдува тоа. |