on
Postgre를 사용하여 그래프 데이터베이스 및 API 만들기SQL 및 Node.js
Postgre를 사용하여 그래프 데이터베이스 및 API 만들기SQL 및 Node.js
반응형
네, "SQL"을 들을 때마다 가장 먼저 떠오르는 개념은 관계형 데이터베이스, 외부 키, 테이블 관리 등 컴퓨터 과학 초기에 배운 모든 것입니다. SQL이 관계형 데이터베이스를 관리하도록 설계되었기 때문에 각 데이터 추상화가 테이블로 표현되며 이러한 테이블 간의 관계는 일대일, 일대일 또는 다수 등 외부 키 제약에 의해 강화됩니다.
이 구조는 시간이 많이 걸렸지만 일부 프로젝트나 애플리케이션에서는 제대로 작동하지 않습니다. 우리가 소셜 미디어 프로젝트를 진행하고 있다고 가정해 봅시다. 관계형 패러다임으로 어떻게 모델을 만들 수 있을까요?
개인(ID, 이름, 이메일, ...) /* 개인 표 */ PersonFriends (id_1, id_2) /* 친구의 N-N 관계 */
사람이 친구를 추가할 때마다 이 관계는 양방향적이기 때문에 두 번 퍼스낼 프렌즈 테이블에 추가된다. 우리 데이터베이스에 닉과 멜리사라는 두 사람이 각각 1과 2의 ID를 가지고 있다고 가정해 봅시다.
PersonFriends 값('1', '2', '1')에 삽입합니다.
우리는 하나의 관계에만 두 개의 행을 사용하고 있는데, 왜 그렇게 하는 것일까요? 닉의 친구들을 얻고 싶을 때, 우리는 다음과 같이 한다.
PersonFriends에서 person2_id를 선택합니다. 어디에서 person1_id = 1; - 또는 WHERE 조항의 '2'를 사용하여 멜리사의 친구를 찾습니다.
이 관계를 나타내기 위해 하나의 행을 사용할 수도 있지만, 이렇게 하면 복잡하고 비효율적인 쿼리가 발생하므로 시간을 보존하기 위해 일부 공간을 희생할 수 있습니다.
이제 새로운 표에 좀 더 복잡한 내용을 추가해 보겠습니다.
그룹(ID, 이름)
이 경우 많은 사람과 많은 사람의 관계(또는 N-N)에 가입할 수 있는 그룹입니다. 한 그룹은 여러 사람을 가질 수 있지만 각 사람 은 여러 그룹의 구성원이 될 수도 있습니다.
해결책은 퍼스널프렌즈와 같은 전략을 실행하는 것이 될 것이기 때문에 우리는 다른 테이블이 필요하다.
사람이 그룹의 일원이면 포스트를 만들 수 있는 기능 등 모델 디자인은 모든 게 잘 돼간다. 이 경우 그룹에 속하는 게시물을 나열하는 또 다른 관계형 테이블을 만들어야 하지만 작성자의 외부 키(사용자)도 사용해야 합니다.
이게 어디로 가는지 알겠어? 모델의 수평적 확장은 우리에게 많은 문제를 일으키고 있습니다. 우리는 단지 이러한 문제들이 해결된 NoSQL 데이터베이스로 전환하거나 Neo4j나 Dgraph와 같은 네이티브 그래프 데이터베이스를 사용하기를 원할지도 모릅니다.
하지만 우리는 SQL을 사랑합니다. 왜냐하면 SQL은 고전적이고 오랜 기간 동안 표준이 되어왔기 때문입니다. 왜냐하면 SQL은 새로운 DBMS가 할 수 없는 멋진 것들을 할 수 있기 때문입니다. 그냥 좀 다르게 쓰자.
그래프 이론
그래프는 일반적으로 점과 선으로 만들어집니다. 점은 정점이라고 하며 선은 링크 또는 모서리로 알려져 있습니다. 응용 프로그램에서 정점은 개체이고 링크는 그 사이의 관계입니다.
소셜 네트워크의 예를 들어 간단한 그래프 모델을 만들어 보겠습니다.
그래프에는 인물, 그룹, 포스트 등 3가지 유형의 개체가 나온다. 이 경우 이러한 객체의 각 인스턴스는 다이어그램에 표시된 정점입니다.
우리는 닉이 한 그룹에 속해 있고 두 명의 친구가 있는 3명이고, 그 중 한 명은 닉이다.
간단해 보이지만 데이터베이스 관리 시스템에서 어떻게 구현할 수 있을까요? 좀 더 구체적으로 PostgreSQL을 사용하시겠습니까?
먼저 기초부터 적어보겠습니다. 그래프는 정점 및 링크라는 두 가지 주요 구성 요소로 구성됩니다.
꼭지점
CREATE TABLE 정점( ID UUID가 NULL 기본 키 기본값 gen_random_uuid()입니다. JSONB 필드가 NULL이 아닙니다. VARCHAR(255) 형식 not NULL, 생성_TASMAMPTZ NOW NULL DEFAUL NOW(), 업데이트_TASEMAMPTZ NOW NULL DEFAUL NOW() )
정점 테이블 필드:
이것은 DB의 모든 노드 또는 개체에 대한 범용 구조이므로 단일 노드 유형에서만 사용할 수 있는 필드는 추가하지 마십시오.
아직 쿼리를 실행하지 마십시오. 이 테이블의 파티션을 결정해야 합니다. 그 이유는 데이터베이스에 있는 우리의 모든 물건들은 버티스 테이블 안에 있게 되므로 시간이 지나면 매우 커질 것이기 때문에 우리는 버텍스 가 가지고 있는 물건의 유형 에 따라 테이블을 분할해야 한다.
여기서 흥미로운 점은 현재는 인물, 그룹, 포스트 등 3가지 유형을 선택할 수 있지만 앞으로는 구조에 지장을 주지 않고 각 유형별로 칸막이만 만들면 되기 때문에 수평적 확장성을 보장할 수 있다는 것이다.d have, 이 내용을 create table 문에 추가하고 기본 키를 수정해 봅시다.
CREATE TABLE 정점( ID UUID가 NULL DEFAULT gen_random_uuid()가 아닙니다. JSON 필드가 NULL이 아닙니다. VARCHAR(255) 형식 not NULL, 생성_TASMAMPTZ NOW NULL DEFAUL NOW(), 업데이트_TASEMAMPTZ NOW NULL DEFAUL NOW(), 제약 조건 pkey_vertices 기본 키(ID, 유형) ) 목록별 파티션(유형);
우리의 기본 키는 id 와 type 에서 구성되어야 한다. 이는 파티션 테이블의 고유한 제약 조건에 파티션 키 열이 모두 포함되어야 하기 때문입니다. 그렇지 않으면 오류가 발생합니다.
쿼리를 실행하면 버텍스 정의가 생성되었습니다. 이제 몇 가지 값을 삽입해 보겠습니다(스포일러 경고: 작동하지 않음).
정점에 삽입(유형, 필드) VALUES('person', '{"name": "Nick"}); ``` 다음과 같은 흥미로운 오류가 발생합니다. ```js 오류: 행에 대한 관계 "수직" 파티션을 찾을 수 없습니다. 세부 정보: 실패한 행의 파티션 키에는 (유형) = (사용자)가 포함되어 있습니다. ``` 이것은 우리가 이 테이블이 `유형` 리스트로 분할되고 있다고 정의했기 때문에 우리가 `인물` 유형에 대한 파티션을 만들지 않았음을 말해준다. 이제 파티션을 생성해 보겠습니다. ```js 테이블 정점 생성_인 값에 대한 정점의 파티션('사람'); ``` INSERT 쿼리를 다시 실행하십시오. ```js 정점(유형, 필드) 값에 삽입('person', '{"name": "Nick"}); INSERT 0 1 ``` 파티션 이름의 경우 `Vertice_ 또한 `Vertice_person` 테이블을 분할하려면 이 문서를 확인하십시오. 좋습니다. 이제 이 모델을 확장하는 것이 얼마나 쉬운지 보시죠. `Post` 유형에 맞는 파티션을 새로 추가해 보겠습니다. ```js 테이블 정점 생성_꼭지점 분할 위치('post'); ``` 그리고 게시물을 삽입하자. ```js 꼭지점(타입, 필드)에 값 삽입('post', '{"title", "My First Post", "content", "이것은 나의 첫 번째 게시물"})). ``` 다음 명령을 실행하면 `버티스` 표에 대한 세부 정보를 확인할 수 있습니다. ```js \d+ 꼭지점; ``` 아래에는 파티션에 대한 정보가 표시됩니다. ```js ... 파티션 키: LIST(유형) 인덱스: "pkey_vertice" 기본 키, btree(ID, 유형) 파티션: 정점_인 값('사람') 다음 값에 대한 정점_post('post') ``` 실제로 이 표를 일반 표처럼 쿼리할 수 있습니다. ```js SELECT ID FROM 정점_person; 이드 -------------------------------------- 4630ee69-32de-4127-b354-4216f2e25f9a (1열) ``` 또는 모든 `수직` 테이블을 쿼리하여 행이 속한 파티션을 찾을 수 있습니다. ```js SELECT tableoid::regclass, ID FROM 정점; 테이블로이드 -----------------+-------------------------------------- 꼭지점_person | 4630ee69-32de-4127-b354-4216f2e25f9a 꼭지점_post | ccf24086-c9a1-44b1-b3e6-42575b7bcd69 (2열) ``` 버텍스 구현이 완료되어 링크로 이동할 시간이 되었다고 말할 수 있습니다. # 링크스 잠시 그래프 이론으로 돌아가 봅시다. 링크는 두 꼭지점을 연결하는 선입니다. 이 링크는 단일 방향 또는 양방향일 수 있습니다. 예를 들어 사람이 쓴 포스트를 입수하려면 사람부터 그가 가진 포스트 하나까지, 한 포스트의 저자를 확보하려면 포스트에서 인물까지 또 다른 연결을 해야 하므로 양방향 링크가 된다. 링크의 주요 구성 요소를 알아보겠습니다. 물론, 우리는 기계 학습과 관련된 해결책에 사용될 무게와 같은 더 많은 정보를 추가할 수 있지만, 이 경우, 우리는 그것을 단순하게 유지하려고 노력하고 있다. 다음은 링크스 테이블에 대한 SQL 정의입니다. ```js CREATE TABLE 링크( 소스 UUID가 NULL이 아닙니다. 대상 UUID가 NULL이 아닙니다. 이름 VARCHAR(25)이 NULL이 아닙니다. 제약 조건 pkey_link 기본 키(소스, 대상, 이름) ); ``` 기본 키는 원본, 대상 및 이름으로 구성되므로 동일한 링크를 두 번 이상 사용할 수 없습니다. 파티셔닝은 포함하지 않았습니다. 기본 키를 사용하여 값에 항상 액세스할 수 있기 때문에 속도가 빠릅니다. 우리의 인물을 우리가 만든 포스트와 연결시켜 보자. ```js 링크 값에 삽입( '4630ee69-32de-4127-b354-4216f2e25f9a', -- 소스 ID 'ccf24086-c9a1-44b1-b3e6-42575b7bcd69', -- 목적지 ID 'posts' -- 링크 이름 ); ``` 우리는 이제 `사람`으로부터 `포스트` ID를 얻는 첫 번째 부분을 갖게 되었습니다. ```js 링크에서 대상 선택 WHERE 소스 = '4630ee69-32de-4127-b354-4216f2e25f9a' 및 이름 = '추적'; 증류 -------------------------------------- ccf24086-c9a1-44b1-b3e6-42575b7bcd69 ``` 우리는 이것을 우리가 찾고 있는 꼭지점을 찾기 위해 서브쿼리로 사용할 수 있다. ```js 선택 * 꼭짓점에서 WHER 유형 = '포스트' 및 ID 입력 ('4630ee69-32de-4127-b354-4216f2e25f9a' 및 이름 = '추적'에서 대상 선택) ``` 실제로 이 질문을 `설명`하여 무슨 일이 일어나고 있는지 알아보도록 하자. ```js 선택 설명 * 꼭짓점에서 유형 = '포스트' 및 ID 입력 ('4630ee69-32de-4127-b354-4216f2e25f9a' 및 이름 = '추적'에서 대상 선택) 중첩 루프(비용=0.29).16.34 행=1 폭=580) -> 링크상의 pkey_link를 이용한 Index Scan (비용=0.14.8.16 행=1 폭=16) 색인 조건: (출처 = '4630ee69-32de-4127-b354-4216f2e25f9a':201) 필터: (이름):text = 'filter':text) -> 꼭짓점에 있는 꼭지점_post_pkey를 사용하여 인덱스 스캔(비용=0.14.8.16 행=1 폭=580) 색인 조건: (id = link.dest) AND ((type):text = 'post':text) ``` 테이블 파티셔닝이 어떤 유형에 액세스하고 있는지 확인해 보십시오. 쿼리에 `WHERE 유형 = `post`를 사용하고 있기 때문입니다. 인덱스도 이 결과를 달성하는 데 도움이 됩니다. 이것은 나중에 Node.js를 통합할 때 훨씬 쉬워질 수 있는 Postgres 자체에서 이 작업을 수행하는 방법을 보여주기 위한 것입니다. 이제 `저자`라는 링크인 `Post to Person`의 역 접속을 추가해 보자. ```js 링크 값에 삽입('ccf24086-c9a1-44b1-b3e6-42575bcd69', '4630ee69-32de-4127-b354-42f2e25f9a', '저자'). ``` 이제 인물과 포스트는 양방향으로 연결된다. # API 이러한 유형의 응용 프로그램에는 API가 필요하며 다음을 보장합니다. 우리는 익스프레스 프레임워크와 함께 Node.js를 사용할 것입니다. 왜냐하면 API를 만드는 것이 매우 빠르기 때문입니다. 그리고 JSON 필드를 사용하고 있기 때문에 자바스크립트가 우리에게 많은 도움을 줄 것입니다. graph-api라는 폴더를 새로 만든 다음 Node.js 프로젝트를 초기화하고 필요한 종속성을 설치합니다. ```js mkdir graph-api cd graph-api 실 init ... 패키지가 성공적으로 저장되었습니다.제이슨 실 추가 익스프레스 pg ``` 이 코드를 프로젝트의 루트에 있는 새 `index.js`에 추가합니다. ```js const express = required species'; {Pool } = requiredpg' 구성; const app = express; // 구성에 따라 연결 조정 풀 = 새 풀({) 구성 사용자: 'postgres', host: 'localhost', 데이터베이스: 'postgres', 암호: 'mysecretpassword' 포트: 49153 }); app.facebooks', 비동기 () => { { row } = wait pool.query('SELECT * FROM Begets'); console.log(행); }); ``` `node index.js`를 실행하면 다음과 같은 결과를 얻을 수 있습니다. ```js [ { ID: '64b99562-5c03-461f-971e-915995845cab', 필드: { 이름: '닉' }, type: 'person', 생성됨: 2021-09-30T21:15:34.986Z, update_at: 2021-09-30T21:15:34.986Z }, { id: 'f6bfca20-ecb2-4a05-9e67-072252307f85', 필드: { 제목: '처음 게시물', 내용: '처음 게시물입니다' }, 입력: 'post', 생성됨: 2021-09-30T21:15:47.081Z, 업데이트_at: 2021-09-30T21:15:47.081Z } ] ``` `vertice.js`라고 하는 새로운 파일을 만들어 봅시다. 여기서 정점과 관련된 모든 함수를 정의하겠습니다. 보시다시피 `parseVertex`라는 함수도 포함했는데, 이 함수는 일반 SQL 행을 사용하고 필요한 속성을 가진 JSON 개체를 반환합니다. 정점 간의 링크도 처리할 수 있는 함수가 필요합니다. 다음은 API의 몇 가지 예입니다. ```js app.getdom/:id', 비동기(req, res) => { 상수 꼭지점 = 꼭지점 대기.getVertex(req.params.id) res.json(성공자); }) app.gets/:id/:link', 비동기(req, res) => { resp = 링크를 기다립니다.getLinks(req.params.id, req.params.link) res.json(존경); }) app.gets/:id/:link/:linkid', 비동기(req, res) => { resp = wait links.getLink(req.params.id, req.params.linkid, -req.params.linkid) res.json(존경); }) ... ``` …잘 아시겠죠. # 결론 축하합니다! 당신이 해냈어요! 정점 및 링크를 사용하여 데이터를 저장하는 완전히 다른 방법으로 이제 확장 가능한 데이터베이스 모델을 갖게 되었습니다! 음…거의 양방향 링크를 구현해야 하며, 링크를 삭제하기 위해 `Vertex`를 삭제할 때마다 트리거를 만들어야 합니다(데이터 유출이 발생하지 않음). 그러나 이 접근 방식의 주요 목표를 이해했습니다. Postgres의 그래프 데이터베이스 시스템에 대해 조금 알아 보셨기를 바랍니다. 댓글로 궁금한 점이나 건설적인 비판, 제가 뭔가 놓쳤거나 데이터를 저장하는 더 나은 방법을 아시는 분이라면 언제든지 문의해 주십시오. 아이디어가 너무 많은 아이디어가 있습니다.
from http://sup-poster.tistory.com/19 by ccl(A) rewrite - 2021-10-10 05:26:59