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.
道玄坂LT祭り
Kuduを調べてみた
株式会社サイバーエージェント
技術本部 秋葉原ラボ
鈴木俊裕
自己紹介
● 鈴木俊裕(すずき としひろ)
● ソフトウェアエンジニア
● サイバーエージェント 技術本部 秋葉原ラボ
○ Hadoopを用いたログ解析基盤
○ HBaseを用いた基盤システム
● 最近の興味:Go, NewSQL
● Twit...
今日話すこと
● Kuduを調べてみた
○ Kuduについて
○ デモ(時間があれば)
Kuduについて
Kuduとは
● Clouderaが開発したHadoopのエコシステム
● カラムナ(列指向)ストレージエンジン
● オープンソース(Apache インキュベータプロジェクト)
Kuduの位置づけ
● HDFS + Parquet or ORC
○ スキャン ◎
○ ランダムアクセス ☓
● HBase, Cassandra
○ スキャン ☓ or △
○ ランダムアクセス ◎
● Kudu
○ スキャン ◯
○ ラン...
Kuduを使うと何が嬉しいのか
● スキャンもランダムアクセスも必要なユースケース
○ Kuduがなかった時には複雑なアーキテクチャになりがち
Kuduを使うと何が嬉しいのか
● スキャンもランダムアクセスも必要なユースケース
○ Kuduを使うとシンプルになる
データモデル
● RDBに似ている
○ Tableを事前定義する必要がある(CREATE TABLE)
■ 主キーを必ず定義する
■ 有限個のColumnを定義
● 動的にColumnを追加できない
○ Columnは型を持つ(e.g. INT...
データモデル
● HBase等のように値をbytesにしなかった理由
○ 型に特化したエンコードや圧縮が可能だから
● サポートしているエンコード
○ Bitshffule, Run Length, Dictionary, Prefix
● サ...
API
● Java, C++, Python(実験段階)
● 書き込みは Insert, Update, Delete
○ 主キーを指定しなければならない
○ (HBase等のように)書き込み時にタイムスタンプを指定で
きない
○ 複数Row...
API
● 読み込みは Scan のみ
○ Filterの指定はできる
■ Colmunの値と定数の比較(e.g. col = “aaa”)
■ 主キーのレンジ(e.g. key >= 100 AND key <= 200)
○ プロジェクショ...
インテグレーション
● Impala
○ 将来的にはHive, Drill, Prestoも?
● Spark
○ DataSourceとしてKuduのテーブルを指定できる
● MapReduce
○ KuduTableInputFormat/...
アーキテクチャ
● データのパーティショニング
○ TableはTabletと呼ばれる単位に分割される
■ Rowは必ず1つのTabletに配置される
■ どのTabletに所属するかは主キーで決まる
○ パーティショニングの方式は2種類
■ ...
アーキテクチャ
● データのパーティショニング
○ key-range-based の例
CREATE TABLE customers (
id STRING,
name STRING,
age INT
)
DISTRIBUTE BY RANG...
アーキテクチャ
● データのパーティショニング
○ hash-based の例
CREATE TABLE customers (
id STRING,
name STRING,
age INT
)
DISTRIBUTE BY HASH(id) ...
アーキテクチャ
● データのレプリケーション
○ Raftを採用している
Client
Tabletサーバ
Tabletサーバ Tabletサーバ
Tablet
(LEADER)
Tablet
(FOLLOWER)
Tablet
(FOLLOW...
アーキテクチャ
● データのレプリケーション
○ Raftを採用している
Client
Tabletサーバ
Tabletサーバ Tabletサーバ
Tablet
(LEADER)
Tablet
(FOLLOWER)
Tablet
(FOLLOW...
アーキテクチャ
● データのレプリケーション
○ Raftを採用している
Client
Tabletサーバ
Tabletサーバ Tabletサーバ
Tablet
(LEADER)
Tablet
(FOLLOWER)
Tablet
(FOLLOW...
アーキテクチャ
● データのレプリケーション
○ Raftを採用している
Client
Tabletサーバ
Tabletサーバ Tabletサーバ
Tablet
(LEADER)
Tablet
(FOLLOWER)
Tablet
(FOLLOW...
アーキテクチャ
● データのレプリケーション
○ Raftを採用している
Client
Tabletサーバ
Tabletサーバ Tabletサーバ
Tablet
(LEADER)
Tablet
(FOLLOWER)
Tablet
(FOLLOW...
アーキテクチャ
● データのレプリケーション
○ Raftを採用している
Client
Tabletサーバ
Tabletサーバ Tabletサーバ
Tablet
(LEADER)
Tablet
(FOLLOWER)
Tablet
(FOLLOW...
アーキテクチャ
● 2つのコンポーネント
○ Master
■ メタデータの管理
● Tabletの場所等
■ Tabletサーバのコーディネーション
● 死活監視・フェイルオーバー
○ Tabletサーバ
■ 担当Tabletの実際のデータの...
アーキテクチャ
● Master と Tabletサーバ
まとめ
● データモデルはRDBに近い
● APIはInsert, Update, Delete, Scan
● TableはTabletにパーティショニングされる
● パーティション方式はkey-range-based, hash-based...
デモ
インストール
● 詳細は省略
● Cloudera Managerを使えば簡単
○ Kudu
■ http://getkudu.io/docs/installation.html
○ Impala_Kudu
■ http://getkudu.i...
デモ
● Cloudera Manager
● KuduのWebUI
● Impalaで操作してみる
○ テーブルを作ってみる
○ データを入れてみる
○ クエリを投げてみる
○ 更新してみる
終わり
● ご清聴ありがとうございました!
資料は作ったけど
話せなかった内容
整合性モデル
● デフォルトではExternal Consistencyの保証を提供してない
○ 2つのRowへの書き込みがあった時にクライアント間でど
ちらの更新が先に見えるかわからない
● 2つのConsistency Mode
○ CLI...
整合性モデル
● CLIENT_PROPEGATED
○ AsyncKuduClient#getLastPropagatedTimestamp()
○ AsyncKuduClient#setLastPropagatedTimestamp()
○...
整合性モデル
● COMMIT_WAIT
○ KuduSession#setExternalConsistencyMode()で
COMMIT_WAITを指定
○ Spannerと同じ手法
○ 確実にcommitが終わっている時間まで待つイメー...
フェイルオーバ
● Raft
○ TabletのレプリカのLEADERがダウンしたらリーダエレク
ションされて、新しいリーダーが選出される
○ FOLLOWERが落ちても問題ない((N-1)/2 まで)
○ MasterもメタデータをTable...
Tabletストレージ
● Tabletは更にRowSetと呼ばれる単位に分割される
● RowSetは2種類ある
○ MemRowSet
○ DiskRowSet
Tabletストレージ
● MemRowSet
○ メモリー上にある
○ 各Tabletに1つだけある
○ Insertされたデータはまずここに入る
○ 主キーでソートされた状態で格納される
○ 定期的にディスクにフラッシュされてDiskRow...
Tabletストレージ
● DiskRowSet
○ ディスクに配置される
○ 各Column毎に別ファイルとしてディスクに書かれる
○ MemRowSetが主キーでソートされているので、必然的に
DiskRowSetも主キーでソートされる
○...
Tabletストレージ
● INSERT時
Client
INSERT
MemRowSet
Tablet
Tabletストレージ
● INSERT時
Client
INSERT
MemRowSet
Tablet
flush
DiskRowSet1
col1 col2 col3
Tabletストレージ
● INSERT時
Client
INSERT
MemRowSet
Tablet
flush
DiskRowSet2
col1 col2 col3
DiskRowSet1
col1 col2 col3
Tabletストレージ
● UPDATE, DELETE時には、更新情報がRowSet毎にある
delta store に置かれる
● delta store も2種類ある
○ DeltaMemStore
■ メモリ上
■ 更新されたらまずここ...
Tabletストレージ
● UPDATE・DELETE時
MemRowSet
DiskRowSet2
col2 col3
DiskRowSet1
col2 col3
col1
col1
Tablet
Tabletストレージ
● UPDATE・DELETE時
MemRowSet
DiskRowSet2
col2 col3
DiskRowSet1
col2 col3
col1
col1
BloomFilterや主キーのイン
デックスを使って更新...
Tabletストレージ
● UPDATE・DELETE時
MemRowSet
DiskRowSet2
col2 col3
DiskRowSet1
col2 col3
col1
col1 DeltaMemStore
Rowが見つかったら更
新情報...
Tabletストレージ
● Scan時
○ 指定されたColumn毎にScan
■ 主キーのレンジが指定されていたら、RowSetをスキャ
ンする範囲を減らせるか決める
■ RowSetを読みだしながら結果を作っていく
■ 最後にdelta s...
Tabletストレージ
● Scan時
MemRowSet
DiskRowSet2
DiskRowSet1
DeltaMemStore
Tablet
col2 col3
col2 col3
col1
col1
Tabletストレージ
● Scan時
MemRowSet
DiskRowSet2
DiskRowSet1
DeltaMemStore
Tablet
col2 col3
col2 col3
col1
col1
指定されたColumnを
Scan
Tabletストレージ
● Scan時
MemRowSet
DiskRowSet2
DiskRowSet1
DeltaMemStore
Tablet
col2 col3
col2 col3
col1
col1
delta storeと突き合わ
...
Tabletストレージ
● Kuduのスキャンとランダムアクセスとのトレードオフ
○ 更新時に既にRowが存在するかを確認する必要があるの
で遅くなる
○ Scan時にRowSet間のマージが必要ないので高速
Tabletストレージ
● Delta Compaction
○ delta store が多くなってくると、TabletのScanのが遅くなっ
ていく
○ 定期的にbase dataとdelta sotreをマージする
Tabletストレージ
● Delta Compaction
DiskRowSet
col2 col3col1 DeltaMemStoreDeltaFileDeltaFileDeltaFile
Base Data Base Dataから
の差分
Tabletストレージ
● Delta Compaction
DiskRowSet
col2 col3col1 DeltaMemStoreDeltaFileDeltaFileDeltaFile
DiskRowSet
col2 col3col1 ...
Tabletストレージ
● RowSet Compaction
○ 削除されたデータの物理削除
○ Key RangeがオーバラップしたDiskRowSetを減らす
■ Rowが含まれてるかもしれないDiskRowSetを減らす
ことができるで...
Tabletストレージ
● RowSet Compaction
DiskRowSet1PK=alice PK=joe PK=linda PK=linda
DiskRowSet2PK=bob PK=jon PK=mary PK=zeke
Disk...
Tabletストレージ
● RowSet Compaction
DiskRowSet1PK=alice PK=joe PK=linda PK=linda
DiskRowSet2PK=bob PK=jon PK=mary PK=zeke
Disk...
Upcoming SlideShare
Loading in …5
×

Kuduを調べてみた #dogenzakalt

2,240 views

Published on

道玄坂LT祭り "LAST GIGS" で発表した資料。

Published in: Technology
  • Be the first to comment

Kuduを調べてみた #dogenzakalt

  1. 1. 道玄坂LT祭り Kuduを調べてみた 株式会社サイバーエージェント 技術本部 秋葉原ラボ 鈴木俊裕
  2. 2. 自己紹介 ● 鈴木俊裕(すずき としひろ) ● ソフトウェアエンジニア ● サイバーエージェント 技術本部 秋葉原ラボ ○ Hadoopを用いたログ解析基盤 ○ HBaseを用いた基盤システム ● 最近の興味:Go, NewSQL ● Twitter @brfrn169 ● 著書「HBase徹底入門」
  3. 3. 今日話すこと ● Kuduを調べてみた ○ Kuduについて ○ デモ(時間があれば)
  4. 4. Kuduについて
  5. 5. Kuduとは ● Clouderaが開発したHadoopのエコシステム ● カラムナ(列指向)ストレージエンジン ● オープンソース(Apache インキュベータプロジェクト)
  6. 6. Kuduの位置づけ ● HDFS + Parquet or ORC ○ スキャン ◎ ○ ランダムアクセス ☓ ● HBase, Cassandra ○ スキャン ☓ or △ ○ ランダムアクセス ◎ ● Kudu ○ スキャン ◯ ○ ランダムアクセス ◯
  7. 7. Kuduを使うと何が嬉しいのか ● スキャンもランダムアクセスも必要なユースケース ○ Kuduがなかった時には複雑なアーキテクチャになりがち
  8. 8. Kuduを使うと何が嬉しいのか ● スキャンもランダムアクセスも必要なユースケース ○ Kuduを使うとシンプルになる
  9. 9. データモデル ● RDBに似ている ○ Tableを事前定義する必要がある(CREATE TABLE) ■ 主キーを必ず定義する ■ 有限個のColumnを定義 ● 動的にColumnを追加できない ○ Columnは型を持つ(e.g. INT32, STRING) ○ 今のところ、セカンダリインデックスやユニークキー制約な どは実装されてない
  10. 10. データモデル ● HBase等のように値をbytesにしなかった理由 ○ 型に特化したエンコードや圧縮が可能だから ● サポートしているエンコード ○ Bitshffule, Run Length, Dictionary, Prefix ● サポートしている圧縮 ○ LZ4, snappy, zlib
  11. 11. API ● Java, C++, Python(実験段階) ● 書き込みは Insert, Update, Delete ○ 主キーを指定しなければならない ○ (HBase等のように)書き込み時にタイムスタンプを指定で きない ○ 複数RowのトランザクショナルAPIはない ■ 単一Row内の更新はアトミック
  12. 12. API ● 読み込みは Scan のみ ○ Filterの指定はできる ■ Colmunの値と定数の比較(e.g. col = “aaa”) ■ 主キーのレンジ(e.g. key >= 100 AND key <= 200) ○ プロジェクション(射影)を指定できる(e.g. SELECT key, col where ...) ■ KuduはカラムナストレージエンジンなのでColumnを絞 るとパフォーマンスがよくなる ○ タイムスタンプを指定して point-in-time クエリが可能
  13. 13. インテグレーション ● Impala ○ 将来的にはHive, Drill, Prestoも? ● Spark ○ DataSourceとしてKuduのテーブルを指定できる ● MapReduce ○ KuduTableInputFormat/KuduTableOutputFormat
  14. 14. アーキテクチャ ● データのパーティショニング ○ TableはTabletと呼ばれる単位に分割される ■ Rowは必ず1つのTabletに配置される ■ どのTabletに所属するかは主キーで決まる ○ パーティショニングの方式は2種類 ■ key-range-based ■ hash-based ■ 組み合わせも可能
  15. 15. アーキテクチャ ● データのパーティショニング ○ key-range-based の例 CREATE TABLE customers ( id STRING, name STRING, age INT ) DISTRIBUTE BY RANGE(id) SPLIT ROWS(('a'), ('b'), ('c'), .., ('y'), ('z')) TBLPROPERTIES( 'storage_handler' = 'com.cloudera.kudu.hive.KuduStorageHandler', 'kudu.table_name' = 'customers', 'kudu.master_addresses' = 'kudu-master1:7051', 'kudu.key_columns' = 'id' );
  16. 16. アーキテクチャ ● データのパーティショニング ○ hash-based の例 CREATE TABLE customers ( id STRING, name STRING, age INT ) DISTRIBUTE BY HASH(id) INTO 16 BUCKETS TBLPROPERTIES( 'storage_handler' = 'com.cloudera.kudu.hive.KuduStorageHandler', 'kudu.table_name' = 'customers', 'kudu.master_addresses' = 'kudu-master1:7051', 'kudu.key_columns' = 'id' );
  17. 17. アーキテクチャ ● データのレプリケーション ○ Raftを採用している Client Tabletサーバ Tabletサーバ Tabletサーバ Tablet (LEADER) Tablet (FOLLOWER) Tablet (FOLLOWER) WAL WAL WAL
  18. 18. アーキテクチャ ● データのレプリケーション ○ Raftを採用している Client Tabletサーバ Tabletサーバ Tabletサーバ Tablet (LEADER) Tablet (FOLLOWER) Tablet (FOLLOWER) WAL WAL WAL Write
  19. 19. アーキテクチャ ● データのレプリケーション ○ Raftを採用している Client Tabletサーバ Tabletサーバ Tabletサーバ Tablet (LEADER) Tablet (FOLLOWER) Tablet (FOLLOWER) WAL WAL WAL WALに書き込む UpdateConsensus() UpdateConsensus()
  20. 20. アーキテクチャ ● データのレプリケーション ○ Raftを採用している Client Tabletサーバ Tabletサーバ Tabletサーバ Tablet (LEADER) Tablet (FOLLOWER) Tablet (FOLLOWER) WAL WAL WAL WALに書き込むWALに書き込む
  21. 21. アーキテクチャ ● データのレプリケーション ○ Raftを採用している Client Tabletサーバ Tabletサーバ Tabletサーバ Tablet (LEADER) Tablet (FOLLOWER) Tablet (FOLLOWER) WAL WAL WAL Success 過半数が成功した ら... Success
  22. 22. アーキテクチャ ● データのレプリケーション ○ Raftを採用している Client Tabletサーバ Tabletサーバ Tabletサーバ Tablet (LEADER) Tablet (FOLLOWER) Tablet (FOLLOWER) WAL WAL WAL Success
  23. 23. アーキテクチャ ● 2つのコンポーネント ○ Master ■ メタデータの管理 ● Tabletの場所等 ■ Tabletサーバのコーディネーション ● 死活監視・フェイルオーバー ○ Tabletサーバ ■ 担当Tabletの実際のデータのやり取り
  24. 24. アーキテクチャ ● Master と Tabletサーバ
  25. 25. まとめ ● データモデルはRDBに近い ● APIはInsert, Update, Delete, Scan ● TableはTabletにパーティショニングされる ● パーティション方式はkey-range-based, hash-based, それら の組み合わせ ● レプリケーションはRaftで ● Master + Tabletサーバ 構成
  26. 26. デモ
  27. 27. インストール ● 詳細は省略 ● Cloudera Managerを使えば簡単 ○ Kudu ■ http://getkudu.io/docs/installation.html ○ Impala_Kudu ■ http://getkudu.io/docs/kudu_impala_integration.html ● ただし、CPUがSSE4.2やSSSE3をサポートしていないと起動 しない
  28. 28. デモ ● Cloudera Manager ● KuduのWebUI ● Impalaで操作してみる ○ テーブルを作ってみる ○ データを入れてみる ○ クエリを投げてみる ○ 更新してみる
  29. 29. 終わり ● ご清聴ありがとうございました!
  30. 30. 資料は作ったけど 話せなかった内容
  31. 31. 整合性モデル ● デフォルトではExternal Consistencyの保証を提供してない ○ 2つのRowへの書き込みがあった時にクライアント間でど ちらの更新が先に見えるかわからない ● 2つのConsistency Mode ○ CLIENT_PROPEGATED ○ COMMIT_WAIT
  32. 32. 整合性モデル ● CLIENT_PROPEGATED ○ AsyncKuduClient#getLastPropagatedTimestamp() ○ AsyncKuduClient#setLastPropagatedTimestamp() ○ 更新した後に propagetedTimestamp を取得して、別クラ イアントでそれを指定して取得する
  33. 33. 整合性モデル ● COMMIT_WAIT ○ KuduSession#setExternalConsistencyMode()で COMMIT_WAITを指定 ○ Spannerと同じ手法 ○ 確実にcommitが終わっている時間まで待つイメージ ○ ただし、現状だと結構遅い(100-1000ms) ■ NTPベースのため?
  34. 34. フェイルオーバ ● Raft ○ TabletのレプリカのLEADERがダウンしたらリーダエレク ションされて、新しいリーダーが選出される ○ FOLLOWERが落ちても問題ない((N-1)/2 まで) ○ MasterもメタデータをTableとして管理してるので同様。 TabletのレプリカのLEADERがActive Masterとなる
  35. 35. Tabletストレージ ● Tabletは更にRowSetと呼ばれる単位に分割される ● RowSetは2種類ある ○ MemRowSet ○ DiskRowSet
  36. 36. Tabletストレージ ● MemRowSet ○ メモリー上にある ○ 各Tabletに1つだけある ○ Insertされたデータはまずここに入る ○ 主キーでソートされた状態で格納される ○ 定期的にディスクにフラッシュされてDiskRowSetになる
  37. 37. Tabletストレージ ● DiskRowSet ○ ディスクに配置される ○ 各Column毎に別ファイルとしてディスクに書かれる ○ MemRowSetが主キーでソートされているので、必然的に DiskRowSetも主キーでソートされる ○ 主キーのインデックスや、Bloom Filterも書き出される
  38. 38. Tabletストレージ ● INSERT時 Client INSERT MemRowSet Tablet
  39. 39. Tabletストレージ ● INSERT時 Client INSERT MemRowSet Tablet flush DiskRowSet1 col1 col2 col3
  40. 40. Tabletストレージ ● INSERT時 Client INSERT MemRowSet Tablet flush DiskRowSet2 col1 col2 col3 DiskRowSet1 col1 col2 col3
  41. 41. Tabletストレージ ● UPDATE, DELETE時には、更新情報がRowSet毎にある delta store に置かれる ● delta store も2種類ある ○ DeltaMemStore ■ メモリ上 ■ 更新されたらまずここに更新情報を書く ○ DeltaFile ■ DeltaMemStoreがフラッシュされてDeltaFileになる
  42. 42. Tabletストレージ ● UPDATE・DELETE時 MemRowSet DiskRowSet2 col2 col3 DiskRowSet1 col2 col3 col1 col1 Tablet
  43. 43. Tabletストレージ ● UPDATE・DELETE時 MemRowSet DiskRowSet2 col2 col3 DiskRowSet1 col2 col3 col1 col1 BloomFilterや主キーのイン デックスを使って更新する Rowを保持しているRowSet を探す Tablet
  44. 44. Tabletストレージ ● UPDATE・DELETE時 MemRowSet DiskRowSet2 col2 col3 DiskRowSet1 col2 col3 col1 col1 DeltaMemStore Rowが見つかったら更 新情報を該当RowSetの DeltaMemStoreに入れ る Tablet
  45. 45. Tabletストレージ ● Scan時 ○ 指定されたColumn毎にScan ■ 主キーのレンジが指定されていたら、RowSetをスキャ ンする範囲を減らせるか決める ■ RowSetを読みだしながら結果を作っていく ■ 最後にdelta storeに更新情報があるかどうか
  46. 46. Tabletストレージ ● Scan時 MemRowSet DiskRowSet2 DiskRowSet1 DeltaMemStore Tablet col2 col3 col2 col3 col1 col1
  47. 47. Tabletストレージ ● Scan時 MemRowSet DiskRowSet2 DiskRowSet1 DeltaMemStore Tablet col2 col3 col2 col3 col1 col1 指定されたColumnを Scan
  48. 48. Tabletストレージ ● Scan時 MemRowSet DiskRowSet2 DiskRowSet1 DeltaMemStore Tablet col2 col3 col2 col3 col1 col1 delta storeと突き合わ せる
  49. 49. Tabletストレージ ● Kuduのスキャンとランダムアクセスとのトレードオフ ○ 更新時に既にRowが存在するかを確認する必要があるの で遅くなる ○ Scan時にRowSet間のマージが必要ないので高速
  50. 50. Tabletストレージ ● Delta Compaction ○ delta store が多くなってくると、TabletのScanのが遅くなっ ていく ○ 定期的にbase dataとdelta sotreをマージする
  51. 51. Tabletストレージ ● Delta Compaction DiskRowSet col2 col3col1 DeltaMemStoreDeltaFileDeltaFileDeltaFile Base Data Base Dataから の差分
  52. 52. Tabletストレージ ● Delta Compaction DiskRowSet col2 col3col1 DeltaMemStoreDeltaFileDeltaFileDeltaFile DiskRowSet col2 col3col1 DeltaMemStore DeltaFile (REDO) DeltaFile (UNDO) 新しい Base Data マージされなかった DeltaFile Base Dataより過去の差分 (point-in-timeクエリ用)
  53. 53. Tabletストレージ ● RowSet Compaction ○ 削除されたデータの物理削除 ○ Key RangeがオーバラップしたDiskRowSetを減らす ■ Rowが含まれてるかもしれないDiskRowSetを減らす ことができるで効率が上がる
  54. 54. Tabletストレージ ● RowSet Compaction DiskRowSet1PK=alice PK=joe PK=linda PK=linda DiskRowSet2PK=bob PK=jon PK=mary PK=zeke DiskRowSet3PK=carl PK=julie PK=omar PK=zoe
  55. 55. Tabletストレージ ● RowSet Compaction DiskRowSet1PK=alice PK=joe PK=linda PK=linda DiskRowSet2PK=bob PK=jon PK=mary PK=zeke DiskRowSet3PK=carl PK=julie PK=omar PK=zoe DiskRowSet4PK=alice PK=bob PK=carl PK=joe DiskRowSet5PK=jon PK=julie PK=linda PK=mary DiskRowSet6PK=omar PK=zach PK=zeka PK=zoe

×