Escrivim a PostgreSQL en sublight: 1 host, 1 dia, 1TB

Fa poc us vaig explicar com, fent servir receptes estàndard augmentar el rendiment de les consultes de lectura SQL de la base de dades PostgreSQL. Avui parlarem de com la gravació es pot fer de manera més eficient a la base de dades sense utilitzar cap "gir" a la configuració, simplement organitzant correctament els fluxos de dades.

Escrivim a PostgreSQL en sublight: 1 host, 1 dia, 1TB

#1. Seccionament

Un article sobre com i per què val la pena organitzar-se partició aplicada "en teoria" ja ha estat, aquí parlarem de la pràctica d'aplicar alguns enfocaments dins del nostre servei de supervisió per a centenars de servidors PostgreSQL.

"Coses d'abans..."

Inicialment, com qualsevol MVP, el nostre projecte va començar amb una càrrega bastant lleugera: la supervisió només es va dur a terme per als deu servidors més crítics, totes les taules eren relativament compactes... Però a mesura que passava el temps, el nombre d'amfitrions supervisats es va anar fent cada cop més gran. , i una vegada més vam intentar fer alguna cosa amb un de taules de 1.5 TB de mida, ens vam adonar que tot i que era possible seguir vivint així, era molt incòmode.

Els temps eren gairebé com èpics, les diferents versions de PostgreSQL 9.x eren rellevants, de manera que totes les particions s'havien de fer "manualment", mitjançant herència de taules i activadors encaminament amb dinàmica EXECUTE.

Escrivim a PostgreSQL en sublight: 1 host, 1 dia, 1TB
La solució resultant va resultar prou universal com per poder traduir-se a totes les taules:

  • Es va declarar una taula pare "capçalera" buida, que ho descriu tot índexs i activadors necessaris.
  • El registre des del punt de vista del client es va fer a la taula "arrel", i utilitzant internament disparador d'encaminament BEFORE INSERT el registre es va inserir "físicament" a la secció requerida. Si encara no existia, vam agafar una excepció i...
  • … mitjançant l'ús de CREATE TABLE ... (LIKE ... INCLUDING ...) es va crear a partir de la plantilla de la taula pare secció amb una restricció a la data desitjadade manera que quan es recuperen les dades, la lectura només es realitza en ella.

PG10: primer intent

Però el particionament mitjançant l'herència no ha estat històricament adequat per fer front a un flux d'escriptura actiu o un gran nombre de particions fills. Per exemple, podeu recordar que l'algorisme per seleccionar la secció requerida tenia complexitat quadràtica, que funciona amb més de 100 seccions, tu mateix entens com...

A PG10 aquesta situació es va optimitzar molt amb la implementació de suport partició nativa. Per tant, vam intentar aplicar-lo immediatament després de migrar l'emmagatzematge, però...

Com va resultar després d'explorar el manual, la taula particionada de manera nativa en aquesta versió és:

  • no admet descripcions d'índex
  • no admet activadors
  • no pot ser el "descendent" de ningú
  • no donen suport INSERT ... ON CONFLICT
  • no pot generar una secció automàticament

Després d'haver rebut un dolorós cop al front amb un rasclet, ens vam adonar que seria impossible prescindir de modificar l'aplicació i vam ajornar més investigacions durant sis mesos.

PG10: segona oportunitat

Així doncs, vam començar a resoldre els problemes que van sorgir un per un:

  1. Perquè desencadena i ON CONFLICT Ens vam adonar que encara els necessitàvem aquí i allà, així que vam fer una etapa intermèdia per treballar-los taula de proxy.
  2. M'he desfet de l'"encaminament" en desencadenants - és a dir, de EXECUTE.
  3. El van treure per separat plantilla de taula amb tots els índexsde manera que ni tan sols estiguin presents a la taula de proxy.

Escrivim a PostgreSQL en sublight: 1 host, 1 dia, 1TB
Finalment, després de tot això, vam particionar la taula principal de manera nativa. La creació d'una nova secció encara queda a la consciència de l'aplicació.

Diccionaris de “serrar”.

Com en qualsevol sistema analític, també en teníem "fets" i "talls" (Diccionaris). En el nostre cas, en aquesta qualitat van actuar, per exemple, cos de la plantilla consultes lentes similars o el text de la consulta.

Els "fets" ja es van seccionar diàriament durant molt de temps, així que vam eliminar amb calma les seccions obsoletes i no ens van molestar (registres!). Però hi havia un problema amb els diccionaris...

No vol dir que n'hi hagués molts, però aproximadament 100 TB de "fets" van donar lloc a un diccionari de 2.5 TB. No podeu suprimir res d'aquesta taula de manera convenient, no podeu comprimir-la en el temps adequat i escriure-hi es va anar fent més lenta.

Com un diccionari... en ell, cada entrada s'ha de presentar exactament una vegada... i això és correcte, però!.. Ningú ens impedeix tenir un diccionari separat per a cada dia! Sí, això comporta una certa redundància, però permet:

  • escriure/llegir més ràpid a causa de la mida de la secció més petita
  • consumeix menys memòria treballant amb índexs més compactes
  • emmagatzemar menys dades a causa de la capacitat d'eliminar ràpidament els obsolets

Com a resultat de tot el conjunt de mesures La càrrega de la CPU ha disminuït un ~30%, la càrrega del disc un ~50%:

Escrivim a PostgreSQL en sublight: 1 host, 1 dia, 1TB
Al mateix temps, vam continuar escrivint exactament el mateix a la base de dades, només amb menys càrrega.

#2. Evolució i refactorització de bases de dades

Així que ens hem decidit amb el que tenim cada dia té la seva secció amb dades. En realitat, CHECK (dt = '2018-10-12'::date) — i hi ha una clau de partició i la condició perquè un registre caigui en una secció específica.

Com que tots els informes del nostre servei es creen en el context d'una data específica, els índexs per a ells des de "temps no particionats" han estat de tot tipus (Servidor, Data, Plantilla de pla), (Servidor, Data, node de pla), (Data, classe d'error, servidor), ...

Però ara viuen a cada secció les teves còpies cadascun d'aquests índexs... I dins de cada secció la data és una constant... Resulta que ara estem a cadascun d'aquests índexs simplement introduïu una constant com un dels camps, que augmenta tant el seu volum com el temps de cerca, però no aporta cap resultat. Es van deixar el rasclet per ells mateixos, vaja...

Escrivim a PostgreSQL en sublight: 1 host, 1 dia, 1TB
La direcció de l'optimització és òbvia, senzilla elimina el camp de data de tots els índexs en taules particionades. Tenint en compte els nostres volums, el guany és aproximat 1 TB/setmana!

Ara observem que aquest terabyte encara s'havia d'enregistrar d'alguna manera. És a dir, nosaltres també ara el disc hauria de carregar menys! Aquesta imatge mostra clarament l'efecte obtingut de la neteja, a la qual vam dedicar una setmana:

Escrivim a PostgreSQL en sublight: 1 host, 1 dia, 1TB

#3. "Repartir" la càrrega màxima

Un dels grans problemes dels sistemes carregats és sincronització redundant algunes operacions que no ho requereixen. De vegades “perquè no se n’han adonat”, de vegades “era més fàcil així”, però tard o d’hora t’has de desfer.

Ampliem la imatge anterior i veiem que tenim un disc "bombes" sota la càrrega amb doble amplitud entre mostres adjacents, cosa que clarament "estadísticament" no hauria de passar amb aquest nombre d'operacions:

Escrivim a PostgreSQL en sublight: 1 host, 1 dia, 1TB

Això és bastant fàcil d'aconseguir. Ja hem començat el seguiment gairebé 1000 servidors, cadascun es processa per un fil lògic separat i cada fil restableix la informació acumulada que s'enviarà a la base de dades a una freqüència determinada, una cosa així:

setInterval(sendToDB, interval)

El problema aquí rau precisament en el fet que tots els fils comencen aproximadament al mateix temps, de manera que els seus temps d'enviament gairebé sempre coincideixen "al punt". Vaja #2...

Afortunadament, això és bastant fàcil de solucionar, afegint un augment "atzar". per temps:

setInterval(sendToDB, interval * (1 + 0.1 * (Math.random() - 0.5)))

#4. Emmagatzemem el que necessitem

El tercer problema tradicional de càrrega alta és sense memòria cau on es troba podria ser.

Per exemple, hem fet possible analitzar en termes de nodes de pla (tots aquests Seq Scan on users), però immediatament penseu que són, en la seva majoria, els mateixos; s'han oblidat.

No, per descomptat, no es torna a escriure res a la base de dades, això talla el disparador amb INSERT ... ON CONFLICT DO NOTHING. Però aquestes dades encara arriben a la base de dades i són innecessàries llegir per comprovar si hi ha conflictes haver de fer. Vaja #3...

La diferència en el nombre de registres enviats a la base de dades abans/després d'habilitar la memòria cau és òbvia:

Escrivim a PostgreSQL en sublight: 1 host, 1 dia, 1TB

I aquesta és la caiguda acompanyant de la càrrega d'emmagatzematge:

Escrivim a PostgreSQL en sublight: 1 host, 1 dia, 1TB

En total

"Terabyte-per-day" només sona espantós. Si ho fas tot bé, això és just 2^40 bytes / 86400 segons = ~12.5 MB/sque fins i tot els cargols IDE d'escriptori es mantenen. 🙂

Però, seriosament, fins i tot amb un "desviació" deu vegades de la càrrega durant el dia, podeu satisfer fàcilment les capacitats dels SSD moderns.

Escrivim a PostgreSQL en sublight: 1 host, 1 dia, 1TB

Font: www.habr.com

Afegeix comentari