การทดลองทดสอบการบังคับใช้ของ DBMS กราฟ JanusGraph เพื่อแก้ปัญหาในการค้นหาเส้นทางที่เหมาะสม

การทดลองทดสอบการบังคับใช้ของ DBMS กราฟ JanusGraph เพื่อแก้ปัญหาในการค้นหาเส้นทางที่เหมาะสม

สวัสดีทุกคน. เรากำลังพัฒนาผลิตภัณฑ์สำหรับการวิเคราะห์ปริมาณการใช้ข้อมูลแบบออฟไลน์ โครงการนี้มีงานที่เกี่ยวข้องกับการวิเคราะห์ทางสถิติของเส้นทางผู้มาเยือนข้ามภูมิภาค

ในส่วนหนึ่งของงานนี้ ผู้ใช้สามารถสอบถามคำสั่งระบบประเภทต่อไปนี้:

  • จำนวนผู้เยี่ยมชมที่ผ่านจากพื้นที่ "A" ไปยังพื้นที่ "B";
  • จำนวนผู้เยี่ยมชมที่ผ่านจากพื้นที่ "A" ไปยังพื้นที่ "B" ผ่านพื้นที่ "C" แล้วผ่านพื้นที่ "D"
  • ผู้เข้าชมบางประเภทใช้เวลานานเท่าใดในการเดินทางจากพื้นที่ “A” ไปยังพื้นที่ “B”

และคำถามเชิงวิเคราะห์ที่คล้ายกันจำนวนหนึ่ง

การเคลื่อนไหวของผู้เยี่ยมชมในพื้นที่ต่างๆ เป็นกราฟที่มีการกำหนดทิศทาง หลังจากอ่านอินเทอร์เน็ต ฉันพบว่ากราฟ DBMS ยังใช้สำหรับรายงานการวิเคราะห์ด้วย ฉันมีความปรารถนาที่จะเห็นว่ากราฟ DBMS จะรับมือกับข้อความค้นหาดังกล่าวได้อย่างไร (TL; DR; ไม่ดี)

ฉันเลือกใช้ DBMS เจนัสกราฟในฐานะตัวแทนที่โดดเด่นของ DBMS โอเพ่นซอร์สแบบกราฟซึ่งอาศัยเทคโนโลยีที่ครบถ้วนซึ่ง (ในความคิดของฉัน) ควรให้คุณลักษณะการปฏิบัติงานที่เหมาะสม:

  • แบ็กเอนด์ที่เก็บข้อมูล BerkeleyDB, Apache Cassandra, Scylla;
  • ดัชนีที่ซับซ้อนสามารถเก็บไว้ใน Lucene, Elasticsearch, Solr

ผู้เขียน JanusGraph เขียนว่าเหมาะสำหรับทั้ง OLTP และ OLAP

ฉันเคยร่วมงานกับ BerkeleyDB, Apache Cassandra, Scylla และ ES และผลิตภัณฑ์เหล่านี้มักใช้ในระบบของเรา ดังนั้นฉันจึงมองในแง่ดีเกี่ยวกับการทดสอบ DBMS แบบกราฟนี้ ฉันพบว่ามันแปลกที่จะเลือก BerkeleyDB มากกว่า RocksDB แต่นั่นอาจเป็นเพราะข้อกำหนดในการทำธุรกรรม ไม่ว่าในกรณีใด สำหรับการใช้งานผลิตภัณฑ์ที่สามารถปรับขนาดได้ ขอแนะนำให้ใช้แบ็กเอนด์บน Cassandra หรือ Scylla

ฉันไม่ได้พิจารณา Neo4j เนื่องจากการทำคลัสเตอร์ต้องใช้เวอร์ชันเชิงพาณิชย์ กล่าวคือ ผลิตภัณฑ์ไม่ได้เปิดอยู่

Graph DBMS พูดว่า: “ถ้ามันดูเหมือนกราฟ ให้ปฏิบัติเหมือนกราฟ!” - ความงาม!

ก่อนอื่น ฉันวาดกราฟซึ่งสร้างขึ้นตามหลักการของกราฟ DBMS ทุกประการ:

การทดลองทดสอบการบังคับใช้ของ DBMS กราฟ JanusGraph เพื่อแก้ปัญหาในการค้นหาเส้นทางที่เหมาะสม

มีสาระสำคัญคือ 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) กระทืบโดยไม่ย้อนกลับจนกว่าคุณจะพบ ZoneSteps เหล่านั้นซึ่งมีขอบไปยังโซนด้วย 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 คอร์และ RAM ขนาด 16 GB บน SSD JanusGraph ถูกปรับใช้โดยใช้คำสั่งนี้:

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

ในกรณีนี้ ข้อมูลและดัชนีที่ใช้สำหรับการค้นหาแบบตรงทั้งหมดจะถูกจัดเก็บไว้ใน BerkeleyDB หลังจากดำเนินการตามคำขอที่ให้ไว้ก่อนหน้านี้ ฉันได้รับเวลาเท่ากับหลายสิบวินาที

ด้วยการรันสคริปต์ทั้ง 4 ตัวข้างต้นแบบขนาน ฉันสามารถเปลี่ยน DBMS ให้เป็นฟักทองได้ด้วยสตรีม Java Stacktraces ที่สนุกสนาน (และเราทุกคนชอบอ่าน Java Stacktraces) ในบันทึกของ Docker

หลังจากคิดอยู่พักหนึ่ง ฉันจึงตัดสินใจลดความซับซ้อนของแผนภาพกราฟดังต่อไปนี้:

การทดลองทดสอบการบังคับใช้ของ DBMS กราฟ JanusGraph เพื่อแก้ปัญหาในการค้นหาเส้นทางที่เหมาะสม

การตัดสินใจว่าการค้นหาตามแอตทริบิวต์เอนทิตีจะเร็วกว่าการค้นหาตามขอบ ด้วยเหตุนี้ คำขอของฉันจึงกลายเป็นสิ่งต่อไปนี้:

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 เป็นการใช้งาน Cassandra ที่เร็วที่สุด แต่ก็ไม่ได้นำไปสู่การเปลี่ยนแปลงประสิทธิภาพที่สำคัญใดๆ เลย

ดังนั้นแม้ว่าข้อเท็จจริงที่ว่า "ดูเหมือนกราฟ" แต่ฉันก็ไม่สามารถให้กราฟ DBMS ประมวลผลได้อย่างรวดเร็ว ฉันถือว่าฉันไม่รู้อะไรบางอย่างและเป็นไปได้ที่จะทำให้ JanusGraph ทำการค้นหานี้ภายในเสี้ยววินาที อย่างไรก็ตาม ฉันไม่สามารถทำได้

เนื่องจากปัญหายังคงต้องได้รับการแก้ไข ฉันจึงเริ่มคิดถึง JOIN และ Pivots ของตาราง ซึ่งไม่ได้สร้างแรงบันดาลใจในการมองโลกในแง่ดีในแง่ของความสง่างาม แต่อาจเป็นทางเลือกที่ใช้งานได้จริงในทางปฏิบัติ

โครงการของเราใช้ 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 หากต้องการย้ายจากจุด 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 ได้สร้างภาระขั้นต่ำบนระบบย่อยของดิสก์

ข้อสรุป

เราตัดสินใจใช้ ClickHouse เพื่อให้บริการคำขอประเภทนี้ เราสามารถเพิ่มประสิทธิภาพการสืบค้นเพิ่มเติมได้เสมอโดยใช้มุมมองที่เป็นรูปธรรมและการทำงานแบบขนานโดยการประมวลผลสตรีมเหตุการณ์ล่วงหน้าโดยใช้ Apache Flink ก่อนที่จะโหลดลงใน ClickHouse

ประสิทธิภาพดีมากจนเราอาจไม่ต้องคิดถึงการหมุนตารางโดยทางโปรแกรมด้วยซ้ำ ก่อนหน้านี้ เราต้องทำ pivot ข้อมูลที่ดึงมาจาก 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}

ฉันจัดการ "ใส่" JanusGraph เวอร์ชัน BerkeleyDB โดยไม่ตั้งใจ

เอกสารประกอบค่อนข้างคดโกงในแง่ของดัชนี เนื่องจากการจัดการดัชนีกำหนดให้คุณต้องแสดงหมอผีที่ค่อนข้างแปลกใน 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()

ซึ่งสะท้อนถึงระยะการเดิน อย่างไรก็ตาม แม้แต่ข้อมูลดังกล่าว กราฟ DBMS ก็แสดงผลลัพธ์ที่เกินไม่กี่วินาที... แน่นอนว่าเป็นเพราะเส้นทางเช่น 0 -> X -> Y ... -> 1ซึ่งกราฟเอ็นจิ้นก็ตรวจสอบด้วย

แม้แต่สำหรับข้อความค้นหาเช่น:

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

ฉันไม่สามารถรับการตอบกลับที่มีประสิทธิผลด้วยเวลาประมวลผลน้อยกว่าหนึ่งวินาทีได้

คุณธรรมของเรื่องราวคือความคิดที่สวยงามและการสร้างแบบจำลองเชิงกระบวนทัศน์ไม่ได้นำไปสู่ผลลัพธ์ที่ต้องการ ซึ่งแสดงให้เห็นด้วยประสิทธิภาพที่สูงกว่ามากโดยใช้ตัวอย่างของ ClickHouse กรณีการใช้งานที่นำเสนอในบทความนี้เป็นการต่อต้านรูปแบบที่ชัดเจนสำหรับกราฟ DBMS แม้ว่าดูเหมือนว่าจะเหมาะสำหรับการสร้างแบบจำลองในกระบวนทัศน์ก็ตาม

ที่มา: will.com

เพิ่มความคิดเห็น