Ngayon nais kong ipakita sa mga mambabasa ng Habr ang isang utility na nakasulat sa Python para sa pagtatrabaho sa mga dependency ng talahanayan sa PostgreSQL DBMS.
Ang API ng utility ay simple at binubuo ng tatlong pamamaraan:
- archive_table - recursive na pag-archive/pagtanggal ng mga row na may tinukoy na Pangunahing Key
- get_table_references β maghanap ng mga dependency para sa isang talahanayan (ipapakita ang mga talahanayan na isinangguni ng tinukoy at ang mga tumutukoy dito)
- get_rows_references - maghanap ng mga row sa ibang mga table na tumutukoy sa mga tinukoy na row sa gustong table
prehistory
Ang pangalan ko ay Oleg Borzov, isa akong developer sa CRM team para sa mga manager ng mortgage lending sa Domklik.
Ang pangunahing database ng aming CRM system ay isa sa pinakamalaki sa mga tuntunin ng volume sa kumpanya. Isa rin ito sa pinakaluma: lumitaw ito sa mismong paglulunsad ng proyekto, nang malalaki ang mga puno, ang Domklik ay isang startup, at sa halip na isang microservice sa isang naka-istilong Python asynchronous na balangkas ay mayroong isang malaking monolith sa PHP.
Ang paglipat mula sa PHP patungo sa Python ay napakatagal at nangangailangan ng sabay-sabay na suporta ng parehong mga sistema, na nakaapekto sa disenyo ng database.
Bilang resulta, mayroon kaming database na may malaking bilang ng lubos na konektado at malalaking talahanayan na may grupo ng mga index para sa iba't ibang uri ng mga query. Ang lahat ng ito ay negatibong nakakaapekto sa pagganap ng database: dahil sa malalaking talahanayan at isang bungkos ng mga ugnayan sa pagitan ng mga ito, ang pagiging kumplikado ng mga query ay patuloy na tumataas, na partikular na kritikal para sa mga pinaka-load na mga talahanayan.
Upang bawasan ang pag-load sa database, nagpasya kaming magsulat ng script na maglilipat ng mga lumang talaan mula sa pinakamarami at na-load na mga talahanayan patungo sa mga naka-archive (halimbawa, mula sa task
Π² task_archive
).
Ang gawaing ito ay kumplikado sa pamamagitan ng malaking bilang ng mga ugnayan sa pagitan ng mga talahanayan: ilipat lamang ang mga hilera mula sa task
Π² task_archive
ay hindi sapat, bago iyon kailangan mong gawin ang parehong recursively sa lahat ng mga reference task
mga mesa.
Ipapakita ko sa isang halimbawa
Sabihin nating kailangan nating magtanggal ng mga tala mula sa isang talahanayan Flights
. Hindi kami papayagan ng mga postgres na gawin ito nang ganoon lang: kailangan muna naming tanggalin ang mga talaan mula sa lahat ng mga talahanayan ng pagtukoy, at iba pa nang paulit-ulit sa mga talahanayan na walang tinutukoy.
Sa aming halimbawa sa Flights
tumutukoy Ticket_flights
, at sa kanya - Boarding_passes
.
Samakatuwid, kailangan mong tanggalin ito sa ganitong pagkakasunud-sunod:
- Nakukuha namin ang mga pangunahing susi (PK) na halaga ng mga hilera
Ticket_flights
, na tumutukoy sa mga row na tatanggalinFlights
. - Nakukuha namin ang mga hilera ng PK
Boarding_passes
, na tumutukoy saTicket_flights
. - Tinatanggal namin ang mga hilera ayon sa PK mula sa hakbang 2 sa talahanayan
Boarding_passes
. - Tanggalin ang mga linya ayon sa PK mula sa hakbang 1 sa
Ticket_flights
. - Pag-alis ng mga linya mula sa
Flights
.
Ang resulta ay isang utility na tinatawag na PgGraph, na nagpasya kaming gawing open source.
Paano gamitin
Sinusuportahan ng utility ang dalawang mode ng paggamit:
- Tawag mula sa command line (
pggraph β¦
). - Paggamit sa Python code (class
PgGraphApi
).
Pag-install at pagsasaayos
Una kailangan mong i-install ang utility mula sa Pypi repository:
pip3 install pggraph
Pagkatapos ay lumikha ng isang config.ini file sa lokal na makina na may configuration ng database at ang script ng pag-archive:
[db]
host = localhost
port = 5432
user = postgres
password = postgres
dbname = postgres
schema = public ; ΠΠ΅ΠΎΠ±ΡΠ·Π°ΡΠ΅Π»ΡΠ½ΡΠΉ ΠΏΠ°ΡΠ°ΠΌΠ΅ΡΡ, ΡΠΊΠ°Π·Π°Π½ΠΎ Π·Π½Π°ΡΠ΅Π½ΠΈΠ΅ ΠΏΠΎ ΡΠΌΠΎΠ»ΡΠ°Π½ΠΈΡ
[archive] ; ΠΠ°Π½Π½ΡΠΉ ΡΠ°Π·Π΄Π΅Π» Π·Π°ΠΏΠΎΠ»Π½ΡΡΡ Π½Π΅ΠΎΠ±ΡΠ·Π°ΡΠ΅Π»ΡΠ½ΠΎ, Π½ΠΈΠΆΠ΅ ΡΠΊΠ°Π·Π°Π½Ρ Π·Π½Π°ΡΠ΅Π½ΠΈΡ ΠΏΠΎ ΡΠΌΠΎΠ»ΡΠ°Π½ΠΈΡ
is_debug = false
chunk_size = 1000
max_depth = 20
to_archive = true
archive_suffix = 'archive'
Tumakbo mula sa console
Mga Parameter
$ 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 show this help message and exit
--table TABLE table name
--ids IDS primary key ids, separated by comma, e.g. 1,2,3
--config_path CONFIG_PATH path to config.ini
--log_path LOG_PATH path to log dir
--log_level LOG_LEVEL log level (debug, info, error)
Mga posisyong argumento:
action
- kinakailangang aksyon:archive_table
,get_table_references
oget_rows_references
.
Mga pinangalanang argumento:
--config_path
β landas sa config file;--table
β isang talahanayan kung saan kailangan mong magsagawa ng isang aksyon;--ids
β listahan ng id na pinaghihiwalay ng mga kuwit, halimbawa,1,2,3
(opsyonal na parameter);--log_path
β landas sa folder para sa mga log (opsyonal na parameter, bilang default β folder ng bahay);--log_level
β antas ng pag-log (opsyonal na parameter, ang default ay INFO).
Mga halimbawa ng utos
Pag-archive ng talahanayan
Ang pangunahing pag-andar ng utility ay ang pag-archive ng data, i.e. paglilipat ng mga hilera mula sa pangunahing talahanayan patungo sa talahanayan ng archive (halimbawa, mula sa talahanayan mga libro Π² books_archive).
Sinusuportahan din ang pagtanggal nang walang pag-archive: para dito kailangan mong itakda ang parameter sa config.ini to_archive = mali).
Mga kinakailangang parameter - config_path, talahanayan at mga id.
Pagkatapos ng paglunsad, ang mga tala ay muling tatanggalin ids
sa mesa table
at sa lahat ng mga talahanayan na tumutukoy dito.
$ 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 - END
Maghanap ng mga dependency para sa isang tinukoy na talahanayan
Pag-andar upang mahanap ang mga dependency ng isang tinukoy na talahanayan table
. Mga kinakailangang parameter - config_path
ΠΈ table
.
Pagkatapos ng paglunsad, may ipapakitang diksyunaryo sa screen, kung saan:
in_refs
β isang diksyunaryo ng mga talahanayan na tumutukoy sa isang ibinigay, kung saan ang susi ay ang pangalan ng talahanayan, ang halaga ay isang listahan ng mga bagay na Foreign Key (pk_main
- pangunahing susi sa pangunahing talahanayan,pk_ref
- pangunahing susi sa reference table,fk_ref
β ang pangalan ng column na foreign key sa source table);out_refs
β isang diksyunaryo ng mga talahanayan na tinutukoy ng isang ito.
$ 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')]}}
Paghahanap ng mga sanggunian sa mga string na may tinukoy na Pangunahing Key
Function na maghanap ng mga row sa iba pang mga table na tumutukoy sa mga row sa pamamagitan ng Foreign Key ids
mga talahanayan table
. Mga kinakailangang parameter - config_path
, table
ΠΈ ids
.
Pagkatapos ng paglunsad, ang isang diksyunaryo na may sumusunod na istraktura ay ipapakita sa screen:
{
pk_id_1: {
reffering_table_name_1: {
foreign_key_1: [
{row_pk_1: value, row_pk_2: value},
...
],
...
},
...
},
pk_id_2: {...},
...
}
Halimbawa ng tawag:
$ 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'}]}}}
Paggamit sa code
Bilang karagdagan sa pagpapatakbo nito sa console, ang library ay maaaring gamitin sa Python code. Ang mga halimbawa ng mga tawag sa iPython interactive na kapaligiran ay ipinapakita sa ibaba.
Pag-archive ng talahanayan
>>> 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 - END
Maghanap ng mga dependency para sa isang tinukoy na talahanayan
>>> 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')]}}
Paghahanap ng mga sanggunian sa mga string na may tinukoy na Pangunahing Key
>>> 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'}]}}}
Available ang source code ng library sa
Ako ay natutuwa sa mga komento, pangako at mungkahi.
Susubukan kong sagutin ang mga tanong sa abot ng aking makakaya dito at sa repositoryo.
Pinagmulan: www.habr.com