یوه تجربه د مناسبو لارو موندلو ستونزې حل کولو لپاره د جانوس ګراف ګراف DBMS د تطبیق وړتیا ازموي

یوه تجربه د مناسبو لارو موندلو ستونزې حل کولو لپاره د جانوس ګراف ګراف DBMS د تطبیق وړتیا ازموي

سلام و ټولو ته. موږ د آفلاین ترافیک تحلیل لپاره محصول رامینځته کوو. پروژه په ټولو سیمو کې د لیدونکو لارو احصایوي تحلیل پورې اړوند دنده لري.

د دې دندې د یوې برخې په توګه، کاروونکي کولی شي د لاندې ډول سیسټم پوښتنو څخه پوښتنه وکړي:

  • څومره لیدونکي د "A" ساحې څخه "B" سیمې ته تیریدل؛
  • څومره لیدونکي د "A" ساحې څخه سیمې "B" ته د ساحې "C" له لارې او بیا د "D" ساحې له لارې تیریدل؛
  • د "A" ساحې څخه "B" ساحې ته د یو ځانګړي ډول لیدونکي سفر کولو لپاره څومره وخت نیسي.

او یو شمیر ورته تحلیلي پوښتنې.

په سیمو کې د لیدونکي حرکت یو لارښود ګراف دی. د انټرنیټ لوستلو وروسته، ما وموندله چې ګراف DBMSs د تحلیلي راپورونو لپاره هم کارول کیږي. ما هیله درلوده چې وګورم چې څنګه ګراف DBMSs به د داسې پوښتنو سره مقابله وکړي (TL؛ DR؛ په خراب ډول).

ما د DBMS کارولو لپاره غوره کړه جانوس ګراف، د ګراف د خلاصې سرچینې DBMS د نامتو استازي په توګه ، کوم چې د بالغ ټیکنالوژیو سټک باندې تکیه کوي ، کوم چې (زما په نظر) باید دا د ښه عملیاتي ځانګړتیاو سره چمتو کړي:

  • د برکلي ډی بی ذخیره کولو پس منظر، اپاچی کیسیندرا، سکیلا؛
  • پیچلي شاخصونه په Lucene، Elasticsearch، Solr کې زیرمه کیدی شي.

د JanusGraph لیکوالان لیکي چې دا د OLTP او OLAP دواړو لپاره مناسب دی.

ما د BerkeleyDB، Apache Cassandra، Scylla او ES سره کار کړی دی، او دا محصولات اکثرا زموږ په سیسټمونو کې کارول کیږي، نو زه د دې ګراف DBMS ازموینې په اړه خوشبین وم. ما د RocksDB په پرتله د BerkeleyDB غوره کول عجیب ولیدل، مګر دا شاید د لیږد اړتیاو له امله وي. په هر حالت کې، د توزیع وړ، د محصول کارولو لپاره، دا وړاندیز کیږي چې په Cassandra یا Scylla کې یو پس منظر وکاروئ.

ما Neo4j ته پام نه دی کړی ځکه چې کلستر کول سوداګریزې نسخې ته اړتیا لري، دا دی، محصول خلاص سرچینه نه ده.

ګراف DBMSs وايي: "که دا د ګراف په څیر ښکاري، د ګراف په څیر چلند وکړئ!" - ښکلا!

لومړی، ما یو ګراف رسم کړ، کوم چې د ګراف DBMSs د کانونو سره سم جوړ شوی و:

یوه تجربه د مناسبو لارو موندلو ستونزې حل کولو لپاره د جانوس ګراف ګراف 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)، پرته له دې چې شاته لاړ شئ تر هغه پورې چې تاسو هغه زون سټیپونه ومومئ چې له هغې څخه زون ته یوه څنډه شتون لري. ID=19، د داسې زنځیرونو شمیره شمیره.

زه په ګرافونو کې د لټون ټول پیچلتیاوې نه پیژنم، مګر دا پوښتنه د دې کتاب پر بنسټ رامینځته شوې (https://kelvinlawrence.net/book/Gremlin-Graph-Guide.html).

ما د BerkeleyDB بیکینډ په کارولو سره د 50 څخه تر 3 نقطو پورې اوږدوالی 20 زره ټریکونه د JanusGraph ګراف ډیټابیس کې بار کړل، د مطابق شاخصونه یې جوړ کړل. مشرتابه.

د پایتون ډاونلوډ سکریپټ:


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)

موږ په SSD کې د 4 کور او 16 GB رام سره VM کارولی. JanusGraph د دې کمانډ په کارولو سره ځای پرځای شوی و:

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

په دې حالت کې، ډاټا او شاخصونه چې د دقیق میچ لټونونو لپاره کارول کیږي په BerkeleyDB کې زیرمه شوي. مخکې ورکړل شوې غوښتنې پلي کولو سره ، ما د څو لسګونو ثانیو سره مساوي وخت ترلاسه کړ.

په موازي ډول د 4 پورتني سکریپټونو په چلولو سره ، ما په ډاکر لاګونو کې د جاوا سټیکټریسونو خوشحاله جریان سره DBMS په کدو بدل کړ (او موږ ټول د جاوا سټریکټریس لوستلو سره مینه لرو).

د یو څه فکر کولو وروسته ، ما پریکړه وکړه چې لاندې ته د ګراف ډیاګرام ساده کړم:

یوه تجربه د مناسبو لارو موندلو ستونزې حل کولو لپاره د جانوس ګراف ګراف DBMS د تطبیق وړتیا ازموي

پریکړه کول چې د وجود ځانګړتیاو لخوا لټون کول به د څنډو لخوا د لټون په پرتله ګړندي وي. د پایلې په توګه، زما غوښتنه په لاندې ډول بدله شوه:

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

په روسی کې څه شی داسې دی: د ID=0 سره ZoneStep ومومئ، پرته له دې چې بیرته لاړ شئ تر هغه پورې چې تاسو د ID=19 سره ZoneStep ونه موندلئ، د داسې زنځیرونو شمیر شمیرئ.

ما پورته ورکړل شوی د بارولو سکریپټ هم ساده کړی ترڅو غیر ضروري اړیکې رامینځته نه کړي ، ځان په ځانګړتیاو پورې محدود کړي.

غوښتنې لاهم بشپړیدو ته څو ثانیې وخت نیولی ، کوم چې زموږ د دندې لپاره په بشپړ ډول د منلو وړ نه و ، ځکه چې دا د هر ډول AdHoc غوښتنو موخو لپاره مناسب نه و.

ما هڅه وکړه چې جانوس ګراف د سکیلا په کارولو سره د کاسندرا ترټولو ګړندۍ پلي کولو په توګه وکاروم ، مګر دا د فعالیت کوم مهم بدلون لامل نه شو.

نو د دې حقیقت سره سره چې "دا د ګراف په څیر ښکاري"، زه نشم کولی ګراف DBMS ترلاسه کړم ترڅو دا په چټکۍ سره پروسس کړم. زه په بشپړه توګه ګومان کوم چې داسې یو څه شتون لري چې زه یې نه پوهیږم او دا چې جانوس ګراف کولی شي دا لټون د یوې ثانیې په یوه برخه کې ترسره کړي، په هرصورت، زه دا نشم کولی.

له هغه ځایه چې ستونزه لاهم حل کیدو ته اړتیا لري ، ما د جدولونو JOINs او Pivots په اړه فکر پیل کړ ، کوم چې د ښکلا په شرایطو کې خوشبیني نه هڅوي ، مګر په عمل کې په بشپړ ډول د کار وړ انتخاب کیدی شي.

زموږ پروژه دمخه د اپاچي کلیک هاؤس کاروي ، نو ما پریکړه وکړه چې پدې تحلیلي DBMS کې زما څیړنه و ازموم.

د ساده ترکیب په کارولو سره د کلیک هاوس ځای په ځای شوی:

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
    )

څرنګه چې داخلونه په بیچونو کې راځي، ډکول د جانوس ګراف په پرتله خورا ګړندي وو.

د JOIN په کارولو سره دوه پوښتنې جوړې کړې. د A نقطې څخه 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 وکاروو. موږ تل کولی شو پوښتنو ته د مادي لیدونو او موازي کولو په کارولو سره د اپاچي فلینک په کارولو سره د پیښې جریان دمخه پروسس کولو سره د کلیک هاؤس کې بار کولو دمخه اصلاح کړو.

فعالیت دومره ښه دی چې موږ شاید حتی په برنامه کې د محور میزونو په اړه فکر ونه کړو. مخکې، موږ باید د اپاچي پارکیټ ته د اپلوډ کولو له لارې له ویرټیکا څخه ترلاسه شوي ډیټا پیوټس ترسره کړو.

له بده مرغه، د ګراف DBMS کارولو بله هڅه ناکامه وه. ما جانوس ګراف ونه موندل چې دوستانه ایکوسیستم ولري چې د محصول سره سرعت ته رسیدل یې اسانه کړي. په ورته وخت کې ، د سرور تنظیم کولو لپاره ، د جاوا دودیزه لاره کارول کیږي ، کوم چې به هغه خلک رامینځته کړي چې له جاوا سره بلد نه وي د وینې اوښکې بهوي:

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}

ما په تصادفي ډول د جانوس ګراف د برکلي ډی بی نسخه "کیښودل" اداره وکړه.

اسناد د شاخصونو له مخې خورا کثافات دي، ځکه چې د شاخصونو اداره کول تاسو ته اړتیا لري چې په ګرووي کې یو څه عجیب شیمانیزم ترسره کړئ. د مثال په توګه ، د شاخص رامینځته کول باید د ګریملین کنسول کې د کوډ لیکلو سره ترسره شي (کوم چې په لاره کې ، د بکس څخه بهر کار نه کوي). د رسمي 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()

کوم چې د تګ واټن منعکس کوي. په هرصورت، حتی د داسې معلوماتو په اړه، ګراف DBMS پایلې ښودلې چې د څو ثانیو څخه بهر تللي ... دا، البته، د دې حقیقت له امله دی چې داسې لارې وې 0 -> X -> Y ... -> 1، کوم چې د ګراف انجن هم چیک کړی.

حتی د یوې پوښتنې لپاره لکه:

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

زه نشم کولی د یوې ثانیې څخه لږ د پروسس کولو وخت سره ګټور ځواب ترلاسه کړم.

د کیسې اخلاق دا دي چې یو ښکلی مفکوره او تمثیل ماډل مطلوب پایلې ته نه رسوي، کوم چې د کلک هاوس مثال په کارولو سره د خورا لوړ موثریت سره ښودل کیږي. په دې مقاله کې وړاندې شوي د کارولو قضیه د ګراف DBMSs لپاره روښانه ضد نمونه ده، که څه هم دا د دوی په تمثیل کې د ماډل کولو لپاره مناسب ښکاري.

سرچینه: www.habr.com

Add a comment