注1:版權全部,轉載必須註明出處。
注2:此手冊根據2018年10月JanusGraph的官方文檔進行整理,後續會陸續更新新知識
注3:本博文原始地址,兩個博客由同一博主管理。java
JanusGraph做爲Titan的繼承者,汲取了Titan和TinkerPop框架諸多的優勢,成爲了當前熱門的圖數據庫領域的佼佼者,而後,JanusGraph的中文文檔卻相對匱乏, 成爲了JanusGraph入門學習者的障礙之一。本文以JanusGraph中文文檔爲基礎,結合做者我的的思考,總計了JanusGraph中文文檔中的關鍵點,但願給你們帶來幫助。node
JanusGraph is a graph database engine. JanusGraph itself is focused on compact graph serialization, rich graph data modeling, and efficient query execution. In addition, JanusGraph utilizes Hadoop for graph analytics and batch graph processing. JanusGraph implements robust, modular interfaces for data persistence, data indexing, and client access. JanusGraph’s modular architecture allows it to interoperate with a wide range of storage, index, and client technologies; it also eases the process of extending JanusGraph to support new ones.git
總的來講,JanusGraph的一大特性是和其餘組件的交互能力強,好比像存儲的Hbase, full-text search的elasticsearch等,也能夠很方便地和graphx等olap引擎交互。github
Broadly speaking, applications can interact with JanusGraph in two ways:web
Embed JanusGraph inside the application executing Gremlin queries directly against the graph within the same JVM. Query execution, JanusGraph’s caches, and transaction handling all happen in the same JVM as the application while data retrieval from the storage backend may be local or remote.算法
Interact with a local or remote JanusGraph instance by submitting Gremlin queries to the server. JanusGraph natively supports the Gremlin Server component of the Apache TinkerPop stack.docker
應用程序能夠經過兩種方式使用JanusGraph,shell
- 直接把JanusGraph集成到本身的系統中,在同一個JVM中執行Query
- 經過將query submit到JanusGraph的instance來執行
架構圖:數據庫
一個基於Cassandra+ElasticSearch的配置例子:
storage.backend=cql storage.hostname=localhost index.search.backend=elasticsearch index.search.hostname=100.100.101.1, 100.100.101.2 index.search.elasticsearch.client-only=true
數據的Schema是JanusGraph的一個很重要的部分,數據schema主要定義在三個元素上:頂點,邊和屬性。聽起來可能有點繞口,schema是幹什麼呢?其實就是去定義頂點的label,邊的label的Property的key有哪些,再通俗點講,就是有哪些類型的點(好比 god
和 human
),有哪些類型的邊(好比 trade
和 friends
),而後點和邊的屬性列表都有哪些key(好比 human
有一個property key是name
和 age
)。
須要注意的幾點:
user
和 product
。makeVertexLabel(String).make()
來建立vertice label下面是個建立vertex label的例子:
mgmt = graph.openManagement() person = mgmt.makeVertexLabel('person').make() mgmt.commit() // Create a labeled vertex person = graph.addVertex(label, 'person') // Create an unlabeled vertex v = graph.addVertex() graph.tx().commit()
注意,須要顯示地調用 builder
的 make()
函數來完成vertex label的定義。
makeEdgeLabel(String)
來定義一個edge label,注意,edge label必須是惟一的,這個方法會返回一個 builder
,這個 builder
能夠用來獲取這種edge label的多度關係multiplicity,這個指標限定了每對(pair)之間edge最大的數量。
multiplicity
包含的類型有:multi, simple, many2one, one2many, one2one (結合數據庫E-R model這個概念不難理解)。multiplicity
是 MULTI
。下面是建立edge label的一個例子:
mgmt = graph.openManagement() follow = mgmt.makeEdgeLabel('follow').multiplicity(MULTI).make() mother = mgmt.makeEdgeLabel('mother').multiplicity(MANY2ONE).make() mgmt.commit()
注意,須要顯示地調用 builder
的 make()
函數來完成edge label的定義。
屬性Property定義在頂點和邊上,固然也能夠用來定義其餘東西,property是 key-value
對的形式。舉個例子,'name' = 'John'
這就能夠看作一個屬性,它定義了 name
這個屬性,這個屬性的value是 'John'。
經過 makePropertyKey(String)
方法來常見Property key。屬性名應該儘量避免使用空格和特殊字符。
屬性須要關注兩點:
每一個屬性都有本身的數據類型,也就是說它的value必須是某種支持的數據類型,能夠經過 dataType(Class)
方法來定義數據類型。JanusGraph會保證加入進來的屬性數據都知足該數據類型。
屬性數據類型能夠申明爲 Object.class
,但這並不推薦,最好控制好數據類型,一來能夠節省空間,二來可讓JanusGraph來幫咱們檢查導入的數據類型是否是符合要求。
數據類型必須使用具體的類,不能是接口或者抽象類。JanusGraph使用的是徹底至關去判斷數據類型是否符合,因此使用子類的數據去導入到父類的屬性中也是不會成功的。
用 cardinality(Cardinality)
方法來定義某一個頂點的某個屬性的基底(cardinality)。
基底有三種狀況:
birthDate
就是一個single基底,由於每一個人最多隻能有一個生日。sensorRecords
,那麼,對於這個屬性,可能有一系列的值。注意,LIST是容許有重複元素的。默認的cardinality是single。注意,對於邊屬性來講,property key的基底始終是single。
下面是在定義property key的時候定義對應的cardinality的例子:
mgmt = graph.openManagement() birthDate = mgmt.makePropertyKey('birthDate').dataType(Long.class).cardinality(Cardinality.SINGLE).make() name = mgmt.makePropertyKey('name').dataType(String.class).cardinality(Cardinality.SET).make() sensorReading = mgmt.makePropertyKey('sensorReading').dataType(Double.class).cardinality(Cardinality.LIST).make() mgmt.commit()
Edge label和property key共同地被稱爲relation type。
能夠經過 containsRelationType()
方法來檢測relation type是否存在。
下面是個例子:
mgmt = graph.openManagement() if (mgmt.containsRelationType('name')) name = mgmt.getPropertyKey('name') mgmt.getRelationTypes(EdgeLabel.class) mgmt.commit()
一旦咱們建立好Vertex label, edge label和property key後,就不能更改了,咱們惟一能改的是schema的名字,好比下面這個例子:
mgmt = graph.openManagement() place = mgmt.getPropertyKey('place') mgmt.changeName(place, 'location') mgmt.commit()
上面這個例子中,咱們把property key的名字從place改爲了location。
多個property能夠綁定到同一個vertex或者edge上面,用 JanusGraphManagement.addProperties(VertexLabel, PropertyKey...)
方法:
// 例子1 mgmt = graph.openManagement() person = mgmt.makeVertexLabel('person').make() name = mgmt.makePropertyKey('name').dataType(String.class).cardinality(Cardinality.SET).make() birthDate = mgmt.makePropertyKey('birthDate').dataType(Long.class).cardinality(Cardinality.SINGLE).make() mgmt.addProperties(person, name, birthDate) mgmt.commit()
// 例子2 mgmt = graph.openManagement() follow = mgmt.makeEdgeLabel('follow').multiplicity(MULTI).make() name = mgmt.makePropertyKey('name').dataType(String.class).cardinality(Cardinality.SET).make() mgmt.addProperties(follow, name) mgmt.commit()
有一種很通俗的定義關係的方法:
mgmt = graph.openManagement() person = mgmt.makeVertexLabel('person').make() company = mgmt.makeVertexLabel('company').make() works = mgmt.makeEdgeLabel('works').multiplicity(MULTI).make() mgmt.addConnection(works, person, company) mgmt.commit()
這裏,用到是 addConnection()
方法。
Gremlin是JanusGraph默認的query language。
Gremlin用來從JanusGraph裏面查詢數據,修改數據,Gremlin是一種traversal查詢語言,能夠很方便地查詢此類查詢:
從小明開始,遍歷到他的父親,再從他的父親遍歷到他父親的父親,而後返回他父親的父親的名字
Gremlin是Apache TinkerPop項目的一部分。
關於Gremlin這裏就很少作介紹了,汪文星的文檔裏作了很詳細的說明。
更多關於JanusGraph中Gremlin相關的說明,參考這個文檔。
這種方式主要是用來學習使用JanusGraph,生產環境下不建議用這種配置。
./bin/janusgraph.sh start
就能夠啓動了,會自動fork cassandra的包,elasticsearch的包,gremlin-server的包,並鏈接到對應的服務器$ bin/janusgraph.sh start Forking Cassandra... Running `nodetool statusthrift`.. OK (returned exit status 0 and printed string "running"). Forking Elasticsearch... Connecting to Elasticsearch (127.0.0.1:9300)... OK (connected to 127.0.0.1:9300). Forking Gremlin-Server... Connecting to Gremlin-Server (127.0.0.1:8182)... OK (connected to 127.0.0.1:8182). Run gremlin.sh to connect.
./bin/janusgraph.sh stop
命令就能夠了$ bin/janusgraph.sh stop Killing Gremlin-Server (pid 59687)... Killing Elasticsearch (pid 59494)... Killing Cassandra (pid 58809)...
上面pre-package的方式實際上是使用的內置的backend和index backend(在這個例子裏面,分別是cassandra和elasticsearch),其實咱們也能夠把JanusGraph鏈接到本身的HBase等Backend。
配置方式和使用WebSocket的方式基本相同,能夠用Curl命令來test服務的可用性:
curl -XPOST -Hcontent-type:application/json -d '{"gremlin":"g.V().count()"}' http://[IP for JanusGraph server host]:8182
修改gremlin-server.yaml文件,更改channelizer爲:
channelizer: org.apache.tinkerpop.gremlin.server.channel.WsAndHttpChannelizer
還有一些高級用法,好比認證(Authentication over HTTP),具體這裏就不細說了,能夠參考官方文檔。
部署方式:
這個概念比較難以理解。
ConfiguredGraphFactory
是一個singleton,和JanusGraphFactory
同樣。
它們提供了一套API(methods,方法)來動態地操做服務器上的圖。
在gremlin-console下咱們能夠直接用這個接口去操做圖,以下:
gremlin> :remote connect tinkerpop.server conf/remote.yaml ==>Configured localhost/127.0.0.1:8182 gremlin> :remote console ==>All scripts will now be sent to Gremlin Server - [localhost/127.0.0.1:8182] - type ':remote console' to return to local mode gremlin> ConfiguredGraphFactory.open("graph1") ==>standardjanusgraph[cassandrathrift:[127.0.0.1]] gremlin> graph1 ==>standardjanusgraph[cassandrathrift:[127.0.0.1]] gremlin> graph1_traversal ==>graphtraversalsource[standardjanusgraph[cassandrathrift:[127.0.0.1]], standard]
先來談一談 JanusGraphFactory
,它是咱們在gremlin-console裏面操做一個圖的時候的entry-point,每當咱們訪問一個圖的時候,系統就爲咱們建立了一個Configuration
類的實例。
能夠將這個東西和spark-shell裏面的sparkSession作類比來方便理解。
ConfiguredGraphFactory
不太同樣,它也是咱們訪問、操做一個圖的時候的entry-point,但配置是經過另外一個singleton來實現的,叫ConfigurationManagementGraph
。
ConfigurationManagementGraph
使咱們能夠很方便地管理圖的配置。
就像上面例子同樣,咱們能夠經過下面兩種方法來訪問一個圖:
ConfiguredGraphFactory.create("graphName")
或者
ConfiguredGraphFactory.open("graphName")
能夠經過下面的方法來羅列全部配置好了的圖。配置好是指以前有用ConfigurationManagementGraph
的API配置過:
ConfiguredGraphFactory.getGraphNames()
用下面的放來來drop一個graph database:
ConfiguredGraphFactory.drop("graphName")
若是想使用ConfiguredGraphFactory
這個接口,好比在啓動前JanusGraph server前配置好。修改gremlin-server.yaml文件,在graphs這個section下面,添加一行:
graphs: { ConfigurationManagementGraph: conf/JanusGraph-configurationmanagement.properties }
在這個例子中,ConfigurationManagementGraph
這個graph即是使用位於onf/JanusGraph-configurationmanagement.properties
下的配置文件來配置,下面是配置文件的一個例子:
gremlin.graph=org.janusgraph.core.JanusGraphFactory storage.backend=cql graph.graphname=ConfigurationManagementGraph storage.hostname=127.0.0.1
具體ConfigurationManagementGraph
怎麼用呢?下面是一個例子(在gremlin-console下):
map = new HashMap<String, Object>(); map.put("storage.backend", "cql"); map.put("storage.hostname", "127.0.0.1"); map.put("graph.graphname", "graph1"); ConfiguredGraphFactory.createConfiguration(new MapConfiguration(map)); // then access the graph ConfiguredGraphFactory.open("graph1");
graph.graphname
這個屬性指定了上述配置是針對哪張graph的。
ConfiguredGraphFactory
,這要求JanusGraph集羣的每一個server的配置文件裏面都聲明瞭使用JanusGraphManager
和ConfigurationManagementGraph
,具體方法,見這個連接。ConfiguredGraphFactory
(保持集羣上每一個node server的配置一致性)。經過下面的方法建立Composite Index:
graph.tx().rollback() //Never create new indexes while a transaction is active mgmt = graph.openManagement() name = mgmt.getPropertyKey('name') age = mgmt.getPropertyKey('age') mgmt.buildIndex('byNameComposite', Vertex.class).addKey(name).buildCompositeIndex() mgmt.buildIndex('byNameAndAgeComposite', Vertex.class).addKey(name).addKey(age).buildCompositeIndex() mgmt.commit() //Wait for the index to become available ManagementSystem.awaitGraphIndexStatus(graph, 'byNameComposite').call() ManagementSystem.awaitGraphIndexStatus(graph, 'byNameAndAgeComposite').call() //Reindex the existing data mgmt = graph.openManagement() mgmt.updateIndex(mgmt.getGraphIndex("byNameComposite"), SchemaAction.REINDEX).get() mgmt.updateIndex(mgmt.getGraphIndex("byNameAndAgeComposite"), SchemaAction.REINDEX).get() mgmt.commit()
Composite Index方式不須要特殊地去配置底層的存儲引擎(好比cassandra),主要的底層存儲引擎都支持這種方式。
經過Composite Index能夠來保證Property key的惟一性,用下面這種方法:
graph.tx().rollback() //Never create new indexes while a transaction is active mgmt = graph.openManagement() name = mgmt.getPropertyKey('name') mgmt.buildIndex('byNameUnique', Vertex.class).addKey(name).unique().buildCompositeIndex() mgmt.commit() //Wait for the index to become available ManagementSystem.awaitGraphIndexStatus(graph, 'byNameUnique').call() //Reindex the existing data mgmt = graph.openManagement() mgmt.updateIndex(mgmt.getGraphIndex("byNameUnique"), SchemaAction.REINDEX).get() mgmt.commit()
經過下面的方式來建立Mixed Index:
graph.tx().rollback() //Never create new indexes while a transaction is active mgmt = graph.openManagement() name = mgmt.getPropertyKey('name') age = mgmt.getPropertyKey('age') mgmt.buildIndex('nameAndAge', Vertex.class).addKey(name).addKey(age).buildMixedIndex("search") mgmt.commit() //Wait for the index to become available ManagementSystem.awaitGraphIndexStatus(graph, 'nameAndAge').call() //Reindex the existing data mgmt = graph.openManagement() mgmt.updateIndex(mgmt.getGraphIndex("nameAndAge"), SchemaAction.REINDEX).get() mgmt.commit()
Mixed Index方式須要特殊地去配置底層的存儲引擎(好比cassandra)的索引。
Mixed Index比Composite Index更加靈活,可是對於含有相等關係的謂語關係的查詢效率更慢。
buildMixedIndex
方法的參數string要和properties文件中配置的一致,好比:
index.search.backend
這個配置中間的部分search
就是buildMixedIndex
方法的參數。
有了Mixed Index,這面這些query就支持用索引來加速了:
g.V().has('name', textContains('hercules')).has('age', inside(20, 50)) g.V().has('name', textContains('hercules')) g.V().has('age', lt(50)) g.V().has('age', outside(20, 50)) g.V().has('age', lt(50).or(gte(60))) g.V().or(__.has('name', textContains('hercules')), __.has('age', inside(20, 50)))
總結兩種Graph Index的區別:
Use a composite index for exact match index retrievals. Composite indexes do not require configuring or operating an external index system and are often significantly faster than mixed indexes.
a. As an exception, use a mixed index for exact matches when the number of distinct values for query constraint is relatively small or if one value is expected to be associated with many elements in the graph (i.e. in case of low selectivity).
- Use a mixed indexes for numeric range, full-text or geo-spatial indexing. Also, using a mixed index can speed up the order().by() queries.
graph.tx().rollback() //Never create new indexes while a transaction is active mgmt = graph.openManagement() time = mgmt.getPropertyKey('time') rating = mgmt.makePropertyKey('rating').dataType(Double.class).make() battled = mgmt.getEdgeLabel('battled') mgmt.buildEdgeIndex(battled, 'battlesByRatingAndTime', Direction.OUT, Order.decr, rating, time) mgmt.commit() //Wait for the index to become available ManagementSystem.awaitRelationIndexStatus(graph, 'battlesByRatingAndTime', 'battled').call() //Reindex the existing data mgmt = graph.openManagement() mgmt.updateIndex(mgmt.getRelationIndex(battled, 'battlesByRatingAndTime'), SchemaAction.REINDEX).get() mgmt.commit()
注意上面的Index是有順序的,先對rating作index,再對time作index,所以:
h = g.V().has('name', 'hercules').next() g.V(h).outE('battled').property('rating', 5.0) //Add some rating properties g.V(h).outE('battled').has('rating', gt(3.0)).inV() // query-1 g.V(h).outE('battled').has('rating', 5.0).has('time', inside(10, 50)).inV() // query-2 g.V(h).outE('battled').has('time', inside(10, 50)).inV() // query-3
上述3個query中,前兩個被加速了,但第三沒並無。
JanusGraph automatically builds vertex-centric indexes per edge label and property key. That means, even with thousands of incident battled edges, queries like g.V(h).out('mother') or g.V(h).values('age') are efficiently answered by the local index.
一般,使用:
graph.V(...) and graph.tx().commit()
這樣的ThreadLocal
接口來執行一次事務。
根據TinkerPop框架事務機制的描述,每個線程在它執行第一個操做的時候開啓一個事務:
graph = JanusGraphFactory.open("berkeleyje:/tmp/janusgraph") juno = graph.addVertex() //Automatically opens a new transaction juno.property("name", "juno") graph.tx().commit() //Commits transaction
在上面這個例子中,addVertex()
函數執行的時候,事務被開啓,而後當咱們顯示執行graph.tx().commit()
的時候,事務關閉。
當事務尚未完成,卻調用了graph.close()
關閉了數據庫,那麼這個事務的後期行爲是不得而知的,通常狀況下,事務應該會被回滾。但執行close()
的線程所對應的事務會被順利地commit。
根據TinkerPop框架事務機制的描述,每個線程在它執行第一個操做的時候開啓一個事務,全部的圖操做元素(頂點,邊,類型等變量等)均和該事務自動綁定了,當咱們使用commit()
或者rollback()
關閉/回滾一個事務的時候,這些圖操做元素就會失效,可是,頂點和類型會被自動地轉移到下一個事務中,以下面的例子:
graph = JanusGraphFactory.open("berkeleyje:/tmp/janusgraph") juno = graph.addVertex() //Automatically opens a new transaction graph.tx().commit() //Ends transaction juno.property("name", "juno") //Vertex is automatically transitioned
可是,邊不能自動轉移到下一個事務中,須要顯式地刷新,以下面的例子:
e = juno.addEdge("knows", graph.addVertex()) graph.tx().commit() //Ends transaction e = g.E(e).next() //Need to refresh edge e.property("time", 99)
當咱們在建立一個事務,並作一系列操做的時候,應該事先考慮到事務失敗的可能性(IO exceptions, network errors, machine crashes or resource unavailability...),因此推薦用下面的方式處理異常:
try { if (g.V().has("name", name).iterator().hasNext()) throw new IllegalArgumentException("Username already taken: " + name) user = graph.addVertex() user.property("name", name) graph.tx().commit() } catch (Exception e) { //Recover, retry, or return error message println(e.getMessage()) }
上面的例子描述了一個註冊功能,先檢查名字存不存在,若是不存在,則建立該user,而後commit。
若是事務失敗了,會拋出JanusGraphException
異常,事務失敗有不少種可能,JanusGraph區分了兩種常見的failure:
PermanentLockingException(Local lock contention)
:另外一個線程已經被賦予了競爭鎖PermanentLockingException(Expected value mismatch for X: expected=Y vs actual=Z)
:
Tips: The verification that the value read in this transaction is the same as the one in the datastore after applying for the lock failed. In other words, another transaction modified the value after it had been read and modified.
多線程事務指的是同一個事務能夠充分利用機器的多核架構來多線程執行,見TinkerPop關於threaded transaction的描述。
能夠經過createThreadedTx()
方法來建立一個線程獨立的事務:
threadedGraph = graph.tx().createThreadedTx(); // Create a threaded transaction threads = new Thread[10]; for (int i=0; i<threads.length; i++) { threads[i]=new Thread({ println("Do something with 'threadedGraph''"); }); threads[i].start(); } for (int i=0; i<threads.length; i++) threads[i].join(); threadedGraph.tx().commit();
createThreadedTx()
方法返回了一個新的Graph
對象,tx()
對象在建立線程的時候,並無爲每一個線程建立一個事務,也就是說,全部的線程都運行在同一個事務中,這樣咱們就實現了threaded transaction。
JanusGraph能夠支持數百個線程運行在同一個事務中。
經過createThreadedTx()
接口能夠很輕鬆的建立並行算法(Concurrent Algorithms),尤爲是那些適用於並行計算的算法。
createThreadedTx()
接口的另外一個應用是建立嵌套式的事務(Nested Transactions),具體的例子見這裏,這對於那些long-time running的事務尤爲有做用。
再次強調一下,JanusGraph的邏輯是,當咱們對一個graph
進行第一次操做的時候,事務就自動被打開了,咱們並不須要去手動的建立一個事務,除非咱們但願建立一個multi-threaded transaction。
每一個事務都要顯式地用commit()
和rollback()
方法去關閉。
一個事務在開始的時候,就會去維持它的狀態,在多線程應用的環境中,可能會出現不可預知的問題,好比下面這個例子:
v = g.V(4).next() // Retrieve vertex, first action automatically starts transaction g.V(v).bothE() >> returns nothing, v has no edges //thread is idle for a few seconds, another thread adds edges to v g.V(v).bothE() >> still returns nothing because the transactional state from the beginning is maintained
這種狀況在客戶端應用端很常見,server會維持多個線程去相應服務器的請求。比較好的一個習慣是,在沒完成一部分工做後,就去顯式地terminate任務,以下面這個例子:
v = g.V(4).next() // Retrieve vertex, first action automatically starts transaction g.V(v).bothE() graph.tx().commit() //thread is idle for a few seconds, another thread adds edges to v g.V(v).bothE() >> returns the newly added edge graph.tx().commit()
多線程事務(Multi-Threaded Transactions)還能夠經過newTransaction()
方法來建立,但要注意的是,在該事務中建立的點和邊只在該事務中可用,事務被關閉之後,訪問這些點和邊就會拋出異常,若是還想使用這些點和邊怎麼辦?答案是顯式地去刷新這些點和邊,以下面這個例子:
g.V(existingVertex) g.E(existingEdge)
JanusGraph.buildTransaction()
也能夠啓動一個多線程的事務,所以它和上面提到的newTransaction()
方法實際上是同樣的功能,只不過buildTransaction()
方法提供了附加的配置選項。
buildTransaction()
會返回一個TransactionBuilder
實例,TransactionBuilder
實例能夠配置選項,這裏就詳述了。
配置好後,接着能夠調用start()
方法來啓動一個線程,這樣會返回一個JanusGraphTransaction
實例。
經過graph.buildTransaction().setVertexCacheSize(int)
能夠來設置事務的緩存大小(cache.tx-cache-size)。
當一個事務被打開的時候,維持了兩類緩存:
Vertex cache主要包含了事務中使用到的點,以及這些點的鄰接點列表(adjacency list)的一個子集,這個子集包括了在同一個事務中被取出的該點的鄰接點。因此,heap中vertex cache使用的空間不只與事務中存在的頂點數有關,還與這些點的鄰接點數目有關。
Vertex cache中能維持的最多的頂點數等於transaction cache size。Vertex cache能顯著地提升iteractive traversal的速度。固然,若是一樣的vertex在後面並無被從新訪問,那vertex cache就沒有起到做用。
若是在一個事務中,前面部分的query用到了某個索引(index),那麼後面使用這個query的時候,獲取結果的速度會大大加快,這就是index cache的做用,固然,若是後面並無再使用相同的index和相同的query,那Index query的做用也就沒有體現了。
Index cache中的每一個條目都會被賦予一個權重,這個權重等與 2 + result set size
,整個index cache的權重不會超過transaction cache size的一半。
Database-level caching實現了多個transaction之間cache的共享,從空間利用率上來講,database-lvel caching更經濟,但訪問速度比transaction-level稍慢。
Database-level cache不會在一個事務結束後立刻失效,這使得處於多個事務中的讀操做速度顯著地提升。
Cache expiration time經過 cache.db-cache-time
(單位爲:milliseconds)參數來調整。
這裏有一個trick,若是咱們只啓動了一個JanusGraph instance,由於沒有另外一個instance去改變玩咱們的圖(graph),cache expiration time即可以設置爲 0
,這樣的話,cache就會無限期保留處於cache中的元素(除非由於空間不足被頂替)。
若是咱們啓動了多個JanusGraph實例,這個時間應該被設置成當一個instance修改了圖,另外一個instance可以看到修改所須要等待的最大時間。若是但願修改被其餘的instance迅速可以看到,那麼應該中止使用Database-level cache。容許的時延越長,cache的效率越高。
固然,一個JanusGraph instance始終可以立刻看到它本身作出的修改。
Cache size經過 cache.db-cache-size
參數來控制Database-level的cache能使用多少heap space,cache越大, 效率越高,但由此帶來的GC性能問題卻不容小覷。
cache size也能夠配置成百分比(佔整個剩餘的heap空間的比例)。
注意cache size的配置是排他性的,也就是說cache會獨佔你配置的空間,其餘資源(e.g. Gremlin Server, embedded Cassandra, etc)也須要heap spave,因此不要把cache size配置得太過度,不然可能會引發out of memory錯誤或者GC問題。
還有一個須要注意的參數是 cache.db-cache-clean-wait
,當一個頂點被修改後,全部與該頂點有關的Database-level的cache都會失效而且會被驅逐。JanusGraph會從底層的storage backend從新fetch新的頂點數據,並re-populate緩存。
cache.db-cache-clean-wait
這個參數能夠控制,cache會等待 cache.db-cache-clean-wait
milliseconds時間再repopulate新的cache。
底層的storage backend也會有本身的cache策略,這個就要參考對應的底層存儲的文檔了。
能夠啓動記錄事務的變化日誌,日誌能夠用來在後期進行處理,或者做爲一個record備份。
在啓動一個事務的時候指定是否須要採集日誌:
tx = graph.buildTransaction().logIdentifier('addedPerson').start() u = tx.addVertex(label, 'human') u.property('name', 'proteros') u.property('age', 36) tx.commit()
這裏有個 log processor
的概念,其實就是內置的日誌監聽處理器。例如,能夠統計在一個transaction裏面,某一個label下被添加的頂點的數目。
默認地,當遇到一個新的type的時候,janusGrpah會自動地去建立property keys和邊的label。對於schema這個問題,仍是建議用戶本身去定義schema,而不要使用自動發現schema的方式,能夠在配置文件裏面以下申明關閉自動infer schema的功能:
schema.default = none
建立type的操做也不宜放在不一樣的線程中,由於這會引發不可知的後果,最好放到一個batch操做中把全部的type都事先建立好,而後再去作其餘的圖操做。
能夠本身去建立class,做爲value的類別。
邊應該先取出來,再操做,每一個transaction都是有scope的,若是超出了這個scope,去訪問以前的邊,會報錯。
這個概念比較新奇,指的是:咱們在一個事務中刪除了一個vertex,卻同時在另外一個transaction中修改了它。這種狀況下,這個vertex仍是會依然存在的,咱們稱這種vertex爲ghost vertex。
對於這個問題的解決辦法最好是暫時容許ghost vertices,而後按期地寫腳本去刪除它們。
Log level若是被設置成了 DEBUG
,輸出可能會很大,日誌中會包括一個query如何被編譯、優化和執行的過程,這會顯著地影響處理的性能,在生產環境下,不建議使用 DEBUG
level,建議是用 INFO
level。
當有不少客戶端鏈接到Elasticsearch的時候,可能會報 OutOfMemoryException
異常,一般這不是一個內存溢出的問題,而是OS不容許運行ES的用戶起太多的進程。
能夠經過調整運行es的用戶可運行的進程數(number of allowed processes)也許能夠解決這個問題。
刪除一個graph instance,可使用:
JanusGraphFactory.drop(graph)
接口graph = JanusGraphFactory.open('path/to/configuration.properties') JanusGraphFactory.drop(graph);
ConfiguredGraphFactory
接口graph = ConfiguredGraphFactory.open('example') ConfiguredGraphFactory.drop('example');
Note:
0.3.0
之前的版本除了須要執行上面的命令,還須要顯示地調用 JanusGraphFactory.close(graph)
和 ConfiguredGraphFactory.close("example")
來關閉這個graph,以防cache中還存在這個graph,形成錯誤。
這個部分能夠理解JanusGrpah還存在的一些可改進或者沒法改進的地方。
下面這些缺陷是JanusGraph自然設計上的一些缺陷,這些缺陷短時間內是得不到解決的。
JanusGraph can store up to a quintillion edges (2^60) and half as many vertices. That limitation is imposed by JanusGraph’s id scheme.
當咱們用 dataType(Class)
接口去定義property key的數據類型的時候,JanusGraph會enforce該key的全部屬性都嚴格是該類型。這是嚴格的類型判斷,子類也不可行。好比,若是咱們定義的數據類型是Number.class
,使用的倒是Integer
或者Long
型,這種狀況大多數狀況下會報錯。
用Object.class
或許能夠解決這個問題,比較靈活,但帶來的問題也顯而易見,那就是性能上會下降,同時數據類型的check也會失效。
Edge label, vertex label和property key一旦建立就不能改變了,固然,type能夠被重命名,新的類型也能夠在runtime中建立,因此schema是支持envolving的。
下面是JanusGraph保留的關鍵字,不要使用這些關鍵字做爲變量名、函數名等。
下面這些缺陷在未來的release中會被逐漸解決。
Mixed Index只支持JanusGraph所支持的數據類型(Data type)的一個子集,Mixed Index目前也不支持 SET
和 LIST
做爲基數(cardinality)的property key。
能夠經過下面的configuration來開啓 batch loading mode
:
Name | Description | Datatype | Default Value | Mutability |
---|---|---|---|---|
storage.batch-loading | Whether to enable batch loading into the storage backend | Boolean | false | LOCAL |
這個trick其實並無使用底層的backend的batch loading技術。
另外一個限制是,若是同時向一個頂點導入數百萬條邊,這種狀況下極可能failure,咱們稱這種loading爲 supernode loading
,這種loading之因此失敗是受到了後端存儲的限制,具體這裏就不細數了。
JanusGraph後端經過四種方式來支持整合Cassandra:
Cassandra能夠做爲一個standalone的數據庫與JanusGraph同樣運行在localhost,在這種狀況下,JanusGraph和Cassandra之間經過socket通訊。
經過下面的步驟配置JanusGraph on Cassandra:
conf/cassandra.yaml
and conf/log4j-server.properties
Now, you can create a Cassandra JanusGraph as follows
JanusGraph g = JanusGraphFactory.build(). set("storage.backend", "cql"). set("storage.hostname", "127.0.0.1"). open();
經過docker安裝Cassandra,去release界面看一下JanusGraph版本測試經過的Cassandra版本,使用下面的docker命令運行Cassandra:
docker run --name jg-cassandra -d -e CASSANDRA_START_RPC=true -p 9160:9160 \ -p 9042:9042 -p 7199:7199 -p 7001:7001 -p 7000:7000 cassandra:3.11
Note: Port 9160 is used for the Thrift client API. Port 9042 is for CQL native clients. Ports 7000, 7001 and 7099 are for inter-node communication.
集羣模式下,有一個Cassandra集羣,而後全部的JanusGraph的instance經過socket的方式去讀寫Cassandra集羣,客戶端應用程序也能夠和JanusGraph的實例運行在同一個JVM中。
舉個例子,咱們啓動了一個Cassandra的集羣,其中一個機器的IP是77.77.77.77
,咱們能夠經過如下方式鏈接:
JanusGraph graph = JanusGraphFactory.build(). set("storage.backend", "cql"). set("storage.hostname", "77.77.77.77"). open();
能夠在JanusGraph server外面包上一層Gremlin server,這樣,不只能夠和JanusGraph server交互,也能夠和Gremlin server交互。
經過:
bin/gremlin-server.sh
啓動Gremlin server,而後經過 bin/gremlin.sh
打開Gremlin的終端,而後運行:
:plugin use tinkerpop.server :remote connect tinkerpop.server conf/remote.yaml :> g.addV()
即可以了。
Cassandra也能夠整合到JanusGraph中,在這種狀況下,JanusGraph和Cassandra運行在同一個JVM中,本次經過進程間通訊而不是網絡傳輸信息,這種狀況經過在performance上有很大的幫助。
若是想使用Cassandra的embedded mode,須要配置embeddedcassandra
做爲存儲後端。
這種embedded模式推薦經過Gremlin server來暴露JanusGraph。
須要注意的是,embedded方式須要GC調優。
start-hbase.sh
script in the bin directory inside the extracted HBase directory. To stop HBase, use stop-hbase.sh
.而後經過:
JanusGraph graph = JanusGraphFactory.build() .set("storage.backend", "hbase") .open();
鏈接到HBase。
集羣模式,JanusGraph的實例經過socket與HBase創建鏈接,並進行讀寫操做。
假設咱們啓動了一個HBase並使用zookeeper做爲協調器,zk的IP是 77.77.77.77
, 77.77.77.78
和77.77.77.79
,那麼咱們經過下面的方式鏈接到HBase:
JanusGraph g = JanusGraphFactory.build() .set("storage.backend", "hbase") .set("storage.hostname", "77.77.77.77, 77.77.77.78, 77.77.77.79") .open();
和Cassandra章節講的同樣,咱們能夠在JanusGraph server外面再包一層Gremlin server:
http://gremlin-server.janusgraph.machine1/mygraph/vertices/1 http://gremlin-server.janusgraph.machine2/mygraph/tp/gremlin?script=g.v(1).out('follows').out('created')
Gremlin-server的配置文件也要作響應的修改,下面是個例子:
... graphs: { g: conf/janusgraph-hbase.properties } scriptEngines: { gremlin-groovy: { plugins: { org.janusgraph.graphdb.tinkerpop.plugin.JanusGraphGremlinPlugin: {}, org.apache.tinkerpop.gremlin.server.jsr223.GremlinServerGremlinPlugin: {}, org.apache.tinkerpop.gremlin.tinkergraph.jsr223.TinkerGraphGremlinPlugin: {}, org.apache.tinkerpop.gremlin.jsr223.ImportGremlinPlugin: {classImports: [java.lang.Math], methodImports: [java.lang.Math#*]}, org.apache.tinkerpop.gremlin.jsr223.ScriptFileGremlinPlugin: {files: [scripts/empty-sample.groovy]}}}} ...
在配置說明章節,有一系列配置選項,要注意 storage.hbase.table
參數,默認table的名字是janusgraph
。
JanusGraph over HBase 支持全局的點和邊的遍歷,但這種狀況下,會把全部的點和邊導入到內存中,可能會報 OutOfMemoryException
錯誤。可使用 Gremlin-hadoop 的方式去遍歷。
JanusGraph支持使用純內存,能夠經過下面的屬性配置:
storage.backend=inmemory
能夠在Gremlin-console直接打開內存中的圖:
graph = JanusGraphFactory.build().set('storage.backend', 'inmemory').open()
這種狀況下,若是關閉該圖或者中止graph的進程,圖的全部數據都會丟失。這種模式也只支持單點模式,不支持多個JanusGraph的圖實例共享。
這種存儲策略不適合在生產中使用。
主要能夠用這些operator作full-text search,常見的有兩類:
區間操做謂語包括:
g.V().has("name", "hercules") // 2) Find all vertices with an age greater than 50 g.V().has("age", gt(50)) // or find all vertices between 1000 (inclusive) and 5000 (exclusive) years of age and order by increasing age g.V().has("age", inside(1000, 5000)).order().by("age", incr) // which returns the same result set as the following query but in reverse order g.V().has("age", inside(1000, 5000)).order().by("age", decr) // 3) Find all edges where the place is at most 50 kilometers from the given latitude-longitude pair g.E().has("place", geoWithin(Geoshape.circle(37.97, 23.72, 50))) // 4) Find all edges where reason contains the word "loves" g.E().has("reason", textContains("loves")) // or all edges which contain two words (need to chunk into individual words) g.E().has("reason", textContains("loves")).has("reason", textContains("breezes")) // or all edges which contain words that start with "lov" g.E().has("reason", textContainsPrefix("lov")) // or all edges which contain words that match the regular expression "br[ez]*s" in their entirety g.E().has("reason", textContainsRegex("br[ez]*s")) // or all edges which contain words similar to "love" g.E().has("reason", textContainsFuzzy("love")) // 5) Find all vertices older than a thousand years and named "saturn" g.V().has("age", gt(1000)).has("name", "saturn")
Composite index能夠支持任何類型的index,mixed index只支持下面的數據類型:
只有mixed indexes
支持Geoshape Data Type,支持的數據類型有point, circle, box, line, polygon, multi-point, multi-line 和 multi-polygon。
若是使用 Elasticsearch
,能夠索引cardinality爲 SET
或者 LIST
的屬性,以下面的例子:
mgmt = graph.openManagement() nameProperty = mgmt.makePropertyKey("names").dataType(String.class).cardinality(Cardinality.SET).make() mgmt.buildIndex("search", Vertex.class).addKey(nameProperty, Mapping.STRING.asParameter()).buildMixedIndex("search") mgmt.commit() //Insert a vertex person = graph.addVertex() person.property("names", "Robert") person.property("names", "Bob") graph.tx().commit() //Now query it g.V().has("names", "Bob").count().next() //1 g.V().has("names", "Robert").count().next() //1
當咱們定義一個Mixed index的時候,每個被添加到索引中的property key都有一系列參數能夠設置。
全局索引,這是一個很重要的功能。當咱們去索引字符串類型的property key的時候,咱們能夠選擇從character層面後者text層面去索引,這須要改變 mapping
參數。
當咱們從text層面去索引的時候,字符串會被tokenize成bag of words,用戶即可以去query是否包含一個或多個詞,這叫作 full-text search。
當咱們從char層面去索引的時候,string會直接和char串作match,不會有futher analysis後者tokenize操做。這能夠方便咱們去查找是否包含某個字符序列,這也叫作 string search。
下面分開講:
默認地,string會使用text層面的索引,能夠經過下面的方式顯示地去建立:
mgmt = graph.openManagement() summary = mgmt.makePropertyKey('booksummary').dataType(String.class).make() mgmt.buildIndex('booksBySummary', Vertex.class).addKey(summary, Mapping.TEXT.asParameter()).buildMixedIndex("search") mgmt.commit()
能夠看到,這和普通的建立索引惟一的一個區別是咱們在調用 addKey()
方法的時候,多添加了一個 Mapping.TEXT
映射參數。
前面咱們提到過,若是是使用text層面的index,JanusGraph會本身去維護一個bag of words,JanusGraph默認的tokenization方案是:它會使用非字母數字的字段去split,而後會移除到全部小於2個字符的token。
當咱們使用text層面的index的時候,只有全局索引的謂語才真正用到了咱們建立的索引,包括textContains
方法,textContainsPrefix
方法,textContainsRegex
方法和textContainsFuzzy
方法,注意,full-text search是case-insensitive的,下面是具體的例子:
import static org.janusgraph.core.attribute.Text.* g.V().has('booksummary', textContains('unicorns')) g.V().has('booksummary', textContainsPrefix('uni')) g.V().has('booksummary', textContainsRegex('.*corn.*')) g.V().has('booksummary', textContainsFuzzy('unicorn'))
首先要明確的是,string search會把數據load到內存中,這實際上是很是costly的。
能夠經過下面的方式去顯示地建立string search:
mgmt = graph.openManagement() name = mgmt.makePropertyKey('bookname').dataType(String.class).make() mgmt.buildIndex('booksBySummary', Vertex.class).addKey(name, Mapping.STRING.asParameter()).buildMixedIndex("search") mgmt.commit()
這種 bookname
會按照 as-is
的方式去分析,包括stop word和no-letter character。
當咱們使用string層面的index的時候,只有下面的謂語才真正用到了咱們建立的索引,包括eq
,neq
、textPrefix
、textRegex
和textFuzzy
。注意,string search是case-insensitive的,下面是具體的例子:
import static org.apache.tinkerpop.gremlin.process.traversal.P.* import static org.janusgraph.core.attribute.Text.* g.V().has('bookname', eq('unicorns')) g.V().has('bookname', neq('unicorns')) g.V().has('bookname', textPrefix('uni')) g.V().has('bookname', textRegex('.*corn.*')) g.V().has('bookname', textFuzzy('unicorn'))
若是咱們使用elasticsearch做爲後端,這樣就能夠用全部的謂語去作精確或者模糊的查詢了。
經過下面的方式建立這種叫作 Mapping.TEXTSTRING
的full-text search方案:
mgmt = graph.openManagement() summary = mgmt.makePropertyKey('booksummary').dataType(String.class).make() mgmt.buildIndex('booksBySummary', Vertex.class).addKey(summary, Mapping.TEXTSTRING.asParameter()).buildMixedIndex("search") mgmt.commit()
默認地,JanusGraph支持索引點(point)的索引,而且去查詢circle或者box類型的property,若是想索引一個非-點類型的property,須要使用 Mapping.PREFIX_TREE
:
mgmt = graph.openManagement() name = mgmt.makePropertyKey('border').dataType(Geoshape.class).make() mgmt.buildIndex('borderIndex', Vertex.class).addKey(name, Mapping.PREFIX_TREE.asParameter()).buildMixedIndex("search") mgmt.commit()
能夠直接向index backend發送query,下面是個例子:
ManagementSystem mgmt = graph.openManagement(); PropertyKey text = mgmt.makePropertyKey("text").dataType(String.class).make(); mgmt.buildIndex("vertexByText", Vertex.class).addKey(text).buildMixedIndex("search"); mgmt.commit(); // ... Load vertices ... for (Result<Vertex> result : graph.indexQuery("vertexByText", "v.text:(farm uncle berry)").vertices()) { System.out.println(result.getElement() + ": " + result.getScore()); }
須要指明兩個元素:
vertexByText
。v.text:(farm uncle berry)
。下載包裏面自己包含兼容的elasticsearch的distribution,經過:
elasticsearch/bin/elasticsearch
來運行elasticsearch。要注意的是,es不能使用root運行。
JanusGraph支持經過HTTP客戶端鏈接到正在運行的ES集羣。
在配置文件中,Elasticsearch client須要經過下面這一行指明:
index.search.backend=elasticsearch
經過 index.[X].hostname
指明某一個或者一系列es的實例的地址:
index.search.backend=elasticsearch index.search.hostname=10.0.0.10:9200
能夠經過下面的方式綁定要一段連續的IP:PORT對:
index.search.backend=elasticsearch index.search.hostname=10.0.0.10, 10.0.0.20:7777
Rest client能夠經過 index.[X].bulk-refresh
參數控制改變多久能被索引到。
REST Client 既能夠配置成HTTP的方式也能夠配置成HTTPS的方式。
能夠經過 index.[X].elasticsearch.ssl.enabled 開啓HTTP的SSL支持。注意,這可能須要修改 index.[X].port 參數,由於ES的HTTPS服務的端口號可能和一般意義的REST API端口(9200)不同。
能夠經過配置 index.[X].elasticsearch.http.auth.basic.realm
參數來經過HTTP協議作認證。
index.search.elasticsearch.http.auth.type=basic index.search.elasticsearch.http.auth.basic.username=httpuser index.search.elasticsearch.http.auth.basic.password=httppassword
tips:
能夠本身實現class來實現認證:
index.search.elasticsearch.http.auth.custom.authenticator-class=fully.qualified.class.Name index.search.elasticsearch.elasticsearch.http.auth.custom.authenticator-args=arg1,arg2,...
本身實現的class必須實現 org.janusgraph.diskstorage.es.rest.util.RestClientAuthenticator
接口。
Vertex label能夠定義爲static的,一旦建立,就不能修改了。
mgmt = graph.openManagement() tweet = mgmt.makeVertexLabel('tweet').setStatic().make() mgmt.commit()
邊和頂點能夠配置對應的time-to-live(TTL)
,這個概念有點相似於數據庫中的臨時表的概念,用這種方式建立的點和邊在使用一段時間之後會被自動移除掉。
mgmt = graph.openManagement() visits = mgmt.makeEdgeLabel('visits').make() mgmt.setTTL(visits, Duration.ofDays(7)) mgmt.commit()
須要注意的是,這種方法後端數據庫必須支持cell level TTL,目前只有Cassandra和HBase支持。
mgmt = graph.openManagement() sensor = mgmt.makePropertyKey('sensor').cardinality(Cardinality.LIST).dataType(Double.class).make() mgmt.setTTL(sensor, Duration.ofDays(21)) mgmt.commit()
mgmt = graph.openManagement() tweet = mgmt.makeVertexLabel('tweet').setStatic().make() mgmt.setTTL(tweet, Duration.ofHours(36)) mgmt.commit()
mgmt = graph.openManagement() mgmt.makeEdgeLabel('author').unidirected().make() mgmt.commit()
這種undirected edge只能經過out-going的方向去遍歷,這有點像萬維網。
底層數據的最終一致性問題。
Eventually consistent storage backend有哪些?Apache Cassandra 或者 Apache HBase其實都是這種數據庫類型。
經過 JanusGraphManagement.setConsistency(element, ConsistencyModifier.LOCK)
方法去定義數據的一致性問題, 以下面的例子:
mgmt = graph.openManagement() name = mgmt.makePropertyKey('consistentName').dataType(String.class).make() index = mgmt.buildIndex('byConsistentName', Vertex.class).addKey(name).unique().buildCompositeIndex() mgmt.setConsistency(name, ConsistencyModifier.LOCK) // Ensures only one name per vertex mgmt.setConsistency(index, ConsistencyModifier.LOCK) // Ensures name uniqueness in the graph mgmt.commit()
使用鎖其實開銷仍是很大的,在對數據一致性要求不高的情形,最好不用鎖,讓後期數據庫本身在讀操做中去解決數據一致性問題。
當有兩個事務同時對一個元素進行寫操做的時候,怎麼辦呢?咱們能夠先讓寫操做成功,而後後期再去解決一致性問題,具體有兩種思路解決這個問題:
思想就是,每個事務fork一個對應的要修改的edge,再根據時間戳去在後期修改。
下面是個例子:
mgmt = graph.openManagement() related = mgmt.makeEdgeLabel('related').make() mgmt.setConsistency(related, ConsistencyModifier.FORK) mgmt.commit()
這裏,咱們建立了一個edge label,叫作 related
,而後咱們把一致性屬性設置成了 ConsistencyModifier.FORK
。
這個策略只對MULTI類別的邊適用。其餘的multiplicity並不適用,由於其它multiplicity顯式地應用了鎖。
失敗和恢復,主要是兩個部分:
事務若是在調用 commit()
以前失敗,是能夠恢復的。commit()
以前的改變也會被回滾。
有時候,數據persist到存儲系統的過程成功了,但建立index的的過程卻失敗了。這種狀況下,該事務會被認爲成功了,由於底層存儲纔是source of graph。
但這樣會帶來數據和索引的不一致性。JanusGraph維護了一份 transaction write-ahead log
,對應的有兩個參數能夠調整:
tx.log-tx = true tx.max-commit-time = 10000
若是一個事務的persistance過程超過了 max-commit-time
,JanusGrpah會嘗試從中恢復。與此同時,另外有一個進程去掃描維護好的這份log,去identify那些只成功了一半的事務。建議使用另外一臺機器專門去作失敗恢復,運行:
recovery = JanusGraphFactory.startTransactionRecovery(graph, startTime, TimeUnit.MILLISECONDS);
transaction write-ahead log
自己也有維護成本,由於涉及到大量的寫操做。transaction write-ahead log
自動維護的時間是2天,2天前的數據會被自動刪除。
對於這樣的系統,如何 fine tune log system 也是須要仔細考慮的因素。
若是某個JanusGraph instance宕機了,其餘的實例應該不能受影響。若是涉及到schema相關的操做,好比建立索引,這就須要不一樣instance保持協做了,JanusGraph會自動地去維護一份running instance的列表,若是某一個實例被意外關閉了,建立索引的操做就會失敗。
在這種狀況下,有一個方案是去手動地remove某一個實例:
mgmt = graph.openManagement() mgmt.getOpenInstances() //all open instances ==>7f0001016161-dunwich1(current) ==>7f0001016161-atlantis1 mgmt.forceCloseInstance('7f0001016161-atlantis1') //remove an instance mgmt.commit()
但這樣作有數據不一致的風險,應該儘可能少使用這種方式。
通常來說,咱們在建立schema的時候,就應該把索引創建好,若是事先沒有建立好索引,就須要從新索引了。
能夠經過兩種方式來執行重索引:
具體的代碼能夠參考:https://docs.janusgraph.org/latest/index-admin.html
刪除索引分兩步:
DISABLED
狀態,此時JanusGraph便會中止使用該索引去回答查詢,或者更新索引,索引相關的底層數據還保留但會被忽略。JanusGraphManagement
或者 MapReduce
去刪除,若是是mixed索引就比較麻煩了,由於這涉及到後端存儲的索引,因此須要手動地去後端drop掉對應的索引。當一個索引剛剛被創建,就執行重索引的時候,可能會報以下錯誤:
The index mixedExample is in an invalid state and cannot be indexed. The following index keys have invalid status: desc has status INSTALLED (status must be one of [REGISTERED, ENABLED])
這是由於創建索引後,索引信息會被慢慢地廣播到集羣中其餘的Instances,這須要必定的時間,因此,最好不要在索引剛剛創建之後就去執行重索引任務。
經過 storage.batch-loading
參數來支持 Bulk loading。
若是打開了 Builk loading,最好關閉自動建立schema的功能(schema.default = none
)。由於automatic type creation會不斷地check來保證數據的一致性和完整性,對於Bulk loading的場合,這或許是不須要的。
另一個須要關注的參數是 ids.block-size
,能夠經過增大這個參數來減小id獲取過程的數量(id block acquisition process),但這會形成大量的id浪費,這個參數須要根據每臺機器添加的頂點的數量來作調整,默認值已經比較合理了,若是不行,能夠適當地增大這個數值(10倍,100倍,好比)。
對於這個參數,有個技巧:Rule of thumb: Set ids.block-size to the number of vertices you expect to add per JanusGraph instance per hour.
Note:要保證全部JanusGraph instance這個參數的配置都同樣,若是須要調整這個參數,最好先關閉全部的instance,調整好後再上線。
若是有多個實例,這些實例在不斷地分配id,可能會形成衝突問題,有時候甚至會報出異常,通常來講,對於這個問題,能夠調整下面幾個參數:
ids.authority.wait-time
:單位是milliseconds,id pool mamager在等待id block應用程序得到底層存儲所須要等待的時間,這個時間越短,越容易出問題。Rule of thumb: Set this to the sum of the 95th percentile read and write times measured on the storage backend cluster under load. Important: This value should be the same across all JanusGraph instances.
ids.renew-timeout
:單位是milliseconds,JanusGraph 的 id pool manager 在獲取新一個id以前會等待的總時間。Rule of thumb: Set this value to be as large feasible to not have to wait too long for unrecoverable failures. The only downside of increasing it is that JanusGraph will try for a long time on an unavailable storage backend cluster.
還有一些須要注意的讀寫參數:
storage.buffer-size
:咱們執行不少query的時候,JanusGraph會把它們封裝成一個個的小batch,而後推送到後端的存儲執行,當咱們在短期內執行大量的寫操做的時候,後端存儲可能承受不了這麼大的壓力。在這種狀況下,咱們能夠增大這個buffer參數,但與此相對的代價是每秒中能夠發送的request數量會減少。這個參數不建議在用事務的方式導入數據的時候進行修改。
storage.read-attempts
和 storage.write-attempts
參數,這個參數指的是每一個推送到後端的batch會被嘗試多少次(直至認爲這個batch fail),若是但願在導數據的時候支持 high load,最好調大這幾個參數。
storage.attempt-wait
參數指定了JanusGraph在從新執行一次失敗的操做以前會等待的時間(millisecond),這個值越大,後端能抗住的load越高。
分區策略,主要是兩種:
砍邊策略,常常一塊兒遍歷到的點儘可能放在同一個機器上。
砍點策略。砍邊策略的目的是減少通訊量,砍點策略主要是爲了處理hotspot問題(超級點問題),好比有的點,入度很是大,這種狀況下,用鄰接表的方式+砍邊的方式存儲的話,勢必形成某一個分區上某一個點的存儲量過大(偏移),這個時候,利用砍點策略,把這種點均勻地分佈到不一樣的partition上面就顯得很重要了。
一個典型的場景是 User
和 Product
的關係,product
可能只有幾千個,但用戶卻有上百萬個,這種狀況下,product
最好就始終砍點策略。
對與分區這個問題,若是數據量小,就用隨機分區(默認的)就好,若是數據量過大,就要好好地去fine tune分區的策略了。
JanusGraph和TinkerPop的Hadoop框架的整合問題。JanusGraph和Apache Spark還有Hadoop的整合主要是依靠社區的力量。