Grunderna i databasdesign – en jĂ€mförelse mellan PostgreSQL, Cassandra och MongoDB

Hej vÀnner. Innan vi gÄr in i den andra delen av majhelgerna delar vi med oss av material som vi har översatt inför lanseringen av den nya kursen. Relational DBMS.

Grunderna i databasdesign – en jĂ€mförelse mellan PostgreSQL, Cassandra och MongoDB

Apputvecklare lÀgger ner mycket tid pÄ att jÀmföra flera olika operativa databaser för att vÀlja den som passar bÀst för den planerade arbetsbelastningen. Behoven kan inkludera förenklad datamodellering, transaktionsgarantier, lÀs-/skrivprestanda, horisontell skalbarhet och fel tolerans. Traditionellt börjar valet med kategorin databas, SQL eller NoSQL, eftersom varje kategori erbjuder en tydlig uppsÀttning kompromisser. Hög prestanda nÀr det gÀller lÄg latens och hög genomströmning betraktas vanligtvis som ett krav utan kompromisser och Àr dÀrför nödvÀndigt för varje databas i urvalet.

Syftet med denna artikel Ă€r att hjĂ€lpa apputvecklare att göra rĂ€tt val mellan SQL och NoSQL i kontexten av applikationsdatamodellering. Vi kommer att titta pĂ„ en SQL databas, nĂ€mligen PostgreSQL, och tvĂ„ NoSQL databaser – Cassandra och MongoDB, för att berĂ€tta om grunderna i databasdesign, sĂ„som att skapa tabeller, fylla dem, lĂ€sa data frĂ„n tabeller och ta bort data. I nĂ€sta artikel kommer vi att titta pĂ„ index, transaktioner, JOINs, TTL-direktiv och databasdesign baserat pĂ„ JSON.

Vad Àr skillnaden mellan SQL och NoSQL?

SQL-databaser ökar applikationens flexibilitet genom ACID-transaktionsgarantier och deras förmÄga att hÀmta data med JOIN pÄ ovÀntade sÀtt ovanpÄ befintliga normaliserade modeller av relationsdatabaser.

Med tanke pÄ deras monolitiska/en-nodstruktur och anvÀndning av master-slave-replikationsmodellen för redundans har traditionella SQL-databaser inte tvÄ viktiga egenskaper - linjÀr skrivskalerbarhet (d.v.s. automatisk splittring pÄ flera noder) och automatisk/ingen datarförlust. Det betyder att mÀngden mottagna data inte kan överskrida den maximala skrivgenomströmningen för en nod. Dessutom mÄste viss tillfÀllig datarförlust beaktas vid felhantering (i arkitekturer utan resursdelning). HÀr mÄste vi komma ihÄg att nyliga commits Ànnu inte har Äterspeglats i den underordnade (slave) kopian. Uppdateringar utan driftstopp Àr ocksÄ svÄra att uppnÄ i SQL-databaser.

NoSQL-databaser Àr i sin natur vanligtvis distribuerade, vilket innebÀr att data delas upp i sektioner och sprids över flera noder. De krÀver denormalisering. Det innebÀr att inskickade data ocksÄ mÄste kopieras flera gÄnger för att svara pÄ specifika förfrÄgningar som du skickar. Det övergripande mÄlet Àr att uppnÄ hög prestanda genom att minska antalet shards som Àr tillgÀngliga under lÀsning. DÀrför Àr det viktigt att pÄpeka att NoSQL krÀver att du modellerar dina förfrÄgningar, medan SQL krÀver att du modellerar dina data.

NoSQL fokuserar pÄ att uppnÄ hög prestanda i en distribuerad kluster och det Àr den grundlÀggande motiveringen för mÄnga designkompromisser i databaser, som inkluderar förlust av ACID-transaktionsgarantier, JOIN:ar och konsekventa globala sekundÀra index.

Det finns en uppfattning att Àven om NoSQL-databaser erbjuder linjÀr skrivskalerbarhet och hög felhantering, gör förlusten av transaktionsgarantier dem olÀmpliga för kritiska data.

Följande tabell visar hur datamodellering i NoSQL skiljer sig frÄn SQL.

Grunderna i databasdesign – en jĂ€mförelse mellan PostgreSQL, Cassandra och MongoDB

SQL och NoSQL: Varför behöver vi bÄda?

I verkliga applikationer med ett stort antal anvÀndare, som Amazon.com, Netflix, Uber och Airbnb, faller genomförandet av komplexa och olika uppgifter. Till exempel behöver en e-handelsapplikation liknande Amazon.com lagra lÀtta, högkÀnsliga uppgifter, sÄsom anvÀndarinformation, produktinformation, bestÀllningar, fakturor, tillsammans med tyngre, men mindre kÀnsliga uppgifter, som produktrecensioner, kundsupportmeddelanden, anvÀndaraktivitet, feedback och rekommendationer. Naturligtvis litar dessa applikationer pÄ Ätminstone en SQL-databas tillsammans med minst en NoSQL-databas. I interregionala och globala system fungerar NoSQL-databasen som en geografiskt distribuerad cache för data som lagras i en betrodd kÀlla, SQL-databasen som körs i en viss region.

Hur förenar YugaByte DB SQL och NoSQL?

Byggd pÄ en loggorienterad blandmotor för lagring, automatisk sharding, sharddistribution och ACID-distribuerad Consensus-replikerings- och distribuerade transaktioner (inspirerade av Google Spanner), Àr YugaByte DB den första open-source databasen i vÀrlden som Àr samtidigt kompatibel med NoSQL (Cassandra & Redis) och SQL (PostgreSQL). Som det visas i tabellen nedan, lÀgger YCQL, YugaByte DB:s Cassandra-kompatibla API, till begrepp av en- och flernyckel ACID-transaktioner och globala sekundÀra index i NoSQL API:t, vilket öppnar en era av transaktionella NoSQL-databaser. Dessutom lÀgger YCQL, YugaByte DB:s PostgreSQL-kompatibla API, till begrepp för linjÀr skrivskaling och automatisk felsÀkerhet till SQL API:t, och presenterar för vÀrlden distribuerade SQL-databaser. Eftersom YugaByte DB i grunden Àr transaktionell, kan NoSQL-API:t nu anvÀndas i kontexten av kritiskt viktiga data.

Grunderna i databasdesign – en jĂ€mförelse mellan PostgreSQL, Cassandra och MongoDB

Som nÀmnts tidigare i artikeln «Introducing YSQL: A PostgreSQL Compatible Distributed SQL API for YugaByte DB», valet mellan SQL eller NoSQL i YugaByte DB beror helt pÄ egenskaperna hos den underliggande arbetsbelastningen:

  • Om den underliggande arbetsbelastningen bestĂ„r av flernyckeloperationer med JOIN:ar, vĂ€lj YSQL och förstĂ„ att dina nycklar kan vara spridda över flera noder, vilket kommer att resultera i högre latens och/eller minskad genomströmning Ă€n i NoSQL.
  • Annars kan du vĂ€lja nĂ„got av de tvĂ„ NoSQL-API:erna, med vetskap om att du kommer att fĂ„ högre prestanda som ett resultat av förfrĂ„gningar som hanteras av en nod Ă„t gĂ„ngen. YugaByte DB kan fungera som en enda operativ databas för verkligt komplexa applikationer dĂ€r det behövs hantering av flera arbetsbelastningar samtidigt.

I den kommande sektionen av datamodelleringslaboratoriet baseras pÄ YugaByte DB:s API:er som Àr kompatibla med PostgreSQL och Cassandra, till skillnad frÄn de ursprungliga databaserna. Denna metod betonar enkelheten i att interagera med tvÄ olika API:er (pÄ tvÄ olika portar) av samma databas som motsats till att anvÀnda helt oberoende kluster av tvÄ olika databaser.
I de kommande sektionerna kommer vi att utforska datamodelleringslaboratoriet för att illustrera skillnaderna och vissa likheter mellan de granskade databaserna.

Datamodelleringslaboratorium

Installation av databaser

Med fokus pÄ datamodellens design (och inte komplexa distributionsarkitekturer) kommer vi att installera databaserna i Docker-containrar pÄ den lokala datorn och sedan interagera med dem via deras respektive kommandoradsgrÀnssnitt.

YugaByte DB, kompatibel med PostgreSQL & Cassandra

mkdir ~/yugabyte && cd ~/yugabyte
wget https://downloads.yugabyte.com/yb-docker-ctl && chmod +x yb-docker-ctl
docker pull yugabytedb/yugabyte
./yb-docker-ctl create --enable_postgres

MongoDB

docker run --name my-mongo -d mongo:latest

Åtkomst via kommandoraden

LÄt oss ansluta till databaserna med hjÀlp av kommandoradsgrÀnssnittet för de respektive API:erna.

PostgreSQL

psql — detta Ă€r kommandoradsgrĂ€nssnittet för att interagera med PostgreSQL. För enkelhetens skull levereras YugaByte DB med psql direkt i bin-mappen.

docker exec -it yb-postgres-n1 /home/yugabyte/postgres/bin/psql -p 5433 -U postgres

Cassandra

cqlsh — detta Ă€r kommandoradsgrĂ€nssnittet för att interagera med Cassandra och dess kompatibla databaser via CQL (Cassandra Query Language). För anvĂ€ndarvĂ€nlighet gör YugaByte DB det klart att cqlsh i katalogen bin.
Observera att CQL Àr inspirerat av SQL och har liknande begrepp som tabeller, rader, kolumner och index. Men som ett NoSQL-sprÄk lÀgger det till en viss uppsÀttning begrÀnsningar, de flesta av vilka vi ocksÄ kommer att granska i andra artiklar.

docker exec -it yb-tserver-n1 /home/yugabyte/bin/cqlsh

MongoDB

mongo – Ă€r ett kommandoradsgrĂ€nssnitt för att interagera med MongoDB. Det kan hittas i bin-katalogen för MongoDB-installationen.

docker exec -it my-mongo bash 
cd bin
mongo

Skapa en tabell

Nu kan vi interagera med databasen för att utföra olika operationer via kommandoraden. LĂ„t oss börja med att skapa en tabell som lagrar information om lĂ„tar skrivna av olika artister. Dessa lĂ„tar kan vara en del av ett album. Även valfria attribut för lĂ„ten – utgivningsĂ„r, pris, genre och betyg. Vi behöver ta hĂ€nsyn till ytterligare attribut som kan behövas i framtiden genom fĂ€ltet "taggar". Det kan lagra semi-strukturerade data i form av nyckel-vĂ€rde-par.

PostgreSQL

CREATE TABLE Music (
    Artist VARCHAR(20) NOT NULL, 
    SongTitle VARCHAR(30) NOT NULL,
    AlbumTitle VARCHAR(25),
    Year INT,
    Price FLOAT,
    Genre VARCHAR(10),
    CriticRating FLOAT,
    Tags TEXT,
    PRIMARY KEY(Artist, SongTitle)
);	

Cassandra

Att skapa en tabell i Cassandra Àr mycket likt PostgreSQL. En av de stora skillnaderna Àr avsaknaden av integritetsbegrÀnsningar (t.ex. NOT NULL), men detta faller inom applikationens ansvar, inte NoSQL-databasen.. Den primÀra nyckeln bestÄr av en partition key (kolumnen Artist i exemplet nedan) och ett set av clustering columns (kolumnen SongTitle i det nedan givna exemplet). Partition key anger vilken partition/shard som en rad ska placeras i, medan clustering columns anger hur datan ska organiseras inom den aktuella sharden.

CREATE KEYSPACE myapp;
USE myapp;
CREATE TABLE Music (
    Artist TEXT, 
    SongTitle TEXT,
    AlbumTitle TEXT,
    Year INT,
    Price FLOAT,
    Genre TEXT,
    CriticRating FLOAT,
    Tags TEXT,
    PRIMARY KEY(Artist, SongTitle)
);

MongoDB

MongoDB organiserar data i databaser (Database) (motsvarande Keyspace i Cassandra), dĂ€r det finns samlingar (Collections) (motsvarande tabeller), i vilka dokument (Documents) (motsvarande rader i en tabell) finns. I MongoDB krĂ€vs principiellt ingen initial definitionsschema. Kommandot "use database", som visas nedan, skapar en instans av databasen vid första anropet och Ă€ndrar kontexten till den nyss skapade databasen. Även samlingar behöver inte skapas explicit, de skapas automatiskt nĂ€r den första dokumentet lĂ€ggs till i en ny samling. Observera att MongoDB som standard anvĂ€nder en testdatabas, sĂ„ alla operationer pĂ„ samlingsnivĂ„ utan att ange en specifik databas kommer att utföras dĂ€r som standard.

use myNewDatabase;

HĂ€mta information om tabellen
PostgreSQL

d Musik
Tabell "public.music"
    Kolumn    |         Typ          | Sortering | Nullable | Standard 
--------------+-----------------------+-----------+----------+--------
 artist       | tecken varierande(20) |           | ej null | 
 songtitle    | tecken varierande(30) |           | ej null | 
 albumtitle   | tecken varierande(25) |           |          | 
 Är           | heltal               |           |          | 
 pris         | dubbel precision      |           |          | 
 genre        | tecken varierande(10) |           |          | 
 kritikerbetyg | dubbel precision      |           |          | 
 tags         | text                  |           |          | 
Index:
    "music_pkey" PRIMÄR NYCKEL, btree (artist, songtitle)

Cassandra

BESKRIV TABELL MUSIC;
SKAPA TABELL myapp.music (
    artist text,
    songtitle text,
    albumtitle text,
    Är int,
    pris float,
    genre text,
    tags text,
    PRIMÄR NYCKEL (artist, songtitle)
) MED KLUSTERINGSORDNING EFTER (songtitle ASC)
    OCH default_time_to_live = 0
    OCH transaktioner = {'enabled': 'false'};

MongoDB

anvÀnd myNewDatabase;
visa kollektioner;

Införande av data i tabellen
PostgreSQL

INFÖR i Musik 
    (Artist, LÄt, Album, 
    År, Pris, Genre, Kritikerbetyg, 
    Taggar)
VALUES(
    'No One You Know', 'Call Me Today', 'Somewhat Famous',
    2015, 2.14, 'Country', 7.8,
    '{"Composers": ["Smith", "Jones", "Davis"],"LengthInSeconds": 214}'
);
INFÖR i Musik 
    (Artist, LÄt, Album, 
    Pris, Genre, Kritikerbetyg)
VALUES(
    'No One You Know', 'My Dog Spot', 'Hey Now',
    1.98, 'Country', 8.4
);
INFÖR i Musik 
    (Artist, LÄt, Album, 
    Pris, Genre)
VALUES(
    'The Acme Band', 'Look Out, World', 'The Buck Starts Here',
    0.99, 'Rock'
);
INFÖR i Musik 
    (Artist, LÄt, Album, 
    Pris, Genre, 
    Taggar)
VALUES(
    'The Acme Band', 'Still In Love', 'The Buck Starts Here',
    2.47, 'Rock', 
    '{"radioStationsPlaying": ["KHCR", "KBQX", "WTNR", "WJJH"], "tourDates": { "Seattle": "20150625", "Cleveland": "20150630"}, "rotation": Heavy}'
);

Cassandra

Generellt uttryck INSÄTT i Cassandra ser vĂ€ldigt liknande ut som det motsvarande i PostgreSQL. Men det finns en stor skillnad i semantiken. I Cassandra INSÄTT Ă€r det faktiskt en operation UPSERT, dĂ€r de senaste vĂ€rdena lĂ€ggs till i raden, om raden redan finns.

Datainmatning sker pĂ„ ett sĂ€tt som liknar PostgreSQL. INSÄTT metoden.

.

MongoDB

Trots att MongoDB Àr en NoSQL-databas, liknande Cassandra, har dess datainmatningsoperationer inget gemensamt med semantiken i Cassandra. I MongoDB insert() har ingen kapacitet UPSERT, vilket gör att den liknar PostgreSQL. Att lÀgga till data som standard utan _idspecified kommer resultera i att ett nytt dokument lÀggs till i kollektionen.

db.music.insert( {
artist: "Ingen som du kÀnner",
songTitle: "Ring mig idag",
albumTitle: "NÄgot berömt",
year: 2015,
price: 2.14,
genre: "Country",
tags: {
Composers: ["Smith", "Jones", "Davis"],
LengthInSeconds: 214
}
}
);
db.music.insert( {
artist: "Ingen som du kÀnner",
songTitle: "Min hund Spot",
albumTitle: "Hej nu",
price: 1.98,
genre: "Country",
criticRating: 8.4
}
);
db.music.insert( {
artist: "The Acme Band",
songTitle: "Se upp, vÀrlden",
albumTitle:"Dollar börjar hÀr",
price: 0.99,
genre: "Rock"
}
);
db.music.insert( {
artist: "The Acme Band",
songTitle: "Fortfarande förÀlskad",
albumTitle:"Dollar börjar hÀr",
price: 2.47,
genre: "Rock",
tags: {
radioStationsPlaying:["KHCR", "KBQX", "WTNR", "WJJH"],
tourDates: {
Seattle: "20150625",
Cleveland: "20150630"
},
rotation: "Heavy"
}
}
);

FörfrÄgan i tabellen

Kanske den mest betydande skillnaden mellan SQL och NoSQL i termer av att göra förfrĂ„gningar ligger i anvĂ€ndningen av formuleringar FRÅN och DÄR. SQL tillĂ„ter flera tabeller efter uttrycket FRÅN att vĂ€lja, och uttrycket med DÄR kan ha vilken komplexitet som helst (inklusive operationer JOIN mellan tabeller). Men NoSQL tenderar att införa strikta begrĂ€nsningar pĂ„ FRÅN, och fungerar endast med en angiven tabell, medan i DÄR, mĂ„ste en primĂ€rnyckel alltid anges. Detta beror pĂ„ strĂ€van att öka prestandan pĂ„ NoSQL, som vi tidigare har diskuterat. Denna strĂ€van leder till en minskning av all kors-tabell och kors-nyckel interaktion. Det kan orsaka stor fördröjning i nod-nod kommunikation vid svar pĂ„ förfrĂ„gningar och bör dĂ€rför undvikas helt. Till exempel krĂ€ver Cassandra att förfrĂ„gningar begrĂ€nsas till specifika operationer (endast tillĂ„tna =, IN, , =>, <=) pĂ„ partitionsnycklar, förutom i fall av sekundĂ€rindexförfrĂ„gningar (hĂ€r tillĂ„ts endast operatorn =).

PostgreSQL

HÀr Àr tre exempel pÄ förfrÄgningar som lÀtt kan utföras av en SQL-databas.

  • HĂ€mta alla lĂ„tar av artisten;
  • HĂ€mta alla lĂ„tar av artisten som matchar den första delen av titeln;
  • HĂ€mta alla lĂ„tar av artisten som har ett visst ord i titeln och kostar mindre Ă€n 1.00.
SELECT * FROM Music
WHERE Artist='No One You Know';
SELECT * FROM Music
WHERE Artist='No One You Know' AND SongTitle LIKE 'Call%';
SELECT * FROM Music
WHERE Artist='No One You Know' AND SongTitle LIKE '%Today%'
AND Price > 1.00;

Cassandra

Av de ovan nÀmnda frÄgorna kommer endast den första att fungera i Cassandra utan Àndringar, eftersom operatorn LIKE inte kan tillÀmpas pÄ klusterkolumner, sÄsom SongTitle. I detta fall tillÄts endast operatorer = och IN.

SELECT * FROM Music
WHERE Artist='No One You Know';
SELECT * FROM Music
WHERE Artist='No One You Know' AND SongTitle IN ('Call Me Today', 'My Dog Spot')
AND Price > 1.00;

MongoDB

Som visat i de tidigare exemplen, Àr den frÀmsta metoden för att skapa förfrÄgningar i MongoDB db.collection.find(). Denna metod innehÄller tydligt namnet pÄ samlingen (music i exemplet nedan), sÄ förfrÄgningar över flera samlingar Àr inte tillÄtna.

db.music.find( {
  artist: "No One You Know"
 } 
);
db.music.find( {
  artist: "No One You Know",
  songTitle: /Call/
 } 
);

LĂ€sa alla rader i tabellen

Att lÀsa alla rader Àr helt enkelt ett specialfall av den frÄgemall som vi diskuterat tidigare.

PostgreSQL

SELECT * 
FROM Music;

Cassandra

PÄ samma sÀtt som exemplet i PostgreSQL ovan.

MongoDB

db.music.find( {} );

Redigera data i tabellen

PostgreSQL

PostgreSQL tillhandahÄller instruktionen UPPDATERA för att Àndra data. Den har ingen funktionalitet UPSERT, sÄ att köra denna instruktion kommer att resultera i ett felmeddelande om raderna inte lÀngre finns i databasen.

UPDATE Music
SET Genre = 'Disco'
WHERE Artist = 'The Acme Band' AND SongTitle = 'Still In Love';

Cassandra

I Cassandra finns det UPPDATERA en motsvarande PostgreSQL. UPPDATERA har samma semantik UPSERT, liknande INSÄTT.

PÄ samma sÀtt som exemplet i PostgreSQL ovan.

MongoDB
Operation update() I MongoDB kan man helt uppdatera ett befintligt dokument eller endast uppdatera specifika fÀlt. Som standard uppdaterar den endast ett dokument med semantiken inaktiverad. UPSERT. Att uppdatera flera dokument och beteende Àr liknande. UPSERT det kan tillÀmpas genom att stÀlla in ytterligare flaggor för operationen. Precis som i det följande exemplet, uppdateras genren för en specifik artist baserat pÄ deras lÄt.

db.music.update(
  {"artist": "The Acme Band"},
  { 
    $set: {
      "genre": "Disco"
    }
  },
  {"multi": true, "upsert": true}
);

Ta bort data frÄn tabellen

PostgreSQL

DELETE FROM Music
WHERE Artist = 'The Acme Band' AND SongTitle = 'Look Out, World';

Cassandra

PÄ samma sÀtt som exemplet i PostgreSQL ovan.

MongoDB

I MongoDB finns det tvĂ„ typer av operationer för att ta bort dokument — deleteOne() /deleteMany() och remove(). BĂ„da typerna tar bort dokument, men returnerar olika resultat.

db.music.deleteMany( {
        artist: "The Acme Band"
    }
);

Ta bort tabellen

PostgreSQL

DROP TABLE Music;

Cassandra

PÄ samma sÀtt som exemplet i PostgreSQL ovan.

MongoDB

db.music.drop();

Slutsats

Debatten om valet mellan SQL och NoSQL har pÄgÄtt i över 10 Är. Det finns tvÄ huvudaspekter av denna debatt: kÀrnarkitekturen i databasen (monolitisk, transaktionell SQL mot distribuerad, icke-transaktionell NoSQL) och designmetoden för databasen (datamodellering i SQL mot att modellera dina förfrÄgningar i NoSQL).

Med en distribuerad transaktionell databas som YugaByte DB kan debatten om databasarkitektur enkelt skingras. Allt eftersom datamÀngderna blir större Àn vad som kan skrivs pÄ en nod, blir en helt distribuerad arkitektur som stödjer linjÀr skrivskalerbarhet med automatisk sharding/omfördelning nödvÀndig.

Förutom det som nÀmns i en av artiklarna Google Cloud, anvÀnds transaktionella, strikt överenskomna arkitekturer nu i större utstrÀckning för att sÀkerstÀlla bÀttre flexibilitet i utvecklingen Àn icke-transaktionella, slutligen överenskomna arkitekturer.

Återkommer till diskussionen om databasdesign, Ă€r det rimligt att sĂ€ga att bĂ„da designmetoderna (SQL och NoSQL) Ă€r nödvĂ€ndiga för alla komplexa verkliga applikationer. SQL-ansatsen "datamodellering" gör det lĂ€ttare för utvecklare att möta förĂ€nderliga affĂ€rskrav, medan NoSQL-ansatsen "frĂ„gemodellering" tillĂ„ter samma utvecklare att hantera stora datamĂ€ngder med lĂ„g latens och hög genomströmning. Just av denna anledning erbjuder YugaByte DB bĂ„de SQL- och NoSQL-API:er i en gemensam kĂ€rna, snarare Ă€n att föresprĂ„ka nĂ„gon av metoderna. Dessutom, genom att sĂ€kerstĂ€lla kompatibilitet med populĂ€ra databassprĂ„k, inklusive PostgreSQL och Cassandra, garanterar YugaByte DB att utvecklare inte behöver lĂ€ra sig ett annat sprĂ„k för att arbeta med den distribuerade, strikt konsistenta databasisskern.

I den hÀr artikeln har vi undersökt hur grunderna för databasdesign skiljer sig mellan PostgreSQL, Cassandra och MongoDB. I kommande artiklar kommer vi att dyka in i avancerade designkoncept som index, transaktioner, JOIN:ar, TTL-direktiv och JSON-dokument.

Vi önskar er en trevlig kvarvarande helg och inbjuder till gratis webbinarie, som redan Àger rum den 14 maj.

KĂ€lla: habr.com

Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar đŸ”„ Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar | ProHoster