Jah, seda koodi saab kindlasti kiirendada ja optimeerida, eriti kuna tegemist on keeruka ruumilise JOIN-iga, mida tehakse mitmekordselt igas batch’is. Siin on kõige olulisemad punktid, kuidas seda kiiremaks ja tõhusamaks muuta:
nodes tabelit igas batch’isIgas psql -c käsus tehakse:
nodes AS (SELECT * FROM ${pk}.nodes),
See tähendab, et kogu nodes tabel laetakse mällu iga 10 000 rea kaupa – see on ebavajalik kulu. Tee sellest ajutine temp tabel varem:
psql ${CONNECTION_NAME} -c "DROP TABLE IF EXISTS tmp_nodes;
CREATE TEMP TABLE tmp_nodes AS SELECT id, geom FROM ${pk}.nodes;
CREATE INDEX ON tmp_nodes USING GIST (geom);"
Ja siis asenda igas batch’is:
nodes AS (SELECT * FROM tmp_nodes)
-- või lihtsalt viita otse: tmp_nodes
ST_StartPoint(...) ja ST_EndPoint(...) arvutatakse iga JOIN puhul uuesti, iga kord. See on aeglane.
Lahendus: arvuta need korra ette ja salvesta ajutiselt:
psql ${CONNECTION_NAME} -c "DROP TABLE IF EXISTS tmp_link_points;
CREATE TEMP TABLE tmp_link_points AS
SELECT id, ST_StartPoint(geom) AS pt_start, ST_EndPoint(geom) AS pt_end, geom
FROM ${pk}.links;
CREATE INDEX ON tmp_link_points USING GIST (pt_start);
CREATE INDEX ON tmp_link_points USING GIST (pt_end);"
Ja siis batch'i sees tee näiteks:
WITH link_batch AS (
SELECT * FROM tmp_link_points WHERE id BETWEEN $i AND $END
),
link_data AS (
SELECT
l.id,
n1.id AS source,
n2.id AS target,
ST_Length(l.geom) AS cost
FROM link_batch l
JOIN tmp_nodes n1 ON ST_DWithin(l.pt_start, n1.geom, 0.01) AND ST_Distance(l.pt_start, n1.geom) = 0
JOIN tmp_nodes n2 ON ST_DWithin(l.pt_end, n2.geom, 0.01) AND ST_Distance(l.pt_end, n2.geom) = 0
)
UPDATE ${pk}.links l
SET source = d.source,
target = d.target,
cost = ROUND(d.cost::numeric, 1)
FROM link_data d
WHERE l.id = d.id;
psql sessioonisKui võimalik, tee kogu batchimine PostgreSQLi enda sees PL/pgSQL’ga või DO $$ BEGIN ... END $$. Aga isegi Bashis saad sa vältida iga 10k kaupa uut psql protsessi, kui kasutad nt:
psql ${CONNECTION_NAME} <<EOF
-- kõik 5 batchi siia
EOF
Kui Sul on mitu südamikku (CPU), saad paralleelselt töödelda nt:
parallel -j 4 "
psql ${CONNECTION_NAME} -c \"
-- sama batchipõhine SQL siia
\"
" ::: $(seq 1 $BATCH_SIZE $MAX_ID)
Aga siin pead olema ettevaatlik, et paralleelsed update'id ei läheks konflikti (soovitan ainult siis, kui tabelil on sobivad indeksid ja väldid samade id väärtuste kattumist).
| Võte | Mõju |
|---|---|
tmp_nodes ja tmp_link_points tabelid |
korduv SELECT → 1x SELECT |
ST_StartPoint ja ST_EndPoint eelnevalt arvutatud |
väldib 2x geom-funktsiooni iga JOIN-i korral |
Vähem psql kutseid |
väldib protsessikulu ja I/O overheadi |
| Paralleeltäitmine (vajadusel) | kiirem suurte andmehulkade korral |
Soovi korral võin kõik selle koosmõjus ümber kirjutada täpse, optimeeritud bash-skriptina. Kas tahad?
Väga hea – siin on optimeeritud bash-skript, mis:
valmistab ette tmp_nodes ja tmp_link_points (eelarvutatud geomeetriad ja GIST-indeksid),
töötleb links tabeli ridu batchide kaupa (10 000 kaupa),
uuendab source, target ja cost veerud kiirendatud kujul,
kasutab ainult ühte psql sessiooni (vähem süsteemikulusid).
update_links_sources_targets_costs.sh#!/bin/bash
# Määrangud
CONNECTION_NAME="postgresql://user:password@localhost/dbname"
SCHEMA="your_schema" # näiteks: public
BATCH_SIZE=10000
echo "🔄 Alustame ettevalmistusega..."
psql "${CONNECTION_NAME}" -v schema="${SCHEMA}" <<EOF
-- 1. Eeltöötlus: arvuta tippude ja lõikude punktid
DROP TABLE IF EXISTS tmp_nodes;
CREATE TEMP TABLE tmp_nodes AS
SELECT id, geom FROM :schema.nodes;
CREATE INDEX ON tmp_nodes USING GIST (geom);
DROP TABLE IF EXISTS tmp_link_points;
CREATE TEMP TABLE tmp_link_points AS
SELECT id, ST_StartPoint(geom) AS pt_start, ST_EndPoint(geom) AS pt_end, geom
FROM :schema.links;
CREATE INDEX ON tmp_link_points USING GIST (pt_start);
CREATE INDEX ON tmp_link_points USING GIST (pt_end);
EOF
# Leia maksimaalne ID
MAX_ID=$(psql "${CONNECTION_NAME}" -tA -c "SELECT max(id) FROM ${SCHEMA}.links;")
echo "📏 MAX_ID = $MAX_ID"
# 2. Batch processing
i=1
while [ "$i" -le "$MAX_ID" ]; do
END=$((i + BATCH_SIZE - 1))
echo "🚀 Töötlen batch: $i - $END"
psql "${CONNECTION_NAME}" -v schema="${SCHEMA}" -v i="${i}" -v end="${END}" <<EOF
WITH link_batch AS (
SELECT * FROM tmp_link_points WHERE id BETWEEN :i AND :end
),
link_data AS (
SELECT
l.id,
n1.id AS source,
n2.id AS target,
ST_Length(l.geom) AS cost
FROM link_batch l
JOIN tmp_nodes n1
ON ST_DWithin(l.pt_start, n1.geom, 0.01)
AND ST_Distance(l.pt_start, n1.geom) = 0
JOIN tmp_nodes n2
ON ST_DWithin(l.pt_end, n2.geom, 0.01)
AND ST_Distance(l.pt_end, n2.geom) = 0
)
UPDATE ${SCHEMA}.links l
SET source = d.source,
target = d.target,
cost = ROUND(d.cost::numeric, 1)
FROM link_data d
WHERE l.id = d.id;
EOF
i=$((END + 1))
done
echo "✅ Valmis!"
Muuda skriptis:
CONNECTION_NAME – sobivaks PostgreSQL ühendusstringiks
SCHEMA – nt public, mydata, vms
Salvesta faili: update_links_sources_targets_costs.sh
Tee käivitatavaks:
chmod +x update_links_sources_targets_costs.sh
Käivita:
./update_links_sources_targets_costs.sh
| Parandus | Tulem |
|---|---|
| Temp-tabelid | Ei lae nodes ja links uuesti iga kord |
| Eelarvutatud punktid | Ei kordu ST_StartPoint() |
| Ruumiindeksid | Kiirem JOIN |
| Batching | Vähem mälu, väiksemad transaktsioonid |
Üks psql per batch |
Vähem süsteemikoormust |
Kui soovid lisada ka paralleeltöötlust GNU parallel abil või täiendada teise andmeväljaga (nt travel_time, is_main_road), saan kohe ka selle lisada. Kas soovid?