Озмоиши санҷиши татбиқи графи JanusGraph DBMS барои ҳалли мушкилоти дарёфти роҳҳои мувофиқ

Озмоиши санҷиши татбиқи графи JanusGraph DBMS барои ҳалли мушкилоти дарёфти роҳҳои мувофиқ

Салом ба ҳама. Мо маҳсулотеро барои таҳлили трафики офлайнӣ таҳия карда истодаем. Лоиҳа вазифае дорад, ки бо таҳлили омории хатсайрҳои меҳмонон дар байни минтақаҳо алоқаманд аст.

Ҳамчун як қисми ин вазифа, корбарон метавонанд дархостҳои системаро аз намуди зерин пурсанд:

  • аз майдони «А» ба майдони «Б» чанд нафар мехмонон гузаштанд;
  • чи кадар мехмонон аз майдони «А» ба майдони «Б» аз майдони «С» ва баъд аз майдони «Д» гузаштанд;
  • то як намуди муайяни меҳмон аз минтақаи «А» ба минтақаи «Б» чӣ қадар вақт лозим буд.

ва як қатор дархостҳои аналитикии шабеҳ.

Ҳаракати меҳмонон дар минтақаҳо як графики равонашуда мебошад. Пас аз хондани Интернет, ман фаҳмидам, ки DBMS-ҳои графикӣ барои ҳисоботҳои таҳлилӣ низ истифода мешаванд. Ман мехостам бубинам, ки чӣ гуна графикҳои DBMS бо чунин дархостҳо мубориза мебаранд (TL; DR; бад).

Ман истифода бурдани DBMS-ро интихоб кардам JanusGraph, ҳамчун намояндаи барҷастаи графикии DBMS-и кушодаасос, ки ба маҷмӯи технологияҳои баркамол такя мекунад, ки (ба андешаи ман) бояд онро бо хусусиятҳои хуби амалиётӣ таъмин кунанд:

  • Бозгашти нигаҳдории BerkeleyDB, Apache Cassandra, Scylla;
  • индексҳои мураккабро дар Lucene, Elasticsearch, Solr нигоҳ доштан мумкин аст.

Муаллифони JanusGraph менависанд, ки он ҳам барои OLTP ва ҳам OLAP мувофиқ аст.

Ман бо BerkeleyDB, Apache Cassandra, Scylla ва ES кор кардам ва ин маҳсулот аксар вақт дар системаҳои мо истифода мешаванд, аз ин рӯ ман дар озмоиши ин графики DBMS хушбин будам. Интихоби BerkeleyDB бар RocksDB ба ман аҷиб буд, аммо ин эҳтимол ба талаботи транзаксия вобаста аст. Дар ҳар сурат, барои истифодаи миқёспазири маҳсулот, тавсия дода мешавад, ки пуштибониро дар Кассандра ё Скилла истифода баред.

Ман Neo4j-ро ба назар нагирифтам, зеро кластерсозӣ версияи тиҷоратиро талаб мекунад, яъне маҳсулот манбаи кушода нест.

Графикаи DBMS мегӯянд: "Агар он ба график монанд бошад, ба он ҳамчун график муносибат кунед!" - Зебоӣ!

Аввалан, ман графикеро кашидам, ки он маҳз мувофиқи қонунҳои графикии DBMS сохта шудааст:

Озмоиши санҷиши татбиқи графи JanusGraph DBMS барои ҳалли мушкилоти дарёфти роҳҳои мувофиқ

Моҳият вуҷуд дорад Zone, масъули минтақа. Агар ZoneStep ба ин тааллуқ дорад Zone, баъд ба он ишора мекунад. Дар асл Area, ZoneTrack, Person Диққат надиҳед, онҳо ба домен тааллуқ доранд ва ҳамчун қисми санҷиш ҳисобида намешаванд. Дар маҷмӯъ, дархости ҷустуҷӯии занҷир барои чунин сохтори графикӣ чунин хоҳад буд:

g.V().hasLabel('Zone').has('id',0).in_()
       .repeat(__.out()).until(__.out().hasLabel('Zone').has('id',19)).count().next()

Чӣ ба забони русӣ чунин аст: Минтақаро бо ID=0 пайдо кунед, ҳама қуллаҳоеро, ки канори он ба он меравад (ZoneStep) гиред, бе ақиб қадам занед, то он даме, ки он ZoneStepҳоро пайдо кунед, ки аз онҳо канори он ба Минтақа мавҷуд аст. ID=19, шумораи чунин занҷирҳоро ҳисоб кунед.

Ман вонамуд намекунам, ки тамоми нозукиҳои ҷустуҷӯи графикҳоро донам, аммо ин пурсиш дар асоси ин китоб таҳия шудааст (https://kelvinlawrence.net/book/Gremlin-Graph-Guide.html).

Ман 50 ҳазор треки дарозии аз 3 то 20 нуқтаро ба пойгоҳи додаҳои графикии JanusGraph бо истифода аз пуштибонии BerkeleyDB бор кардам, индексҳоро мувофиқи рохбарй.

Скрипти зеркашии Python:


from random import random
from time import time

from init import g, graph

if __name__ == '__main__':

    points = []
    max_zones = 19
    zcache = dict()
    for i in range(0, max_zones + 1):
        zcache[i] = g.addV('Zone').property('id', i).next()

    startZ = zcache[0]
    endZ = zcache[max_zones]

    for i in range(0, 10000):

        if not i % 100:
            print(i)

        start = g.addV('ZoneStep').property('time', int(time())).next()
        g.V(start).addE('belongs').to(startZ).iterate()

        while True:
            pt = g.addV('ZoneStep').property('time', int(time())).next()
            end_chain = random()
            if end_chain < 0.3:
                g.V(pt).addE('belongs').to(endZ).iterate()
                g.V(start).addE('goes').to(pt).iterate()
                break
            else:
                zone_id = int(random() * max_zones)
                g.V(pt).addE('belongs').to(zcache[zone_id]).iterate()
                g.V(start).addE('goes').to(pt).iterate()

            start = pt

    count = g.V().count().next()
    print(count)

Мо як VM-ро бо 4 ядро ​​ва 16 ГБ RAM дар SSD истифода мебарем. JanusGraph бо истифода аз ин фармон ҷойгир карда шуд:

docker run --name janusgraph -p8182:8182 janusgraph/janusgraph:latest

Дар ин ҳолат, маълумот ва индексҳое, ки барои ҷустуҷӯи дақиқи мувофиқ истифода мешаванд, дар BerkeleyDB нигоҳ дошта мешаванд. Пас аз иҷрои дархости қаблан ман вақт гирифтам, ки ба якчанд даҳҳо сония баробар аст.

Бо иҷро кардани 4 скрипти дар боло зикршуда, ман муяссар шудам, ки DBMS-ро ба каду бо ҷараёни шодмонии стектратсияҳои Java табдил диҳам (ва ҳамаи мо хондани стэктрасҳои Java-ро дӯст медорем) дар гузоришҳои Docker.

Пас аз чанд фикр, ман қарор додам, ки диаграммаи графикиро ба таври зерин содда кунам:

Озмоиши санҷиши татбиқи графи JanusGraph DBMS барои ҳалли мушкилоти дарёфти роҳҳои мувофиқ

Қарор қабул кунед, ки ҷустуҷӯ аз рӯи атрибутҳои объект назар ба ҷустуҷӯ аз рӯи кунҷҳо тезтар хоҳад буд. Дар натиҷа, дархости ман ба инҳо табдил ёфт:

g.V().hasLabel('ZoneStep').has('id',0).repeat(__.out().simplePath()).until(__.hasLabel('ZoneStep').has('id',19)).count().next()

Чӣ ба забони русӣ чунин аст: ZoneStep-ро бо ID=0 ёбед, то он даме, ки ZoneStep-ро бо ID=19 пайдо кунед, бе ақиб қадам занед, шумораи чунин занҷирҳоро ҳисоб кунед.

Ман инчунин скрипти боркунии дар боло овардашударо содда кардам, то пайвастҳои нолозимро эҷод накунам ва худро бо атрибутҳо маҳдуд кунам.

Барои анҷом додани дархост ҳанӯз чанд сония вақт лозим буд, ки ин барои вазифаи мо комилан қобили қабул набуд, зеро он барои ҳама гуна дархостҳои AdHoc мувофиқ набуд.

Ман кӯшиш кардам, ки JanusGraph-ро бо истифода аз Scylla ҳамчун зудтарин татбиқи Кассандра истифода барам, аммо ин инчунин ба ягон тағироти назарраси иҷроиш оварда нарасонд.

Ҳамин тавр, сарфи назар аз он, ки "он ба график монанд аст", ман натавонистам графики DBMS-ро зуд коркард кунад. Ман комилан гумон мекунам, ки чизе вуҷуд дорад, ки ман намедонам ва JanusGraph метавонад ин ҷустуҷӯро дар як сония анҷом диҳад, аммо ман ин корро карда натавонистам.

Азбаски мушкилот ҳанӯз бояд ҳал карда мешуд, ман дар бораи JOINs ва Pivots of мизҳо фикр карданро сар кардам, ки аз ҷиҳати зебоӣ хушбиниро илҳом намедоданд, аммо дар амал як варианти комилан қобили амал буда метавонанд.

Лоиҳаи мо аллакай Apache ClickHouse-ро истифода мебарад, бинобар ин ман қарор додам, ки тадқиқоти худро дар ин DBMS таҳлилӣ санҷам.

ClickHouse бо истифода аз як дорухат оддӣ ҷойгир карда шудааст:

sudo docker run -d --name clickhouse_1 
     --ulimit nofile=262144:262144 
     -v /opt/clickhouse/log:/var/log/clickhouse-server 
     -v /opt/clickhouse/data:/var/lib/clickhouse 
     yandex/clickhouse-server

Ман пойгоҳи додаҳо ва ҷадвалро дар он чунин сохтам:

CREATE TABLE 
db.steps (`area` Int64, `when` DateTime64(1, 'Europe/Moscow') DEFAULT now64(), `zone` Int64, `person` Int64) 
ENGINE = MergeTree() ORDER BY (area, zone, person) SETTINGS index_granularity = 8192

Ман онро бо маълумот бо истифода аз скрипти зерин пур кардам:

from time import time

from clickhouse_driver import Client
from random import random

client = Client('vm-12c2c34c-df68-4a98-b1e5-a4d1cef1acff.domain',
                database='db',
                password='secret')

max = 20

for r in range(0, 100000):

    if r % 1000 == 0:
        print("CNT: {}, TS: {}".format(r, time()))

    data = [{
            'area': 0,
            'zone': 0,
            'person': r
        }]

    while True:
        if random() < 0.3:
            break

        data.append({
                'area': 0,
                'zone': int(random() * (max - 2)) + 1,
                'person': r
            })

    data.append({
            'area': 0,
            'zone': max - 1,
            'person': r
        })

    client.execute(
        'INSERT INTO steps (area, zone, person) VALUES',
        data
    )

Азбаски замимаҳо дар гурӯҳҳо меоянд, пуркунӣ нисбат ба JanusGraph хеле тезтар буд.

Бо истифода аз JOIN ду дархост сохта шуд. Барои гузаштан аз нуқтаи А ба нуқтаи B:

SELECT s1.person AS person,
       s1.zone,
       s1.when,
       s2.zone,
       s2.when
FROM
  (SELECT *
   FROM steps
   WHERE (area = 0)
     AND (zone = 0)) AS s1 ANY INNER JOIN
  (SELECT *
   FROM steps AS s2
   WHERE (area = 0)
     AND (zone = 19)) AS s2 USING person
WHERE s1.when <= s2.when

Барои гузаштан аз 3 нуқта:

SELECT s3.person,
       s1z,
       s1w,
       s2z,
       s2w,
       s3.zone,
       s3.when
FROM
  (SELECT s1.person AS person,
          s1.zone AS s1z,
          s1.when AS s1w,
          s2.zone AS s2z,
          s2.when AS s2w
   FROM
     (SELECT *
      FROM steps
      WHERE (area = 0)
        AND (zone = 0)) AS s1 ANY INNER JOIN
     (SELECT *
      FROM steps AS s2
      WHERE (area = 0)
        AND (zone = 3)) AS s2 USING person
   WHERE s1.when <= s2.when) p ANY INNER JOIN
  (SELECT *
   FROM steps
   WHERE (area = 0)
     AND (zone = 19)) AS s3 USING person
WHERE p.s2w <= s3.when

Дархостҳо, албатта, хеле даҳшатнок ба назар мерасанд; барои истифодаи воқеӣ, шумо бояд дастгоҳи генератори нармафзорро эҷод кунед. Бо вуҷуди ин, онҳо кор мекунанд ва зуд кор мекунанд. Ҳарду дархостҳои якум ва дуюм дар камтар аз 0.1 сония иҷро карда мешаванд. Ин аст мисоли вақти иҷрои дархост барои ҳисоб (*) аз 3 нуқта:

SELECT count(*)
FROM 
(
    SELECT 
        s1.person AS person, 
        s1.zone AS s1z, 
        s1.when AS s1w, 
        s2.zone AS s2z, 
        s2.when AS s2w
    FROM 
    (
        SELECT *
        FROM steps
        WHERE (area = 0) AND (zone = 0)
    ) AS s1
    ANY INNER JOIN 
    (
        SELECT *
        FROM steps AS s2
        WHERE (area = 0) AND (zone = 3)
    ) AS s2 USING (person)
    WHERE s1.when <= s2.when
) AS p
ANY INNER JOIN 
(
    SELECT *
    FROM steps
    WHERE (area = 0) AND (zone = 19)
) AS s3 USING (person)
WHERE p.s2w <= s3.when

┌─count()─┐
│   11592 │
└─────────┘

1 rows in set. Elapsed: 0.068 sec. Processed 250.03 thousand rows, 8.00 MB (3.69 million rows/s., 117.98 MB/s.)

Қайд дар бораи IOPS. Ҳангоми пур кардани маълумот, JanusGraph миқдори хеле зиёди IOPS (1000-1300 барои чор риштаи популятсияи додаҳо) тавлид кард ва IOWAIT хеле баланд буд. Дар айни замон, ClickHouse бори ҳадди ақалро дар зерсистемаи диск тавлид кард.

хулоса

Мо тасмим гирифтем, ки ClickHouse-ро барои хидматрасонии ин намуди дархост истифода барем. Мо ҳамеша метавонем дархостҳоро бо истифода аз манзараҳои моддӣ ва параллелизатсия тавассути коркарди пешакии ҷараёни рӯйдодҳо бо истифода аз Apache Flink пеш аз бор кардани онҳо ба ClickHouse такмил диҳем.

Намоиш он қадар хуб аст, ки мо шояд ҳатто дар бораи тағир додани ҷадвалҳо ба таври барномавӣ фикр накунем. Пештар, мо маҷбур будем, ки маълумотеро, ки аз Vertica тавассути боргузорӣ ба Apache Parquet гирифта шуда буд, иҷро кунем.

Мутаассифона, кӯшиши дигар барои истифодаи графикии DBMS муваффақ нашуд. Ман JanusGraph-ро наёфтам, ки экосистемаи дӯстона дошта бошад, ки суръатро бо маҳсулот осон кунад. Ҳамзамон, барои танзими сервер, роҳи анъанавии Java истифода мешавад, ки одамонеро, ки бо Java ошно нестанд, ашки хун гиря мекунанд:

host: 0.0.0.0
port: 8182
threadPoolWorker: 1
gremlinPool: 8
scriptEvaluationTimeout: 30000
channelizer: org.janusgraph.channelizers.JanusGraphWsAndHttpChannelizer

graphManager: org.janusgraph.graphdb.management.JanusGraphManager
graphs: {
  ConfigurationManagementGraph: conf/janusgraph-cql-configurationgraph.properties,
  airlines: conf/airlines.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/airline-sample.groovy]}}}}

serializers:
# GraphBinary is here to replace Gryo and Graphson
  - { className: org.apache.tinkerpop.gremlin.driver.ser.GraphBinaryMessageSerializerV1, config: { ioRegistries: [org.janusgraph.graphdb.tinkerpop.JanusGraphIoRegistry] }}
  - { className: org.apache.tinkerpop.gremlin.driver.ser.GraphBinaryMessageSerializerV1, config: { serializeResultToString: true }}
  # Gryo and Graphson, latest versions
  - { className: org.apache.tinkerpop.gremlin.driver.ser.GryoMessageSerializerV3d0, config: { ioRegistries: [org.janusgraph.graphdb.tinkerpop.JanusGraphIoRegistry] }}
  - { className: org.apache.tinkerpop.gremlin.driver.ser.GryoMessageSerializerV3d0, config: { serializeResultToString: true }}
  - { className: org.apache.tinkerpop.gremlin.driver.ser.GraphSONMessageSerializerV3d0, config: { ioRegistries: [org.janusgraph.graphdb.tinkerpop.JanusGraphIoRegistry] }}
  # Older serialization versions for backwards compatibility:
  - { className: org.apache.tinkerpop.gremlin.driver.ser.GryoMessageSerializerV1d0, config: { ioRegistries: [org.janusgraph.graphdb.tinkerpop.JanusGraphIoRegistry] }}
  - { className: org.apache.tinkerpop.gremlin.driver.ser.GryoMessageSerializerV1d0, config: { serializeResultToString: true }}
  - { className: org.apache.tinkerpop.gremlin.driver.ser.GryoLiteMessageSerializerV1d0, config: {ioRegistries: [org.janusgraph.graphdb.tinkerpop.JanusGraphIoRegistry] }}
  - { className: org.apache.tinkerpop.gremlin.driver.ser.GraphSONMessageSerializerGremlinV2d0, config: { ioRegistries: [org.janusgraph.graphdb.tinkerpop.JanusGraphIoRegistry] }}
  - { className: org.apache.tinkerpop.gremlin.driver.ser.GraphSONMessageSerializerGremlinV1d0, config: { ioRegistries: [org.janusgraph.graphdb.tinkerpop.JanusGraphIoRegistryV1d0] }}
  - { className: org.apache.tinkerpop.gremlin.driver.ser.GraphSONMessageSerializerV1d0, config: { ioRegistries: [org.janusgraph.graphdb.tinkerpop.JanusGraphIoRegistryV1d0] }}

processors:
  - { className: org.apache.tinkerpop.gremlin.server.op.session.SessionOpProcessor, config: { sessionTimeout: 28800000 }}
  - { className: org.apache.tinkerpop.gremlin.server.op.traversal.TraversalOpProcessor, config: { cacheExpirationTime: 600000, cacheMaxSize: 1000 }}

metrics: {
  consoleReporter: {enabled: false, interval: 180000},
  csvReporter: {enabled: false, interval: 180000, fileName: /tmp/gremlin-server-metrics.csv},
  jmxReporter: {enabled: false},
  slf4jReporter: {enabled: true, interval: 180000},
  gangliaReporter: {enabled: false, interval: 180000, addressingMode: MULTICAST},
  graphiteReporter: {enabled: false, interval: 180000}}
threadPoolBoss: 1
maxInitialLineLength: 4096
maxHeaderSize: 8192
maxChunkSize: 8192
maxContentLength: 65536
maxAccumulationBufferComponents: 1024
resultIterationBatchSize: 64
writeBufferHighWaterMark: 32768
writeBufferHighWaterMark: 65536
ssl: {
  enabled: false}

Ман тавонистам тасодуфан версияи BerkeleyDB-и JanusGraph -ро "гузорам".

Ҳуҷҷатҳо аз рӯи нишондиҳандаҳо хеле каҷ аст, зеро идоракунии индексҳо аз шумо талаб мекунад, ки дар Groovy ягон шаманизми аҷиберо иҷро кунед. Масалан, эҷод кардани индекс бояд тавассути навиштани код дар консоли Gremlin анҷом дода шавад (ки, дар омади гап, берун аз қуттӣ кор намекунад). Аз ҳуҷҷатҳои расмии JanusGraph:

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()

Пас аз он

Ба як маъно, таҷрибаи дар боло овардашуда муқоисаи гарм ва нарм аст. Агар шумо дар ин бора фикр кунед, графики DBMS барои ба даст овардани натиҷаҳои якхела амалҳои дигарро иҷро мекунад. Аммо, дар доираи санҷишҳо, ман инчунин як таҷриба бо дархосте гузаронидам:

g.V().hasLabel('ZoneStep').has('id',0)
    .repeat(__.out().simplePath()).until(__.hasLabel('ZoneStep').has('id',1)).count().next()

ки масофаи пиёдагардро инъикос мекунад. Аммо, хатто аз руи ин гуна маълумотхо графики МДМ натичахое нишон дод, ки аз якчанд сония зиёд буд... Ин, албатта, аз он сабаб аст, ки роххо ба монанди 0 -> X -> Y ... -> 1, ки онро муҳаррики графикӣ низ тафтиш кардааст.

Ҳатто барои пурсиш ба монанди:

g.V().hasLabel('ZoneStep').has('id',0).out().has('id',1)).count().next()

Ман натавонистам бо вақти коркард камтар аз як сония посухи судманд гирам.

Ахлоқи ҳикоя дар он аст, ки идеяи зебо ва моделсозии парадигматикӣ ба натиҷаи дилхоҳ оварда намерасонад, ки бо истифода аз мисоли ClickHouse самаранокии баландтар нишон дода мешавад. Мисоли истифода, ки дар ин мақола оварда шудааст, як намунаи возеҳи зидди графикии DBMS мебошад, гарчанде ки он барои моделсозӣ дар парадигмаи онҳо мувофиқ ба назар мерасад.

Манбаъ: will.com

Илова Эзоҳ