Bazy danych wczoraj i dziś: od kart perforowanych do SQL i NoSQL

0
25
4/5 - (1 vote)

Nawigacja:

Dane przed erą baz danych: karty, taśmy, pliki

Karty perforowane i pierwsze systemy ewidencji

Na długo przed SQL i NoSQL dane istniały wyłącznie w postaci papieru i mechaniki. Zanim pojawiły się komputery, rejestry ludności, księgi handlowe czy ewidencja magazynowa były prowadzone w zeszytach, teczkach i segregatorach. Każdy wpis to fizyczny dokument, którego przeszukanie wymagało pracy człowieka.

Karty perforowane wprowadziły do tego świata prostą „maszynową” strukturę. Każda karta reprezentowała jeden rekord, a otwory w określonych kolumnach kodowały wartości. Aby zmienić dane, trzeba było przygotować nową kartę i zastąpić starą. Operacja, która dziś trwa milisekundy, wtedy wymagała fizycznej pracy i organizacji całego procesu.

Główna zaleta kart polegała na możliwości mechanicznego sortowania i zliczania. Maszyny sortujące i liczące potrafiły obrabiać tysiące kart, ale w ściśle sekwencyjny sposób. Zapytanie ad‑hoc typu „pokaż wszystkich klientów, którzy kupili produkt X i mieszkają w mieście Y” wymagało specjalnie przygotowanej talii kart oraz programu skrojonego wyłącznie pod to zadanie.

Ograniczenia były oczywiste: sekwencyjny dostęp, podatność na uszkodzenia fizyczne, brak możliwości szybkiego filtrowania po dowolnym kryterium. Dane były „uwięzione” w konkretnej kolejności i formacie, a zmiana struktury wymagała ponownego przygotowania kart i programów. Z punktu widzenia architektury systemu karta perforowana była jednocześnie nośnikiem danych, indeksem i schematem – nierozerwalnie złączonym z konkretnym zastosowaniem.

Magazyny taśmowe i plikowe systemy danych

Taśmy magnetyczne były naturalnym następcą kart. Pozwalały przechowywać znacznie więcej danych, odczytywać je szybciej i niezależnie od fizycznej obecności operatora przy maszynie. Zmienił się nośnik, ale kluczowa cecha pozostała: dostęp sekwencyjny. Aby znaleźć konkretny rekord, system musiał przewinąć taśmę i przejść przez wszystkie wcześniejsze rekordy.

Na taśmach zaczęły powstawać plikowe systemy danych. Każdy plik był zbiorem rekordów o ustalonej strukturze, którą znał tylko dany program. Aplikacja była odpowiedzialna za to, jak interpretować poszczególne bajty, gdzie znajdują się pola, jaki kod znaków jest użyty. Cała „inteligencja danych” znajdowała się w kodzie programu, nie w systemie operacyjnym ani w wyspecjalizowanym systemie bazodanowym.

Typowa architektura wyglądała tak: plik na taśmie lub dysku, zestaw programów COBOL/Fortran/BAL, z których każdy „wiedział”, jak czytać ten plik. Każda modyfikacja formatu rekordu wymagała dostosowania wszystkich programów, które go używały. W praktyce prowadziło to do powstawania wielu podobnych, ale nieidentycznych plików – każda aplikacja utrzymywała własny wariant danych.

Taśmy stopniowo ustępowały miejsca dyskom, ale logika pozostała identyczna: dane w plikach, logika w programach. Pojawiły się narzędzia indeksujące pliki (np. ISAM), jednak nadal aplikacje kontrolowały strukturę danych i sposób dostępu.

Konsekwencje podejścia „plikowego”

Model „każdy program ma swoje pliki” prowadził do typowych problemów, które dziś przypominają antywzorce projektowe:

  • Duplikacja kodu – te same operacje (wyszukiwanie klienta, aktualizacja salda, generowanie raportu) były implementowane niezależnie w różnych programach.
  • Silne powiązanie programu z danymi – zmiana formatu pliku wymagała rekompilacji i testów całej floty programów, co blokowało rozwój.
  • Brak wspólnego widoku danych – różne systemy przechowywały swoje wersje informacji; spójność między nimi była utrzymywana ręcznie lub wcale.
  • Trudne raportowanie – generowanie nowych przekrojów danych wymagało dedykowanych programów, często pisanych jednorazowo pod konkretne zlecenie.

To doświadczenie – chaos formatów, brak wspólnego schematu i duża kruchość aplikacji – stało się silnym impulsem do szukania bardziej ustrukturyzowanego podejścia. Z tej potrzeby wyrosły pierwsze systemy zarządzania bazami danych (DBMS), które próbowały przejąć odpowiedzialność za strukturę, integralność i udostępnianie danych.

Narodziny systemów zarządzania bazami danych

IBM, mainframe i pierwsze systemy katalogowania danych

W latach 60. i 70. skala przetwarzania danych gwałtownie rosła. Firmy i instytucje państwowe zaczęły budować scentralizowane centra obliczeniowe oparte na mainframe’ach, głównie IBM. Na jednym dużym komputerze działały dziesiątki aplikacji, które potrzebowały dostępu do tych samych danych: klientów, transakcji, produktów.

Model plikowy okazał się zbyt sztywny. Koszt koordynacji zmian i synchronizacji informacji między aplikacjami rósł szybciej niż sprzęt. Pojawiła się potrzeba wspólnego, współdzielonego repozytorium danych, nad którym czuwa wyspecjalizowany system – zaczątek dzisiejszych DBMS.

IBM i inne firmy zaczęły tworzyć systemy katalogowania danych, które pozwalały opisać strukturę rekordów niezależnie od konkretnych programów. Schematy były przechowywane w specjalnych plikach opisowych, a programy odwoływały się do nich po nazwach pól, nie po numerach bajtów. To był duży krok naprzód: część logiki danych została wyciągnięta z kodu aplikacji.

Jednak sama definicja pól nie rozwiązywała problemu złożonych relacji między rekordami. Potrzebny był model, który pozwoli powiązać dane i nawigować po nich w bardziej naturalny sposób niż sekwencyjne czytanie plików.

Hierarchiczne bazy danych: IMS i logiczne drzewa

Hierarchiczny model danych, reprezentowany przez system IMS (Information Management System) IBM, zorganizował dane w strukturę drzewa. Użytkownik definiował „segmenty” (odpowiedniki typów rekordów) i powiązania rodzic–dziecko. Przykładowo: Bank –> Konto –> Transakcja. Dostęp do dziecka był możliwy tylko przez rodzica.

Taki model świetnie pasował do wielu procesów biznesowych tamtych czasów: obsługi kont bankowych, zamówień, logistyki. Dane były fizycznie ułożone na dysku zgodnie z hierarchią, co dawało bardzo dobrą wydajność sekwencyjnego przeglądania i aktualizacji.

Programista korzystał z nawigacyjnego API: „przejdź do pierwszego konta tego klienta”, „przejdź do następnej transakcji na koncie”. Logika bazowała na wskaźnikach i pozycji w drzewie, a nie na deklaratywnych zapytaniach. Zmiana struktury drzewa oznaczała konieczność przeprojektowania aplikacji.

Hierarchiczne bazy danych rozwiązały kilka kluczowych problemów epoki plikowej:

  • zapewniły centralny, współdzielony magazyn danych,
  • wyciągnęły część logiki struktury danych z aplikacji do systemu,
  • umożliwiły stosunkowo wydajny dostęp do powiązanych rekordów.

W zamian narzuciły jednak bardzo konkretny sposób patrzenia na dane: wszystko musiało być wpasowane w drzewo. Gdy pojawiała się potrzeba innego przekroju, np. „wszystkie transakcje po dacie, niezależnie od kont”, trzeba było budować dodatkowe ścieżki lub specjalne struktury indeksów.

Sieciowy model CODASYL: rekordy połączone wskaźnikami

Sieciowy model danych, standaryzowany przez komitet CODASYL, próbował przełamać ograniczenia drzewa. Zamiast jednego nadrzędnego rodzica dopuszczał wiele powiązań między rekordami. Dane były zorganizowane w rekordy i zestawy (sets), które odzwierciedlały relacje między nimi. Rekord mógł należeć do wielu zestawów, co tworzyło sieć powiązań.

W praktyce programista musiał „chodzić po wskaźnikach”: przechodzić od jednego rekordu do kolejnych zgodnie z definicją zestawów. Dawało to bardzo wysoką wydajność przy precyzyjnie zaprojektowanych ścieżkach dostępu, ale kod aplikacji był mocno związany z fizycznym układem danych i strukturą powiązań.

Sieciowe DBMS świetnie sprawdzały się w systemach, gdzie wzorce dostępu były stosunkowo stałe i dobrze znane, np. w przemyśle lotniczym czy telekomunikacji. Problem pojawiał się, gdy zmieniały się potrzeby biznesowe. Dodanie nowego sposobu łączenia danych wymagało rozbudowy zestawów i często poważnego refaktoringu aplikacji.

Hierarchiczne i sieciowe systemy przesuwały logikę danych z aplikacji do wyspecjalizowanego systemu. Jednak nadal zmuszały programistów do myślenia w kategoriach technicznej struktury (drzew, sieci, wskaźników), a nie problemu biznesowego. Na tym tle pojawiła się koncepcja, która zmieniła wszystko.

Rewolucja relacyjna: model Codda i jego następcy

Założenia modelu relacyjnego i ich przełomowy charakter

Edgar F. Codd zaproponował model relacyjny w latach 70. jako radykalne odejście od nawigacyjnego podejścia do danych. Zamiast wskaźników, drzew i zestawów zaproponował abstrakcję tabel (relacji). Każda tabela jest zbiorem wierszy (krotek), a każdy wiersz ma te same kolumny (atrybuty). Relacje między tabelami wyrażane są poprzez wartości, głównie klucze, a nie wskaźniki.

Kluczowe idee modelu relacyjnego:

  • Dane jako zbiory – operacje na danych są operacjami na zbiorach (selekcja, projekcja, złączenie), a nie na pojedynczych rekordach nawigowanych wskaźnikami.
  • Oddzielenie logiki logicznej od fizycznej – użytkownik określa „co” chce uzyskać (warunki, kolumny), ale nie „jak” dane mają być pobrane z dysku.
  • Integracja z logiką matematyczną – model opiera się na teorii zbiorów i logice predykatów, co umożliwia formalne dowodzenie własności systemu i optymalizację zapytań.

Ta zmiana perspektywy miała ogromne konsekwencje praktyczne. Programista przestał być zmuszony do opisywania krok po kroku ścieżki dostępu. Zamiast tego formułował zapytanie, a DBMS decydował o planie wykonania. Otworzyło to drogę do rozbudowanych optymalizatorów, które mogły wybierać najlepsze indeksy, kolejność złączeń i strategie dostępu.

Model relacyjny wprowadził także pojęcie normalizacji. Codd i jego następcy zdefiniowali kolejne postacie normalne (1NF, 2NF, 3NF, BCNF), które eliminowały redundancję i anomalia aktualizacji. Dane o klientach, produktach i zamówieniach przestały być powielane w wielu miejscach; zamiast tego dzielono je na dobrze zdefiniowane tabele połączone kluczami.

Opór rynku i pierwsze relacyjne prototypy

Pomimo teoretycznej elegancji rynek nie przyjął od razu modelu relacyjnego. Istniały duże inwestycje w systemy hierarchiczne i sieciowe, na których opierały się krytyczne procesy biznesowe. Wielu praktyków uważało, że relacyjne DBMS nigdy nie osiągną wydajności na poziomie systemów nawigacyjnych, szczególnie w zastosowaniach OLTP.

Przełomem były prototypy i projekty badawcze, przede wszystkim System R w IBM oraz Ingres na uniwersytecie w Berkeley. Pokazały, że relacyjny model można zaimplementować w praktyce, a optymalizator zapytań potrafi wygenerować plany porównywalne wydajnościowo z ręcznie kodowanymi ścieżkami w modelu sieciowym.

System R wprowadził zalążki języka SQL, który szybko wyparł wcześniejsze koncepcje języków zapytań. Ingres eksperymentował z językiem QUEL, ale ostatecznie również podążył w kierunku SQL. Z tych projektów wyrosły komercyjne produkty: Oracle, DB2, Informix, Sybase.

Relacyjne DBMS zmieniły architekturę aplikacji. Dane przestały należeć do pojedynczej aplikacji i stały się wspólnym zasobem. Powstała rola administratorów baz danych (DBA), którzy definiowali schemat, indeksy, uprawnienia i strategie backupu, oddzielając te decyzje od kodu aplikacyjnego.

Wpływ modelu relacyjnego na projektowanie systemów

Relacyjny sposób myślenia wpłynął na cały cykl życia systemów informatycznych. Projektowanie zaczęto od modelu logicznego: encje, atrybuty, relacje. Z tego modelu tworzono fizyczny schemat bazy: tabele, klucze, indeksy. Aplikacja korzystała z tego schematu poprzez deklaratywne zapytania, nie znając fizycznego rozmieszczenia danych.

To oddzielenie umożliwiło kilka ważnych praktyk:

  • migracje schematu z mniejszym wpływem na kod,
  • optymalizację zapytań bez zmiany aplikacji (dodawanie indeksów, modyfikacja statystyk),
  • wspólne raportowanie i analitykę dla wielu aplikacji korzystających z tej samej bazy.

Model relacyjny okazał się na tyle elastyczny, że przez dekady stał się domyślnym wyborem przy projektowaniu systemów transakcyjnych i raportowych. Jednak prawdziwą „lingua franca” świecie danych stał się dopiero SQL.

SQL jako lingua franca danych

Od prostego języka zapytań do standardu branżowego

Standaryzacja SQL i różnice dialektów

SQL szybko wyszedł poza mury laboratoriów IBM. Producenci komercyjnych baz danych – Oracle, IBM, Microsoft, Sybase, Informix – przyjęli go jako główny język pracy z danymi. Równolegle rozpoczęto prace nad formalnym standardem.

Pierwsza wersja standardu, SQL-86, a potem SQL-89, były stosunkowo skromne. SQL-92 stał się realnym punktem odniesienia dla rynku: definiował składnię złączeń, podzapytania, ograniczenia integralności, typy danych. Od tego momentu można było w miarę sensownie mówić o „przenośności” zapytań między różnymi systemami.

Żaden poważny silnik nie ograniczał się jednak do czystego standardu. Pojawiały się dialekty: T-SQL w SQL Serverze, PL/SQL w Oracle, własne rozszerzenia w DB2 czy PostgreSQL. Oferowały funkcje okna, procedury składowane, obsługę błędów, typy przestrzenne, złożone typy użytkownika.

Dla zespołów projektowych powstał klasyczny dylemat: korzystać z rozszerzeń konkretnego dostawcy i zyskać wygodę oraz wydajność, czy trzymać się standardu i zachować większą swobodę migracji. W praktyce większość większych systemów dość mocno przywiązuje się do konkretnego dialektu.

SQL jako język zapytań, definicji i sterowania

SQL nie ogranicza się do prostego SELECT. Został podzielony na kilka podjęzyków o różnych zadaniach.

  • DML (Data Manipulation Language) – SELECT, INSERT, UPDATE, DELETE. Służy do czytania i modyfikacji danych.
  • DDL (Data Definition Language) – CREATE, ALTER, DROP. Definiuje tabele, indeksy, widoki, schematy.
  • DCL (Data Control Language) – GRANT, REVOKE. Zarządza uprawnieniami i bezpieczeństwem.
  • TCL (Transaction Control Language) – COMMIT, ROLLBACK, SAVEPOINT. Kontroluje granice transakcji.

Dzięki temu jedna technologia obejmuje cały cykl pracy z danymi: od projektowania struktury, przez obsługę logiki, po bezpieczeństwo. Aplikacja może delegować znaczną część odpowiedzialności na silnik bazy, zamiast implementować wszystko w swoim kodzie.

Silniki bazodanowe dodały do SQL rozszerzenia proceduralne. Procedury składowane, funkcje użytkownika, triggery pozwalają przenieść biznesowe reguły bliżej danych. W systemach finansowych walidacja poprawności przelewu czy naliczanie prowizji często odbywa się właśnie w kodzie SQL, nie w aplikacji.

Optymalizator zapytań i indeksy

Deklaratywna natura SQL wymusiła rozwój zaawansowanych optymalizatorów. Użytkownik określa, jakie dane go interesują, a DBMS wybiera, jak je znaleźć.

Optymalizator analizuje zapytanie, statystyki danych i dostępne indeksy. Na tej podstawie tworzy plan wykonania obejmujący kolejność złączeń, użycie indeksów, strategie sortowania, filtrowania i agregacji. W większości przypadków plan jest niewidoczny dla użytkownika; widać jedynie czas wykonania zapytania.

Podstawą są indeksy. Klasyczne B-tree wspierają wyszukiwanie po kluczach i zakresach, indeksy bitmapowe przydają się w hurtowniach danych, indeksy pełnotekstowe wspomagają wyszukiwanie treści. Niewłaściwie zaprojektowane indeksy potrafią zabić wydajność aktualizacji, a dobrze zaprojektowane – zredukować czas odpowiedzi z minut do milisekund.

W praktyce praca z SQL to często cykl: napisanie zapytania, analiza planu wykonania, dodanie lub zmiana indeksów, ewentualnie lekkie przeformułowanie zapytania. Dojrzałe systemy udostępniają narzędzia typu EXPLAIN, które pozwalają zobaczyć szczegóły planu i zidentyfikować wąskie gardła.

SQL poza klasyczną bazą danych

SQL wyszedł daleko poza relacyjne serwery baz danych. Popularność języka sprawiła, że wiele rozwiązań analitycznych i big data dodało swoje warianty SQL lub warstwy kompatybilne z SQL.

Systemy takie jak Hive, Presto/Trino, Spark SQL czy BigQuery umożliwiły użycie składni podobnej do SQL do analizy danych rozproszonych po dziesiątkach czy setkach węzłów. Pod spodem operują na plikach w HDFS, obiektowych storage’ach czy rozproszonych formatach kolumnowych, ale użytkownik nadal pisze SELECT, JOIN, GROUP BY.

Nawet część baz NoSQL dodała interfejsy w stylu SQL (np. SQL API w Cosmos DB, N1QL w Couchbase), bo znajomość SQL wśród programistów i analityków stała się praktycznym standardem. Warstwa zapytań tłumaczy jednak SQL-owe polecenia na wewnętrzne mechanizmy odpowiadające konkretnemu modelowi danych.

Nowoczesna szafa serwerowa z migającymi diodami w centrum danych
Źródło: Pexels | Autor: panumas nikhomkhai

Transakcje, ACID i dojrzewanie relacyjnych baz danych

Transakcje jako jednostka pracy

Relacyjne DBMS od początku musiały wspierać krytyczne procesy biznesowe: księgowanie, rozliczenia, rezerwacje. Podstawowym pojęciem stała się transakcja – logiczna jednostka pracy, która obejmuje jeden lub wiele kroków na danych.

Klasyczny przykład to przelew między rachunkami. Zmiana salda konta nadawcy i odbiorcy musi być niepodzielna: albo zapiszą się obie, albo żadna. Nie można dopuścić do sytuacji, w której pieniądze „znikają” lub są „tworzone” przez częściowo wykonaną operację.

Transakcja zaczyna się z pierwszą instrukcją zmieniającą dane, a kończy poleceniem COMMIT (zapisz na stałe) lub ROLLBACK (wycofaj). Pomiędzy tymi punktami silnik bazy utrzymuje wewnętrzny stan, blokady i dzienniki, które umożliwiają odzyskanie spójności nawet po awarii.

ACID: cztery filary niezawodności

Aby opisać właściwości transakcji, ukształtował się akronim ACID:

  • Atomicity – atomowość. Transakcja jest niepodzielna: albo wszystkie jej operacje wchodzą w życie, albo żadna.
  • Consistency – spójność. Po zakończeniu transakcji dane spełniają wszystkie zdefiniowane reguły (klucze, ograniczenia, reguły biznesowe).
  • Isolation – izolacja. Równoległe transakcje nie powinny widzieć swoich „niedokończonych” efektów w sposób prowadzący do sprzeczności.
  • Durability – trwałość. Po zatwierdzeniu transakcji jej efekt przetrwa awarię systemu czy restart serwera.

Implementacja ACID opiera się na kilku mechanizmach: dzienniku transakcyjnym (log), blokadach i czasem wersjonowaniu rekordów. Dziennik pozwala odtworzyć lub cofnąć zmiany po awarii. Blokady chronią przed równoczesną modyfikacją tych samych danych w sposób sprzeczny. Wersjonowanie (MVCC) umożliwia równoległy odczyt bez blokowania.

Nowoczesne serwery baz danych łączą te techniki, aby zapewnić rozsądny kompromis między spójnością a wydajnością. Konfiguracja odpowiedniego poziomu izolacji stała się jednym z podstawowych zadań architektów systemów.

Poziomy izolacji i problemy współbieżności

Teoretycznie najwyższy poziom izolacji to serializable – tak jakby transakcje wykonywały się jedna po drugiej. W praktyce jest to kosztowne, więc standard SQL wprowadził kilka poziomów izolacji.

  • Read uncommitted – dopuszcza odczyt niezatwierdzonych danych (tzw. dirty reads). Rzadko używany w systemach produkcyjnych.
  • Read committed – transakcja widzi tylko zatwierdzone dane. Najczęściej stosowany kompromis w systemach OLTP.
  • Repeatable read – zapewnia niezmienność odczytanych wierszy w trakcie transakcji, choć nie zawsze chroni przed „phantom reads”.
  • Serializable – najsilniejsza izolacja, implementowana zwykle blokadami zakresów lub wersjonowaniem plus sprawdzanie konfliktów.

Każdy niższy poziom izolacji dopuszcza pewne anomalie: brudne odczyty, niepowtarzalne odczyty, „fantomy” (nowe pasujące wiersze pojawiające się w trakcie transakcji). Wymagania biznesowe decydują, które z nich są akceptowalne.

Silniki takie jak PostgreSQL, Oracle czy wiele systemów analitycznych używa MVCC (Multi-Version Concurrency Control). Zamiast blokować czytelników, tworzą wersje rekordów i pozwalają długim zapytaniom widzieć „migawkę” danych z chwili startu transakcji. To dobrze działa przy dużej liczbie odczytów równoległych do umiarkowanej liczby zapisów.

Od redo/undo logów do replikacji i wysokiej dostępności

Dojrzewanie relacyjnych baz danych to również ewolucja mechanizmów odporności na awarie. Klasyczny dziennik transakcyjny (redo/undo) pozwala przywrócić spójny stan po awarii pojedynczego serwera. Wraz z rosnącymi wymaganiami zaczęto budować bardziej złożone scenariusze.

Pojawiły się replikacja i klastry wysokiej dostępności. Dane z głównego serwera (primary) są na bieżąco kopiowane na serwery zapasowe (standby). W razie awarii możliwe jest przełączenie ruchu na węzeł zapasowy z minimalną utratą danych lub bez niej, zależnie od trybu replikacji (synchroniczna vs asynchroniczna).

Replikacja otworzyła również drogę do skalowania odczytów: serwery podrzędne (read replicas) mogą obsługiwać zapytania analityczne czy raportowe, odciążając główną instancję. W systemach o dużym ruchu czy zasięgu globalnym łączy się to z rozproszonymi centrami danych i balansowaniem ruchu.

Od mainframe do masowych serwerów: bazy danych w erze klient–serwer i WWW

Mainframe: scentralizowana moc obliczeniowa

Początkowo relacyjne bazy danych działały głównie na dużych mainframe’ach. Dostęp odbywał się przez terminale tekstowe, aplikacje pisane w COBOL-u, C czy PL/I, a sam serwer bazy był centralnym punktem przetwarzania.

Taki model pasował do potrzeb dużych instytucji: banków, ubezpieczycieli, administracji publicznej. Wszystko było pod kontrolą działu IT: sprzęt, system operacyjny, DBMS, aplikacje. Skalowanie oznaczało kupno większego mainframe’a, a nie dobudowę nowych serwerów.

Dominowały transakcyjne systemy wsadowe i on-line (CICS, IMS/DC). Integracja między systemami przebiegała przez kolejki, pliki lub dedykowane interfejsy. Sieci były drogie, więc zakładano niewielką liczbę dobrze kontrolowanych punktów dostępu.

Architektura klient–serwer: logika bliżej użytkownika

Rewolucja PC i lokalnych sieci wprowadziła model klient–serwer. Baza danych pozostawała na serwerze, ale część logiki biznesowej i interfejs użytkownika przeniosła się na komputery osobiste.

Typową konfiguracją był serwer z Oracle, Sybase czy SQL Serverem oraz aplikacje klienckie pisane w narzędziach typu PowerBuilder, Delphi, Visual Basic. Każde stanowisko użytkownika zestawiało bezpośrednie połączenie z bazą i wysyłało zapytania SQL.

Dawało to elastyczność i szybki rozwój aplikacji, ale wprowadzało nowe problemy. Silnik bazy był obciążony nie tylko logiką danych, lecz także złymi zapytaniami generowanymi przez klientów. Każdy klient utrzymywał osobne połączenie, co ograniczało skalę systemu.

Pojawiła się praktyka „grubego klienta” i „chudego klienta”. Gruby klient zawierał znaczną część logiki w aplikacji desktopowej i silnie wiązał się z konkretną wersją bazy. Chudy klient delegował więcej pracy na serwer, często wykorzystując procedury składowane i centralny kod SQL.

WWW i trójwarstwowe aplikacje

Upowszechnienie internetu i przeglądarek doprowadziło do kolejnego przesunięcia. Zamiast aplikacji desktopowych pojawiły się aplikacje webowe. Architektura stała się trójwarstwowa: przeglądarka – serwer aplikacyjny – serwer bazy danych.

Przeglądarka odpowiadała za interfejs użytkownika (HTML, CSS, JavaScript). Serwer aplikacyjny (PHP, Java, .NET, Python, Ruby) obsługiwał logikę biznesową i komunikację z bazą. Baza danych zajmowała się przechowywaniem i egzekwowaniem integralności.

Bezpośrednie połączenia wielu klientów z bazą zostały zastąpione ograniczoną pulą połączeń zarządzaną przez serwer aplikacyjny. To znacznie poprawiło skalowalność i kontrolę nad obciążeniem. Wzrosła też rola wzorców projektowych: ORM-y (Hibernate, Entity Framework, Active Record) przejęły mapowanie obiektów na tabele.

W tym modelu SQL stał się „wewnętrznym” językiem między warstwą aplikacyjną a bazą. Wielu programistów przestało pisać czysty SQL na co dzień, polegając na ORM-ach i generatorach zapytań. Jednocześnie rola doświadczonych specjalistów od SQL i optymalizacji zapytań pozostała kluczowa przy projektowaniu krytycznych fragmentów systemu.

Skalowanie w poziomie i początki rozproszenia

Ruch w internecie szybko pokazał ograniczenia pojedynczego serwera bazy danych. Dla prostych aplikacji wystarczał mocniejszy sprzęt, ale przy milionach użytkowników trzeba było szukać innych dróg.

Najprostszym krokiem było pionowe skalowanie (więcej CPU, RAM, szybsze dyski) i replikacja read-only dla odciążenia odczytów. Kolejny etap to sharding – dzielenie danych na partycje rozrzucone po wielu serwerach, zwykle według zakresu kluczy (np. ID użytkownika, region).

Sharding aplikacyjny vs. bazodanowy

Pierwsze podejścia do shardingu były realizowane głównie w warstwie aplikacji. Kod sam wybierał odpowiedni serwer na podstawie klucza (np. ID klienta). Każdy shard był niezależną bazą, często z własnym schematem i procesem wdrożeń.

Takie rozwiązanie dawało swobodę, ale komplikowało rozwój. Migracja danych między shardami, globalne raporty czy zmiany w schemacie wymagały złożonych narzędzi i procedur. Błędy w logice routingu potrafiły prowadzić do niespójności.

Kolejnym krokiem były systemy z automatycznym partycjonowaniem na poziomie DBMS lub proxy. Przykładem są rozszerzenia do MySQL czy PostgreSQL oraz dedykowane warstwy pośrednie, które ukrywają przed aplikacją fizyczny podział danych. To upraszcza kod, ale zwykle wprowadza pewne ograniczenia w zakresie transakcji między shardami czy globalnych kluczy obcych.

Cache, CDN i odciążanie bazy danych

Przy dużym ruchu sensowne staje się zdejmowanie z bazy wszystkiego, co nie musi być trwale przechowywane lub spójne w czasie rzeczywistym. Stąd popularność cache’owania na różnych poziomach.

Podstawą są pamięci podręczne w aplikacji oraz rozproszone cache typu Redis czy Memcached. Przechowują wyniki często wykonywanych zapytań, sesje użytkowników, dane konfiguracyjne. Klasyczny wzorzec to cache-aside: aplikacja najpierw pyta cache, a dopiero potem bazę.

Drugim filarem są systemy CDN i cache’owanie na brzegu sieci. Dotyczy to głównie treści statycznych, ale w praktyce odciąża także bazę: mniej zapytań o te same zasoby, mniej odwołań do metadanych i uprawnień.

Im lepiej zaprojektowane cache’owanie, tym prostsze może być samo zaplecze bazodanowe. Problemem staje się jednak spójność: dane mogą być „stare” przez kilka sekund lub minut, co nie dla każdego systemu jest akceptowalne.

Bazy danych w mikroserwisach

Rozbicie monolitu aplikacyjnego na mikroserwisy pociągnęło za sobą rozproszenie danych. Zamiast jednego centralnego schematu pojawiło się wiele mniejszych baz, zwykle powiązanych z poszczególnymi domenami biznesowymi.

Każdy mikroserwis zarządza „swoimi” danymi i exposesuje je przez API. Transakcje rozproszone (2PC) są stosowane rzadko, ze względu na złożoność i wpływ na dostępność. Zamiast tego używa się komunikacji asynchronicznej i wzorca eventual consistency.

Przykładowo system zamówień może zapisać nowe zlecenie w swojej bazie, a następnie wysłać zdarzenie do systemu płatności i logistyki. Te systemy aktualizują własne bazy i dopiero z ich perspektywy zamówienie staje się w pełni „aktywne”. Przez krótki czas widok danych w różnych usługach może się różnić.

Taki podział zmniejsza centralne wąskie gardła, ale przenosi ciężar na spójność danych i obsługę błędów. Pojawia się także problem raportowania przekrojowego: dane trzeba agregować do wspólnej hurtowni lub lake’a.

NoSQL i nowe paradygmaty przechowywania danych

Od relacji do dokumentów, kluczy i kolumn

Wraz z rozwojem aplikacji internetowych i mobilnych pojawiły się scenariusze, dla których relacyjne bazy danych okazywały się zbyt sztywne lub zbyt drogie w skalowaniu. Wtedy zaczęła się popularyzacja rodziny rozwiązań określanych zbiorczo jako NoSQL.

Pod tym hasłem kryje się kilka klas systemów:

  • Key-value – proste mapy klucz–wartość (np. Redis, Riak), dobre do sesji, cache, liczników.
  • Dokumentowe – przechowują półstrukturalne dokumenty (JSON, BSON) jak MongoDB czy CouchDB.
  • Kolumnowe (wide-column) – optymalizowane pod zapis/odczyt dużych wolumenów danych, np. Cassandra, HBase.
  • Grafowe – koncentrują się na relacjach między węzłami, jak Neo4j czy JanusGraph.

Wspólnym motywem wielu systemów NoSQL jest rezygnacja z części klasycznych gwarancji ACID na rzecz dostępności i skalowalności. Dane często są replikowane między wieloma węzłami, a zapisy mogą być propagowane asynchronicznie.

CAP, BASE i „eventual consistency”

Przy projektowaniu rozproszonych baz danych kluczowe stało się praktyczne rozumienie twierdzenia CAP. W dużym uproszczeniu system w obliczu podziału sieci (partition) musi wybrać między silną spójnością (consistency) a dostępnością (availability).

Wiele baz NoSQL wybiera model BASE (Basically Available, Soft state, Eventually consistent). Dane mogą być przez chwilę niespójne między replikami, ale system pozostaje dostępny, a spójność jest odzyskiwana z czasem.

Dla niektórych zastosowań, jak lajki, logi czy dane telemetryczne, takie podejście jest akceptowalne. Dla przelewów bankowych już nie. W praktyce architekci łączą różne systemy: krytyczne dane trzymają w klasycznej bazie transakcyjnej, a mniej wrażliwe w rozsianych magazynach NoSQL.

Bazy dokumentowe i elastyczne schematy

Bazy dokumentowe zyskały popularność dzięki dopasowaniu do sposobu, w jaki buduje się wiele aplikacji webowych. Zamiast mapować obiekty na tabele, można zapisać strukturę JSON w jednym dokumencie.

Taki dokument może zawierać zarówno podstawowe pola, jak i zagnieżdżone kolekcje. Zmiana „schematu” to często po prostu dodanie nowego pola. Nie wymaga migracji całej tabeli ani blokujących operacji DDL.

MongoDB czy podobne silniki oferują indeksy na polach wewnątrz dokumentów, zapytania z filtrami i agregacją, a nawet częściowe transakcje. Przy odpowiednim modelowaniu danych można znacznie uprościć odczyty – szczególnie dla typowych widoków ekranów aplikacji.

Minusem jest trudniejsza normalizacja i brak naturalnego wsparcia dla relacji wielu-do-wielu. Dane bywają duplikowane między dokumentami, co przy skomplikowanych regułach biznesowych zwiększa koszty utrzymania spójności.

Kolumnowe magazyny dla danych masowych

Systemy typu Cassandra czy HBase powstały z myślą o bardzo dużej skali – miliardach wierszy i zapisach rozproszonych geograficznie. Dane są dzielone na partycje według klucza i rozrzucane po węzłach w klastrze.

Model danych opiera się na rodzinach kolumn, co umożliwia przechowywanie bardzo rzadkich danych (wiele możliwych kolumn, niewiele wypełnionych). To sprawdza się przy danych zdarzeniowych, sensorach, logach czy telemetrii.

Takie bazy zapewniają wysoką przepustowość zapisu, często kosztem ograniczeń w zakresie zapytań ad-hoc. Kluczem jest projektowanie modelu pod konkretne ścieżki odczytu: jakich zapytań aplikacja będzie realnie potrzebowała.

Grafy i eksploracja powiązań

Bazy grafowe rozwiązują problem, z którym relacyjne bazy radzą sobie słabo: eksplorację głębokich sieci powiązań. Chodzi o przypadki typu sieci społecznościowe, rekomendacje, zależności techniczne czy uprawnienia.

Zamiast tabel i kluczy obcych przechowuje się wierzchołki i krawędzie. Zapytania wyrażają ścieżki i wzorce w grafie (np. „znajomi znajomych do trzeciego poziomu, którzy kupili produkt X”).

W relacyjnej bazie takie zapytania często prowadzą do złożonych JOIN-ów i słabej wydajności przy dużej głębokości. W bazie grafowej logika przechodzenia po krawędziach jest wbudowana w silnik i zoptymalizowana.

Nowoczesne SQL: NewSQL, chmura i bazy „as-a-service”

NewSQL: relacje w świecie rozproszonym

Po fali entuzjazmu dla NoSQL pojawiła się potrzeba połączenia zalet klasycznych relacyjnych baz danych z poziomą skalowalnością. Tak narodziła się kategoria rozwiązań NewSQL.

Systemy takie jak Google Spanner, CockroachDB czy YugabyteDB próbują zaoferować transakcje ACID w klastrach rozproszonych geograficznie. Używają rozproszonych protokołów konsensusu (Raft, Paxos) oraz precyzyjnej synchronizacji czasu (np. TrueTime w Spannerze).

Z punktu widzenia programisty interfejs przypomina klasyczny SQL. Różnią się jednak koszty operacji: transakcja obejmująca wiele partycji jest znacznie droższa niż ta mieszcząca się w jednym węźle. Projektowanie schematu wymaga więc świadomości fizycznego rozmieszczenia danych.

Chmurowe usługi bazodanowe

Największą zmianą ostatnich lat jest przeniesienie baz danych do chmury publicznej. Zamiast instalować i utrzymywać własne serwery, zespoły korzystają z usług zarządzanych: Amazon RDS, Azure SQL Database, Cloud SQL, Aurora i wielu innych.

Podstawowa zaleta to outsourcing części obowiązków: kopii zapasowych, aktualizacji, monitoringu, automatycznego przełączania na węzły zapasowe. Skalowanie pionowe staje się kwestią zmiany klasy instancji, a nie zakupu nowego sprzętu.

Cloud providerzy oferują także bazy natywnie rozproszone, jak Aurora, Spanner czy Cosmos DB. Część z nich pozwala na replikację między regionami z niewielkim opóźnieniem i definiowanie kierunków zapisu/odczytu per region.

Zmienia się też model kosztów. Zamiast inwestycji w sprzęt dochodzi rozliczanie za godziny instancji, ilość zapisanych danych, liczbę operacji I/O. Dobrze zaprojektowany schemat i zapytania mają bezpośredni wpływ na rachunek w chmurze.

Serverless i bazy „na żądanie”

Kolejnym krokiem są rozwiązania serverless, w których nie zarządza się nawet instancjami. Przykłady to Aurora Serverless, Cloud Firestore, DynamoDB czy inne bazy oparte na modelu płatności „pay-per-request”.

Silnik sam skaluje się w górę i w dół zależnie od ruchu. Aplikacja nie musi utrzymywać stałych połączeń ani martwić się o rozkład obciążenia w czasie. Dla zmiennych i nieprzewidywalnych obciążeń to wygodny model.

Istnieją jednak ograniczenia: limity throughputu, specyficzne modele zapytań, brak pełnego SQL lub transakcji wielotabelowych. Projekt aplikacji trzeba dostosować do charakteru konkretnej usługi.

Systemy analityczne: kolumnowe i MPP

Równolegle rozwijała się osobna gałąź baz danych nastawiona na analitykę. Klasyczne relacyjne silniki OLTP słabo radzą sobie z ciężkimi zapytaniami agregującymi miliardy rekordów. Stąd wzrost popularności kolumnowych silników analitycznych i architektur MPP (Massively Parallel Processing).

Przykładami są Amazon Redshift, Snowflake, Google BigQuery, Vertica oraz kolumnowe rozszerzenia do PostgreSQL czy SQL Servera. Dane są przechowywane kolumnami, co pozwala efektywnie kompresować i czytać tylko te kolumny, które są potrzebne w zapytaniu.

Silniki MPP rozdzielają przetwarzanie między wiele węzłów. Zapytanie jest dzielone na fragmenty, a każdy węzeł przetwarza swoją część danych. Dzięki temu czas odpowiedzi na skomplikowane raporty skraca się z godzin do minut lub sekund.

Typowym podejściem jest oddzielenie bazy transakcyjnej (OLTP) od hurtowni danych (OLAP). Dane są cyklicznie replikowane lub strumieniowane z systemów źródłowych do magazynu analitycznego, a na nim działają narzędzia BI i raportujące.

Ewolucja roli danych i specjalistów bazodanowych

Od administratora do inżyniera danych

Wraz ze zmianą technologii zmieniły się także role osób odpowiedzialnych za dane. Klasyczny administrator baz danych (DBA) zajmował się głównie instalacją, backupami, indeksami i optymalizacją zapytań w jednym lub kilku centralnych systemach.

Dzisiaj praca z danymi jest rozbita między kilka specjalizacji: inżynierów danych, architektów, specjalistów od platform danych, devopsów bazodanowych. Coraz więcej obowiązków jest automatyzowanych przez narzędzia chmurowe.

Jednocześnie rośnie znaczenie umiejętności projektowania modeli danych i rozumienia przepływów między systemami. Znajomość SQL, zasad normalizacji, indeksowania i podstaw ACID nadal pozostaje fundamentem, nawet jeśli na co dzień pracuje się z NoSQL czy narzędziami ETL.

Dane jako produkt, schemat jako kontrakt

W dużych organizacjach dane zaczynają być traktowane jako osobny produkt. Zespoły odpowiedzialne za konkretne domeny udostępniają swoje dane innym w ustrukturyzowanej formie, z jasno zdefiniowanym schematem i SLA.

Schemat (czy to w relacyjnej bazie, czy w katalogu schematów dla strumieni) staje się kontraktem między producentem a konsumentem danych. Zmiany wymagają wersjonowania i koordynacji, by nie złamać zależnych procesów analitycznych czy integracji.

Ten sposób myślenia przenosi ciężar z „gdzie leżą dane” na „jakie dane udostępniamy i jak inni mogą z nich korzystać”. Bazy danych są jednym z elementów układanki, obok kolejek, strumieni, lake’ów i narzędzi analitycznych.

Najczęściej zadawane pytania (FAQ)

Na czym polegała różnica między kartami perforowanymi a taśmami magnetycznymi?

Karty perforowane były pojedynczymi fizycznymi nośnikami, gdzie każdy rekord miał własną kartę, a zmiana danych oznaczała fizyczne zastąpienie karty nową. Maszyny sortujące i liczące mogły je mechanicznie przetwarzać, ale dostęp był ściśle sekwencyjny i mało elastyczny.

Taśmy magnetyczne pozwalały przechowywać znacznie większe wolumeny danych i przetwarzać je szybciej, już bez stałej obsługi operatora. Nadal jednak dominował dostęp sekwencyjny: aby odczytać konkretny rekord, system musiał „przewinąć” wszystkie wcześniejsze.

Czym różnią się plikowe systemy danych od baz danych?

W systemach plikowych struktura danych była zaszyta w kodzie programu. Każda aplikacja „wiedziała”, jak interpretować bajty w pliku i była mocno przywiązana do konkretnego formatu rekordu.

Baza danych (DBMS) przeniosła tę wiedzę do wyspecjalizowanego systemu: schemat, typy pól, relacje i reguły spójności są definiowane centralnie i współdzielone przez wiele aplikacji. Dzięki temu można zmieniać logikę aplikacji bez ciągłego przebudowywania formatu plików.

Dlaczego model „każdy program ma swoje pliki” stał się problemem?

Taki model prowadził do duplikacji danych i kodu. Te same informacje o kliencie czy produkcie istniały w wielu plikach, a każdy program miał własne funkcje do wyszukiwania i aktualizacji.

Zmiana formatu jednego pliku wymuszała modyfikacje i testy wszystkich aplikacji, które go używały. Utrzymanie spójności między różnymi „wersjami” tych samych danych było w praktyce bardzo trudne, co blokowało rozwój systemów.

Co to jest hierarchiczna baza danych i kiedy była używana?

Hierarchiczne bazy danych, jak IBM IMS, organizowały dane w strukturę drzewa: segment nadrzędny (np. klient) miał dzieci (np. konta), a te z kolei swoje dzieci (np. transakcje). Dostęp odbywał się nawigacyjnie: przechodzenie od rodzica do dzieci według z góry zdefiniowanych ścieżek.

Model ten był szeroko używany w latach 60. i 70. w bankowości, logistyce czy systemach zamówień, gdzie procesy biznesowe naturalnie układały się w hierarchie. Świetnie sprawdzał się przy typowych operacjach, ale był sztywny przy nietypowych przekrojach danych, np. raportach „po dacie” dla wszystkich transakcji.

Na czym polegał sieciowy model baz danych CODASYL?

Sieciowy model danych (CODASYL) rozszerzał hierarchię, pozwalając rekordom należeć do wielu „zestawów” powiązań. Zamiast jednego nadrzędnego rodzica, rekord mógł być powiązany wskaźnikami z wieloma innymi rekordami, tworząc gęstą sieć relacji.

Programista musiał dokładnie znać strukturę tych powiązań i „chodzić po wskaźnikach”, co dawało wysoką wydajność przy dobrze zaprojektowanych ścieżkach dostępu. Ceną była silna zależność kodu od fizycznego układu danych, co utrudniało modyfikacje i rozwój systemu.

Dlaczego doświadczenia z kartami, taśmami i plikami doprowadziły do powstania DBMS?

Praktyka pokazała, że ręczne zarządzanie strukturą danych w aplikacjach prowadzi do chaosu: sprzecznych formatów, duplikacji, trudnych migracji i kłopotów z raportowaniem. Wraz ze wzrostem skali przetwarzania na mainframe’ach problemy te zaczęły dominować nad korzyściami.

Stąd potrzeba wyspecjalizowanych systemów zarządzania bazami danych, które przejmują odpowiedzialność za schemat, integralność i współdzielenie danych. Umożliwiło to wspólny widok informacji dla wielu aplikacji i znacznie łatwiejsze budowanie nowych funkcjonalności na istniejących danych.