쿼리 튜닝#

Cypher 쿼리는 PostgreSQL의 플래너가 계획합니다. 자기 규칙을 가진 별도의 그래프 엔진이 없으므로, EXPLAIN이 동작하고, 통계가 중요하며, 이미 알고 있는 튜닝이 그대로 적용됩니다.

AgensGraph에 특유한 부분은 순회를 위한 몇 개의 실행 계획 노드, 새 그래프마다 반드시 걸리는 통계 문제 하나, 그리고 실행 계획의 모양을 바꾸는 설정 다섯 개입니다.

이 페이지는 실행 계획의 모양만 다룹니다. 자원 쪽 튜닝은 shared_buffers, work_mem, effective_cache_size, 병렬성, autovacuum을 다루며, 그중 그래프에 특유한 것은 없고 구성에 있습니다.

측정할 그래프#

예제 데이터셋은 요소가 46개여서, 플래너가 흥미로운 결정을 내리기에는 부족합니다. 차수 분포가 있는 것을 만들고 끝에서 지웁니다:

CREATE TABLE seed_account AS SELECT g AS n, 'user' || g AS handle FROM generate_series(1, 20000) g;
CREATE TABLE seed_follows AS SELECT (1 + (g % 20000)) AS src, (1 + ((g * 7919) % 20000)) AS dst FROM generate_series(1, 120000) g;
CREATE GRAPH perflab;
SET graph_path = perflab;
CREATE VLABEL account;
CREATE ELABEL follows;
LOAD FROM seed_account AS r CREATE (:account {handle: r.handle, n: r.n});
CREATE PROPERTY INDEX ON account (handle);
LOAD FROM seed_follows AS r MATCH (a:account {n: r.src}), (b:account {n: r.dst}) CREATE (a)-[:follows]->(b);
ANALYZE perflab.account;

계정 20,000개와 follows 간선 120,000개입니다. 정점 레이블만 분석했습니다. 그 누락이 뒤에 나오는 절의 요점입니다.

Cypher의 EXPLAIN 읽기#

EXPLAIN (COSTS OFF) MATCH (a:account {handle: 'user42'})-[r1:follows]->(b:account)-[r2:follows]->(c:account) RETURN c.handle;
                                                                                                 QUERY PLAN                                                                                                 
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Hash Join
   Hash Cond: (r2."end" = c.id)
   ->  Nested Loop
         ->  Nested Loop
               ->  Index Scan using account_handle_idx on account a
                     Index Cond: (properties.'handle' = '"user42"'::jsonb)
               ->  Bitmap Heap Scan on follows r1
                     Recheck Cond: ((a.id = start) AND (start >= '3.0'::graphid) AND (start <= '3.281474976710655'::graphid) AND ("end" >= '3.0'::graphid) AND ("end" <= '3.281474976710655'::graphid))
                     ->  Bitmap Index Scan on follows_start_idx
                           Index Cond: ((start = a.id) AND (start >= '3.0'::graphid) AND (start <= '3.281474976710655'::graphid) AND ("end" >= '3.0'::graphid) AND ("end" <= '3.281474976710655'::graphid))
         ->  Index Scan using follows_start_idx on follows r2
               Index Cond: ((start = r1."end") AND (start >= '3.0'::graphid) AND (start <= '3.281474976710655'::graphid) AND ("end" >= '3.0'::graphid) AND ("end" <= '3.281474976710655'::graphid))
               Filter: (r1.id <> id)
   ->  Hash
         ->  Seq Scan on account c

위에서 AgensGraph에 특유한 것은 네 가지이고, 나머지는 전에 읽어 본 적 있는 실행 계획입니다.

속성 읽기는 properties.'key'로 출력됩니다. 그것이 Cypher 파서가 속성 접근을 표기하는 방식이며, 직접 입력할 수 있는 SQL이 아닙니다. Index Cond에 있으면 표현식 인덱스와 맞춰지고 있는 것입니다. Filter에 있으면 행마다 평가되고 있는 것입니다. 둘 중 어느 쪽을 받는지가 인덱스 페이지의 주제입니다.

graphid 범위 검사는 레이블 검사입니다. start >= '3.0' AND start <= '3.281474976710655'은 “간선의 이쪽 끝에 있는 정점이 account다”라는 뜻입니다. 레이블 id는 graphid의 상위 비트에 있으므로 한 레이블의 id는 연속된 구간을 이루고, 그 검사는 간선 자신의 컬럼에서 답할 수 있습니다. 식별자: graphid를 참고하십시오.

두 번째 간선에 붙은 Filter: (r1.id <> id) 는 고유성 규칙입니다. 어떤 경로도 한 간선을 두 번 쓸 수 없습니다.

이름을 붙이지 않은 패턴 요소에는 합성 별칭이 주어지고, "<0000000002>" 처럼 출력됩니다. 그 뒤의 카운터는 문장마다 올라가므로, 그것이 들어 있는 실행 계획은 실행할 때마다 달라집니다. 기준선과 비교하려는 계획이라면 패턴의 모든 요소에 이름을 붙이십시오.

EXPLAIN (ANALYZE)도 동작하며, 추정치를 실제와 견주어 볼 수 있는 유일한 방법입니다:

EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF, BUFFERS OFF)
MATCH (a:account {handle: 'user42'})-[r1:follows]->(b:account) RETURN count(b);
                                                                                                 QUERY PLAN                                                                                                 
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Aggregate (actual rows=1.00 loops=1)
   ->  Nested Loop (actual rows=6.00 loops=1)
         ->  Nested Loop (actual rows=6.00 loops=1)
               ->  Index Scan using account_handle_idx on account a (actual rows=1.00 loops=1)
                     Index Cond: (properties.'handle' = '"user42"'::jsonb)
                     Index Searches: 1
               ->  Bitmap Heap Scan on follows r1 (actual rows=6.00 loops=1)
                     Recheck Cond: ((a.id = start) AND (start >= '3.0'::graphid) AND (start <= '3.281474976710655'::graphid) AND ("end" >= '3.0'::graphid) AND ("end" <= '3.281474976710655'::graphid))
                     Heap Blocks: exact=1
                     ->  Bitmap Index Scan on follows_start_idx (actual rows=6.00 loops=1)
                           Index Cond: ((start = a.id) AND (start >= '3.0'::graphid) AND (start <= '3.281474976710655'::graphid) AND ("end" >= '3.0'::graphid) AND ("end" <= '3.281474976710655'::graphid))
                           Index Searches: 6
         ->  Index Only Scan using account_pkey on account b (actual rows=1.00 loops=6)
               Index Cond: (id = r1."end")
               Heap Fetches: 6
               Index Searches: 6

참고

가변 길이 계획과 최단 경로 계획은 기본 텍스트 형식으로 읽으십시오. 셋 중 EXPLAIN (FORMAT JSON)으로도 계획이 제대로 읽히는 것은 Dijkstra입니다.

순회를 위한 실행 계획 노드#

길이가 고정된 패턴에는 특별한 노드가 필요하지 않습니다. 위에서 본 것처럼 스캔과 조인입니다. 아래 노드들은 SQL에 대응 연산자가 없는 것들을 위해 나타납니다.

Graph VLE는 가변 길이 간선이며, 간선 레이블과 범위를 이름으로 보여 줍니다:

SET graph_path = moviekb;
EXPLAIN (COSTS OFF) MATCH (a:movie {title: 'The Matrix'})-[r:related_to*1..3]->(b:movie) RETURN b.title;
                          QUERY PLAN                          
--------------------------------------------------------------
 Nested Loop
   ->  Seq Scan on movie a
         Filter: (properties.'title' = '"The Matrix"'::jsonb)
   ->  Hash Join
         Hash Cond: (r."end" = b.id)
         ->  Subquery Scan on r
               ->  Graph VLE on related_to [1..3]
                     ->  Result
         ->  Hash
               ->  Seq Scan on movie b

읽어야 할 줄은 on related_to입니다. 타입 없는 가변 길이 간선은 Graph VLE on ag_edge가 되고, 그것은 매 홉마다 그래프의 모든 간선 레이블을 훑습니다. 경로 쿼리에서 피할 수 있는 가장 큰 비용이 그것입니다. 비교는 경로에 있습니다.

Shortestpath VLE [min..max]는 shortestpath 또는 allshortestpaths이며, Hash2Side 노드 두 개 위에 놓입니다. 그 두 노드는 양쪽 끝에서 각각 자라나는 양방향 탐색의 두 전선입니다.

Dijkstra는 가중 탐색입니다. 그 자식이 확장에 쓰는 스캔이므로, 그것이 훑는 것은 간선 레이블의 start 컬럼에 걸린 인덱스입니다.

Graph Create, Graph Set, Graph Delete가 쓰기 노드입니다:

SET graph_path = perflab;
EXPLAIN (COSTS OFF) MATCH (a:account {handle: 'user42'}) SET a.seen = 1;
                          QUERY PLAN                           
---------------------------------------------------------------
 Graph Set
   ->  Index Scan using account_handle_idx on account a
         Index Cond: (properties.'handle' = '"user42"'::jsonb)

eager 로 표시된 쓰기 노드는 그 위의 어떤 것이 실행되기 전에 끝나야 합니다. 그 위의 무언가가 그 쓰기가 바꾼 것을 읽기 때문입니다:

EXPLAIN (COSTS OFF)
MATCH (a:account {handle: 'user42'}) SET a.seen = 1
WITH a MATCH (a)-[r:follows]->(b:account) RETURN count(b);
                                                                  QUERY PLAN                                                                   
-----------------------------------------------------------------------------------------------------------------------------------------------
 Aggregate
   ->  Nested Loop
         ->  Nested Loop
               ->  Subquery Scan on _agens_default_s
                     ->  Graph Set eager
                           ->  Index Scan using account_handle_idx on account a
                                 Index Cond: (properties.'handle' = '"user42"'::jsonb)
               ->  Index Only Scan using follows_start_idx on follows r
                     Index Cond: ((start = (_agens_default_s.a).id) AND ("end" >= '3.0'::graphid) AND ("end" <= '3.281474976710655'::graphid))
         ->  Index Only Scan using account_pkey on account b
               Index Cond: (id = r."end")

같은 문장 안의 뒤따르는 읽기에 쓰기가 보이게 만드는 것이 이 eagerness입니다. 대가는 쓰기 출력의 구체화이고, 그 크기는 eager_mem으로 제한됩니다. MERGE는 항상 eager이며, 문장의 마지막 절이 아닌 SET·REMOVE·DELETE도 그렇습니다.

_agens_default_s로 시작하는 이름은 파이프라인 단계에 자동으로 붙은 별칭이고, 대략 WITH 하나에 하나씩입니다. 한 실행 계획 안에서는 안정적이며 튜닝할 대상이 아닙니다.

간선 레이블의 통계#

새 그래프에서 가장 먼저 확인할 것이 이것이고, 어떤 인덱스보다 값어치가 큽니다.

순회의 비용은 차수의 곱입니다. 출발한 정점에서 나가는 간선이 몇 개인지, 도달한 정점에서 나가는 간선이 몇 개인지, 그런 식으로 이어집니다. 간선 레이블의 start와 end 컬럼에 대한 통계가 그 차수를 설명합니다. AgensGraph는 레이블을 만들 때 두 컬럼의 통계 목표를 최댓값인 1,000으로 설정합니다:

SELECT attname, attstattarget FROM pg_attribute
WHERE attrelid = 'perflab.follows'::regclass AND attnum > 0 ORDER BY attnum;
  attname   | attstattarget 
------------+---------------
 id         |              
 start      |          1000
 end        |          1000
 properties |              

목표는 표본이 아닙니다. 무언가가 그 레이블을 분석하기 전까지는 아무것도 없습니다:

SELECT count(*) AS stats_rows FROM pg_stats WHERE schemaname = 'perflab' AND tablename = 'follows';
 stats_rows 
------------
          0

그리고 추정치는 짐작입니다. 갓 적재한 그래프에서 두 홉을 세어 보면 이렇습니다:

EXPLAIN MATCH (a:account {handle: 'user42'})-[r1:follows]->(b:account)-[r2:follows]->(c:account) RETURN count(c);
                                                                                                    QUERY PLAN                                                                                                    
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=10088.00..10088.01 rows=1 width=32)
   ->  Hash Join  (cost=677.81..9622.10 rows=186358 width=8)
         Hash Cond: (r2."end" = c.id)
         ->  Nested Loop  (cost=20.81..8475.81 rows=186358 width=24)
               ->  Nested Loop  (cost=20.39..779.08 rows=433 width=16)
                     ->  Index Scan using account_handle_idx on account a  (cost=0.29..8.30 rows=1 width=8)
                           Index Cond: (properties.'handle' = '"user42"'::jsonb)
                     ->  Bitmap Heap Scan on follows r1  (cost=20.10..766.44 rows=433 width=24)
                           Recheck Cond: ((a.id = start) AND (start >= '3.0'::graphid) AND (start <= '3.281474976710655'::graphid) AND ("end" >= '3.0'::graphid) AND ("end" <= '3.281474976710655'::graphid))
                           ->  Bitmap Index Scan on follows_start_idx  (cost=0.00..20.00 rows=433 width=0)
                                 Index Cond: ((start = a.id) AND (start >= '3.0'::graphid) AND (start <= '3.281474976710655'::graphid) AND ("end" >= '3.0'::graphid) AND ("end" <= '3.281474976710655'::graphid))
               ->  Index Scan using follows_start_idx on follows r2  (cost=0.42..13.48 rows=430 width=24)
                     Index Cond: ((start = r1."end") AND (start >= '3.0'::graphid) AND (start <= '3.281474976710655'::graphid) AND ("end" >= '3.0'::graphid) AND ("end" <= '3.281474976710655'::graphid))
                     Filter: (r1.id <> id)
         ->  Hash  (cost=407.00..407.00 rows=20000 width=8)
               ->  Seq Scan on account c  (cost=0.00..407.00 rows=20000 width=8)

계정 하나에서 나가는 간선 433개, 두 홉 쌍 186,358개, 그리고 먼 쪽 끝점을 해결하기 위해 계정 20,000개 전체에 대한 해시입니다. 이제 간선 레이블을 분석합니다:

ANALYZE perflab.follows;
EXPLAIN MATCH (a:account {handle: 'user42'})-[r1:follows]->(b:account)-[r2:follows]->(c:account) RETURN count(c);
                                                                                                    QUERY PLAN                                                                                                    
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=51.17..51.18 rows=1 width=32)
   ->  Nested Loop  (cost=5.52..51.08 rows=36 width=8)
         ->  Nested Loop  (cost=5.23..39.78 rows=36 width=24)
               ->  Nested Loop  (cost=4.81..35.54 rows=6 width=16)
                     ->  Index Scan using account_handle_idx on account a  (cost=0.29..8.30 rows=1 width=8)
                           Index Cond: (properties.'handle' = '"user42"'::jsonb)
                     ->  Bitmap Heap Scan on follows r1  (cost=4.52..27.18 rows=6 width=24)
                           Recheck Cond: ((a.id = start) AND (start >= '3.0'::graphid) AND (start <= '3.281474976710655'::graphid) AND ("end" >= '3.0'::graphid) AND ("end" <= '3.281474976710655'::graphid))
                           ->  Bitmap Index Scan on follows_start_idx  (cost=0.00..4.52 rows=6 width=0)
                                 Index Cond: ((start = a.id) AND (start >= '3.0'::graphid) AND (start <= '3.281474976710655'::graphid) AND ("end" >= '3.0'::graphid) AND ("end" <= '3.281474976710655'::graphid))
               ->  Index Scan using follows_start_idx on follows r2  (cost=0.42..0.65 rows=6 width=24)
                     Index Cond: ((start = r1."end") AND (start >= '3.0'::graphid) AND (start <= '3.281474976710655'::graphid) AND ("end" >= '3.0'::graphid) AND ("end" <= '3.281474976710655'::graphid))
                     Filter: (r1.id <> id)
         ->  Index Only Scan using account_pkey on account c  (cost=0.29..0.31 rows=1 width=8)
               Index Cond: (id = r2."end")

간선 6개, 쌍 36개, 먼 쪽 끝점에는 인덱스 전용 스캔, 그리고 추정 비용은 10,088 대신 51입니다. 그리고 36은 정확히 맞습니다:

MATCH (a:account {handle: 'user42'})-[r1:follows]->(b:account)-[r2:follows]->(c:account) RETURN count(c) AS pairs;
 pairs 
-------
    36

여기서 따라오는 규칙은 이것입니다. 대량 적재 뒤에는 정점 레이블만이 아니라 간선 레이블에도 ANALYZE를 돌리십시오. autovacuum도 언젠가는 하지만, 그 “언젠가”는 첫 사용자들이 이미 그 쿼리를 실행한 뒤입니다. 허브 정점이 들어와 차수의 모양이 바뀐 그래프라면, 행 수 임계치를 기다리지 말고 명시적으로 다시 분석할 만합니다.

실행 계획의 모양을 바꾸는 설정#

그래프 쿼리가 어떻게 계획되는지에 영향을 주는 설정이 다섯 개 있습니다. 아래는 pg_settings에서 읽은 실제 값입니다:

설정

기본값

범위

하는 일

enable_eager

on

세션

끄면, eager 계획이 필요한 문장은 계획되지 않고 거부됩니다.

eager_mem

4 MB

세션

eager 노드가 디스크로 넘기기 전에 쓸 수 있는 메모리입니다.

enable_graph_endpoint_elision

on

세션

쿼리가 값을 전혀 읽지 않는 패턴 끝점의 스캔을 없앱니다.

enable_property_promotion

on

세션

승격된 타입 컬럼이 있으면 Cypher의 속성 읽기를 그 컬럼으로 해석합니다.

auto_gather_graphmeta

off

슈퍼유저

ag_graphmeta의 간선 연결 정보를 유지하며, 그것으로 플래너가 도달할 수 없는 레이블을 스캔에서 잘라 냅니다.

참고

postgresql.conf.sample은 enable_eager와 enable_multiple_update의 기본값을 틀리게 적어 놓았습니다. 둘 다 켜져 있습니다. 샘플 파일이 아니라 pg_settings를 읽으십시오.

이 중 어느 것도 운영에서 돌릴 손잡이가 아닙니다. 네 개는 이미 켜져 있고, 다섯째는 카탈로그를 유지할지에 대한 결정입니다. 진단을 위해 알아 두십시오. 하나를 끄면 그것이 예상하지 못한 실행 계획의 원인인지 알 수 있습니다.

끝점 생략#

쿼리가 값을 전혀 읽지 않는 끝점은 아예 스캔되지 않습니다. 여기서 b는 바인딩되고 레이블도 붙었지만 투영되지 않습니다:

EXPLAIN (COSTS OFF) MATCH (a:account)-[r:follows]->(b:account) RETURN a.handle;
                                                                            QUERY PLAN                                                                             
-------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Hash Join
   Hash Cond: (r.start = a.id)
   ->  Seq Scan on follows r
         Filter: (("end" >= '3.0'::graphid) AND ("end" <= '3.281474976710655'::graphid) AND (start >= '3.0'::graphid) AND (start <= '3.281474976710655'::graphid))
   ->  Hash
         ->  Seq Scan on account a

조인 하나이고, (b:account)는 간선의 "end" 컬럼에 대한 범위 검사로만 남습니다. 이 생략을 끄면 두 번째 스캔과 조인이 돌아옵니다:

SET enable_graph_endpoint_elision = off;
EXPLAIN (COSTS OFF) MATCH (a:account)-[r:follows]->(b:account) RETURN a.handle;
RESET enable_graph_endpoint_elision;
                                                                               QUERY PLAN                                                                                
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Hash Join
   Hash Cond: (r."end" = b.id)
   ->  Hash Join
         Hash Cond: (r.start = a.id)
         ->  Seq Scan on follows r
               Filter: ((start >= '3.0'::graphid) AND (start <= '3.281474976710655'::graphid) AND ("end" >= '3.0'::graphid) AND ("end" <= '3.281474976710655'::graphid))
         ->  Hash
               ->  Seq Scan on account a
   ->  Hash
         ->  Seq Scan on account b

교훈은 그 설정에 관한 것이 아닙니다. 필요하지 않은 끝점을 투영하면 매번 스캔 하나와 조인 하나의 비용이 듭니다. 그러므로 RETURN은 호출하는 쪽이 읽는 것으로 한정하십시오.

ag_graphmeta로 스캔 잘라 내기#

ag_graphmeta는 그래프에 실제로 나타나는 (시작 레이블, 간선 레이블, 끝 레이블) 세 쌍마다 한 행을 담고, 그것을 이루는 간선이 몇 개인지도 함께 담습니다. 그것이 있으면 레이블 없는 패턴 끝점을 그 간선이 도달한다고 알려진 레이블로 좁힐 수 있습니다. 이 정보는 auto_gather_graphmeta가 켜져 있을 때만 유지되므로, 기본적으로 레이블 없는 끝점은 ag_vertex 계열 전체를 스캔합니다:

EXPLAIN (COSTS OFF) MATCH (n)-[r:follows]->(m) RETURN n, m;
                    QUERY PLAN                     
---------------------------------------------------
 Hash Join
   Hash Cond: (m.id = r."end")
   ->  Append
         ->  Seq Scan on ag_vertex m_1
         ->  Seq Scan on account m_2
   ->  Hash
         ->  Hash Join
               Hash Cond: (n.id = r.start)
               ->  Append
                     ->  Seq Scan on ag_vertex n_1
                     ->  Seq Scan on account n_2
               ->  Hash
                     ->  Seq Scan on follows r

수집을 켜면 Append 둘이 접혀 사라집니다. 수집은 데이터베이스의 모든 그래프에 대해 기준선을 만들므로, 의도를 갖고 하십시오:

SET auto_gather_graphmeta = on;
EXPLAIN (COSTS OFF) MATCH (n)-[r:follows]->(m) RETURN n, m;
RESET auto_gather_graphmeta;
               QUERY PLAN                
-----------------------------------------
 Hash Join
   Hash Cond: (r."end" = m.id)
   ->  Hash Join
         Hash Cond: (r.start = n.id)
         ->  Seq Scan on follows r
         ->  Hash
               ->  Seq Scan on account n
   ->  Hash
         ->  Seq Scan on account m

follows는 언제나 account → account로만 이어지므로, 레이블 없는 두 끝점은 계정일 수밖에 없습니다. 레이블이 많은 그래프에서는 이것이 끝점마다 스캔 하나와 레이블마다 스캔 하나의 차이입니다. 대가는 간선 쓰기마다 드는 유지 비용, 그리고 기준선이 무효화될 때마다 드는 재스캔입니다.

이 예제에는 레이블이 둘뿐이므로 여기서의 절약은 작습니다. 요점은 그 모양입니다. pg_catalog.ag_graphmeta가 그 개수를 담고 ag_graph.graphmeta_valid가 그 사용을 통제합니다. 그래프 메타데이터를 참고하십시오.

가장 단순한 대안은 패턴에 레이블을 이름으로 쓰는 것입니다. MATCH (n:account)-[r:follows]->(m:account)에는 카탈로그가 전혀 필요하지 않습니다.

eager 계획 금지하기#

enable_eager = off는 다른 실행 계획을 만들지 않습니다. 계획을 만들기를 거부합니다:

SET enable_eager = off;
EXPLAIN (COSTS OFF)
MATCH (a:account {handle: 'user42'}) SET a.seen = 1
WITH a MATCH (a)-[r:follows]->(b:account) RETURN count(b);
RESET enable_eager;
ERROR:  eagerness plan is not allowed.

작업 부하 안에서 어느 문장이 eagerness에 의존하는지 알아내는 데 쓰십시오. 이것을 끈 채로 운영하지는 마십시오. 그런 문장에서 eagerness는 비용의 선택이 아니라 정확성의 요구 조건이므로, 되돌아갈 더 값싼 계획이 없고 서버는 대신 오류를 냅니다.

승격된 컬럼으로의 해석#

enable_property_promotion = off은 타입 컬럼이 있는 곳에서도 속성 읽기를 jsonb 컬럼인 properties로 되돌려 보냅니다. 실행 계획은 바뀌고 답은 바뀌지 않습니다. 값이 양쪽에 다 있기 때문입니다. 컬럼을 지우지 않고도 예상하지 못한 실행 계획의 배후가 승격인지 확인하는 방법입니다. 전과 후는 해석 기능 끄기에 있습니다.

무엇을 어떤 순서로 할까#

  1. 쿼리가 닿는 모든 레이블에 ANALYZE를 돌리십시오. 간선 레이블도 포함합니다. 이것을 하기 전까지는 이 목록의 다른 어떤 것도 의미가 없습니다.

  2. 고정점을 확인하십시오. 순회에는 선택도가 높고 인덱스가 걸린 출발점 하나가 필요합니다. 그 출발점을 골라 내는 속성에 대해 EXPLAIN이 Filter가 아니라 Index Cond를 보여 줘야 합니다 — 어떤 조건이 그럴 수 있는지는 인덱스와 제약이 말해 줍니다.

  3. 모든 가변 길이 패턴에 간선 레이블을 이름으로 쓰십시오. 실행 계획에 나온 Graph VLE on ag_edge는 플래너가 아니라 쿼리를 가리킵니다.

  4. 가변 길이 패턴의 길이에 상한을 두십시오.

  5. 호출하는 쪽이 읽는 것만 투영하십시오. 그러면 쓰지 않는 끝점이 생략됩니다.

  6. 늘 필터·조인·정렬에 쓰는 속성의 승격을 검토하십시오 — 속성을 컬럼으로 승격하기.

  7. 그러고 나서 자원을 보십시오. 구성.

정리#

DROP GRAPH perflab CASCADE;
DROP TABLE seed_account;
DROP TABLE seed_follows;

다음에 갈 곳#