ืงืืจืืื ืืงืจืื, ืืื ืืื!
ืืืืืจ ืื, ืืืืขืฅ ืืืืืื ืฉื ืชืืื ืืขืกืงืื Big Data Solutions ืฉื Neoflex ืืชืืจ ืืคืืจืื ืืช ืืืคืฉืจืืืืช ืืื ืืืช ืืืื ืืช ืจืืืื ืฉื ืืื ื ืืฉืชื ื ืืืืฆืขืืช Apache Spark.
ืืืืง ืืคืจืืืงื ื ืืชืื ื ืชืื ืื, ืืชืขืืจืจืช ืืขืชืื ืงืจืืืืช ืืืฉืืื ืฉื ืื ืืืช ืืืื ืืช ืจืืืื ืืืืืกืกืื ืขื ื ืชืื ืื ืืขืื ืืื ื ืจืืคืฃ.
ืืืจื ืืื ืืืืืจ ืืืืื ืื, ืื ืชืืืืืช ืืืขืจืืืช ืฉืื ืืช, ืฉื ืฉืืจื ื-JSON ืื XML. ืื ืชืื ืื ืขืืืื ื-Hadoop, ืืื ืฆืจืื ืืื ืืช ืืื ืืืื ืจืืืื. ืื ืื ื ืืืืืื ืืืจืื ืืืฉื ืืืืื ืืจืืืื ืฉื ืืฆืจ, ืืืฉื, ืืจื ืืืืคืื.
ืืืงืจื ืื, ืืกืืืื ืฉื ืืืื ืืจืืืื ืฉื ืืืขื ืืื ื ืืืืขื ืืจืืฉ. ืืชืจื ืืื, ืื ืืช ืืชืืื ืืช ืื ื ืืชื ืืขืจืื ืืจืืฉ, ืฉืื ืืื ืชืืืื ืื ืชืื ืื, ืืื ื ืขืืกืงืื ืื ืชืื ืื ืืืืื ืจืืคืคืื ืืืื.
ืืืืืื, ืืืื ื ืจืฉืื ืืชืืืื ืืืื:
{source: "app1", error_code: ""}
ืืืืจ ืืืืชื ืืขืจืืช ืืืืขื ืืชืฉืืื ืืืื:
{source: "app1", error_code: "error", description: "Network error"}
ืืชืืฆืื ืืื ืฆืจืื ืืืืกืืฃ ืขืื ืฉืื ืืื ืืืืืืจืื ื - ืชืืืืจ, ืืืฃ ืืื ืื ืืืืข ืื ืืื ืืืืข ืื ืื.
ืืืฉืืื ืฉื ืืฆืืจืช ืืืื ืจืืืื ืขื ื ืชืื ืื ืืืื ืืื ืื ืกืื ืืจืืืช, ืื-Spark ืืฉ ืืกืคืจ ืืืื ืืื. ืื ืืชืื ื ืชืื ื ืืืงืืจ, ืงืืืืช ืชืืืื ืื ื-JSON ืืื ื-XML, ืืืกืืืื ืฉืื ืืืืชื ืืืืขื ืืขืืจ, ื ืืชื ืช ืชืืืื ื-schemaEvolution.
ืืืื ืจืืฉืื, ืืคืชืจืื ื ืจืื ืคืฉืื. ืืชื ืฆืจืื ืืงืืช ืชืืงืื ืขื JSON ืืืงืจืื ืืืชื ืืชืื ืืกืืจืช ื ืชืื ืื. Spark ืืฆืืจ ืกืืื, ืืืคืื ื ืชืื ืื ืืงืื ื ืื ืืืื ืื. ืืชืจื ืืื, ืืื ืฆืจืื ืืืืฉืืจ ืืคืจืงื, ืื ืชืื ืื ืืืืืคืื, ืขื ืืื ืจืืฉืื ืืืื ืืจืืืื ื-Hive metastore.
ืืื ื ืจืื ืคืฉืื.
ืขื ืืืช, ืื ืืจืืจ ืืืืืืืืืช ืืงืฆืจืืช ืืชืืขืื ืื ืืขืฉืืช ืขื ืืกืคืจ ืืขืืืช ืืคืืขื.
ืืชืืขืื ืืชืืจ ืืืฉื ืื ืืืฆืืจ ืืืืช ืื ืืช, ืืื ืืงืจืื JSON ืื XML ืืชืื ืืกืืจืช ื ืชืื ืื.
ืืืืืจ, ืื ืคืฉืื ืืจืื ืืืฆื ืืงืจืื ืืื ืชื JSON:
df = spark.read.json(path...)
ืื ืืกืคืืง ืืื ืืืคืื ืืช ืื ืชืื ืื ืืืืื ืื ื-Spark.
ืืคืืขื, ืืกืงืจืืคื ืืจืื ืืืชืจ ืืกืืื ืืกืชื ืงืจืืืช ืงืืฆื JSON ืืชืืงืื ืืืฆืืจืช Dataframe. ืืืฆื ื ืจืื ืื: ืืฉ ืืืจ ืืืื ืจืืืื ืืกืืื, ื ืชืื ืื ืืืฉืื ืืืืขืื ืื ืืื, ืฆืจืื ืืืืกืืฃ ืืืชื ืืืืื ืืจืืืื, ืืื ืืฉืืื ืฉืืชืื ืืช ืขืฉืืื ืืืืืช ืฉืื ื.
ืืชืืื ืืช ืืจืืืื ืืื ืืืช ืืืื ืจืืืื ืืื ืืืืงืื:
ืฉืื 1. ืื ืชืื ืื ื ืืขื ืื ืืชืื Hadoop ืขื ืืขืื ื ืืืืืช ืืืืจ ืืื ืืืชืืืกืคืื ืืืืืฆื ืืืฉื. ืืกืชืืจ ืฉืชืืงืื ืขื ื ืชืื ืื ืจืืฉืื ืืื ืืืืืงืื ืืคื ืืื.
ืฉืื 2. ืืืืื ืืืขืื ื ืืจืืฉืื ืืช, ืชืืงืืื ืื ื ืงืจืืช ืืื ืชืงืช ืขื ืืื Spark. ืืกืืจืช ืื ืชืื ืื ืืืชืงืืืช ื ืฉืืจืช ืืคืืจืื ืฉื ืืชื ืื ืชื, ืืืฉื, ืืคืจืงื, ืฉืืืชื ื ืืชื ืืืืื ืืืืืคืื. ืื ืืืฆืจ ืืืื ืจืืืื ืฉื ืืขื ืขื ืื ืื ืชืื ืื ืฉืืฆืืืจื ืขื ืื ืงืืื ืื.
ืฉืื 3. ื ืืฆืจืช ืืืจืื ืฉืชืขืืื ืืช ืืืื ืืจืืืื ืืื ืืื.
ืืฉื ื ืฉืืื ืฉื ืืขืืกื ืืฆืืืจืช, ืืฆืืจื ืืืืืงืช ืืืืืืจืื ื, ืืฉืืื ืฉื ืฉืืืจื ืขื ืืกืืืื ืืืืืืช ืฉื ืืืืืืจืื ื.
ืืืื ื ืืงื ืืืืื. ื ื ืื ืฉืืฉืื ืืจืืฉืื ืฉื ืื ืืืช ืืืืจ ืืืฉื, ืืงืืฆื JSON ืืืขืืื ืืชืืงืื.
ืืฆืืจืช ืืกืืจืช ื ืชืื ืื ืืื, ืืื ืฉืืืจืชื ืืืืื ืจืืืื, ืืื ื ืืขืื. ืืื ืืฆืขื ืืจืืฉืื ืฉื ืืชื ืืืฆืื ืืงืืืช ืืชืืขืื Spark:
df = spark.read.option("mergeSchema", True).json(".../*")
df.printSchema()
root
|-- a: long (nullable = true)
|-- b: string (nullable = true)
|-- c: struct (nullable = true) |
|-- d: long (nullable = true)
ืืื ื ืจืื ืืกืืจ.
ืงืจืื ื ืืคืจืื ื ืืช JSON, ืืื ืื ืื ื ืฉืืืจืื ืืช ื-dataframe ืืคืจืงื, ืืจืืฉืืื ืืืชื ื-Hive ืืื ืืจื ื ืืื:
df.write.format(โparquetโ).option('path','<External Table Path>').saveAsTable('<Table Name>')
ืื ืื ื ืืงืืืื ืืืื.
ืืื, ืืืืจืช, ื ืืกืคื ื ืชืื ืื ืืืฉืื ืืืืงืืจ. ืืฉ ืื ื ืชืืงืืื ืขื JSON, ืืืืื ืจืืืื ืฉื ืืฆืจ ืืืชืืงืืื ืืื. ืืืืจ ืืขืื ืช ืืฆืืื ืื ืชืื ืื ืืืื ืืืืงืืจ, ืืกืจืื ืืื ืืช ืื ืชืื ืื ื ืชืื ืื ืฉื ืืื ืืื.
ืืคืชืจืื ืืืืืื ื ืืืื ืืืืงื ืฉื ืืืื ืืจืืืื ืืืื, ืื ืฉืืืคืฉืจ ืืืกืคืช ืืืืฆื ืืืฉื ืืื ืืื ืืืืจืช. ืื ืืื ืื ืื ืืื ืืืืข, Spark ืืืคืฉืจ ืืืชืื ืืืืฆืืช ืื ืคืจื.
ืจืืฉืืช, ืื ื ืืืฆืขืื ืืขืื ื ืจืืฉืื ืืช, ืฉืืืจืื ืืช ืื ืชืื ืื ืืืชืืืจ ืืขืื, ืืืืกืืคืื ืจืง ืืืืงื ืืืืืฆืืช. ืคืขืืื ืื ื ืงืจืืช ืืชืืื ืืืืช ืืื ืืช ืืืื ื ืขืฉืืช ืคืขื ืืืช ืืืื:
df.write.partitionBy("date_load").mode("overwrite").parquet(dbpath + "/" + db + "/" + destTable)
ืืืืจืช, ืื ื ืืืขื ืื ืจืง ืืืืฆื ืืืฉื:
df.coalesce(1).write.mode("overwrite").parquet(dbpath + "/" + db + "/" + destTable +"/date_load=" + date_load + "/")
ืื ืื ืฉื ืืชืจ ืืื ืืืืจืฉื ืืืืฉ ืืืืืจืช ืืื ืืขืืื ืืช ืืกืืืื.
ืขื ืืืช, ืืื ืืชืขืืจืจืืช ืืขืืืช.
ืืขืื ืจืืฉืื ื. ืืืืงืื ืื ืืืืืืจ, ืืคืจืงื ืฉืืชืงืื ืืืื ืืืชื ืงืจืื. ืื ื ืืืข ืืืืืคื ืฉืื ืคืจืงื ื-JSON ืืชืืืืกืื ืืืจืช ืืฉืืืช ืจืืงืื.
ืืืื ื ืืงื ืืืฉืืื ืืฆื ืืืคืืกื. ืืืืืื, ืืชืืื ืืืืข JSON:
ะะตะฝั 1: {"a": {"b": 1}},
ืืืืื ืืืชื JSON ื ืจืื ืื:
ะะตะฝั 2: {"a": null}
ื ื ืื ืฉืืฉ ืื ื ืฉืชื ืืืืฆืืช ืฉืื ืืช, ืฉืืื ืืืช ืืฉ ืฉืืจื ืืืช.
ืืืฉืจ ื ืงืจื ืืช ืื ื ืชืื ื ืืืงืืจ, Spark ืืืื ืืงืืืข ืืช ืืกืื, ืืืืื ืฉ"a" ืืื ืฉืื ืืกืื "ืืื ื", ืขื ืฉืื ืืงืื ื "b" ืืกืื INT. ืืื, ืื ืื ืืืืฆื ื ืฉืืจื ืื ืคืจื, ืื ื ืงืื ืคืจืงื ืขื ืกืืืืืช ืืืืฆืืช ืื ืชืืืืืช:
df1 (a: <struct<"b": INT>>)
df2 (a: STRING NULLABLE)
ืืฆื ืื ืืืืข, ืืืื ื ืืกืคื ืืคืฉืจืืช ืืืืืืช - ืืขืช ื ืืชืื ื ืชืื ื ืืืงืืจ, ืืกืจ ืฉืืืช ืจืืงืื:
df = spark.read.json("...", dropFieldIfAllNull=True)
ืืืงืจื ืื, ืืคืจืงื ืืืื ืืืจืื ืืืืืฆืืช ืฉื ืืชื ืืงืจืื ืืื.
ืืืจืืช ืฉืื ืฉืขืฉื ืืืช ืืคืืขื ืืืืื ืืื ืืืจืืจืืช. ืืื? ืื, ืื ืกืืืจ ืฉืืืื ืขืื ืฉื ื ืืฆืืื. ืื ืฉืืืฉ. ืื ืืจืืข. ืืจืืฉืื, ืฉืืชืจืืฉ ืืืขื ืืืืืืืช, ืืื ืฉืกืืืื ืืกืคืจืืื ืืืจืื ืืืจืช ืืงืืฆื JSON ืฉืื ืื. ืืืืืื, {intField: 1} ื-{intField: 1.1}. ืื ืฉืืืช ืืืื ื ืืฆืืื ืืืืืฆื ืืืช, ืืืืื ืืกืืืื ืืงืจื ืืื ืืฆืืจื ื ืืื ื, ืืืืืื ืืกืื ืืืืืืง ืืืืชืจ. ืืื ืื ืืืื ืฉืื ืื, ืื ืืืื ืืืื intField: int, ืืืฉื ื ืืืื intField: double.
ืืฉ ืืช ืืืื ืืื ืืืืคืื ืืืฆื ืื:
df = spark.read.json("...", dropFieldIfAllNull=True, primitivesAsString=True)
ืืขืช ืืฉ ืื ื ืชืืงืืื ืฉืื ืืฉ ืืืืฆืืช ืฉื ืืชื ืืงืจืื ื-dataframe ืืืื ืืคืจืงื ืชืงืฃ ืฉื ืื ืืืืืืจืื ื. ืื? ืื.
ืขืืื ื ืืืืืจ ืฉืจืฉืื ื ืืช ืืืืื ืืืืืจืช. Hive ืืื ื ืจืืืฉ ืืืืชืืืช ืืืืืืช ืืฉืืืช ืฉืืืช, ืืขืื ืคืจืงื ืืื ืจืืืฉ ืจืืฉืืืช. ืืื, ืืืืฆืืช ืขื ืกืืืืืช: field1: int ื-Field1: int ืืืืช ืขืืืจ Hive, ืื ืื ืขืืืจ Spark. ืื ืชืฉืื ืืืืืจ ืืช ืฉืืืช ืืฉืืืช ืืืืชืืืช ืงืื ืืช.
ืืืจื ืื ื ืจืื ืฉืืื ืืกืืจ.
ืขื ืืืช, ืื ืืื ืื ืื ืคืฉืื. ืืฉ ืืขืื ืฉื ืืื, ืื ืืืืขื. ืืืืืื ืฉืื ืืืืฆื ืืืฉื ื ืฉืืจืช ืื ืคืจื, ืชืืงืืืช ืืืืืฆื ืชืืื ืงืืฆื ืฉืืจืืช Spark, ืืืืืื, ืืื ืืฆืืืช ืืคืขืืื _SUCCESS. ืื ืืืจืื ืืฉืืืื ืืขืช ื ืืกืืื ืืคืจืงื. ืืื ืืืืื ืข ืืื, ืขืืื ืืืืืืจ ืืช ืืชืฆืืจื ืืื ืืื ืืข ื-Spark ืืืืกืืฃ ืงืืฆื ืฉืืจืืช ืืชืืงืื:
hadoopConf = sc._jsc.hadoopConfiguration()
hadoopConf.set("parquet.enable.summary-metadata", "false")
hadoopConf.set("mapreduce.fileoutputcommitter.marksuccessfuljobs", "false")
ื ืจืื ืฉืืขืช ืื ืืื ืืชืืืกืคืช ืืืืฆืช ืคืจืงื ืืืฉื ืืชืืงืืืช ืืืื ืืจืืืื ืืืขื, ืื ื ืืฆืืื ืื ืชืื ืื ืืื ืืชืืื ืฉื ืืืื. ืืืื ื ืืจืืฉ ืฉืื ืืืื ืืืืฆืืช ืขื ืืชื ืืฉืืช ืืกืื ืื ืชืื ืื.
ืืื, ืืฉ ืื ื ืืขืื ืฉืืืฉืืช. ืืขืช ืืกืืืื ืืืืืืช ืืื ื ืืืืขื, ืืชืจ ืขื ืื, ืืืืื ื-Hive ืืฉ ืกืืืื ืฉืืืื, ืืืืืื ืฉืื ืืืืฆื ืืืฉื ืืื ืื ืจืื ืืื ืืกื ืขืืืืช ืืกืืืื.
ืขืืื ืืจืฉืื ืืืืฉ ืืช ืืืืื. ื ืืชื ืืขืฉืืช ืืืช ืืคืฉืืืช: ืงืจืื ืฉืื ืืช ืืคืจืงื ืฉื ืืืื ืืจืืืื, ืงื ืืช ืืกืืืื ืืฆืืจ DDL ืขื ืืกืืกื, ืืืชื ื ืืชื ืืจืฉืื ืืืืฉ ืืช ืืชืืงืื ื-Hive ืืืืื ืืืฆืื ืืช, ืชืื ืขืืืื ืืกืืืื ืฉื ืืืื ืืจืืืื ืืืขื.
ืืฉ ืื ื ืืขืื ืจืืืขืืช. ืืฉืจืฉืื ื ืืช ืืืืื ืืคืขื ืืจืืฉืื ื, ืกืืื ื ืขื ืกืคืืจืง. ืขืืฉืื ืื ืื ื ืขืืฉืื ืืช ืื ืืขืฆืื ื, ืืฆืจืื ืืืืืจ ืฉืฉืืืช ืคืจืงื ืืืืืื ืืืชืืื ืืืืืืืช ืฉืืกืืจ ืืืืืจืช. ืืืืืื, Spark ืืืจืง ืงืืืื ืฉืืื ืื ืืฆืืื ืื ืชื ืืฉืื "corrupt_record". ืื ื ืืชื ืืจืฉืื ืฉืื ืืื ืืืืืจืช ืืืื ืืืืืื.
ืืืืืขื ืื, ืื ื ืืงืืืื ืืช ืืชืืื ืืช:
f_def = ""
for f in pf.dtypes:
if f[0] != "date_load":
f_def = f_def + "," + f[0].replace("_corrupt_record", "`_corrupt_record`") + " " + f[1].replace(":", "`:").replace("<", "<`").replace(",", ",`").replace("array<`", "array<")
table_define = "CREATE EXTERNAL TABLE jsonevolvtable (" + f_def[1:] + " ) "
table_define = table_define + "PARTITIONED BY (date_load string) STORED AS PARQUET LOCATION '/user/admin/testJson/testSchemaEvolution/pq/'"
hc.sql("drop table if exists jsonevolvtable")
hc.sql(table_define)
ืงืื ("_corrupt_record", "`_corrupt_record`") + " " + f[1].replace(":", "`:").replace("<", "<`").replace(",", ",`").replace("ืืขืจื<`", "ืืขืจื<") ืขืืฉื DDL ืืืื, ืืืืืจ ืืืงืื:
create table tname (_field1 string, 1field string)
ืขื ืฉืืืช ืฉืืืช ืืื "_field1, 1field", ื ืืฆืจ DDL ืืืื ืืืงืื ืฉืื ืฉืืืช ืืฉืืืช ืืืืืคืื: ืฆืืจ ืืืื `tname` (ืืืจืืืช `_field1`, ืืืจืืืช `1field`).
ื ืฉืืืช ืืฉืืื: ืืืฆื ืืืฉืื ืืจืืื Dataframe ืขื ืกืืืื ืืืื (ืืงืื pf)? ืืื ืืฉืืืื ืืช ื-PF ืืื? ืื ืืืขืื ืืืืืฉืืช. ืืงืจืื ืืืืฉ ืืช ืืกืืืื ืฉื ืื ืืืืืฆืืช ืืืชืืงืืื ืขื ืงืืฆื ืคืจืงื ืฉื ืืืื ืืจืืืื ืืืขื? ืฉืืื ืื ืืื ืืืืืื ืืืืชืจ, ืื ืงืฉื.
ืืกืืืื ืืืจ ื ืืฆืืช ืืืืืจืช. ื ืืชื ืืงืื ืกืืืื ืืืฉื ืขื ืืื ืฉืืืื ืฉื ืืกืืืื ืฉื ืืืืื ืืืื ืืืืืืฆื ืืืืฉื. ืื ืืชื ืฆืจืื ืืงืืช ืืช ืกืืืืช ืืืืื ื-Hive ืืืฉืื ืืืชื ืขื ืืกืืืื ืฉื ืืืืืฆื ืืืืฉื. ื ืืชื ืืขืฉืืช ืืืช ืขื ืืื ืงืจืืืช ืืืื-ื ืชืื ืื ืฉื ืืืืืงื ื-Hive, ืฉืืืจืชื ืืชืืงืืื ืืื ืืช ืืฉืืืืฉ ื-Spark ืืื ืืงืจืื ืืช ืฉืชื ืืืืืฆืืช ืืืช ืืืช.
ืืืขืฉื, ืืฉ ืืช ืื ืื ืฉืืชื ืฆืจืื: ืกืืืืช ืืืืื ืืืงืืจืืช ื-Hive ืืืืืืฆื ืืืืฉื. ืืฉ ืื ื ืื ื ืชืื ืื. ื ืืชืจ ืจืง ืืงืื ืกืืืื ืืืฉื, ืืืฉืืืช ืืช ืกืืืืช ืืืืช ืืื ืืช ืืฉืืืช ืืืฉืื ืืืืืืฆื ืฉื ืืฆืจื:
from pyspark.sql import HiveContext
from pyspark.sql.functions import lit
hc = HiveContext(spark)
df = spark.read.json("...", dropFieldIfAllNull=True)
df.write.mode("overwrite").parquet(".../date_load=12-12-2019")
pe = hc.sql("select * from jsonevolvtable limit 1")
pe.write.mode("overwrite").parquet(".../fakePartiton/")
pf = spark.read.option("mergeSchema", True).parquet(".../date_load=12-12-2019/*", ".../fakePartiton/*")
ืืืืจ ืืื, ืื ื ืืืฆืจืื ืืช ื-DDL ืฉื ืจืืฉืื ืืืืื, ืืื ืืงืืข ืืงืืื.
ืื ืื ืืฉืจืฉืจืช ืขืืืืช ืืื ืฉืฆืจืื, ืืืืืจ, ืืื ืขืืืก ืืชืืื, ืืืืืื ื ืืฆืจื ืืฆืืจื ื ืืื ื ื-Hive, ืื ื ืงืื ืกืืืืช ืืืื ืืขืืืื ืช.
ืืืืขืื ืืืืจืื ื ืืื ืฉืื ืืคืฉืจ ืคืฉืื ืืืืกืืฃ ืืืืฆื ืืืืื ืฉื Hive, ืื ืืื ืชืืฉืืจ. ืืชื ืฆืจืื ืืืืจืื ืืช Hive ืืชืงื ืืช ืืื ื ืืืืืฆื ืฉืื:
from pyspark.sql import HiveContext
hc = HiveContext(spark)
hc.sql("MSCK REPAIR TABLE " + db + "." + destTable)
ืืืฉืืื ืืคืฉืืื ืฉื โโืงืจืืืช JSON ืืืฆืืจืช ืืืื ืจืืืื ืขื ืืกืืกื ืืืืื ืืืชืืืจ ืขื ืืกืคืจ ืงืฉืืื ืืจืืืืื, ืคืชืจืื ืืช ืฉืขืืืจื ืืฉ ืืืคืฉ ืื ืคืจื. ืืืืจืืช ืฉืืคืชืจืื ืืช ืืืื ืคืฉืืืื, ืืืงื ืืจืื ืืื ืืืฆืื ืืืชื.
ืืื ืืืืฉื ืืช ืื ืืืช ืืืืืืจืื ื, ืืืืชื ืฆืจืื:
- ืืืกืฃ ืืืืฆืืช ืืืืื ืืจืืืื, ืืืคืืจ ืืงืืฆื ืฉืืจืืช
- ืืชืืืื ืขื ืฉืืืช ืจืืงืื ืื ืชืื ื ืืงืืจ ืฉ-Spark ืืงืืื
- ืืฆืงื ืืืคืืกืื ืคืฉืืืื ืืืืจืืืช
- ืืืจืช ืฉืืืช ืฉืืืช ืืืืชืืืช ืงืื ืืช
- ืืขืืืช ื ืชืื ืื ื ืคืจืืช ืืจืืฉืื ืืืื ืืืืืจืช (ืืืจ DDL)
- ืื ืชืฉืื ืืืจืื ืืฉืืืช ืฉืืืช ืฉืขืืืืื ืืืืืช ืื ืชืืืืื ื-Hive
- ืืื ืืืฆื ืืขืืื ืืช ืจืืฉืื ืืืืื ืืืืืจืช
ืืกืืืื, ื ืฆืืื ืื ืืืืืื ืืื ืืช ืืืื ืืช ืจืืืื ืืืื ืช ืืืืื ืืืืืืืช ืจืืืช. ืืื, ืืืงืจื ืฉื ืงืฉืืื ืืืืฉืื, ืขืืืฃ ืืคื ืืช ืืฉืืชืฃ ืื ืืกื ืขื ืืืืืืืช ืืืฆืืืช.
ืชืืื ืฉืงืจืืช ืืืืจ ืื, ืื ื ืืงืืืื ืฉืชืืฆื ืืช ืืืืืข ืฉืืืืฉื.
ืืงืืจ: www.habr.com