Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Embulk 20150411

19,946 views

Published on

Embulk talk slides for JJUG CCC #ccc_cd4 / #embulk

Published in: Technology
  • Be the first to comment

Embulk 20150411

  1. 1. Hiroshi Nakamura Software Engineer Treasure Data, K.K. 『Embulk』に見るモダンJavaの実践的テクニック
 ∼並列分散処理システムの実装手法∼ 1 #ccc_cd4 / #embulk
  2. 2. #ccc_cd4 / #embulk Today’s talk Embulkとは > バルクデータ転送の難しさ > Embulkのアプローチ > アーキテクチャ概要 Java実装技術 > Java 7ネイティブ > Guiceによるコンポーネント間の接続 > ServiceLoaderによる拡張 > Jacksonによるモデルクラス、Immutable > Nettyバッファアロケータ、Unsafe 2
  3. 3. #ccc_cd4 / #embulk Embulkとは? - http://embulk.org/ > オープンソースのバルクデータ転送ツール > “A” から “B” へレコードを転送 > プラグイン機構 > 多様な “A” と “B” の組み合わせ > データ連携を容易に > システム構築の頭痛の種の一つ Storage, RDBMS, NoSQL, Cloud Service, … broken records,
 error recovery, maintenance,
 performance, … 3
  4. 4. #ccc_cd4 / #embulk Embulk committers Hiroshi Nakamura @nahi Muga Nishizawa @muga_nishizawa Sadayuki Furuhashi @frsyuki 4
  5. 5. #ccc_cd4 / #embulk バルクデータ転送の難しさ > 入力データの正規化 > エラー処理 > メンテナンス > 性能
  6. 6. #ccc_cd4 / #embulk 入力データ正規化の難しさ データエンコーディングのバリエーション > null、時刻、浮動小数点 > 改行、エスケープ、レコード/カラム区切り > 文字コード、圧縮有無 → 試行によるデータ正規化
  7. 7. #ccc_cd4 / #embulk エラー処理の難しさ 例外値の扱い ネットワークエラーからの復旧 ディスクフルからの復旧 重複データ転送の回避 → データバリデーション、リトライ、リジューム
  8. 8. #ccc_cd4 / #embulk メンテナンスの難しさ 継続的な動作の確保 データ転送要件変更への対応 → ドキュメント、汎用化、OSS化
  9. 9. #ccc_cd4 / #embulk 性能の問題 転送データ量は通常増えていく 対象レコードも増えたりする → 並列・分散処理
  10. 10. #ccc_cd4 / #embulk バルクデータ転送の例 指定された 10GB CSV file をPostgreSQLにロード > 1. コマンド叩いてみる → 失敗 > 2. データを正規化するスクリプトを作成 ”20150127T190500Z”→“2015-01-27 19:05:00 UTC”に “null”→“N”に変換 元データを見ながら気付く限り… > 3. 再度チャレンジ → 取り込まれたが元データと合わない “Inf”→“Infinity”に変換 > 4. ひたすら繰り返す > 5. うっかりレコードが重複して取り込まれた…
  11. 11. #ccc_cd4 / #embulk バルクデータ転送の例 指定された 10GB CSV file をPostgreSQLにロード > 6. スクリプトが完成 > 7. cronに登録して毎日バルクデータロードするよう登録 > 8. ある日別の原因でエラーに… 不正なUTF-8 byte sequenceをU+FFFDに変換
  12. 12. #ccc_cd4 / #embulk バルクデータ転送の例 過去の日次 10GB CSV file を 730個 取り込む(2年分) > 1. たいていのスクリプトは遅い > 最適化してる暇がない > 1ファイル1時間、エラー発生しなかったとして1ヶ月 > 2. 並列データロードするようスクリプト変更 > 3. ある日ディスクフル/ネットワークエラーで失敗 > どこまで読み込まれた? > 4. 障害後に再開し易いよう、データロード単位を調整 > 5. 安全な再開機能をスクリプトに追加
  13. 13. #ccc_cd4 / #embulk システム構築の頭痛の種 様々な転送データ、データストレージ > CSV, TSV, JSON, XML, MessagePack, SequenceFile, RCFile > S3, Salesforce.com, Google Cloud Storage, BigQuery, Elasticsearch > MySQL, PostgreSQL, Oracle, MS SQL Server, Amazon Redshift, Redis, MongoDB
  14. 14. #ccc_cd4 / #embulk HDFS MySQL Amazon S3 CSV Files SequenceFile Salesforce.com Elasticsearch Cassandra Hive Redis Broken script :( Sometimes fails :( No one can fix :( 14
  15. 15. #ccc_cd4 / #embulk HDFS MySQL Amazon S3 CSV Files SequenceFile Salesforce.com Elasticsearch Cassandra Hive Redis Broken script :( Sometimes fails :( No one can fix :( N x M
 scripts! > Poor error handling > No retrying / resuming > Low performance > Often no maitainers 15
  16. 16. #ccc_cd4 / #embulk Embulkのアプローチ > プラグインアーキテクチャ > 入力データ正規化支援: guess, preview > 並列・分散実行 > 繰り返し実行 > トランザクション制御 16
  17. 17. #ccc_cd4 / #embulk HDFS MySQL Amazon S3 CSV Files SequenceFile Salesforce.com Elasticsearch Cassandra Hive Redis Broken script :( Sometimes fails :( No one can fix :( 17
  18. 18. #ccc_cd4 / #embulk HDFS MySQL Amazon S3 CSV Files SequenceFile Salesforce.com Elasticsearch Cassandra Hive Redis Reliable framework :-) 18
  19. 19. #ccc_cd4 / #embulk HDFS MySQL Amazon S3 CSV Files SequenceFile Salesforce.com Elasticsearch Cassandra Hive Redis PluginsPlugins Reusable plugins 19
  20. 20. #ccc_cd4 / #embulk プラグインアーキテクチャ 拡張ポイントが再利用可能なコンポーネントを定義 従っている限りフレームワークの恩恵を受けられる > 並列処理、繰り返し実行、エラー処理、リカバリ 20
  21. 21. #ccc_cd4 / #embulk Embulkプラグインの例 RubyGemsとして配布 - http://www.embulk.org/plugins/ > DB > Oracle, MySQL, PostgreSQL, Amazon Redshift > 検索エンジン > Elasticsearch > クラウドサービス > Salesforce.com > Amazon S3 > Google Cloud Storage, Google BigQuery > ファイルフォーマット > CSV, TSV, JSON, XML > pcap packet capture files > gzip, bzip2, zip, tar, cpio 21
  22. 22. #ccc_cd4 / #embulk デモ > guessとpreview > 並列・分散実行 > 繰り返し実行 > トランザクション処理 > プラグインのサンプル 22
  23. 23. #ccc_cd4 / #embulk Embulkアーキテクチャ概要
  24. 24. #ccc_cd4 / #embulk InputPlugin OutputPlugin Executor plugin Filter plugin Filter plugin Filter plugins records Threads, MapReduce records convert, … input, … output. 24 records config
  25. 25. #ccc_cd4 / #embulk InputPlugin FileInput plugin OutputPlugin Decoder plugin Parser plugin HDFS, S3,
 Riak CS, … gzip, bzip2,
 aes, … CSV, JSON,
 pcap, … buffer buffer Filter plugin Filter plugin Filter plugins records records Executor plugin 25 records config
  26. 26. #ccc_cd4 / #embulk InputPlugin FileInput plugin OutputPlugin FileOutput plugin Encoder plugin Formatter plugin Decoder plugin Parser plugin HDFS, S3,
 Riak CS, … gzip, bzip2,
 aes, … CSV, JSON,
 pcap, … buffer buffer buffer buffer Filter plugin Filter plugin Filter plugins recordsrecords Executor plugin 26 records config
  27. 27. #ccc_cd4 / #embulk Embulkアーキテクチャ概要 4種のプラグインとそれを組み上げるフレームワーク 1. Executor: 実行 2. Input: バルクデータのレコード群を取り込み FileInput, Decoder, Parser: ファイル操作 3. Filter: レコードに対するデータ操作 4. Output: バルクデータのレコード群を出力 FileOutput, Encoder, Formatter: ファイル操作
  28. 28. #ccc_cd4 / #embulk InputPlugin OutputPlugin Executor plugin Filter plugin Filter plugin Filter plugins 28 records records records config diff
  29. 29. #ccc_cd4 / #embulk InputPlugin OutputPlugin Executor plugin Filter plugin Filter plugin Filter plugins 29 task schema report task schema report records records task schema records resume state config
  30. 30. #ccc_cd4 / #embulk Embulkの実装技術 > Java 7ネイティブ > Guiceによるコンポーネント間の接続 > ServiceLoaderによる拡張 > Jacksonによるモデルクラス > Immutableなモデルクラス > Nettyバッファアロケータ > sun.misc.Unsafeによるバッファコピー回避 30
  31. 31. #ccc_cd4 / #embulk Java 7ネイティブ try-with-resources ファイル操作:Files & Paths API ※Date and TimeはJRubyの実装を利用 try (SetCurrentThreadName dontCare = new SetCurrentThreadName(“transaction”)) { return doRun(config); } Path basePath = Paths.get(“.”).normalize(); Path file = basePath.resolve(“relative.csv”);
  32. 32. #ccc_cd4 / #embulk Guiceによるコンポーネント間の接続 32 public class EmbulkService { protected final Injector injector; public EmbulkService(ConfigSource systemConfig) { ImmutableList.Builder<Module> modules = ImmutableList.builder(); modules.add(new SystemConfigModule(systemConfig)); modules.add(new ExecModule()); modules.add(new ExtensionServiceLoaderModule(systemConfig)); modules.add(new BuiltinPluginSourceModule()); modules.add(new JRubyScriptingModule(systemConfig)); injector = Guice.createInjector(modules.build()); } public Injector getInjector() { return injector; } }
  33. 33. #ccc_cd4 / #embulk Guiceによるコンポーネント間の接続 33 public class EmbulkService { protected final Injector injector; public EmbulkService(ConfigSource systemConfig) { ImmutableList.Builder<Module> modules = ImmutableList.builder(); modules.add(new SystemConfigModule(systemConfig)); modules.add(new ExecModule()); modules.add(new ExtensionServiceLoaderModule(systemConfig)); modules.add(new BuiltinPluginSourceModule()); modules.add(new JRubyScriptingModule(systemConfig)); injector = Guice.createInjector(modules.build()); } public Injector getInjector() { return injector; } } public class ExecModule implements Module { @Override public void configure(Binder binder) { ... binder.bind(LocalThreadExecutor.class).in(Scopes.SINGLETON); registerPluginTo(binder, ExecutorPlugin.class, "local", LocalExecutorPlugin.class);
  34. 34. #ccc_cd4 / #embulk Guiceによるコンポーネント間の接続 34 public class EmbulkService { protected final Injector injector; public EmbulkService(ConfigSource systemConfig) { ImmutableList.Builder<Module> modules = ImmutableList.builder(); modules.add(new SystemConfigModule(systemConfig)); modules.add(new ExecModule()); modules.add(new ExtensionServiceLoaderModule(systemConfig)); modules.add(new BuiltinPluginSourceModule()); modules.add(new JRubyScriptingModule(systemConfig)); injector = Guice.createInjector(modules.build()); } public Injector getInjector() { return injector; } } public class ExecModule implements Module { @Override public void configure(Binder binder) { ... binder.bind(LocalThreadExecutor.class).in(Scopes.SINGLETON); registerPluginTo(binder, ExecutorPlugin.class, "local", LocalExecutorPlugin.class); public class LocalExecutorPlugin implements ExecutorPlugin { private final ExecutorService executor; @Inject public LocalExecutorPlugin(LocalThreadExecutor executor) { this.executor = executor.getExecutorService(); ...
 
 (InjectedPluginSource)
 public T newPlugin(Injector injector) { return (T) new FileInputRunner((FileInputPlugin) injector.getInstance(impl));
  35. 35. #ccc_cd4 / #embulk Guiceによるコンポーネント間の接続 XMLでなくすべてJavaで書く系のDI Annotationによる宣言的なDI、
 injectorによる動的なDIの組み合わせ > 動的なモジュール差し替えがし易い
  36. 36. #ccc_cd4 / #embulk ServiceLoaderによる拡張 public class ExtensionServiceLoaderModule implements Module { private final ConfigSource systemConfig; public ExtensionServiceLoaderModule(ConfigSource systemConfig) { this.systemConfig = systemConfig; } @Override public void configure(Binder binder) { ServiceLoader<Extension> serviceLoader = ServiceLoader.load(Extension.class, classLoader); for (Extension extension : serviceLoader) { for (Module module : extension.getModules(systemConfig)) { module.configure(binder); } } } }
  37. 37. #ccc_cd4 / #embulk ServiceLoaderによる拡張 jarをclasspathに入れるだけでモジュール追加/差し替え 簡単でClassLoaderいじるよりは安全 標準Plugin群の登録に利用 ※Pluginの読み込みはClassLoader
  38. 38. #ccc_cd4 / #embulk Jacksonによるモデルクラス(task) public class CsvParserPlugin implements ParserPlugin { public interface PluginTask extends Task, LineDecoder.DecoderTask, TimestampParser.ParserTask { @Config("columns") public SchemaConfig getSchemaConfig(); @Config("header_line") @ConfigDefault("null") public Optional<Boolean> getHeaderLine(); @Config("skip_header_lines") @ConfigDefault("0") public int getSkipHeaderLines(); public void setSkipHeaderLines(int n); @Config("delimiter") @ConfigDefault("","") public char getDelimiterChar();
  39. 39. #ccc_cd4 / #embulk InputPlugin OutputPlugin Executor plugin Filter plugin Filter plugin Filter plugins 39 task schema report task schema report records records task schema records config config diff resume state
  40. 40. #ccc_cd4 / #embulk Jacksonによるモデルクラス(schema) public class ColumnConfig { private final String name; private final Type type; @JsonCreator public ColumnConfig( @JsonProperty("name") String name, @JsonProperty("type") Type type) { this.name = name; this.type = type; } @JsonProperty("name") public String getName() { return name; } @JsonProperty("type") public Type getType() { return type; } }
  41. 41. #ccc_cd4 / #embulk Jacksonによるモデルクラス デ/シリアライズが重要 > 並列・分散実行のため > Ruby <-> Javaのやりとりのため IDL生成でなくすべてJavaで書く系のモデル
  42. 42. #ccc_cd4 / #embulk Immutableなモデルクラス ソースコード中のfinalなメンバー変数の割合 > Presto:83%(4714変数) > Embulk:72%(255変数) > Cassandra:59%(2348変数) > Elasticsearch:51%(6871変数) > Nashorn (OpenJDK 8):43%(852変数) > JRuby:40%(3154変数) > Hadoop:31%(9280変数) > Hive:23%(4600変数)
  43. 43. #ccc_cd4 / #embulk Nettyバッファアロケータ レコード群のためのメモリをすべて自前管理 > OutOfMemoryが起きる前に検出 > GCコスト削減 複数のバルクロードセッションを
 サーバプロセス内で同時実行可能に
  44. 44. #ccc_cd4 / #embulk Nettyバッファアロケータ レコード群のためのメモリをすべて自前管理 > OutOfMemoryが起きる前に検出 > GCコスト削減 複数のバルクロードセッションを
 サーバプロセス内で同時実行可能に public Buffer allocate(int minimumCapacity) { int size = MINIMUM_BUFFER_SIZE; while (size < minimumCapacity) { size *= 2; } return new NettyByteBufBuffer(nettyBuffer.buffer(size)); }
  45. 45. #ccc_cd4 / #embulk Unsafe airlift/slice - sun.misc.Unsafe APIのwrapper > バイト列の直接操作(デ/シリアライズ) > コピー削減 参考: http://frsyuki.hatenablog.com/entry/ 2014/03/12/155231
  46. 46. #ccc_cd4 / #embulk Unsafe airlift/slice - sun.misc.Unsafe APIのwrapper > バイト列の直接操作(デ/シリアライズ) > コピー削減 参考: http://frsyuki.hatenablog.com/entry/ 2014/03/12/155231 public void addRecord() { // record header bufferSlice.setInt(position, nextVariableLengthDataOffset); bufferSlice.setBytes(position + 4, nullBitSet); count++; this.position += nextVariableLengthDataOffset; this.nextVariableLengthDataOffset = fixedRecordSize; Arrays.fill(nullBitSet, (byte) 0); // flush if next record will not fit in this buffer if (buffer.capacity() < position +
 nextVariableLengthDataOffset + stringReferenceSize) { flush(); } }
  47. 47. 47 ユーザー 転送 管理コンソール から実行 トレジャー クラウドストレージ Embulk Worker 管理コンソール からアクセス S3のインポートから,エクスポー トまでを完全自動化
  48. 48. #ccc_cd4 / #embulk Contributing to the Embulk project > Pull-requests & issues on Github > Posting blogs > “使ってみた” > “コードを読んでみた” > “ここがイケてる / イケてない” > Talking on Twitter with a word “embulk" > Writing & releasing plugins > Windows support > Integration to other software > ETL tools, Fluentd, Hadoop, Presto, … 48
  49. 49. 1. Distributed Systems Engineer 2. Integration Engineer 3. Software Engineer, MPP DBMS 4. Sales Engineer 5. Technical Support Engineer (日本,東京,丸の内)
 https://jobs.lever.co/treasure-data We’re hiring! ANALYTICS INFRASTRUCTURE. SIMPLIFIED IN THE CLOUD.
  50. 50. 50
  51. 51. #ccc_cd4 / #embulk FluentdとEmbulk
  52. 52. 52 This?
  53. 53. 53 Or this?
  54. 54. M x N → M + N Nagios MongoDB Hadoop Alerting Amazon S3 Analysis Archiving MySQL Apache Frontend Access logs syslogd App logs System logs Backend Databases buffer/filter/route
  55. 55. #ccc_cd4 / #embulk FluentdとEmbulk ストリーミングデータコレクター
 vs バルクデータローダー ストリーミングデータか、転送単位がはっきり しているバルクデータか 任意データ vs データバリデーション・正規化 即時 vs トランザクション性
  56. 56. #ccc_cd4 / #embulk Embulkの実行 インストール guess preview 繰り返し実行 56
  57. 57. # install $ wget https://bintray.com/artifact/download/ embulk/maven/embulk-0.2.0.jar -o embulk.jar $ chmod 755 embulk.jar Installing embulk Bintray
 releases Embulk is released on Bintray wget embulk.jar
  58. 58. # install $ wget https://bintray.com/artifact/download/ embulk/maven/embulk-0.2.0.jar -o embulk.jar $ chmod 755 embulk.jar
 # guess $ vi partial-config.yml $ ./embulk guess partial-config.yml
 -o config.yml Guess format & schema in: type: file paths: [data/examples/] out:
 type: example in: type: file paths: [data/examples/] decoders: - {type: gzip} parser: charset: UTF-8 newline: CRLF type: csv delimiter: ',' quote: '"' header_line: true columns: - name: time
 type: timestamp
 format: '%Y-%m-%d %H:%M:%S' - name: account
 type: long - name: purchase
 type: timestamp
 format: '%Y%m%d' - name: comment
 type: string out:
 type: example guess by guess plugins
  59. 59. # install $ wget https://bintray.com/artifact/download/ embulk/maven/embulk-0.2.0.jar -o embulk.jar $ chmod 755 embulk.jar
 # guess $ vi partial-config.yml $ ./embulk guess partial-config.yml
 -o config.yml
 # preview $ ./embulk preview config.yml $ vi config.yml # if necessary +--------------------------------------+---------------+--------------------+ | time:timestamp | uid:long | word:string | +--------------------------------------+---------------+--------------------+ | 2015-01-27 19:23:49 UTC | 32,864 | embulk | | 2015-01-27 19:01:23 UTC | 14,824 | jruby | | 2015-01-28 02:20:02 UTC | 27,559 | plugin | | 2015-01-29 11:54:36 UTC | 11,270 | fluentd | +--------------------------------------+---------------+--------------------+ Preview & fix config
  60. 60. # install $ wget https://bintray.com/artifact/download/ embulk/maven/embulk-0.2.0.jar -o embulk.jar $ chmod 755 embulk.jar
 # guess $ vi partial-config.yml $ ./embulk guess partial-config.yml
 -o config.yml
 # preview $ ./embulk preview config.yml $ vi config.yml # if necessary # run $ ./embulk run config.yml -o config.yml in: type: file paths: [data/examples/] decoders: - {type: gzip} parser: charset: UTF-8 newline: CRLF type: csv delimiter: ',' quote: '"' header_line: true columns: - name: time
 type: timestamp
 format: '%Y-%m-%d %H:%M:%S' - name: account
 type: long - name: purchase
 type: timestamp
 format: '%Y%m%d' - name: comment
 type: string last_paths: [data/examples/sample_001.csv.gz] out:
 type: example Deterministic run
  61. 61. in: type: file paths: [data/examples/] decoders: - {type: gzip} parser: charset: UTF-8 newline: CRLF type: csv delimiter: ',' quote: '"' header_line: true columns: - name: time
 type: timestamp
 format: '%Y-%m-%d %H:%M:%S' - name: account
 type: long - name: purchase
 type: timestamp
 format: '%Y%m%d' - name: comment
 type: string last_paths: [data/examples/sample_002.csv.gz] out:
 type: example Repeat # install $ wget https://bintray.com/artifact/download/ embulk/maven/embulk-0.2.0.jar -o embulk.jar $ chmod 755 embulk.jar
 # guess $ vi partial-config.yml $ ./embulk guess partial-config.yml
 -o config.yml
 # preview $ ./embulk preview config.yml $ vi config.yml # if necessary # run $ ./embulk run config.yml -o config.yml # repeat $ ./embulk run config.yml -o config.yml $ ./embulk run config.yml -o config.yml
  62. 62. #ccc_cd4 / #embulk Writing Embulk plugins 62
  63. 63. InputPlugin module Embulk class InputExample < InputPlugin Plugin.register_input('example', self) def self.transaction(config, &control) # read config task = { 'message' => config.param('message', :string, default: nil) } threads = config.param('threads', :int, default: 2) columns = [ Column.new(0, 'col0', :long), Column.new(1, 'col1', :double), Column.new(2, 'col2', :string), ] # BEGIN here commit_reports = yield(task, columns, threads) # COMMIT here puts "Example input finished" return {} end def run(task, schema, index, page_builder) puts "Example input thread #{@index}…" 10.times do |i| @page_builder.add([i, 10.0, "example"]) end @page_builder.finish commit_report = { } return commit_report end end end
  64. 64. OutputPlugin module Embulk class OutputExample < OutputPlugin Plugin.register_output('example', self) def self.transaction( config, schema, processor_count, &control) # read config task = { 'message' => config.param('message', :string, default: "record") } puts "Example output started." commit_reports = yield(task) puts "Example output finished. Commit reports = #{commit_reports.to_json}" return {} end def initialize(task, schema, index) puts "Example output thread #{index}..." super @message = task.prop('message', :string) @records = 0 end def add(page) page.each do |record| hash = Hash[schema.names.zip(record)] puts "#{@message}: #{hash.to_json}" @records += 1 end end def finish end def abort end def commit commit_report = { "records" => @records } return commit_report end end end
  65. 65. GuessPlugin # guess_gzip.rb module Embulk class GzipGuess < GuessPlugin Plugin.register_guess('gzip', self) GZIP_HEADER = "x1f x8b".force_encoding('ASCII-8BIT').freeze def guess(config, sample_buffer) if sample_buffer[0,2] == GZIP_HEADER return {"decoders" => [{"type" => "gzip"}]} end return {} end end end # guess_ module Embulk class GuessNewline < TextGuessPlugin Plugin.register_guess('newline', self) def guess_text(config, sample_text) cr_count = sample_text.count("r") lf_count = sample_text.count("n") crlf_count = sample_text.scan(/rn/).length if crlf_count > cr_count / 2 && crlf_count > lf_count / 2 return {"parser" => {"newline" => "CRLF"}} elsif cr_count > lf_count / 2 return {"parser" => {"newline" => "CR"}} else return {"parser" => {"newline" => "LF"}} end end end end
  66. 66. #ccc_cd4 / #embulk Releasing to RubyGems Examples > embulk-plugin-postgres-json.gem > https://github.com/frsyuki/embulk-plugin-postgres-json > embulk-plugin-redis.gem > https://github.com/komamitsu/embulk-plugin-redis > embulk-plugin-input-sfdc-event-log-files.gem > https://github.com/nahi/embulk-plugin-input-sfdc-event- log-files
  67. 67. #ccc_cd4 / #embulk plugin bundle > embulk bundle <dir> > Gemfile
  68. 68. Attention! Presto Presto Presto Presto Presto Presto Presto Presto Presto Presto Presto Presto Presto Presto Presto Presto Presto Presto Presto Presto 68
  69. 69. Attention! Hive Hive Hive Hive Hive Hive Hive Hive Hive Hive Hive Hive Hive Hive Hive Hive 69
  70. 70. Hive Hive Hive Hive Hive Hive HiveHive PrestoPrestogres hba.conf PostgreSQL 70

×