
I dag vill jag introducera Habr-lÀsarna för ett verktyg skrivet i Python för att hantera tabellberoenden i databashanteringssystemet PostgreSQL.
API:t för verktyget Àr enkelt och bestÄr av tre metoder:
- archive_table â rekursiv arkivering/borttagning av rader med angivna primĂ€rnycklar
- get_table_references â sökning av beroenden för en tabell (visar tabellerna som den angivna refererar till och som refererar till den)
- get_rows_references â sökning av rader i andra tabeller som refererar till de angivna raderna i den önskade tabellen
Bakgrund
Jag heter Oleg Borzov, jag Àr utvecklare i CRM-teamet för hypotekslÄnechefer pÄ Domclick.
Den primÀra databasen för vÄrt CRM-system Àr en av de största i företaget. Den Àr ocksÄ en av de Àldsta: den skapades vid projektets start, nÀr trÀden var stora, Domclick var en startup och istÀllet för en mikrotjÀnst pÄ en modern python-asynkron ramverk fanns det en stor monolit pÄ PHP.
ĂvergĂ„ngen frĂ„n PHP till Python var mycket lĂ„ng och krĂ€vde att bĂ„da systemen stödjdes samtidigt, vilket pĂ„verkade databaskonstruktionen.
Som ett resultat har vi en databas med ett stort antal starkt kopplade och stora tabeller med massor av index för olika typer av frÄgor. Allt detta pÄverkar databasens prestanda negativt: pÄ grund av stora tabeller och mÄnga kopplingar mellan dem ökar stÀndigt komplexiteten i frÄgorna, vilket Àr sÀrskilt kritiskt för de mest belastade tabellerna.
För att minska belastningen pÄ databasen beslutade vi att skriva ett skript som dagligen via cron skulle flytta gamla poster frÄn de mest omfattande och belastade tabellerna till arkiverade (till exempel frÄn task pÄ task_archive).
Denna uppgift kompliceras av det stora antalet kopplingar mellan tabellerna: det Àr inte tillrÀckligt att bara flytta rader frÄn task pÄ task_archive det behövs det samma göras rekursivt med alla tabeller som refererar till task tabeller.
Jag ska demonstrera med ett exempel :

Anta att vi behöver ta bort poster frÄn tabellen Flights. Bara sÄ hÀr kan inte Postgres tillÄta oss att göra det: först mÄste vi ta bort poster frÄn alla refererande tabeller, och sÄ rekursivt till tabellerna som ingen refererar till.
I vĂ„rt exempel pĂ„ Flights refererar till Ticket_flights, och till den â Boarding_passes.
DÀrför mÄste vi ta bort i följande ordning:
- Vi fÄr de primÀra nyckelvÀrdena (Primary Keys, PK) för rader i
Ticket_flights, som refererar till de borttagna raderna iFlights. - Vi fÄr PK för rader
Boarding_passes, som refererar tillTicket_flights. - Tar bort rader efter PK frÄn punkt 2 i tabellen
Boarding_passes. - Tar bort rader efter PK frÄn punkt 1 i
Ticket_flights. - Tar bort rader frÄn
Flights.
I slutÀndan skapades ett verktyg som heter PgGraph, som vi bestÀmde oss för att göra open source.
Hur man anvÀnder
Verktyget stöder tvÄ anvÀndningslÀgen:
- Anrop frÄn kommandoraden (
pggraph âŠ). - AnvĂ€ndning i Python-kod (klass
PgGraphApi).
Installation och konfiguration
Först mÄste verktyget installeras frÄn Pypi-repositoryt:
pip3 install pggraphSkapa sedan en fil config.ini pÄ den lokala maskinen med konfigurationen för databasen och arkiveringsskriptet:
[db]
host = localhost
port = 5432
user = postgres
password = postgres
dbname = postgres
schema = public ; Valfritt parameter, standardvÀrdet anges nedan
[archive] ; Denna sektion behöver inte fyllas i, nedan anges standardvÀrden
is_debug = false
chunk_size = 1000
max_depth = 20
to_archive = true
archive_suffix = 'archive'Körning frÄn konsolen
Parametrar
$ pggraph -h
usage: pggraph action [-h] --table TABLE [--ids IDS] [--config_path CONFIG_PATH]
positional arguments:
action required action: archive_table, get_table_references, get_rows_references
optional arguments:
-h, --help visa detta hjÀlpbibliotek och avsluta
--table TABLE tabellnamn
--ids IDS primÀr nyckel ids, separerade med komma, t.ex. 1,2,3
--config_path CONFIG_PATH sökvÀg till config.ini
--log_path LOG_PATH sökvÀg till loggkatalog
--log_level LOG_LEVEL loggnivÄ (debug, info, error)Positional arguments:
Ă„tgĂ€rdâ Ă„tgĂ€rden som krĂ€vs:archive_table,get_table_referencesellerget_rows_references.
Named arguments:
--config_pathâ sökvĂ€g till konfigurationsfilen;--tableâ tabellen dĂ€r Ă„tgĂ€rden ska utföras;--idsâ lista över id:n separerade med komma, till exempel,1,2,3(valfritt parameter);--log_pathâ sökvĂ€g till loggmappen (valfritt parameter, standardvĂ€rde â hemkatalogen);--log_levelâ loggnivĂ„ (valfritt parameter, standardvĂ€rde â INFO).
Exempel pÄ kommandon
Arkivering av tabell
Huvudfunktionen i verktyget Àr att arkivera data, dvs. att flytta rader frÄn huvudtabellen till arkivtabellen (t.ex. frÄn tabellen books pÄ books_archive).
Det stöder ocksÄ borttagning utan arkivering: för detta mÄste parametrarna i config.ini stÀllas in pÄ to_archive = false).
Obligatoriska parametrar â config_path, table och ids.
Efter körning kommer posterna ids i tabellen tabell och i alla tabeller som refererar till den.
$ pggraph archive_table --config_path config.hw.local.ini --table flights --ids 1,2,3
2020-06-20 19:27:44 INFO: flights - START
2020-06-20 19:27:44 INFO: flights - start archive_recursive 3 rows (depth=0)
2020-06-20 19:27:44 INFO: START ARCHIVE REFERRING TABLES
2020-06-20 19:27:44 INFO: ticket_flights - start archive_recursive 3 rows (depth=1)
2020-06-20 19:27:44 INFO: START ARCHIVE REFERRING TABLES
2020-06-20 19:27:44 INFO: boarding_passes - start archive_recursive 3 rows (depth=2)
2020-06-20 19:27:44 INFO: START ARCHIVE REFERRING TABLES
2020-06-20 19:27:44 INFO: END ARCHIVE REFERRING TABLES
2020-06-20 19:27:44 INFO: boarding_passes - archive_by_ids 3 rows by ticket_no, flight_id
2020-06-20 19:27:44 INFO: boarding_passes - start archive_recursive 3 rows (depth=2)
2020-06-20 19:27:44 INFO: START ARCHIVE REFERRING TABLES
2020-06-20 19:27:44 INFO: END ARCHIVE REFERRING TABLES
2020-06-20 19:27:44 INFO: boarding_passes - archive_by_ids 3 rows by ticket_no, flight_id
2020-06-20 19:27:44 INFO: boarding_passes - start archive_recursive 3 rows (depth=2)
2020-06-20 19:27:44 INFO: START ARCHIVE REFERRING TABLES
2020-06-20 19:27:44 INFO: END ARCHIVE REFERRING TABLES
2020-06-20 19:27:44 INFO: boarding_passes - archive_by_ids 3 rows by ticket_no, flight_id
2020-06-20 19:27:44 INFO: boarding_passes - start archive_recursive 3 rows (depth=2)
2020-06-20 19:27:44 INFO: START ARCHIVE REFERRING TABLES
2020-06-20 19:27:44 INFO: END ARCHIVE REFERRING TABLES
2020-06-20 19:27:44 INFO: boarding_passes - archive_by_ids 3 rows by ticket_no, flight_id
2020-06-20 19:27:44 INFO: END ARCHIVE REFERRING TABLES
2020-06-20 19:27:44 INFO: ticket_flights - archive_by_ids 3 rows by ticket_no, flight_id
2020-06-20 19:27:44 INFO: END ARCHIVE REFERRING TABLES
2020-06-20 19:27:44 INFO: flights - archive_by_ids 3 rows by id
2020-06-20 19:27:44 INFO: flights - ENDSökning efter beroenden för den angivna tabellen
Funktion för att hitta beroenden av den angivna tabellen tabell. Obligatoriska parametrar â config_path och tabell.
Efter körning kommer en ordbok att visas pÄ skÀrmen, dÀr:
in_refsâ ordbok över tabeller som refererar till denna, dĂ€r nyckeln Ă€r tabellnamnet och vĂ€rdet Ă€r en lista över Foreign Key-objekt (pk_mainâ primĂ€rnyckel i huvudtabellen,pk_refâ primĂ€rnyckel i den refererande tabellen,fk_refâ namnet pĂ„ kolumnen som Ă€r en foreign key till den ursprungliga tabellen);out_refsâ ordbok över tabeller som denna hĂ€nvisar till.
$ pggraph get_table_references --config_path config.hw.local.ini --table flights
{'in_refs': {'ticket_flights': [ForeignKey(pk_main='flight_id', pk_ref='ticket_no, flight_id', fk_ref='flight_id')]},
'out_refs': {'aircrafts': [ForeignKey(pk_main='aircraft_code', pk_ref='flight_id', fk_ref='aircraft_code')],
'airports': [ForeignKey(pk_main='airport_code', pk_ref='flight_id', fk_ref='arrival_airport'),
ForeignKey(pk_main='airport_code', pk_ref='flight_id', fk_ref='departure_airport')]}}Sökning efter referenser till rader med angivna primÀra nycklar
Funktion för att hitta rader i andra tabeller som hĂ€nvisar via foreign key till rader ids i tabellen tabell. Obligatoriska parametrar â config_path, tabell och ids.
Efter starten kommer en ordbok med följande struktur att visas pÄ skÀrmen:
{
pk_id_1: {
reffering_table_name_1: {
foreign_key_1: [
{row_pk_1: value, row_pk_2: value},
...
],
...
},
...
},
pk_id_2: {...},
...
}Exempel pÄ anrop:
$ pggraph get_rows_references --config_path config.hw.local.ini --table flights --ids 1,2,3
{1: {'ticket_flights': {'flight_id': [{'flight_id': 1,
'ticket_no': '0005432816945'},
{'flight_id': 1,
'ticket_no': '0005432816941'}]}},
2: {'ticket_flights': {'flight_id': [{'flight_id': 2,
'ticket_no': '0005433101832'},
{'flight_id': 2,
'ticket_no': '0005433101864'},
{'flight_id': 2,
'ticket_no': '0005432919715'}]}},
3: {'ticket_flights': {'flight_id': [{'flight_id': 3,
'ticket_no': '0005432817560'},
{'flight_id': 3,
'ticket_no': '0005432817568'},
{'flight_id': 3,
'ticket_no': '0005432817559'}]}}}AnvÀndning i koden
Förutom att köra i konsolen kan biblioteket anvÀndas i Python-kod. Nedan visas exempel pÄ anrop i den interaktiva miljön iPython.
Arkivering av tabell
>>> from pg_graph.main import setup_logging
>>> setup_logging(log_level='DEBUG')
>>> from pg_graph.api import PgGraphApi
>>> api = PgGraphApi('config.hw.local.ini')
>>> api.archive_table('flights', [4,5])
2020-06-20 23:12:08 INFO: flights - START
2020-06-20 23:12:08 INFO: flights - start archive_recursive 2 rows (depth=0)
2020-06-20 23:12:08 INFO: START ARCHIVE REFERRING TABLES
2020-06-20 23:12:08 DEBUG: ticket_flights - ForeignKey(pk_main='flight_id', pk_ref='flight_id, ticket_no', fk_ref='flight_id')
2020-06-20 23:12:08 DEBUG: SQL('SELECT flight_id, ticket_no FROM bookings.ticket_flights WHERE (flight_id) IN (%s, %s)')
2020-06-20 23:12:08 INFO: ticket_flights - start archive_recursive 30 rows (depth=1)
2020-06-20 23:12:08 INFO: START ARCHIVE REFERRING TABLES
2020-06-20 23:12:08 DEBUG: boarding_passes - ForeignKey(pk_main='flight_id, ticket_no', pk_ref='flight_id, ticket_no', fk_ref='flight_id, ticket_no')
2020-06-20 23:12:08 INFO: boarding_passes - archive_by_fk 30 rows by ForeignKey(pk_main='flight_id, ticket_no', pk_ref='flight_id, ticket_no', fk_ref='flight_id, ticket_no')
2020-06-20 23:12:08 DEBUG: SQL('CREATE TABLE IF NOT EXISTS bookings.boarding_passes_archive (LIKE bookings.boarding_passes)')
2020-06-20 23:12:08 DEBUG: DELETE FROM boarding_passes by FK flight_id, ticket_no - 30 rows
2020-06-20 23:12:08 INFO: END ARCHIVE REFERRING TABLES
2020-06-20 23:12:08 INFO: ticket_flights - archive_by_ids 30 rows by flight_id, ticket_no
2020-06-20 23:12:08 DEBUG: SQL('CREATE TABLE IF NOT EXISTS bookings.ticket_flights_archive (LIKE bookings.ticket_flights)')
2020-06-20 23:12:08 DEBUG: DELETE FROM ticket_flights by flight_id, ticket_no - 30 rows
2020-06-20 23:12:08 DEBUG: INSERT INTO ticket_flights_archive - 30 rows
2020-06-20 23:12:08 INFO: ticket_flights - start archive_recursive 30 rows (depth=1)
2020-06-20 23:12:08 INFO: START ARCHIVE REFERRING TABLES
2020-06-20 23:12:08 DEBUG: boarding_passes - ForeignKey(pk_main='flight_id, ticket_no', pk_ref='flight_id, ticket_no', fk_ref='flight_id, ticket_no')
2020-06-20 23:12:08 INFO: boarding_passes - archive_by_fk 30 rows by ForeignKey(pk_main='flight_id, ticket_no', pk_ref='flight_id, ticket_no', fk_ref='flight_id, ticket_no')
2020-06-20 23:12:08 DEBUG: SQL('CREATE TABLE IF NOT EXISTS bookings.boarding_passes_archive (LIKE bookings.boarding_passes)')
2020-06-20 23:12:08 DEBUG: DELETE FROM boarding_passes by FK flight_id, ticket_no - 30 rows
2020-06-20 23:12:08 INFO: END ARCHIVE REFERRING TABLES
2020-06-20 23:12:08 INFO: ticket_flights - archive_by_ids 30 rows by flight_id, ticket_no
2020-06-20 23:12:08 DEBUG: SQL('CREATE TABLE IF NOT EXISTS bookings.ticket_flights_archive (LIKE bookings.ticket_flights)')
2020-06-20 23:12:08 DEBUG: DELETE FROM ticket_flights by flight_id, ticket_no - 30 rows
2020-06-20 23:12:08 DEBUG: INSERT INTO ticket_flights_archive - 30 rows
2020-06-20 23:12:08 INFO: ticket_flights - start archive_recursive 30 rows (depth=1)
2020-06-20 23:12:08 INFO: START ARCHIVE REFERRING TABLES
2020-06-20 23:12:08 DEBUG: boarding_passes - ForeignKey(pk_main='flight_id, ticket_no', pk_ref='flight_id, ticket_no', fk_ref='flight_id, ticket_no')
2020-06-20 23:12:08 INFO: boarding_passes - archive_by_fk 30 rows by ForeignKey(pk_main='flight_id, ticket_no', pk_ref='flight_id, ticket_no', fk_ref='flight_id, ticket_no')
2020-06-20 23:12:08 DEBUG: SQL('CREATE TABLE IF NOT EXISTS bookings.boarding_passes_archive (LIKE bookings.boarding_passes)')
2020-06-20 23:12:08 DEBUG: DELETE FROM boarding_passes by FK flight_id, ticket_no - 30 rows
2020-06-20 23:12:08 INFO: END ARCHIVE REFERRING TABLES
2020-06-20 23:12:08 INFO: ticket_flights - archive_by_ids 30 rows by flight_id, ticket_no
2020-06-20 23:12:08 DEBUG: SQL('CREATE TABLE IF NOT EXISTS bookings.ticket_flights_archive (LIKE bookings.ticket_flights)')
2020-06-20 23:12:08 DEBUG: DELETE FROM ticket_flights by flight_id, ticket_no - 30 rows
2020-06-20 23:12:08 DEBUG: INSERT INTO ticket_flights_archive - 30 rows
2020-06-20 23:12:08 INFO: ticket_flights - start archive_recursive 3 rows (depth=1)
2020-06-20 23:12:08 INFO: START ARCHIVE REFERRING TABLES
2020-06-20 23:12:08 DEBUG: boarding_passes - ForeignKey(pk_main='flight_id, ticket_no', pk_ref='flight_id, ticket_no', fk_ref='flight_id, ticket_no')
2020-06-20 23:12:08 INFO: boarding_passes - archive_by_fk 3 rows by ForeignKey(pk_main='flight_id, ticket_no', pk_ref='flight_id, ticket_no', fk_ref='flight_id, ticket_no')
2020-06-20 23:12:08 DEBUG: SQL('CREATE TABLE IF NOT EXISTS bookings.boarding_passes_archive (LIKE bookings.boarding_passes)')
2020-06-20 23:12:08 DEBUG: DELETE FROM boarding_passes by FK flight_id, ticket_no - 3 rows
2020-06-20 23:12:08 INFO: END ARCHIVE REFERRING TABLES
2020-06-20 23:12:08 INFO: ticket_flights - archive_by_ids 3 rows by flight_id, ticket_no
2020-06-20 23:12:08 DEBUG: SQL('CREATE TABLE IF NOT EXISTS bookings.ticket_flights_archive (LIKE bookings.ticket_flights)')
2020-06-20 23:12:08 DEBUG: DELETE FROM ticket_flights by flight_id, ticket_no - 3 rows
2020-06-20 23:12:08 DEBUG: INSERT INTO ticket_flights_archive - 3 rows
2020-06-20 23:12:08 INFO: END ARCHIVE REFERRING TABLES
2020-06-20 23:12:08 INFO: flights - archive_by_ids 2 rows by flight_id
2020-06-20 23:12:09 DEBUG: SQL('CREATE TABLE IF NOT EXISTS bookings.flights_archive (LIKE bookings.flights)')
2020-06-20 23:12:09 DEBUG: DELETE FROM flights by flight_id - 2 rows
2020-06-20 23:12:09 DEBUG: INSERT INTO flights_archive - 2 rows
2020-06-20 23:12:09 INFO: flights - ENDSökning efter beroenden för den angivna tabellen
>>> from pg_graph.api import PgGraphApi
>>> from pprint import pprint
>>> api = PgGraphApi('config.hw.local.ini')
>>> res = api.get_table_references('flights')
>>> pprint(res)
{'in_refs': {'ticket_flights': [ForeignKey(pk_main='flight_id', pk_ref='flight_id, ticket_no', fk_ref='flight_id')]},
'out_refs': {'aircrafts': [ForeignKey(pk_main='aircraft_code', pk_ref='flight_id', fk_ref='aircraft_code')],
'airports': [ForeignKey(pk_main='airport_code', pk_ref='flight_id', fk_ref='arrival_airport'),
ForeignKey(pk_main='airport_code', pk_ref='flight_id', fk_ref='departure_airport')]}}Sökning efter referenser till rader med angivna primÀra nycklar
>>> from pg_graph.api import PgGraphApi
>>> from pprint import pprint
>>> api = PgGraphApi('config.hw.local.ini')
>>> rows = api.get_rows_references('flights', [1,2,3])
>>> pprint(rows)
{1: {'ticket_flights': {'flight_id': [{'flight_id': 1,
'ticket_no': '0005432816945'},
{'flight_id': 1,
'ticket_no': '0005432816941'}]}},
2: {'ticket_flights': {'flight_id': [{'flight_id': 2,
'ticket_no': '0005433101832'},
{'flight_id': 2,
'ticket_no': '0005433101864'},
{'flight_id': 2,
'ticket_no': '0005432919715'}]}},
3: {'ticket_flights': {'flight_id': [{'flight_id': 3,
'ticket_no': '0005432817560'},
{'flight_id': 3,
'ticket_no': '0005432817568'},
{'flight_id': 3,
'ticket_no': '0005432817559'}]}}}Den ursprungliga koden för biblioteket finns tillgÀnglig pÄ under MIT-licensen, samt i repositoryt .
Jag ser fram emot kommentarer, commits och förslag.
Jag ska försöka svara pÄ frÄgor efter bÀsta förmÄga hÀr och i repositoryt.
KĂ€lla: habr.com
