1.概念
1.1 XID <-> UID
所有實體會被分配一個唯一的64位整型id,即UID。如果某個尸體有一個外部id(external id),即XID,DGraph會取出XID的指紋,并保存對應的UID。如果某個實體沒有XID,DGraph只會添加一個新的UID,并標明這個UID已用。所有的posting list都通過UID引用實體。由于UID是8字節(jié)的整數,這種數據表示非常高效。在數據導入的時候,我們需要每個唯一的實體有一個唯一的UID。
1.2 Edges
典型的數據格式是RDF NQuad:
- 主語(Subject), 謂語(Predicate), 賓語(Object), 標簽(Label), aka
- 實體(Entity), 屬性(Attribute), 另一個實體或值(Other Entity / Value),標簽(Label)
兩種專門用語在Dgraph的代碼里可以交替使用。Dgraph的edge是有向的,例如Subject -> Object。這也是查詢執(zhí)行的方向
可以自動生成一個反向的邊。如果用戶想按相反的方向執(zhí)行查詢,需要將reverse edge定義為schema的一部分
在Dgraph內部,RDF NQuad倍解析為下面的格式:
type DirectedEdge struct {
Entity uint64
Attr string
Value []byte
ValueType uint32
ValueId uint64
Label string
Lang string
Op DirectedEdge_Op // Set or Delete
Facets []*facetsp.Facet
}
不考慮輸入的話,Entity以及 Object/ValueId都通過上面的XID <-> UID被轉化成UID格式。
1.3 Posting List
概念上,posting list包含與一個Attribute關聯的所有有向邊,通過如下格式:
Attribute: Entity -> sorted list of ValueId
//Everything in uint64 representation.
例如,如果我們存儲一個朋友的列表,例如:
| Entity | Attribute | ValueId |
|---|---|---|
| Me | friend | person0 |
| Me | friend | person1 |
| Me | friend | person2 |
| Me | friend | person3 |
那么,將生成一個friend的posting list。在這個PL中查找Me會生成一個friend的列表,即[person0, person1, person2, person3]
擁有這個結構的一個很大優(yōu)勢是可以把需要做一個join的所有數據保存在一個PL中。這意味著,一個到持有PL的服務器RPC調用的結果是一個連接,而不需要更多的網絡調用,減少查詢需要的連接
顧名思義,PL是Posting的列表,用Protocol Buffers表示的話,它是這個樣子:
message Posting {
fixed64 uid = 1;
bytes value = 2;
enum ValType {
DEFAULT = 0;
BINARY = 1;
INT = 2; // We treat it as int64.
FLOAT = 3;
BOOL = 4;
DATE = 5;
DATETIME = 6;
GEO = 7;
UID = 8;
PASSWORD = 9;
STRING = 10;
}
ValType val_type = 3;
enum PostingType {
REF=0; // UID
VALUE=1; // simple, plain value
VALUE_LANG=2; // value with specified language
// VALUE_TIMESERIES=3; // value from timeseries, with specified timestamp
}
PostingType posting_type = 4;
bytes metadata = 5; // for VALUE_LANG: Language, for VALUE_TIMESERIES: timestamp, etc..
string label = 6;
uint64 commit = 7; // More inclination towards smaller values.
repeated facetsp.Facet facets = 8;
// TODO: op is only used temporarily. See if we can remove it from here.
uint32 op = 12;
}
message PostingList {
repeated Posting postings = 1;
bytes checksum = 2;
uint64 commit = 3; // More inclination towards smaller values.
}
Dgraph中的所有數據都會首先使用Protocol Buffers序列化為字節(jié)數組后被存儲或傳輸。當結果被返回給用戶的時候,protocol buffer對象會被轉換為JSON對象
在一個PL中通常有超過一個的Posting
RDF標簽在每個posting中用label表示
現在Dgraph還不能通過查詢獲得label,但是將來會可以的
1.4 Badger
PostingList是通過Badger提供服務的,BadgerDB是一個嵌入式的、持久化的、簡單的快速鍵值數據庫,它使用純Go語言開發(fā).BadgerDB決定應該從內存、SSD或磁盤上提供多少數據。此外,它還支持在key上使用布隆過濾器,使隨機查詢的效率更高
為了讓Badger對內存有完全的訪問權限,以優(yōu)化cache,每臺服務器上都有一個Badger。每個實例包含這臺服務器上的所有posting list
Posting list在Badger上以鍵值對的格式存儲,例如:
(Predicate, Subject) --> PostingList
1.5 Group
包含同一個謂語(Predicate)的那些Posting list組成了一個group。每臺服務器可以存儲多個不同的group。
Group的配置文件用于確定每臺服務器應該保存哪些group。在將來的版本中,在線的Dgraph服務器將可以使用探試法(heuristics)移動標簽(tablets)
如果某個group變的太大,它可以被分割。在這種情況下,一個謂語實際上被分到兩個group上,像下面這樣:
Original Group:
(Predicate, Sa..z)
After split:
Group 1: (Predicate, Sa..i)
Group 2: (Predicate, Sj..z)
注意,這些key是被存儲在RocksDB里的,因此group的split將會保持排序。例如,按字段順序排序,在主語(subject)之前的被分到一個分組里,在主語之后的分到另一個分組里
1.6 復制與服務器故障
如果可能的話,每個group至少需要被三個服務器保存。當某臺服務器故障時,其他保存同一個group數據的服務器可以繼續(xù)提供服務
1.7 新服務器及發(fā)現
Dgraph集群可以自動檢測加入到集群中的新服務器,建立連接,并將新服務器應該保存的group數據發(fā)過去
1.8 預寫日志
對數據庫的寫入不會立馬通過RocksDB寫到磁盤上,而是先寫到磁盤上的預寫日志(Write ahead logs)里,這樣可以避免posting list的經常重建
1.9 修改
除了被寫入預寫日志之外,對數據庫的修改還會被存儲在內存中,作為不可變的Posting list之上的一個可變層。它允許用戶遍歷Postings,仿佛它們是被排過序的,而無需重建posting list
當一個posting list在內存中有被修改后,它被視為dirty的。Dgraph會周期性地重新生成不可變的版本,并將改變寫入RocksDB。注意,寫入RocksDB的過程是異步的,這意味著它們并不會立刻被刷寫到磁盤,但在服務器宕機的時候,這可能會導致數據丟失。但是不用擔心,在Posting list被初始化的時候,會讀取預寫日志,這時丟失的數據會被補上
每當重新生成posting list的時候,Dgraph還會寫一個包含最后一次提交日志的時間戳,用來決定在初始化posting list的時候從預寫日志回溯多久的數據。(Every time we regenerate a posting list, we also write the max commit log timestamp that was included – this helps us figure out how long back to seek in write-ahead logs when initializing the posting list, the first time it’s brought back into memory.)
1.10 事務
Dgraph現在還不支持事務。
0.9版本已經支持事務
With the availability of transactions, a user can query for data, and then write their mods back atomically. Thus, upsert and mutation variables can be done via client-side logic, instead of baking this limiting functionality in the server.
一個修改可以由多條邊組成,每條邊可能屬于不同的Posting list。Dgraph需要RWMutex來提供posting list上的鎖。而在多個posting list上不存在鎖
這意味著,一些邊會比其他邊先寫入,而這時候讀的話,只能讀到部分被提交的數據。但是,還是有一致性保證的,當一個mutation成功的時候,任何成功的讀操作都可以讀到所有被更新的數據
如果一個mutation失敗了,由用戶決定是擦出部分寫入的數據,還是用正確的邏輯重寫。Dgraph的回復會說清楚哪些邊沒有被寫入成功,用戶可以設置正確的邊來重新寫入
局限性:
- 你應該考慮好是否你的讀操作需要原子性的事務。除非你需要處理財務數據,這不是Dgraph的適用場景
- 為了保證原子性,每個mutation只能有一條邊(RDF NQuads) 。這意味需要在clent及server之間反復地執(zhí)行多次網絡調用,這將影響你的寫入吞吐量,但是會使錯誤處理的邏輯變得更簡單
1.11版本
大體上來說,Dgraph存儲有兩種類型的數據,一種是關系(relationship)數據,一種是值
Me friend person0 [Relation]
Me name "Константи?н" [Value]
DGraph保存Posting list的方式,