1.1 ืžื™ืœื™ืืจื“ ื ืกื™ืขื•ืช ื‘ืžื•ื ื™ื•ืช: ืืฉื›ื•ืœ ClickHouse ื‘ืขืœ 108 ืœื™ื‘ื•ืช

ืชืจื’ื•ื ื”ืžืืžืจ ื”ื•ื›ืŸ ื‘ืžื™ื•ื—ื“ ืขื‘ื•ืจ ืชืœืžื™ื“ื™ ื”ืงื•ืจืก ืžื”ื ื“ืก ื ืชื•ื ื™ื.

1.1 ืžื™ืœื™ืืจื“ ื ืกื™ืขื•ืช ื‘ืžื•ื ื™ื•ืช: ืืฉื›ื•ืœ ClickHouse ื‘ืขืœ 108 ืœื™ื‘ื•ืช

ืงืœื™ืงื”ืื•ืก ื”ื•ื ืžืกื“ ื ืชื•ื ื™ื ืขืžื•ื“ื™ื ื‘ืงื•ื“ ืคืชื•ื—. ื–ื•ื”ื™ ืกื‘ื™ื‘ื” ื ื”ื“ืจืช ืฉื‘ื” ืžืื•ืช ืื ืœื™ืกื˜ื™ื ื™ื›ื•ืœื™ื ืœื‘ืฆืข ืฉืื™ืœืชื•ืช ื‘ืžื”ื™ืจื•ืช ื‘ื ืชื•ื ื™ื ืžืคื•ืจื˜ื™ื, ืืคื™ืœื• ื›ืืฉืจ ืžื•ื–ื ื™ื ืขืฉืจื•ืช ืžื™ืœื™ืืจื“ื™ ืจืฉื•ืžื•ืช ื—ื“ืฉื•ืช ื‘ื™ื•ื. ืขืœื•ื™ื•ืช ื”ืชืฉืชื™ืช ืœืชืžื™ื›ื” ื‘ืžืขืจื›ืช ื›ื–ื• ื™ื›ื•ืœื•ืช ืœื”ื’ื™ืข ืœ-$100 ืœืฉื ื”, ื•ืื•ืœื™ ื—ืฆื™ ืžื–ื” ืชืœื•ื™ ื‘ืฉื™ืžื•ืฉ. ื‘ืฉืœื‘ ืžืกื•ื™ื, ื”ืชืงื ืช ClickHouse ืž-Yandex Metrics ื”ื›ื™ืœื” 10 ื˜ืจื™ืœื™ื•ืŸ ืจืฉื•ืžื•ืช. ื‘ื ื•ืกืฃ ืœ-Yandex, ClickHouse ืžืฆืื” ื”ืฆืœื—ื” ื’ื ืขื ื‘ืœื•ืžื‘ืจื’ ื•-Cloudflare.

ืœืคื ื™ ืฉื ืชื™ื™ื ื‘ื™ืœื™ืชื™ ื ื™ืชื•ื— ื”ืฉื•ื•ืืชื™ ืžืกื“ื™ ื ืชื•ื ื™ื ื‘ืืžืฆืขื•ืช ืžื›ื•ื ื” ืื—ืช, ื•ื–ื” ื”ืคืš ื”ื›ื™ ืžื”ื™ืจ ืชื•ื›ื ืช ืžืกื“ ื ืชื•ื ื™ื ื—ื™ื ืžื™ืช ืฉืื™ ืคืขื ืจืื™ืชื™. ืžืื–, ืžืคืชื—ื™ื ืœื ื”ืคืกื™ืงื• ืœื”ื•ืกื™ืฃ ืชื›ื•ื ื•ืช, ื›ื•ืœืœ ืชืžื™ื›ื” ื‘ื“ื—ื™ืกืช Kafka, HDFS ื•-ZStandard. ื‘ืฉื ื” ืฉืขื‘ืจื” ื”ื ื”ื•ืกื™ืคื• ืชืžื™ื›ื” ื‘ืฉื™ื˜ื•ืช ื“ื—ื™ืกื” ืžื“ื•ืจื’ืช, ื• ื“ืœืชื-ืž-ื“ืœืชื ืงื™ื“ื•ื“ ื”ืคืš ืœืืคืฉืจื™. ื‘ืขืช ื“ื—ื™ืกื” ืฉืœ ื ืชื•ื ื™ ืกื“ืจื•ืช ื–ืžืŸ, ื ื™ืชืŸ ืœื“ื—ื•ืก ืขืจื›ื™ ืžื“ ื”ื™ื˜ื‘ ื‘ืืžืฆืขื•ืช ืงื™ื“ื•ื“ ื“ืœืชื, ืืš ืขื‘ื•ืจ ืžื•ื ื™ื ืขื“ื™ืฃ ืœื”ืฉืชืžืฉ ื‘ืงื™ื“ื•ื“ ื“ืœืชื-ืœื“ืœืชื. ื“ื—ื™ืกื” ื˜ื•ื‘ื” ื”ืคื›ื” ืœืžืคืชื— ืœื‘ื™ืฆื•ืขื™ื ืฉืœ ClickHouse.

ClickHouse ืžื•ืจื›ื‘ ืž-170 ืืœืฃ ืฉื•ืจื•ืช ืฉืœ ืงื•ื“ C++, ืœื ื›ื•ืœืœ ืกืคืจื™ื•ืช ืฉืœ ืฆื“ ืฉืœื™ืฉื™, ื•ื”ื•ื ืื—ื“ ืžื‘ืกื™ืกื™ ื”ืงื•ื“ ื”ืžื‘ื•ื–ืจื™ื ื‘ื™ื•ืชืจ ืฉืœ ืžืกื“ื™ ื ืชื•ื ื™ื. ืœืฉื ื”ืฉื•ื•ืื”, SQLite ืœื ืชื•ืžื›ืช ื‘ื”ืคืฆื” ื•ืžื•ืจื›ื‘ืช ืž-235 ืืœืฃ ืฉื•ืจื•ืช ืฉืœ ืงื•ื“ C. ื ื›ื•ืŸ ืœื›ืชื™ื‘ืช ืฉื•ืจื•ืช ืืœื”, 207 ืžื”ื ื“ืกื™ื ืชืจืžื• ืœ-ClickHouse, ื•ืขื•ืฆืžืช ื”-commits ืขืœืชื” ืœืื—ืจื•ื ื”.

ื‘ืžืจืฅ 2017, ืงืœื™ืงื”ืื•ืก ื”ื—ืœื” ืœื ื”ืœ ื™ื•ืžืŸ ืฉื™ื ื•ื™ื™ื ื›ื“ืจืš ืงืœื” ืœืขืงื•ื‘ ืื—ืจ ื”ืคื™ืชื•ื—. ื”ื ื’ื ืคื™ืฆืœื• ืืช ืงื•ื‘ืฅ ื”ืชื™ืขื•ื“ ื”ืžื•ื ื•ืœื™ื˜ื™ ืœื”ื™ืจืจื›ื™ื™ืช ืงื‘ืฆื™ื ืžื‘ื•ืกืกืช Markdown. ืžืขืงื‘ ืื—ืจ ื‘ืขื™ื•ืช ื•ืชื›ื•ื ื•ืช ืžืชื‘ืฆืข ื‘ืืžืฆืขื•ืช GitHub, ื•ื‘ืื•ืคืŸ ื›ืœืœื™ ื”ืชื•ื›ื ื” ื”ืคื›ื” ืœื ื’ื™ืฉื” ื”ืจื‘ื” ื™ื•ืชืจ ื‘ืฉื ื™ื ื”ืื—ืจื•ื ื•ืช.

ื‘ืžืืžืจ ื–ื”, ืื ื™ ื”ื•ืœืš ืœื”ืกืชื›ืœ ืขืœ ื”ื‘ื™ืฆื•ืขื™ื ืฉืœ ืืฉื›ื•ืœ ClickHouse ื‘-AWS EC2 ื‘ืืžืฆืขื•ืช ืžืขื‘ื“ื™ 36 ืœื™ื‘ื•ืช ื•ืื—ืกื•ืŸ NVMe.

ืขื“ื›ื•ืŸ: ืฉื‘ื•ืข ืœืื—ืจ ืคืจืกื•ื ื”ืคื•ืกื˜ ื”ื–ื” ื‘ืžืงื•ืจ, ืจืฆืชื™ ืžื—ื“ืฉ ืืช ื”ื‘ื“ื™ืงื” ืขื ืชืฆื•ืจื” ืžืฉื•ืคืจืช ื•ื”ืฉื’ืชื™ ืชื•ืฆืื•ืช ื˜ื•ื‘ื•ืช ื‘ื”ืจื‘ื”. ืคื•ืกื˜ ื–ื” ืขื•ื“ื›ืŸ ื›ื“ื™ ืœืฉืงืฃ ืืช ื”ืฉื™ื ื•ื™ื™ื ื”ืœืœื•.

ื”ืฉืงืช ืืฉื›ื•ืœ AWS EC2

ืื ื™ ืืฉืชืžืฉ ื‘ืฉืœื•ืฉื” ืžื•ืคืขื™ c5d.9xlarge EC2 ืขื‘ื•ืจ ื”ืคื•ืกื˜ ื”ื–ื”. ื›ืœ ืื—ื“ ืžื”ื ืžื›ื™ืœ 36 ืžืขื‘ื“ื™ื ื•ื™ืจื˜ื•ืืœื™ื™ื, 72 GB ืฉืœ ื–ื™ื›ืจื•ืŸ RAM, 900 GB ืฉืœ ืื—ืกื•ืŸ NVMe SSD ื•ืชื•ืžืš ื‘ืจืฉืช 10 Gigabit. ื”ื ืขื•ืœื™ื $1,962 ืœืฉืขื” ื›ืœ ืื—ื“ ื‘ืื–ื•ืจ eu-west-1 ื›ืืฉืจ ื”ื ืคื•ืขืœื™ื ืœืคื™ ื“ืจื™ืฉื”. ืื ื™ ืืฉืชืžืฉ ื‘-Ubuntu Server 16.04 LTS ื›ืžืขืจื›ืช ื”ื”ืคืขืœื”.

ื—ื•ืžืช ื”ืืฉ ืžื•ื’ื“ืจืช ื›ืš ืฉื›ืœ ืžื—ืฉื‘ ื™ื›ื•ืœ ืœืชืงืฉืจ ืื—ื“ ืขื ื”ืฉื ื™ ืœืœื ื”ื’ื‘ืœื•ืช, ื•ืจืง ื›ืชื•ื‘ืช ื”-IPv4 ืฉืœื™ ืจืฉื•ืžื” ืขืœ ื™ื“ื™ SSH ื‘ืืฉื›ื•ืœ.

ื›ื•ื ืŸ NVMe ื‘ืžืฆื‘ ืžื•ื›ื ื•ืช ืชืคืขื•ืœื™ืช

ื›ื“ื™ ืฉ-ClickHouse ื™ืขื‘ื•ื“, ืืฆื•ืจ ืžืขืจื›ืช ืงื‘ืฆื™ื ื‘ืคื•ืจืžื˜ EXT4 ื‘ื›ื•ื ืŸ NVMe ื‘ื›ืœ ืื—ื“ ืžื”ืฉืจืชื™ื.

$ sudo mkfs -t ext4 /dev/nvme1n1
$ sudo mkdir /ch
$ sudo mount /dev/nvme1n1 /ch

ืœืื—ืจ ืฉื”ื›ืœ ืžื•ื’ื“ืจ, ืืชื” ื™ื›ื•ืœ ืœืจืื•ืช ืืช ื ืงื•ื“ืช ื”ื”ืจื›ื‘ื” ื•ืืช 783 GB ืฉืœ ืฉื˜ื— ื–ืžื™ืŸ ื‘ื›ืœ ืžืขืจื›ืช.

$ lsblk

NAME        MAJ:MIN RM   SIZE RO TYPE MOUNTPOINT
loop0         7:0    0  87.9M  1 loop /snap/core/5742
loop1         7:1    0  16.5M  1 loop /snap/amazon-ssm-agent/784
nvme0n1     259:1    0     8G  0 disk
โ””โ”€nvme0n1p1 259:2    0     8G  0 part /
nvme1n1     259:0    0 838.2G  0 disk /ch

$ df -h

Filesystem      Size  Used Avail Use% Mounted on
udev             35G     0   35G   0% /dev
tmpfs           6.9G  8.8M  6.9G   1% /run
/dev/nvme0n1p1  7.7G  967M  6.8G  13% /
tmpfs            35G     0   35G   0% /dev/shm
tmpfs           5.0M     0  5.0M   0% /run/lock
tmpfs            35G     0   35G   0% /sys/fs/cgroup
/dev/loop0       88M   88M     0 100% /snap/core/5742
/dev/loop1       17M   17M     0 100% /snap/amazon-ssm-agent/784
tmpfs           6.9G     0  6.9G   0% /run/user/1000
/dev/nvme1n1    825G   73M  783G   1% /ch

ืžืขืจืš ื”ื ืชื•ื ื™ื ืฉื‘ื• ืืฉืชืžืฉ ื‘ื‘ื“ื™ืงื” ื–ื• ื”ื•ื ืžื–ื‘ืœื” ืฉืœ ื ืชื•ื ื™ื ืฉื™ืฆืจืชื™ ืž-1.1 ืžื™ืœื™ืืจื“ ื ืกื™ืขื•ืช ื‘ืžื•ื ื™ื•ืช ืฉื‘ื•ืฆืขื• ื‘ื ื™ื• ื™ื•ืจืง ื‘ืžืฉืš ืฉืฉ ืฉื ื™ื. ื‘ื‘ืœื•ื’ ืžื™ืœื™ืืจื“ ื ืกื™ืขื•ืช ื‘ืžื•ื ื™ืช ื‘ื”ื™ืกื˜ ืœืื“ื•ื ืคื™ืจื•ื˜ ื›ื™ืฆื“ ืืกืคืชื™ ืืช ืžืขืจืš ื”ื ืชื•ื ื™ื ื”ื–ื”. ื”ื ืžืื•ื—ืกื ื™ื ื‘-AWS S3, ืื– ืื ื™ ืื’ื“ื™ืจ ืืช ื”-AWS CLI ืขื ื”ื’ื™ืฉื” ื•ื”ืžืคืชื—ื•ืช ื”ืกื•ื“ื™ื™ื ืฉืœื™.

$ sudo apt update
$ sudo apt install awscli
$ aws configure

ืื ื™ ืื’ื“ื™ืจ ืืช ืžื’ื‘ืœืช ื”ื‘ืงืฉื•ืช ื‘ื•-ื–ืžื ื™ืช ืฉืœ ื”ืœืงื•ื— ืœ-100 ื›ื“ื™ ืœื”ื•ืจื™ื“ ืงื‘ืฆื™ื ืžื”ืจ ื™ื•ืชืจ ืžื”ื’ื“ืจื•ืช ื‘ืจื™ืจืช ื”ืžื—ื“ืœ.

$ aws configure set 
    default.s3.max_concurrent_requests 
    100

ืื ื™ ืื•ืจื™ื“ ืืช ืžืขืจืš ื”ื ืกื™ืขื•ืช ื‘ืžื•ื ื™ื•ืช ืž-AWS S3 ื•ืืื—ืกืŸ ืื•ืชื• ื‘ื›ื•ื ืŸ NVMe ื‘ืฉืจืช ื”ืจืืฉื•ืŸ. ืžืขืจืš ื”ื ืชื•ื ื™ื ื”ื–ื” ื”ื•ื ~104GB ื‘ืคื•ืจืžื˜ CSV ื“ื—ื•ืก GZIP.

$ sudo mkdir -p /ch/csv
$ sudo chown -R ubuntu /ch/csv
$ aws s3 sync s3://<bucket>/csv /ch/csv

ื”ืชืงื ืช ClickHouse

ืื ื™ ืืชืงื™ืŸ ืืช ื”ืคืฆืช OpenJDK ืขื‘ื•ืจ Java 8 ื›ืคื™ ืฉื”ื™ื ื ื“ืจืฉืช ื›ื“ื™ ืœื”ืคืขื™ืœ ืืช Apache ZooKeeper, ืืฉืจ ื ื“ืจืฉืช ืœื”ืชืงื ื” ืžื‘ื•ื–ืจืช ืฉืœ ClickHouse ื‘ื›ืœ ืฉืœื•ืฉืช ื”ืžื›ื•ื ื•ืช.

$ sudo apt update
$ sudo apt install 
    openjdk-8-jre 
    openjdk-8-jdk-headless

ื•ืื– ืื ื™ ืžื’ื“ื™ืจ ืืช ืžืฉืชื ื” ื”ืกื‘ื™ื‘ื” JAVA_HOME.

$ sudo vi /etc/profile
 
export JAVA_HOME=/usr
 
$ source /etc/profile

ืœืื—ืจ ืžื›ืŸ ืืฉืชืžืฉ ื‘ืžืขืจื›ืช ื ื™ื”ื•ืœ ื”ื—ื‘ื™ืœื•ืช ืฉืœ ืื•ื‘ื•ื ื˜ื• ื›ื“ื™ ืœื”ืชืงื™ืŸ ืืช ClickHouse 18.16.1, ืžื‘ื˜ื™ื ื•-ZooKeeper ื‘ื›ืœ ืฉืœื•ืฉืช ื”ืžื›ื•ื ื•ืช.

$ sudo apt-key adv 
    --keyserver hkp://keyserver.ubuntu.com:80 
    --recv E0C56BD4
$ echo "deb http://repo.yandex.ru/clickhouse/deb/stable/ main/" | 
    sudo tee /etc/apt/sources.list.d/clickhouse.list
$ sudo apt-get update

$ sudo apt install 
    clickhouse-client 
    clickhouse-server 
    glances 
    zookeeperd

ืื ื™ ืืฆื•ืจ ืกืคืจื™ื™ื” ืขื‘ื•ืจ ClickHouse ื•ื’ื ืืขืฉื” ื›ืžื” ืขืงื™ืคื•ืช ืชืฆื•ืจื” ื‘ื›ืœ ืฉืœื•ืฉืช ื”ืฉืจืชื™ื.

$ sudo mkdir /ch/clickhouse
$ sudo chown -R clickhouse /ch/clickhouse

$ sudo mkdir -p /etc/clickhouse-server/conf.d
$ sudo vi /etc/clickhouse-server/conf.d/taxis.conf

ืืœื• ื”ืŸ ืขืงื™ืคื•ืช ื”ืชืฆื•ืจื” ืฉื‘ื”ืŸ ืืฉืชืžืฉ.

<?xml version="1.0"?>
<yandex>
    <listen_host>0.0.0.0</listen_host>
    <path>/ch/clickhouse/</path>

 <remote_servers>
        <perftest_3shards>
            <shard>
                <replica>
                    <host>172.30.2.192</host>
                    <port>9000</port>
                 </replica>
            </shard>
            <shard>
                 <replica>
                    <host>172.30.2.162</host>
                    <port>9000</port>
                 </replica>
            </shard>
            <shard>
                 <replica>
                    <host>172.30.2.36</host>
                    <port>9000</port>
                 </replica>
            </shard>
        </perftest_3shards>
    </remote_servers>

  <zookeeper-servers>
        <node>
            <host>172.30.2.192</host>
            <port>2181</port>
        </node>
        <node>
            <host>172.30.2.162</host>
            <port>2181</port>
        </node>
        <node>
            <host>172.30.2.36</host>
            <port>2181</port>
        </node>
    </zookeeper-servers>

 <macros>
        <shard>03</shard>
        <replica>01</replica>
    </macros>
</yandex>

ืœืื—ืจ ืžื›ืŸ ืืคืขื™ืœ ืืช ZooKeeper ื•ืืช ืฉืจืช ClickHouse ื‘ื›ืœ ืฉืœื•ืฉืช ื”ืžื›ื•ื ื•ืช.

$ sudo /etc/init.d/zookeeper start
$ sudo service clickhouse-server start

ื”ืขืœืืช ื ืชื•ื ื™ื ืœืงืœื™ืงื”ืื•ืก

ื‘ืฉืจืช ื”ืจืืฉื•ืŸ ืืฆื•ืจ ื˜ื‘ืœืช ื˜ื™ื•ืœื™ื (trips), ืืฉืจ ื™ืื—ืกืŸ ืžืขืจืš ื ืชื•ื ื™ื ืฉืœ ื ืกื™ืขื•ืช ื‘ืžื•ื ื™ื•ืช ื‘ืืžืฆืขื•ืช ืžื ื•ืข Log.

$ clickhouse-client --host=0.0.0.0
 
CREATE TABLE trips (
    trip_id                 UInt32,
    vendor_id               String,

    pickup_datetime         DateTime,
    dropoff_datetime        Nullable(DateTime),

    store_and_fwd_flag      Nullable(FixedString(1)),
    rate_code_id            Nullable(UInt8),
    pickup_longitude        Nullable(Float64),
    pickup_latitude         Nullable(Float64),
    dropoff_longitude       Nullable(Float64),
    dropoff_latitude        Nullable(Float64),
    passenger_count         Nullable(UInt8),
    trip_distance           Nullable(Float64),
    fare_amount             Nullable(Float32),
    extra                   Nullable(Float32),
    mta_tax                 Nullable(Float32),
    tip_amount              Nullable(Float32),
    tolls_amount            Nullable(Float32),
    ehail_fee               Nullable(Float32),
    improvement_surcharge   Nullable(Float32),
    total_amount            Nullable(Float32),
    payment_type            Nullable(String),
    trip_type               Nullable(UInt8),
    pickup                  Nullable(String),
    dropoff                 Nullable(String),

    cab_type                Nullable(String),

    precipitation           Nullable(Int8),
    snow_depth              Nullable(Int8),
    snowfall                Nullable(Int8),
    max_temperature         Nullable(Int8),
    min_temperature         Nullable(Int8),
    average_wind_speed      Nullable(Int8),

    pickup_nyct2010_gid     Nullable(Int8),
    pickup_ctlabel          Nullable(String),
    pickup_borocode         Nullable(Int8),
    pickup_boroname         Nullable(String),
    pickup_ct2010           Nullable(String),
    pickup_boroct2010       Nullable(String),
    pickup_cdeligibil       Nullable(FixedString(1)),
    pickup_ntacode          Nullable(String),
    pickup_ntaname          Nullable(String),
    pickup_puma             Nullable(String),

    dropoff_nyct2010_gid    Nullable(UInt8),
    dropoff_ctlabel         Nullable(String),
    dropoff_borocode        Nullable(UInt8),
    dropoff_boroname        Nullable(String),
    dropoff_ct2010          Nullable(String),
    dropoff_boroct2010      Nullable(String),
    dropoff_cdeligibil      Nullable(String),
    dropoff_ntacode         Nullable(String),
    dropoff_ntaname         Nullable(String),
    dropoff_puma            Nullable(String)
) ENGINE = Log;

ืœืื—ืจ ืžื›ืŸ ืื ื™ ืžื—ืœืฅ ื•ื˜ื•ืขืŸ ื›ืœ ืื—ื“ ืžืงื‘ืฆื™ ื”-CSV ืœื˜ื‘ืœืช ื˜ื™ื•ืœ (trips). ื”ื“ื‘ืจ ื”ื‘ื ื”ื•ืฉืœื ืชื•ืš 55 ื“ืงื•ืช ื•-10 ืฉื ื™ื•ืช. ืœืื—ืจ ืคืขื•ืœื” ื–ื•, ื’ื•ื“ืœ ืกืคืจื™ื™ืช ื”ื ืชื•ื ื™ื ื”ื™ื” 134 GB.

$ time (for FILENAME in /ch/csv/trips_x*.csv.gz; do
            echo $FILENAME
            gunzip -c $FILENAME | 
                clickhouse-client 
                    --host=0.0.0.0 
                    --query="INSERT INTO trips FORMAT CSV"
        done)

ืžื”ื™ืจื•ืช ื”ื™ื™ื‘ื•ื โ€‹โ€‹ื”ื™ื™ืชื” 155 MB ืฉืœ ืชื•ื›ืŸ CSV ืœื ื“ื—ื•ืก ืœืฉื ื™ื™ื”. ืื ื™ ื—ื•ืฉื“ ืฉื–ื” ื ื‘ืข ืžืฆื•ื•ืืจ ื‘ืงื‘ื•ืง ื‘ืคื™ืจื•ืง GZIP. ื™ืชื›ืŸ ืฉื”ื™ื” ืžื”ื™ืจ ื™ื•ืชืจ ืœืคืจื•ืก ืืช ื›ืœ ื”ืงื‘ืฆื™ื ื”-gzipped ื‘ืžืงื‘ื™ืœ ื‘ืืžืฆืขื•ืช xargs ื•ืœืื—ืจ ืžื›ืŸ ืœื˜ืขื•ืŸ ืืช ื”ื ืชื•ื ื™ื ืฉื ืคืจืžื•. ืœื”ืœืŸ ืชื™ืื•ืจ ืฉืœ ืžื” ืฉื“ื•ื•ื— ื‘ืžื”ืœืš ืชื”ืœื™ืš ื™ื™ื‘ื•ื โ€‹โ€‹ื”-CSV.

$ sudo glances

ip-172-30-2-200 (Ubuntu 16.04 64bit / Linux 4.4.0-1072-aws)                                                                                                 Uptime: 0:11:42
CPU       8.2%  nice:     0.0%                           LOAD    36-core                           MEM      9.8%  active:    5.20G                           SWAP      0.0%
user:     6.0%  irq:      0.0%                           1 min:    2.24                            total:  68.7G  inactive:  61.0G                           total:       0
system:   0.9%  iowait:   1.3%                           5 min:    1.83                            used:   6.71G  buffers:   66.4M                           used:        0
idle:    91.8%  steal:    0.0%                           15 min:   1.01                            free:   62.0G  cached:    61.6G                           free:        0

NETWORK     Rx/s   Tx/s   TASKS 370 (507 thr), 2 run, 368 slp, 0 oth sorted automatically by cpu_percent, flat view
ens5        136b    2Kb
lo         343Mb  343Mb     CPU%  MEM%  VIRT   RES   PID USER        NI S    TIME+ IOR/s IOW/s Command
                           100.4   1.5 1.65G 1.06G  9909 ubuntu       0 S  1:01.33     0     0 clickhouse-client --host=0.0.0.0 --query=INSERT INTO trips FORMAT CSV
DISK I/O     R/s    W/s     85.1   0.0 4.65M  708K  9908 ubuntu       0 R  0:50.60   32M     0 gzip -d -c /ch/csv/trips_xac.csv.gz
loop0          0      0     54.9   5.1 8.14G 3.49G  8091 clickhous    0 S  1:44.23     0   45M /usr/bin/clickhouse-server --config=/etc/clickhouse-server/config.xml
loop1          0      0      4.5   0.0     0     0   319 root         0 S  0:07.50    1K     0 kworker/u72:2
nvme0n1        0     3K      2.3   0.0 91.1M 28.9M  9912 root         0 R  0:01.56     0     0 /usr/bin/python3 /usr/bin/glances
nvme0n1p1      0     3K      0.3   0.0     0     0   960 root       -20 S  0:00.10     0     0 kworker/28:1H
nvme1n1    32.1M   495M      0.3   0.0     0     0  1058 root       -20 S  0:00.90     0     0 kworker/23:1H

ืื ื™ ืืคื ื” ืžืงื•ื ื‘ื›ื•ื ืŸ NVMe ืขืœ ื™ื“ื™ ืžื—ื™ืงืช ืงื‘ืฆื™ ื”-CSV ื”ืžืงื•ืจื™ื™ื ืœืคื ื™ ืฉืืžืฉื™ืš.

$ sudo rm -fr /ch/csv

ื”ืžืจ ืœื˜ื•ืคืก ืขืžื•ื“ื”

ืžื ื•ืข Log ClickHouse ื™ืื—ืกืŸ ื ืชื•ื ื™ื ื‘ืคื•ืจืžื˜ ืžื›ื•ื•ืŸ ืฉื•ืจื”. ื›ื“ื™ ืœื‘ืฆืข ืฉืื™ืœืชื•ืช ื ืชื•ื ื™ื ืžื”ืจ ื™ื•ืชืจ, ืื ื™ ืžืžื™ืจ ืื•ืชื ืœืคื•ืจืžื˜ ืขืžื•ื“ื•ืช ื‘ืืžืฆืขื•ืช ืžื ื•ืข MergeTree.

$ clickhouse-client --host=0.0.0.0

ื”ื“ื‘ืจ ื”ื‘ื ื”ื•ืฉืœื ืชื•ืš 34 ื“ืงื•ืช ื•-50 ืฉื ื™ื•ืช. ืœืื—ืจ ืคืขื•ืœื” ื–ื•, ื’ื•ื“ืœ ืกืคืจื™ื™ืช ื”ื ืชื•ื ื™ื ื”ื™ื” 237 GB.

CREATE TABLE trips_mergetree
    ENGINE = MergeTree(pickup_date, pickup_datetime, 8192)
    AS SELECT
        trip_id,
        CAST(vendor_id AS Enum8('1' = 1,
                                '2' = 2,
                                'CMT' = 3,
                                'VTS' = 4,
                                'DDS' = 5,
                                'B02512' = 10,
                                'B02598' = 11,
                                'B02617' = 12,
                                'B02682' = 13,
                                'B02764' = 14)) AS vendor_id,
        toDate(pickup_datetime)                 AS pickup_date,
        ifNull(pickup_datetime, toDateTime(0))  AS pickup_datetime,
        toDate(dropoff_datetime)                AS dropoff_date,
        ifNull(dropoff_datetime, toDateTime(0)) AS dropoff_datetime,
        assumeNotNull(store_and_fwd_flag)       AS store_and_fwd_flag,
        assumeNotNull(rate_code_id)             AS rate_code_id,

        assumeNotNull(pickup_longitude)         AS pickup_longitude,
        assumeNotNull(pickup_latitude)          AS pickup_latitude,
        assumeNotNull(dropoff_longitude)        AS dropoff_longitude,
        assumeNotNull(dropoff_latitude)         AS dropoff_latitude,
        assumeNotNull(passenger_count)          AS passenger_count,
        assumeNotNull(trip_distance)            AS trip_distance,
        assumeNotNull(fare_amount)              AS fare_amount,
        assumeNotNull(extra)                    AS extra,
        assumeNotNull(mta_tax)                  AS mta_tax,
        assumeNotNull(tip_amount)               AS tip_amount,
        assumeNotNull(tolls_amount)             AS tolls_amount,
        assumeNotNull(ehail_fee)                AS ehail_fee,
        assumeNotNull(improvement_surcharge)    AS improvement_surcharge,
        assumeNotNull(total_amount)             AS total_amount,
        assumeNotNull(payment_type)             AS payment_type_,
        assumeNotNull(trip_type)                AS trip_type,

        pickup AS pickup,
        pickup AS dropoff,

        CAST(assumeNotNull(cab_type)
            AS Enum8('yellow' = 1, 'green' = 2))
                                AS cab_type,

        precipitation           AS precipitation,
        snow_depth              AS snow_depth,
        snowfall                AS snowfall,
        max_temperature         AS max_temperature,
        min_temperature         AS min_temperature,
        average_wind_speed      AS average_wind_speed,

        pickup_nyct2010_gid     AS pickup_nyct2010_gid,
        pickup_ctlabel          AS pickup_ctlabel,
        pickup_borocode         AS pickup_borocode,
        pickup_boroname         AS pickup_boroname,
        pickup_ct2010           AS pickup_ct2010,
        pickup_boroct2010       AS pickup_boroct2010,
        pickup_cdeligibil       AS pickup_cdeligibil,
        pickup_ntacode          AS pickup_ntacode,
        pickup_ntaname          AS pickup_ntaname,
        pickup_puma             AS pickup_puma,

        dropoff_nyct2010_gid    AS dropoff_nyct2010_gid,
        dropoff_ctlabel         AS dropoff_ctlabel,
        dropoff_borocode        AS dropoff_borocode,
        dropoff_boroname        AS dropoff_boroname,
        dropoff_ct2010          AS dropoff_ct2010,
        dropoff_boroct2010      AS dropoff_boroct2010,
        dropoff_cdeligibil      AS dropoff_cdeligibil,
        dropoff_ntacode         AS dropoff_ntacode,
        dropoff_ntaname         AS dropoff_ntaname,
        dropoff_puma            AS dropoff_puma
    FROM trips;

ื›ืš ื ืจืื” ืคืœื˜ ื”ืžื‘ื˜ ื‘ืžื”ืœืš ื”ืคืขื•ืœื”:

ip-172-30-2-200 (Ubuntu 16.04 64bit / Linux 4.4.0-1072-aws)                                                                                                 Uptime: 1:06:09
CPU      10.3%  nice:     0.0%                           LOAD    36-core                           MEM     16.1%  active:    13.3G                           SWAP      0.0%
user:     7.9%  irq:      0.0%                           1 min:    1.87                            total:  68.7G  inactive:  52.8G                           total:       0
system:   1.6%  iowait:   0.8%                           5 min:    1.76                            used:   11.1G  buffers:   71.8M                           used:        0
idle:    89.7%  steal:    0.0%                           15 min:   1.95                            free:   57.6G  cached:    57.2G                           free:        0

NETWORK     Rx/s   Tx/s   TASKS 367 (523 thr), 1 run, 366 slp, 0 oth sorted automatically by cpu_percent, flat view
ens5         1Kb    8Kb
lo           2Kb    2Kb     CPU%  MEM%  VIRT   RES   PID USER        NI S    TIME+ IOR/s IOW/s Command
                           241.9  12.8 20.7G 8.78G  8091 clickhous    0 S 30:36.73   34M  125M /usr/bin/clickhouse-server --config=/etc/clickhouse-server/config.xml
DISK I/O     R/s    W/s      2.6   0.0 90.4M 28.3M  9948 root         0 R  1:18.53     0     0 /usr/bin/python3 /usr/bin/glances
loop0          0      0      1.3   0.0     0     0   203 root         0 S  0:09.82     0     0 kswapd0
loop1          0      0      0.3   0.1  315M 61.3M 15701 ubuntu       0 S  0:00.40     0     0 clickhouse-client --host=0.0.0.0
nvme0n1        0     3K      0.3   0.0     0     0     7 root         0 S  0:00.83     0     0 rcu_sched
nvme0n1p1      0     3K      0.0   0.0     0     0   142 root         0 S  0:00.22     0     0 migration/27
nvme1n1    25.8M   330M      0.0   0.0 59.7M 1.79M  2764 ubuntu       0 S  0:00.00     0     0 (sd-pam)

ื‘ื‘ื“ื™ืงื” ื”ืื—ืจื•ื ื” ื”ื•ืžืจื• ืžืกืคืจ ืขืžื•ื“ื•ืช ื•ื—ื•ืฉื‘ื• ืžื—ื“ืฉ. ื’ื™ืœื™ืชื™ ืฉื—ืœืง ืžื”ืคื•ื ืงืฆื™ื•ืช ื”ืืœื” ื›ื‘ืจ ืœื ืขื•ื‘ื“ื•ืช ื›ืฆืคื•ื™ ื‘ืžืขืจืš ื”ื ืชื•ื ื™ื ื”ื–ื”. ื›ื“ื™ ืœืคืชื•ืจ ื‘ืขื™ื” ื–ื•, ื”ืกืจืชื™ ืืช ื”ืคื•ื ืงืฆื™ื•ืช ื”ื‘ืœืชื™ ืžืชืื™ืžื•ืช ื•ื˜ืขื ืชื™ ืืช ื”ื ืชื•ื ื™ื ืžื‘ืœื™ ืœื”ืžื™ืจ ืœืกื•ื’ื™ื ื™ื•ืชืจ ื’ืจืขื™ื ื™ื™ื.

ื”ืคืฆื” ืฉืœ ื ืชื•ื ื™ื ื‘ืจื—ื‘ื™ ื”ืืฉื›ื•ืœ

ืื ื™ ืืคื™ืฅ ืืช ื”ื ืชื•ื ื™ื ืขืœ ืคื ื™ ื›ืœ ืฉืœื•ืฉืช ืฆืžืชื™ ื”ืืฉื›ื•ืœ. ื›ื“ื™ ืœื”ืชื—ื™ืœ, ืœื”ืœืŸ ืืฆื•ืจ ื˜ื‘ืœื” ืขืœ ื›ืœ ืฉืœื•ืฉืช ื”ืžื›ื•ื ื•ืช.

$ clickhouse-client --host=0.0.0.0

CREATE TABLE trips_mergetree_third (
    trip_id                 UInt32,
    vendor_id               String,
    pickup_date             Date,
    pickup_datetime         DateTime,
    dropoff_date            Date,
    dropoff_datetime        Nullable(DateTime),
    store_and_fwd_flag      Nullable(FixedString(1)),
    rate_code_id            Nullable(UInt8),
    pickup_longitude        Nullable(Float64),
    pickup_latitude         Nullable(Float64),
    dropoff_longitude       Nullable(Float64),
    dropoff_latitude        Nullable(Float64),
    passenger_count         Nullable(UInt8),
    trip_distance           Nullable(Float64),
    fare_amount             Nullable(Float32),
    extra                   Nullable(Float32),
    mta_tax                 Nullable(Float32),
    tip_amount              Nullable(Float32),
    tolls_amount            Nullable(Float32),
    ehail_fee               Nullable(Float32),
    improvement_surcharge   Nullable(Float32),
    total_amount            Nullable(Float32),
    payment_type            Nullable(String),
    trip_type               Nullable(UInt8),
    pickup                  Nullable(String),
    dropoff                 Nullable(String),

    cab_type                Nullable(String),

    precipitation           Nullable(Int8),
    snow_depth              Nullable(Int8),
    snowfall                Nullable(Int8),
    max_temperature         Nullable(Int8),
    min_temperature         Nullable(Int8),
    average_wind_speed      Nullable(Int8),

    pickup_nyct2010_gid     Nullable(Int8),
    pickup_ctlabel          Nullable(String),
    pickup_borocode         Nullable(Int8),
    pickup_boroname         Nullable(String),
    pickup_ct2010           Nullable(String),
    pickup_boroct2010       Nullable(String),
    pickup_cdeligibil       Nullable(FixedString(1)),
    pickup_ntacode          Nullable(String),
    pickup_ntaname          Nullable(String),
    pickup_puma             Nullable(String),

    dropoff_nyct2010_gid    Nullable(UInt8),
    dropoff_ctlabel         Nullable(String),
    dropoff_borocode        Nullable(UInt8),
    dropoff_boroname        Nullable(String),
    dropoff_ct2010          Nullable(String),
    dropoff_boroct2010      Nullable(String),
    dropoff_cdeligibil      Nullable(String),
    dropoff_ntacode         Nullable(String),
    dropoff_ntaname         Nullable(String),
    dropoff_puma            Nullable(String)
) ENGINE = MergeTree(pickup_date, pickup_datetime, 8192);

ืœืื—ืจ ืžื›ืŸ ืื•ื•ื“ื ืฉื”ืฉืจืช ื”ืจืืฉื•ืŸ ื™ื›ื•ืœ ืœืจืื•ืช ืืช ื›ืœ ืฉืœื•ืฉืช ื”ืฆืžืชื™ื ื‘ืืฉื›ื•ืœ.

SELECT *
FROM system.clusters
WHERE cluster = 'perftest_3shards'
FORMAT Vertical;
Row 1:
โ”€โ”€โ”€โ”€โ”€โ”€
cluster:          perftest_3shards
shard_num:        1
shard_weight:     1
replica_num:      1
host_name:        172.30.2.192
host_address:     172.30.2.192
port:             9000
is_local:         1
user:             default
default_database:
Row 2:
โ”€โ”€โ”€โ”€โ”€โ”€
cluster:          perftest_3shards
shard_num:        2
shard_weight:     1
replica_num:      1
host_name:        172.30.2.162
host_address:     172.30.2.162
port:             9000
is_local:         0
user:             default
default_database:

Row 3:
โ”€โ”€โ”€โ”€โ”€โ”€
cluster:          perftest_3shards
shard_num:        3
shard_weight:     1
replica_num:      1
host_name:        172.30.2.36
host_address:     172.30.2.36
port:             9000
is_local:         0
user:             default
default_database:

ืœืื—ืจ ืžื›ืŸ ืื’ื“ื™ืจ ื˜ื‘ืœื” ื—ื“ืฉื” ื‘ืฉืจืช ื”ืจืืฉื•ืŸ ืฉืžื‘ื•ืกืกืช ืขืœ ื”ืกื›ืžื” trips_mergetree_third ื•ืžืฉืชืžืฉ ื‘ืžื ื•ืข ื”ืžื‘ื•ื–ืจ.

CREATE TABLE trips_mergetree_x3
    AS trips_mergetree_third
    ENGINE = Distributed(perftest_3shards,
                         default,
                         trips_mergetree_third,
                         rand());

ืœืื—ืจ ืžื›ืŸ ืืขืชื™ืง ืืช ื”ื ืชื•ื ื™ื ืžื”ื˜ื‘ืœื” ื”ืžื‘ื•ืกืกืช ืขืœ MergeTree ืœื›ืœ ืฉืœื•ืฉืช ื”ืฉืจืชื™ื. ื”ื“ื‘ืจ ื”ื‘ื ื”ื•ืฉืœื ืชื•ืš 34 ื“ืงื•ืช ื•-44 ืฉื ื™ื•ืช.

INSERT INTO trips_mergetree_x3
    SELECT * FROM trips_mergetree;

ืœืื—ืจ ื”ืคืขื•ืœื” ื”ื "ืœ, ื ืชืชื™ ืœ-ClickHouse 15 ื“ืงื•ืช ืœื”ืชืจื—ืง ืžืกื™ืžื•ืŸ ืจืžืช ื”ืื—ืกื•ืŸ ื”ืžืงืกื™ืžืœื™ืช. ืกืคืจื™ื•ืช ื”ื ืชื•ื ื™ื ื‘ืกื•ืคื• ืฉืœ ื“ื‘ืจ ื”ื™ื• 264 GB, 34 GB ื•-33 GB ื‘ื”ืชืืžื” ื‘ื›ืœ ืื—ื“ ืžืฉืœื•ืฉืช ื”ืฉืจืชื™ื.

ื”ืขืจื›ืช ื‘ื™ืฆื•ืขื™ ืืฉื›ื•ืœ ClickHouse

ืžื” ืฉืจืื™ืชื™ ืื—ืจ ื›ืš ื”ื™ื” ื”ื–ืžืŸ ื”ืžื”ื™ืจ ื‘ื™ื•ืชืจ ืฉืจืื™ืชื™ ืžืจื™ืฆื™ื ื›ืœ ืฉืื™ืœืชื” ื‘ื˜ื‘ืœื” ืžืกืคืจ ืคืขืžื™ื trips_mergetree_x3.

$ clickhouse-client --host=0.0.0.0

ื”ืคืจืง ื”ื‘ื ื”ื•ืฉืœื ืชื•ืš 2.449 ืฉื ื™ื•ืช.

SELECT cab_type, count(*)
FROM trips_mergetree_x3
GROUP BY cab_type;

ื”ืคืจืง ื”ื‘ื ื”ื•ืฉืœื ืชื•ืš 0.691 ืฉื ื™ื•ืช.

SELECT passenger_count,
       avg(total_amount)
FROM trips_mergetree_x3
GROUP BY passenger_count;

ื”ื“ื‘ืจ ื”ื‘ื ื”ื•ืฉืœื ืชื•ืš 0 ืฉื ื™ื•ืช.

SELECT passenger_count,
       toYear(pickup_date) AS year,
       count(*)
FROM trips_mergetree_x3
GROUP BY passenger_count,
         year;

ื”ืคืจืง ื”ื‘ื ื”ื•ืฉืœื ืชื•ืš 0.983 ืฉื ื™ื•ืช.

SELECT passenger_count,
       toYear(pickup_date) AS year,
       round(trip_distance) AS distance,
       count(*)
FROM trips_mergetree_x3
GROUP BY passenger_count,
         year,
         distance
ORDER BY year,
         count(*) DESC;

ืœืฉื ื”ืฉื•ื•ืื”, ื”ืจืฆืชื™ ืืช ืื•ืชืŸ ืฉืื™ืœืชื•ืช ื‘ื˜ื‘ืœื” ืžื‘ื•ืกืกืช MergeTree ืฉืฉื•ื›ื ืช ืืš ื•ืจืง ื‘ืฉืจืช ื”ืจืืฉื•ืŸ.

ื”ืขืจื›ืช ื‘ื™ืฆื•ืขื™ื ืฉืœ ืฆื•ืžืช ClickHouse ืื—ื“

ืžื” ืฉืจืื™ืชื™ ืื—ืจ ื›ืš ื”ื™ื” ื”ื–ืžืŸ ื”ืžื”ื™ืจ ื‘ื™ื•ืชืจ ืฉืจืื™ืชื™ ืžืจื™ืฆื™ื ื›ืœ ืฉืื™ืœืชื” ื‘ื˜ื‘ืœื” ืžืกืคืจ ืคืขืžื™ื trips_mergetree_x3.

ื”ืคืจืง ื”ื‘ื ื”ื•ืฉืœื ืชื•ืš 0.241 ืฉื ื™ื•ืช.

SELECT cab_type, count(*)
FROM trips_mergetree
GROUP BY cab_type;

ื”ืคืจืง ื”ื‘ื ื”ื•ืฉืœื ืชื•ืš 0.826 ืฉื ื™ื•ืช.

SELECT passenger_count,
       avg(total_amount)
FROM trips_mergetree
GROUP BY passenger_count;

ื”ืคืจืง ื”ื‘ื ื”ื•ืฉืœื ืชื•ืš 1.209 ืฉื ื™ื•ืช.

SELECT passenger_count,
       toYear(pickup_date) AS year,
       count(*)
FROM trips_mergetree
GROUP BY passenger_count,
         year;

ื”ืคืจืง ื”ื‘ื ื”ื•ืฉืœื ืชื•ืš 1.781 ืฉื ื™ื•ืช.

SELECT passenger_count,
       toYear(pickup_date) AS year,
       round(trip_distance) AS distance,
       count(*)
FROM trips_mergetree
GROUP BY passenger_count,
         year,
         distance
ORDER BY year,
         count(*) DESC;

ื”ืจื”ื•ืจื™ื ืขืœ ื”ืชื•ืฆืื•ืช

ื–ื• ื”ืคืขื ื”ืจืืฉื•ื ื” ืฉืžืกื“ ื ืชื•ื ื™ื ื—ื™ื ืžื™ ืžื‘ื•ืกืก CPU ื”ืฆืœื™ื— ืœื”ืชืขืœื•ืช ืขืœ ืžืกื“ ื ืชื•ื ื™ื ืžื‘ื•ืกืก GPU ื‘ื‘ื“ื™ืงื•ืช ืฉืœื™. ืžืกื“ ื”ื ืชื•ื ื™ื ืžื‘ื•ืกืก ื”-GPU ื”ื–ื” ืขื‘ืจ ืฉืชื™ ื’ืจืกืื•ืช ืžืื–, ืื‘ืœ ื”ื‘ื™ืฆื•ืขื™ื ืฉ-ClickHouse ืกื™ืคืงื” ืขืœ ืฆื•ืžืช ื‘ื•ื“ื“ ื‘ื›ืœ ื–ืืช ืžืจืฉื™ืžื™ื ืžืื•ื“.

ื™ื—ื“ ืขื ื–ืืช, ื‘ืขืช ื‘ื™ืฆื•ืข ืฉืื™ืœืชื” 1 ื‘ืžื ื•ืข ืžื‘ื•ื–ืจ, ืขืœื•ื™ื•ืช ื”ืชืงื•ืจื” ื’ื‘ื•ื”ื•ืช ื‘ืกื“ืจ ื’ื•ื“ืœ. ืื ื™ ืžืงื•ื•ื” ืฉืคืกืคืกืชื™ ืžืฉื”ื• ื‘ืžื—ืงืจ ืฉืœื™ ืขื‘ื•ืจ ื”ืคื•ืกื˜ ื”ื–ื” ื›ื™ ื–ื” ื™ื”ื™ื” ื ื—ืžื“ ืœืจืื•ืช ืืช ื–ืžื ื™ ื”ืฉืื™ืœืชื” ื™ื•ืจื“ื™ื ื›ื›ืœ ืฉืื ื™ ืžื•ืกื™ืฃ ืฆืžืชื™ื ื ื•ืกืคื™ื ืœืืฉื›ื•ืœ. ืขื ื–ืืช, ื–ื” ื ื”ื“ืจ ืฉื›ืืฉืจ ืžื‘ืฆืขื™ื ืฉืื™ืœืชื•ืช ืื—ืจื•ืช, ื”ื‘ื™ืฆื•ืขื™ื ื’ื“ืœื• ื‘ืขืจืš ืคื™ 2.

ื–ื” ื™ื”ื™ื” ื ื—ืžื“ ืœืจืื•ืช ืืช ClickHouse ืžืชืคืชื—ืช ืœืงืจืืช ื”ื™ื›ื•ืœืช ืœื”ืคืจื™ื“ ื‘ื™ืŸ ืื—ืกื•ืŸ ื•ืžื—ืฉื•ื‘ ื›ื“ื™ ืฉื™ื•ื›ืœื• ืœื”ืชืื™ื ื‘ืื•ืคืŸ ืขืฆืžืื™. ืชืžื™ื›ืช HDFS, ืฉื ื•ืกืคื” ื‘ืฉื ื” ืฉืขื‘ืจื”, ื™ื›ื•ืœื” ืœื”ื™ื•ืช ืฆืขื“ ืœืงืจืืช ื–ื”. ื‘ืžื•ื ื—ื™ื ืฉืœ ืžื—ืฉื•ื‘, ืื ื ื™ืชืŸ ืœื”ืื™ืฅ ืฉืื™ืœืชื” ื‘ื•ื“ื“ืช ืขืœ ื™ื“ื™ ื”ื•ืกืคืช ืฆืžืชื™ื ื ื•ืกืคื™ื ืœืืฉื›ื•ืœ, ืื– ื”ืขืชื™ื“ ืฉืœ ื”ืชื•ื›ื ื” ื”ื–ื• ื‘ื”ื™ืจ ืžืื•ื“.

ืชื•ื“ื” ืฉื”ืงื“ืฉืช ืžื–ืžื ืš ืœืงืจื•ื ืืช ื”ืคื•ืกื˜ ื”ื–ื”. ืื ื™ ืžืฆื™ืข ืฉื™ืจื•ืชื™ ื™ื™ืขื•ืฅ, ืืจื›ื™ื˜ืงื˜ื•ืจื” ื•ืคื™ืชื•ื— ืชืจื’ื•ืœ ืœืœืงื•ื—ื•ืช ื‘ืฆืคื•ืŸ ืืžืจื™ืงื” ื•ืื™ืจื•ืคื”. ืื ืชืจืฆื” ืœื“ื•ืŸ ื›ื™ืฆื“ ื”ื”ืฆืขื•ืช ืฉืœื™ ื™ื›ื•ืœื•ืช ืœืขื–ื•ืจ ืœืขืกืง ืฉืœืš, ืื ื ืฆื•ืจ ืื™ืชื™ ืงืฉืจ ื‘ืืžืฆืขื•ืช ืœื™ื ืงื“ื™ืŸ.

ืžืงื•ืจ: www.habr.com

ื”ื•ืกืคืช ืชื’ื•ื‘ื”