We encounter exception when using same index name in different keyspaces.
This feature won't exist until Cassandra 3.0, but we're using 1.2.
Cassandra:3.0
https://github.com/bitus-dt/cassandra/blob/cassandra-3.0/src/java/org/apache/cassandra/config/CFMetaData.java#L877
https://github.com/bitus-dt/cassandra/blob/cassandra-3.0/src/java/org/apache/cassandra/schema/KeyspaceMetadata.java#L110
Cassandra-1.2
https://github.com/bitus-dt/cassandra/blob/cassandra-1.2/src/java/org/apache/cassandra/config/CFMetaData.java#L1054
In order to use same index name in different keyspaces without upgrade Cassandra.
We fork and patch this feature to our branch.
Branch: https://github.com/bitus-dt/cassandra/tree/cassandra-1.2
Commit: https://github.com/bitus-dt/cassandra/commit/85487ad55c253ac43d4980cf30b9cfeac256af0f
BitUs DT
2015-11-01
Add thread pool reject counter to metrics-3.1.2 branch
metrics support thread pool reject counter in 4.0, which is under development (master),
we'are using 3.1.2 and want to use this feature, so fork and patch this feature.
Branch: https://github.com/bitus-dt/metrics/tree/3.1.2-branch
Commit: https://github.com/bitus-dt/metrics/commit/d0c2c0d2e8afd691de82a51701dcd4d793a9761a
we'are using 3.1.2 and want to use this feature, so fork and patch this feature.
Branch: https://github.com/bitus-dt/metrics/tree/3.1.2-branch
Commit: https://github.com/bitus-dt/metrics/commit/d0c2c0d2e8afd691de82a51701dcd4d793a9761a
2015-07-25
Karaf + Log4j2
First of all, Log4j2 for Karaf is provided by Pax Logging which requires pax-logging-api-1.8.x.
Dependencies
startup.properties
org.ops4j.pax.logging.cfg
log4j2.yaml
Create your log4j2.yaml in ${karaf.etc} and then you can configure log4j2 in it as normal Java application.
Furthermore, migrate log4j.rootLogger = INFO, osgi:VmLogAppender in log4j settings to log4j2.
Dependencies
- Apache Karaf 2.4.3 (the version for the example below)
- pax-logging-log4j2-1.8.3 (http://search.maven.org/#search|ga|1|pax-logging-log4j2)
- jackson-annotations-2.4.6
- jackson-core-2.4.6
- jackson-databind-2.4.6.1
- jackson-dataformat-yaml-2.4.6 (if you choose YAML as your log4j2 configuration format which is the one I prefer because it's much more human-readable than XML and JSON)
- disruptor-3.3.2 (if using asynchronous loggers)
startup.properties
#org/ops4j/pax/logging/pax-logging-service/1.8.3/pax-logging-service-1.8.3.jar=8 org/ops4j/pax/logging/pax-logging-log4j2/1.8.3/pax-logging-log4j2-1.8.3.jar=8 # Add other libraries you need below com/fasterxml/jackson/core/jackson-annotations/2.4.6/jackson-annotations-2.4.6.jar=35 com/fasterxml/jackson/core/jackson-core/2.4.6/jackson-core-2.4.6.jar=35 com/fasterxml/jackson/core/jackson-databind/2.4.6.1/jackson-databind-2.4.6.1.jar=35 com/fasterxml/jackson/dataformat/jackson-dataformat-yaml/2.4.6/jackson-dataformat-yaml-2.4.6.jar=35 com/lmax/disruptor/3.3.2/disruptor-3.3.2.jar=35
org.ops4j.pax.logging.cfg
org.ops4j.pax.logging.log4j2.config.file = ${karaf.etc}/log4j2.yaml # Use asynchronous logger for all loggers #org.ops4j.pax.logging.log4j2.async = false
log4j2.yaml
Create your log4j2.yaml in ${karaf.etc} and then you can configure log4j2 in it as normal Java application.
Furthermore, migrate log4j.rootLogger = INFO, osgi:VmLogAppender in log4j settings to log4j2.
appenders: PaxOsgi: name: paxosgi filter: VmLogAppender loggers: root: level: INFO AppenderRef: - ref: paxosgi
2015-01-21
多個Cassandra Node在不同Data Center
本實作是採用Cassandra 1.0.12來實施
Token分配
由於Cassandra 1.0.12並沒有token generator, 建議下載https://raw.github.com/riptano/ComboAMI/2.2/tokentoolv2.py, 產生Token分配表, 如下所示:
Snitch用來設定Topology環境, 目的避免單一node failure.
其中環境可分為DataCenter和Rack, 在本文的測試環境分為DC1和DC2, 統一都是使用第一組機台
原文如下:
Set this to a class that implements
# IEndpointSnitch. The snitch has two functions:
# - it teaches Cassandra enough about your network topology to route
# requests efficiently
# - it allows Cassandra to spread replicas around your cluster to avoid
# correlated failures. It does this by grouping machines into
# "datacenters" and "racks." Cassandra will do its best not to have
# more than one replica on the same "rack" (which may not actually
# be a physical location)
Seed Node
- class_name: org.apache.cassandra.locator.SimpleSeedProvider
parameters:
- seeds: "clouddb1.gc.ubicloud.net,clouddb4.gc.ubicloud.net"
================================================
後記
新增一個sample keyspace
$ ./cqlsh localhost
> CREATE KEYSPACE sample WITH strategy_class = 'NetworkTopologyStrategy' AND strategy_options:DC1 = '3' and strategy_options:DC2 = '3';
Ring的結果:
$ ./nodetool -h self ring
Address DC Rack Status State Load Owns Token
169417178424467235000914166253263322299
node0 172.16.70.32 DC1 RAC1 Up Normal 93.18 KB 0.43% 0
node4 172.16.70.44 DC2 RAC1 Up Normal 74.67 KB 32.91% 55989722784154413846455963776007251813
node1 172.16.70.41 DC1 RAC1 Up Normal 97.89 KB 0.43% 56713727820156410577229101238628035242
node5 172.16.70.45 DC2 RAC1 Up Normal 81.01 KB 32.91% 112703450604310824423685065014635287055
node2 172.16.70.42 DC1 RAC1 Up Normal 97.66 KB 0.43% 113427455640312821154458202477256070484
node3 172.16.70.43 DC2 RAC1 Up Normal 81.01 KB 32.91% 169417178424467235000914166253263322299
$ ./nodetool -h self describering sample
TokenRange:
TokenRange(start_token:55989722784154413846455963776007251813, end_token:56713727820156410577229101238628035242, endpoints:[172.16.70.45, 172.16.70.43, 172.16.70.44, 172.16.70.41, 172.16.70.42, 172.16.70.32], rpc_endpoints:[0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0], endpoint_details:[EndpointDetails(host:172.16.70.45, datacenter:DC2, rack:RAC1), EndpointDetails(host:172.16.70.43, datacenter:DC2, rack:RAC1), EndpointDetails(host:172.16.70.44, datacenter:DC2, rack:RAC1), EndpointDetails(host:172.16.70.41, datacenter:DC1, rack:RAC1), EndpointDetails(host:172.16.70.42, datacenter:DC1, rack:RAC1), EndpointDetails(host:172.16.70.32, datacenter:DC1, rack:RAC1)])
TokenRange(start_token:113427455640312821154458202477256070484, end_token:169417178424467235000914166253263322299, endpoints:[172.16.70.43, 172.16.70.44, 172.16.70.45, 172.16.70.32, 172.16.70.41, 172.16.70.42], rpc_endpoints:[0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0], endpoint_details:[EndpointDetails(host:172.16.70.43, datacenter:DC2, rack:RAC1), EndpointDetails(host:172.16.70.44, datacenter:DC2, rack:RAC1), EndpointDetails(host:172.16.70.45, datacenter:DC2, rack:RAC1), EndpointDetails(host:172.16.70.32, datacenter:DC1, rack:RAC1), EndpointDetails(host:172.16.70.41, datacenter:DC1, rack:RAC1), EndpointDetails(host:172.16.70.42, datacenter:DC1, rack:RAC1)])
TokenRange(start_token:169417178424467235000914166253263322299, end_token:0, endpoints:[172.16.70.44, 172.16.70.45, 172.16.70.43, 172.16.70.32, 172.16.70.41, 172.16.70.42], rpc_endpoints:[0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0], endpoint_details:[EndpointDetails(host:172.16.70.44, datacenter:DC2, rack:RAC1), EndpointDetails(host:172.16.70.45, datacenter:DC2, rack:RAC1), EndpointDetails(host:172.16.70.43, datacenter:DC2, rack:RAC1), EndpointDetails(host:172.16.70.32, datacenter:DC1, rack:RAC1), EndpointDetails(host:172.16.70.41, datacenter:DC1, rack:RAC1), EndpointDetails(host:172.16.70.42, datacenter:DC1, rack:RAC1)])
TokenRange(start_token:56713727820156410577229101238628035242, end_token:112703450604310824423685065014635287055, endpoints:[172.16.70.45, 172.16.70.43, 172.16.70.44, 172.16.70.42, 172.16.70.32, 172.16.70.41], rpc_endpoints:[0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0], endpoint_details:[EndpointDetails(host:172.16.70.45, datacenter:DC2, rack:RAC1), EndpointDetails(host:172.16.70.43, datacenter:DC2, rack:RAC1), EndpointDetails(host:172.16.70.44, datacenter:DC2, rack:RAC1), EndpointDetails(host:172.16.70.42, datacenter:DC1, rack:RAC1), EndpointDetails(host:172.16.70.32, datacenter:DC1, rack:RAC1), EndpointDetails(host:172.16.70.41, datacenter:DC1, rack:RAC1)])
TokenRange(start_token:112703450604310824423685065014635287055, end_token:113427455640312821154458202477256070484, endpoints:[172.16.70.43, 172.16.70.44, 172.16.70.45, 172.16.70.42, 172.16.70.32, 172.16.70.41], rpc_endpoints:[0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0], endpoint_details:[EndpointDetails(host:172.16.70.43, datacenter:DC2, rack:RAC1), EndpointDetails(host:172.16.70.44, datacenter:DC2, rack:RAC1), EndpointDetails(host:172.16.70.45, datacenter:DC2, rack:RAC1), EndpointDetails(host:172.16.70.42, datacenter:DC1, rack:RAC1), EndpointDetails(host:172.16.70.32, datacenter:DC1, rack:RAC1), EndpointDetails(host:172.16.70.41, datacenter:DC1, rack:RAC1)])
TokenRange(start_token:0, end_token:55989722784154413846455963776007251813, endpoints:[172.16.70.44, 172.16.70.45, 172.16.70.43, 172.16.70.41, 172.16.70.42, 172.16.70.32], rpc_endpoints:[0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0], endpoint_details:[EndpointDetails(host:172.16.70.44, datacenter:DC2, rack:RAC1), EndpointDetails(host:172.16.70.45, datacenter:DC2, rack:RAC1), EndpointDetails(host:172.16.70.43, datacenter:DC2, rack:RAC1), EndpointDetails(host:172.16.70.41, datacenter:DC1, rack:RAC1), EndpointDetails(host:172.16.70.42, datacenter:DC1, rack:RAC1), EndpointDetails(host:172.16.70.32, datacenter:DC1, rack:RAC1)])
從describering的結果顯示Ring的排列方式如下:
4 -> 1 -> 5 -> 2 -> 3 -> 0 -> 4
Token分配
由於Cassandra 1.0.12並沒有token generator, 建議下載https://raw.github.com/riptano/ComboAMI/2.2/tokentoolv2.py, 產生Token分配表, 如下所示:
Node | hostname | IP Address | Token | Data Center | Rack |
---|---|---|---|---|---|
node0 | clouddb1.gc.net | 172.16.70.32 | 0 | DC1 | RAC1 |
node1 | clouddb2.gc.net | 172.16.70.41 | 56713727820156410577229101238628035242 | DC1 | RAC1 |
node2 | clouddb3.gc.net | 172.16.70.42 | 113427455640312821154458202477256070485 | DC1 | RAC1 |
node3 | clouddb4.gc.net | 172.16.70.43 | 28356863910078205288614550619314017621 | DC2 | RAC1 |
node4 | clouddb5.gc.net | 172.16.70.44 | 85070591730234615865843651857942052863 | DC2 | RAC1 |
node5 | clouddb6.gc.net | 172.16.70.45 | 141784319550391026443072753096570088106 | DC2 | RAC1 |
修改cassandra.yaml
依token分配表將token和hostname填入initial_token和listen_address,例如
In node 0
initial_token: 0
listen_address: clouddb1.gc.net
initial_token: 0
listen_address: clouddb1.gc.net
In node 1
initial_token: 56713727820156410577229101238628035242
listen_address: clouddb2.gc.net
initial_token: 56713727820156410577229101238628035242
listen_address: clouddb2.gc.net
依此類推
Snitch用來設定Topology環境, 目的避免單一node failure.
其中環境可分為DataCenter和Rack, 在本文的測試環境分為DC1和DC2, 統一都是使用第一組機台
原文如下:
Set this to a class that implements
# IEndpointSnitch. The snitch has two functions:
# - it teaches Cassandra enough about your network topology to route
# requests efficiently
# - it allows Cassandra to spread replicas around your cluster to avoid
# correlated failures. It does this by grouping machines into
# "datacenters" and "racks." Cassandra will do its best not to have
# more than one replica on the same "rack" (which may not actually
# be a physical location)
Cassandra提供幾種Snitch的方式
在node0~node5的conf/cassandra.yaml
且修改conf/cassandra-topology.properties, 指定data center和rack
- SimpleSnitch
Treats Strategy order as proximity. This improves cache
locality when disabling read repair, which can further improve
throughput. Only appropriate for single-datacenter deployments.
- PropertyFileSnitch
Proximity is determined by rack and data center, which are explicitly configured in cassandra-topology.properties.
- RackInferringSnitch
Proximity is determined by rack and data center, which are
assumed to correspond to the 3rd and 2nd octet of each node's IP
address, respectively. Unless this happens to match your deployment
conventions (as it did Facebook's), this is best used as an example of
writing a custom Snitch class.
- Ec2Snitch
Appropriate for EC2 deployments in a single Region. Loads
Region and Availability Zone information from the EC2 API. The Region
is treated as the Datacenter, and the Availability Zone as the rack.
Only private IPs are used, so this will not work across multiple
Regions.
- Ec2MultiRegionSnitch
Uses public IPs as broadcast_address to allow cross-region
connectivity. (Thus, you should set seed addresses to the public IP
as well.) You will need to open the storage_port or ssl_storage_port on
the public IP firewall. (For intra-Region traffic, Cassandra will
switch to the private IP after establishing a connection.)
在node0~node5的conf/cassandra.yaml
endpoint_snitch: PropertyFileSnitch
172.16.70.32=DC1:RAC1
172.16.70.41=DC1:RAC1
172.16.70.42=DC1:RAC1
172.16.70.43=DC2:RAC1
172.16.70.44=DC2:RAC1
172.16.70.45=DC2:RAC1
default=DC1:r1
172.16.70.41=DC1:RAC1
172.16.70.42=DC1:RAC1
172.16.70.43=DC2:RAC1
172.16.70.44=DC2:RAC1
172.16.70.45=DC2:RAC1
default=DC1:r1
Seed Node
指定node0, node1和node2為seed node
node0~node5的conf/cassandra.yaml
seed_provider:node0~node5的conf/cassandra.yaml
- class_name: org.apache.cassandra.locator.SimpleSeedProvider
parameters:
- seeds: "clouddb1.gc.ubicloud.net,clouddb4.gc.ubicloud.net"
開起服務
================================================
後記
新增一個sample keyspace
$ ./cqlsh localhost
> CREATE KEYSPACE sample WITH strategy_class = 'NetworkTopologyStrategy' AND strategy_options:DC1 = '3' and strategy_options:DC2 = '3';
Ring的結果:
$ ./nodetool -h self ring
Address DC Rack Status State Load Owns Token
169417178424467235000914166253263322299
node0 172.16.70.32 DC1 RAC1 Up Normal 93.18 KB 0.43% 0
node4 172.16.70.44 DC2 RAC1 Up Normal 74.67 KB 32.91% 55989722784154413846455963776007251813
node1 172.16.70.41 DC1 RAC1 Up Normal 97.89 KB 0.43% 56713727820156410577229101238628035242
node5 172.16.70.45 DC2 RAC1 Up Normal 81.01 KB 32.91% 112703450604310824423685065014635287055
node2 172.16.70.42 DC1 RAC1 Up Normal 97.66 KB 0.43% 113427455640312821154458202477256070484
node3 172.16.70.43 DC2 RAC1 Up Normal 81.01 KB 32.91% 169417178424467235000914166253263322299
$ ./nodetool -h self describering sample
TokenRange:
TokenRange(start_token:55989722784154413846455963776007251813, end_token:56713727820156410577229101238628035242, endpoints:[172.16.70.45, 172.16.70.43, 172.16.70.44, 172.16.70.41, 172.16.70.42, 172.16.70.32], rpc_endpoints:[0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0], endpoint_details:[EndpointDetails(host:172.16.70.45, datacenter:DC2, rack:RAC1), EndpointDetails(host:172.16.70.43, datacenter:DC2, rack:RAC1), EndpointDetails(host:172.16.70.44, datacenter:DC2, rack:RAC1), EndpointDetails(host:172.16.70.41, datacenter:DC1, rack:RAC1), EndpointDetails(host:172.16.70.42, datacenter:DC1, rack:RAC1), EndpointDetails(host:172.16.70.32, datacenter:DC1, rack:RAC1)])
TokenRange(start_token:113427455640312821154458202477256070484, end_token:169417178424467235000914166253263322299, endpoints:[172.16.70.43, 172.16.70.44, 172.16.70.45, 172.16.70.32, 172.16.70.41, 172.16.70.42], rpc_endpoints:[0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0], endpoint_details:[EndpointDetails(host:172.16.70.43, datacenter:DC2, rack:RAC1), EndpointDetails(host:172.16.70.44, datacenter:DC2, rack:RAC1), EndpointDetails(host:172.16.70.45, datacenter:DC2, rack:RAC1), EndpointDetails(host:172.16.70.32, datacenter:DC1, rack:RAC1), EndpointDetails(host:172.16.70.41, datacenter:DC1, rack:RAC1), EndpointDetails(host:172.16.70.42, datacenter:DC1, rack:RAC1)])
TokenRange(start_token:169417178424467235000914166253263322299, end_token:0, endpoints:[172.16.70.44, 172.16.70.45, 172.16.70.43, 172.16.70.32, 172.16.70.41, 172.16.70.42], rpc_endpoints:[0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0], endpoint_details:[EndpointDetails(host:172.16.70.44, datacenter:DC2, rack:RAC1), EndpointDetails(host:172.16.70.45, datacenter:DC2, rack:RAC1), EndpointDetails(host:172.16.70.43, datacenter:DC2, rack:RAC1), EndpointDetails(host:172.16.70.32, datacenter:DC1, rack:RAC1), EndpointDetails(host:172.16.70.41, datacenter:DC1, rack:RAC1), EndpointDetails(host:172.16.70.42, datacenter:DC1, rack:RAC1)])
TokenRange(start_token:56713727820156410577229101238628035242, end_token:112703450604310824423685065014635287055, endpoints:[172.16.70.45, 172.16.70.43, 172.16.70.44, 172.16.70.42, 172.16.70.32, 172.16.70.41], rpc_endpoints:[0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0], endpoint_details:[EndpointDetails(host:172.16.70.45, datacenter:DC2, rack:RAC1), EndpointDetails(host:172.16.70.43, datacenter:DC2, rack:RAC1), EndpointDetails(host:172.16.70.44, datacenter:DC2, rack:RAC1), EndpointDetails(host:172.16.70.42, datacenter:DC1, rack:RAC1), EndpointDetails(host:172.16.70.32, datacenter:DC1, rack:RAC1), EndpointDetails(host:172.16.70.41, datacenter:DC1, rack:RAC1)])
TokenRange(start_token:112703450604310824423685065014635287055, end_token:113427455640312821154458202477256070484, endpoints:[172.16.70.43, 172.16.70.44, 172.16.70.45, 172.16.70.42, 172.16.70.32, 172.16.70.41], rpc_endpoints:[0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0], endpoint_details:[EndpointDetails(host:172.16.70.43, datacenter:DC2, rack:RAC1), EndpointDetails(host:172.16.70.44, datacenter:DC2, rack:RAC1), EndpointDetails(host:172.16.70.45, datacenter:DC2, rack:RAC1), EndpointDetails(host:172.16.70.42, datacenter:DC1, rack:RAC1), EndpointDetails(host:172.16.70.32, datacenter:DC1, rack:RAC1), EndpointDetails(host:172.16.70.41, datacenter:DC1, rack:RAC1)])
TokenRange(start_token:0, end_token:55989722784154413846455963776007251813, endpoints:[172.16.70.44, 172.16.70.45, 172.16.70.43, 172.16.70.41, 172.16.70.42, 172.16.70.32], rpc_endpoints:[0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0], endpoint_details:[EndpointDetails(host:172.16.70.44, datacenter:DC2, rack:RAC1), EndpointDetails(host:172.16.70.45, datacenter:DC2, rack:RAC1), EndpointDetails(host:172.16.70.43, datacenter:DC2, rack:RAC1), EndpointDetails(host:172.16.70.41, datacenter:DC1, rack:RAC1), EndpointDetails(host:172.16.70.42, datacenter:DC1, rack:RAC1), EndpointDetails(host:172.16.70.32, datacenter:DC1, rack:RAC1)])
從describering的結果顯示Ring的排列方式如下:
4 -> 1 -> 5 -> 2 -> 3 -> 0 -> 4
2015-01-14
關於Cassandra的compaction
以下為最近研究Compaction時的一些心得,有錯歡迎指正
Compaction做什麼?
Compaction指的是將數個 SSTable合併成一個SSTable的動作,並且在合併的同時會merges keys, combines columns, discards tombstones and creates a new index in the merged SSTable.
Compaction的種類
一般將Compaction分成 minor compaction與major compaction兩種
Major compaction
當一個column family在做Major compaction時,會將這個column family的所有的SSTable合併成一個
Minor compaction
Minor compaction是指 Cassandra 在 runtime 時,經由compaction strategy判斷而做的compaction,這類的compaction通常一次只會處理部份的SSTable
Major compaction 與 Minor compaction的定義真的是如此嗎?
cassandra 官方似乎沒有很明確的定義 minor compaction。但在 Datastax的這份nodetool compact 文件中指出:"You can specify a keyspace for compaction. If you do not specify a keyspace, the nodetool command uses the current keyspace. You can specify one or more tables for compaction. If you do not specify a table(s), compaction of all tables in the keyspace occurs. This is called a major compaction. If you do specify a table(s), compaction of the specified table(s) occurs. This is called a minor compaction. " 這說法其實是有問題的,實際去追source code (0.8~2.1版)會發現,不管有沒有指定 column family,nodetool compact最後都是用ColumnFamilyStore.forceMajorCompaction() 這個method,而實際操作確實不管有沒有指定column family,nodetool compact完之後,column family的SSTable都會被合併成一個。因此本文件以行為來區分Major與Minor,至少到2.1版為止的行為都可以解釋得通(不過更之後的版本可能未必還是如此)
Cassandra一定要Major compaction才會清除tombstone嗎?
早期cassandra (<0.6版) 確實是如此,因此會需要定期執行 nodetool compact,但是後來minor compact也可以清除 tombstone了 ( CASSANDRA-1074 )
Compaction是怎麼發生的?
AutoCompaction
Force compaction
Compaction會自動發生(稱為 autoCompaction),也可以手動強制執行。
AutoCompaction
根據column family的 compaction options 來決定是否觸發,基本上 autoCompaction只會觸發 minor compaction。關於compaction options 請自行參考適合你的cassandra column family設定文件
Force compaction
透過 nodetool compact 的指令或是JMX可以觸發強制 compaction。強制compaction會觸發 major compaction
做過手動 compaction 之後,autoCompaction就不會再做了嗎?
錯,只要沒特地把 compaction關掉,不然auto compaction還是會持續被執行。這個問題在網路上有不少人在詢問,主要應該是因為Datastax的幾篇舊的tuning文件( 0.8, 1.0 )有以下這樣的敘述:"once you run a major compaction, automatic minor compactions are no longer triggered frequently forcing you to manually run major compactions on a routine basis. So while read performance will be good immediately following a major compaction, it will continually degrade until the next major compaction is manually invoked. ",這段文字其實是指當做過手動 major compaction之後,column family的SSTable會被合併成一個,這個新的SSTable會是一個相對很大的檔案,而SizeTieredCompactionStrategy的觸發條件是當有幾個接近大小的SSTable存在時,才會做minor compaction將這幾個同大小的SSTable做compaction,因此這個相對較大的SSTable在SizeTieredCompactionStrategy的運作下,可能很難再發生minor compaction,導致管理者未來只能透過major compaction才有機會對這個SSTable做compaction。(可能是因為這段話造成太多誤會,Datastax在 1.1版文件 裡也修改了描述方式) (在cassandra 1.2之後提供了 sstablesplit 的指令可以將這類的大 SSTable檔案切成數個小檔)
Major compaction只會做用在 SizeTieredCompactionStrategy 或 DateTieredCompactionStrategy 這兩種strategy嗎?
是的,Major compaction只會作用在這兩種strategy的column family,對LeveledCompactionStrategy執行major compaction不會有任何反應
要怎麼關掉autoCompaction
- ColumnFamily的JMX method disableAutoCompaction() 可用來在runtime時暫時關掉autoCompaction,但是並沒有enable的method,所以只有重啟node才能回復。
- 1.x版的Cassandra,將SizeTieredCompactionStrategy的 min_compaction_threshold跟max_compaction_threshold 都設成0可以讓該column family不再做autoCompaction
- 2.x版的column family的Compaction options有個 enable的選項可以控制是否啟用autoCompaction
2014-12-16
Understanding The Cassandra Data Model
Comparing the Cassandra Data Model to a Relational Database
- Keyspace 是一個應用程式資料的容器,類似關聯式資料庫的 database 或 schema。
- 在 keyspace 內部有很多的 column family, 類似 table。
- Column family 包含了很多的 columns,而一個 row key 指定了一個 columns 集合。
- 每一個 row key 所指定的 column 集合,這些 column 可以不同 (與關聯式資料庫不同)。
- Cassandra 不強制關聯 column families,而它們之間沒有 foreign keys,在 query 資料的時候也不能 join 起來。
- 以反正規化資料為設計。
About keyspace
- 一個 cluster 裡會有一個 keyspace 提供給每個應用程式使用。
- Replication 的控制是以 keyspace 為基礎,若資料有不同複本需求時,應該以不同的 keyspace 為管理。
- 使用 Data Definition Language (DDL) 命令(CLI 或 CQL) 來定義 keyspace。
About Column Families
- 在 Cassandra,你定義了 column family,每個 column family 能夠定義 column 的 metadata,但實際上儲存每一筆 row 的時候,會有哪些 columns 則是應用程式決定的,而且每個 row 可以有不一樣的 columns。
- 雖然 column family 可以有非常大的彈性,實行上,並不是完全的 "schema-less"。每個 column family 都應該被設計成只包含單一資料型態。
- Column family 的設計模式有兩種類型:static 和 dynamic column family。
- Static column family
- 使用一組靜態的欄位名稱,這很像傳統資料庫的 table。
- 可以預先定義 每個 column 的 metadata。
- 例如,一個用來儲存 user 的 column family,可能會有欄位:user name, address, email, phone number. 雖然每一個 row 都有相同的 column 集合,但卻不一定在儲存時都要有值。
- Dynamic column family
- 預先計算結果集(result sets),並儲存到資料列中以提供高效的資料檢索。
- 每一筆資料列都是一筆為了滿足搜尋的資料的快照(snapshot),有點像是具體化視觀表 (materialized view)。
- Dynamic column family 為每個 column 的名稱(name)和值(value) 定義了型態資訊(comparators,validators),而不是去定義每個 column 的 metadata,每個 column 實際的名稱和值,是應用程式在新增欄位時決定的。
- Row key 是唯一的,用來識別每一筆資料列,類似關聯式資料庫的 primary key (主鍵)。
- Column family 靠 row key 來達到資料分割 (partition) 的目的,而 row key 也隱含著索引的意味,它不能是空值。
About Columns
- 在 Cassandra,column 是最小的資料單位,它是由一個名稱(name)、一個值(value)、一個時間戳記(timestamp),所組成的。
- 如果排序對你來說很重要,那麼你可以自己維護一個索引表,利用這個索引表達成排序的需求。
- Column 並不一定要有值(可以是空的),允許只存 column name。
- Cassandra 的每個 column 都有 timestamp,它是用來判斷誰是最新的資料,並且可以被更新。
- timestamp 是客戶端應用程式所提供的,當多個客戶端同時儲存同一個欄位時,只有最新的那筆資料會被儲存。
About Special Columns (Counter, Expiring, Super)
- Expiring Columns
- Column 可以設定過期日 (expiration date),稱為 TTL ( time ot live ),單位是秒。
- 當有設定 TTL 的 column到期之後,就會被標記為 deleted ( with a tombstone)。
- 一但欄位被標記為 tombstone, 在 normal compaction (gc_grace_seconds) 以及 repair 的時候就會自動被清除。
- 可以透過以下兩種方式來設定,
CLI:SET
users['bobbyjo'][utf8('coupon_code')] = utf8('SAVE20')
WITHttl = 864000
;
cqlsh> UPDATE
users
USING TTL
432000
SET
'password' = 'ch@ngem3a'
WHEREKEY = 'jsmith'
;
- 如果你想要重設該 column 的 TTL,那麼就要重新 insert,並指定新的 TTL。
- 過期日的計算是在主要的 host (接受 insert 操作),但會被其它的 node 所解讀,所以 Server 之間的時間必須要同步,否則會不精準。
- Counter Columns
- 用來儲存計數的欄位。
- Counter column families 必須使用 CounterColumnType 做為 validator,所以 counter 必須被使用在專門的 column family。(目前無法與標準欄位放一起)
- 它只會做增量 (incrementing) 或減量 (decrementing)的更新。
- 要更新 counter column,客戶端要指定 counter 名稱和要增加或減少的值。
- counter 能夠以任何的 consistency level 被讀或寫入。
- 與一般的欄位不同,當寫入一個 counter 時,會在背景裡有發出一個讀的請求,以確保分散式 counter 的值在複本間保持一致性。
- Consistency Level ONE 是寫入 counter 最普遍的一致性級別,在這個級別下,所隱含的 read 請求,不會對寫入的延遲造成什麼衝擊。
- Super Columns
- Cassandra 的 column family 可以包含 regular columns 或是 super columns。
- Super column 包含一個 column name 和一個 sub-columns 的 map。
- Super column 可以指定一個 comparator 給 super column name 和 sub-column names。
- 類似實例化視圖(materialized view),可以做資料檢索。
- Data Types (comparators and validators)
- Column value 或 row key 的 data type,我們稱為 validator。
- Column name 的 data type,我們稱為 comparator。
- 在 cassandra 內部,若在建立 column family 的時候沒有指定 data type,預設是以 16 進位的位元陣列 (Bytes type) 來存。
- Cassandra 提供 validator 和 comparator 使用的 data type 中 CounterColumnType 只能用自在 column value。
- data type 請查詢:DataStax
- Validator
- 使用 key_validation_class property,定義預設的 row key validator 給所有的 column families。
- CounterColumnType 不能被用在 row key validator。
- static column family:使用 column_metadata property 來定義 column family 時,你應該要定義每個 column 和它的 data type。
- dynamic column family:你應該定義一個預設的 validator class:default_validation_class,而不是定義每個 column 的 data type。
- Key 和 column validators 在之後可以隨意的增加或修改。
- Comparator
- Row 和 column 會以 column name 的排序順序來存放。
- Comparator 指定 column name 的 data type。
- 當建立 column family 的時候指定了 comparator 後,之後就不能在變更了,這點與 validator 不同。
- Column Family Compression
- 每個 column family 都能設定資料壓縮。
- 壓縮可以讓資料量減少,這樣就可以最大化你的硬碟容量。
- 壓縮的好處,除了節省磁碟空間,也能減少磁碟的 IO,特別是以讀為主的工作。
- Cassandra 能夠快速從 SSTable 索引,找到 row 的位置,然後只解壓縮跟那筆 row 有關的資料的 chunk。
- 壓縮對於寫入的效能而言並沒有負面的影響。事實上,寫到壓縮的 table,效能有 10% 左右的提升。
- Cassandra 的 SSTable 資料檔案是 immutable 的,所以也就沒有重複壓縮的循環。SSTable 只會被壓縮一次,然後就寫到磁碟了。
- 壓縮能夠帶來以下的好處,依不同的 column families 的資料特性有所不同:
- 2X - 4X 資料量的減少。
- 25% - 35% 讀校能改善。
- 5% - 10% 寫校能改善。
- 使用時機:
- 壓縮最合適的 column family 是,有很多筆 row,每一筆 row 有同樣的 columns。
舉例來說,一個 column family 包含 user 相關的資料,如 username, email, 等,可以被選為要壓縮的對象。更多更類似的 row 的資料,將得到更大的壓縮率,而在讀取時會有更大的效能提升。 - 在每筆 row 會有不同的 columns set 的 column family,就不是很適合壓縮,所以 Dynamic column families 不會有太好壓縮率。
- 設定
- 透過 compression_options 來設定 column family 的壓縮。
- 當你在一個 column family 設定壓縮後,那些已經存在磁碟的 SSTable 不會馬上進行壓縮,不過在之後的 compaction 程序會進行壓縮,但新建立的 SSTable 會立即被壓縮。
- 若有必要,你可以使用 nodetool upgradesstables 或 scrub 強迫重寫並壓縮這些 SSTable。
- 透過 CLI 設定
UPDATE COLUMN FAMILY
users
WITH compaction_strategy=LeveledCompactionStrategy
AND compaction_strategy_options={sstable_size_in_mb: 10}; - Cassandra 1.1 之後才能透過 CQL 來設定 compression (DataStax)。
About Primary Indexes
- Column family 的 primary index 就是 row key 的索引。
- Row 的分配是藉由 cluster-configured partitioner 和 key-configured replia placement strategy。
- Primary index 能讓 Cassandra 透過 row key 找出 row。
- 每一個節點都知道任何一個節點所管理的 row key 的範圍。
- 每個 row 請求都能夠透過 row indexes 並只在相關的複本節點上,地找到 row 的位置。
About Secondary Indexes
- Secondary indexes 是用來參考 column value。
- Cassandra 支援 type KEYS 的 secondary index (hash index)。
- 使用 secondary indexes 來查找資料時,透過指定的值和使用對應的欄位名稱(predicate)。
WHERE column x = value y
- 何時使用
- 當資料列包含了索引値。
- 在一個特定的 column 其中的值都是唯一的。(ex, mail)
- 當你查詢或維護一個 index 表花了很多時間時。
- 舉例,若你想要 透過 mail address 來查找一個 user,這會比維護一個做為 index 表格的 dynamic column family 還要有效率。
- 如何使用
- 當你在 column 建立 secondary index時,這會在背景裡建立資料的索引。
- 建立的方式
- 建立 column family時
CREATE COLUMN FAMILY
users
WITH
comparator=UTF8Type
AND
column_metadata=[
{column_name: full_name, validation_class: UTF8Type},{column_name: email, validation_class: UTF8Type},
{column_name: birth_year, validation_class: LongType, index_type: KEYS},
{column_name: state, validation_class: UTF8Type, index_type: KEYS}]
;
- 用 UPDATE 來建立已經存在的 column
UPDATE COLUMN FAMILY
users
WITH
comparator=UTF8Type
AND
column_metadata=[
{column_name: full_name, validation_class: UTF8Type},{column_name: email, validation_class: UTF8Type},
{column_name: birth_year, validation_class: LongType, index_type: KEYS},
{column_name: state, validation_class: UTF8Type, index_type: KEYS}]
;
- 使用 secondary index
- CLI
GET
users
WHERE
state = 'TX'
;
- CQL
SELECT
*
FROM
users
WHERE
state = 'TX'
;
2014-12-04
Understanding Cassandra Architecture
Reading Document : http://www.datastax.com/docs/1.0/cluster_architecture/index
Gossip
- Cassandra 使用 gossip 協定來找出其它參與 Cassandra 叢集的節點位置和狀態資訊。
- Gossip 訊息有版本號,比較老舊的訊息,不能覆寫較新的訊息。
- 為了預防 gossip 溝通上的隔閡,在一個叢集內的所有節點都應該擁有相同的種子節點(seed nodes)清單。
- Seed node 的目的只有在新的節點加入時啟動 gossip 程序,它也不是單點失敗 (Single point of failure) 的設定。
- 每個節點都必須知道它自己的 token 值。當一個叢集被初始化時,應該要建立整個叢集的 tokens,並且為每個節點分配一個 token。之後,每個節點都會把它的 token 拿來跟其他節點溝通。
- 錯誤偵測 (Failure detection) 是一個採取 gossip state 來決定的方法。錯誤偵測也被來避免客戶端請求被繞送到到不了的節點 (unreachable node)。
- 透過 dynamic snitch,Cassandra 也能夠避免把客戶的請求,繞送到活著但效能不佳的節點。
- Cassandra 使用一個累積(accrual)偵測機制,用來計算每一個節點的門檻值,這個門檻值必須考量到網路情況、工作量、或其它跟影響心跳感應率有關的條件。
- 透過設定 phi_convict_threshold 屬性,來調整錯誤偵測的敏感度。目前的預設值已經是大部分情況下的最佳值。
- 個節點的中斷不表示,這個節點從叢集中永遠離開,節點不會自動從 ring 中離開,其它的節點會定期的透過 gossip 和它聯繫,去看看它是否回來了。(如果要將一個節點,加入一個 ring 或從一個 ring 移掉,那麼就要使用 nodetool。)
Data Partitioning in Cassandra
- Data Partitioning 決定資料如何分佈在叢集內的節點。
- 與資料分散有關的三個要素:
- Partitioner:決定哪個節點來存資料。
- 資料副本數量:透過副本放置策略 (replica placement strategy) 來決定。
- 叢集拓撲:節點數量,節點在機櫃 (rack) 的分佈和資料中心 (data center) 的數量。
- 要決定一個 row 的第一個副本 (replica) 要存到哪一個節點,便要順時針方向走訪這個 ring 的每個節點,直到遇到一個節點的 token 值大於 row key 值。
- 一個節點的責任區在於他自己和前面節點的那一段區域。
- Cassandra 1.0 提供兩個 Partitioner。
- RandomPartitioner
- ByteOrderedPartitioner
- RandomPartitioner 是預設的 partitioning strategy,在大部份的情況下是最佳選擇。
- 把資料均勻的分佈在數個節點,row key 是用一個雜湊演算法所建立一個 MD5 的雜湊值。(雜湊值的範圍為 0 ~ 2127 - 1)
- 另一個使用 RandomPartitioner 的好處是,簡單化了叢集負載平衡,因為每一個區塊的雜湊範圍平均接受了等量的資料。
- ByteOrderedPartitioner 從字面上來看,這個 Partitioner 的 row key 是 bytes。
- Datastax 強烈建議不要使用這個 Partitioner,原因如下:
1) Sequential writes can cause hot spots
如果你的應用程式傾向一次寫入或更新一批連續資料列,這些寫入的資料並不分散在整個叢集裡,也就是都存到單一個節點,這經常會是一個應用程式會有 處理與timestamped 資料有關的問題。
2) More administrative overhead to load balance the cluster
Ordered Partitioner 需要管理者去計算出 token 的範圍,以估算出 row key 的分散。
3) Uneven load balancing for multiple column families
若你的應用程式有很多列族,有個可能性是這些列族們有不同的 row key 並且有不同的資料分佈。 - 佈署在多個 data center 的副本放置策略(replication placement strategy) 比較推薦的是 NetworkTopologyStrategy,這個策略對每個 data center 計算副本的放置。
- 這個策略,為每一個列,藉由 token 值來分配節點以放置第一個副本,之後,要在同一個 data center 裡放置額外的副本,就必須以順時鐘方向走訪 ring 的節點,直到遇到在另一個 rack 的第一個節點。
- 在 data center 內不均勻的資料分佈,會導致不均勻的資料分佈。
- 要先確保每一個 data center 裡的每一個節點所獲得的 token 可以讓它們均勻分布在 ring 裡,這樣可以避免 row key 不均勻的分配在每一個 data center 裡。
- 無論如何,你必須避免節點有相同的 token 值,而造成衝突,即使不在同一個叢集的節點。必須要確認每一個節點都有一個唯一值的 token。
Replication in Cassandra
- Replication 是一個把資料複本儲存到多個節點的程序,用來確保可靠度和容錯。
- Cassandra 的儲存複本稱為 replica (複製品;複寫),針對 row key 的每個 row 來存。
- 這個策略將複本分散儲存到叢集內的一些節點,取決於叢集的拓撲 (topology)。
- 叢集內複本的總數參考到複本因子(replication factor)。
- 複本因子為 1 的意思是,針對每一筆資料列只會有1 筆複本在一個節點裡。複本因子為 2 的意思是,每一筆資料列有 2 筆複本,分別存到不同節點裡。
- 所有複本一樣重要,沒有主要或次要複本的差別。
- 個通則是,副本因子的數量不能超過叢集內節點的總數,當然你可以增加副本因子的數量,在之後增加節點。如果副本因子超過節點數,就無法寫成功,但讀取只要可以符合一致性等級(consistency level) 仍然可以成功。
- 複本策略仰賴叢集設定 snitch。
- Replication Strategy 可用設定有:SimpleStrategy、NetworkTopologyStrategy
- SimpleStrategy
- 針對單一 data center 的話,就使用這個策略。
- 若你透過 CLI 建立 keyspace 時,這是預設的複本放置策略。
CREATE KEYSPACE myspace WITH placement_strategy = 'org.apache.cassandra.locator.SimpleStrategy'
AND strategy_options = {replication_factor:3};
- 這個策略放置第一個複本的依據是來自 partitioner,剩下的複本則是依序走訪 ring 中的節點,而不需要考慮機櫃 (rack) 或資料中心 (data center)。
- 下圖顯示 3 個資料列的 3 個複本被放置在 4 個節點的叢集中。
- NetworkTopologyStrategy
- 當你已經或想要佈署你的 Cassandra 叢集橫跨多個 data center。這個策略可以指定有你想要放置多少複本在每個 data center。
- 當你想決定要將多少複本設置到每一個 data center 時,底下兩件事情是需要考慮的:
1) 能夠滿足本地端的讀取,而不需要跨 data center
2) 錯誤的情形 - Two replicas in each data center
這個設定能夠容忍一個複本群組的單點失效狀態,並允許在 consistency level 設為 1 時的本地端讀取。 - Three replicas in each data center
這個設定能夠在 consistency level 為 LOCAL_QUORUM 的情況下,容忍一個複本群組的單點失效狀態。或者,在 consistency level 設為 1 的情況下,容忍 data center 有多個失敗節點。 - 每個 data center 都是獨立決定資料複本的放置,規則如下:
1) 第一個複本,會根據 partitioner 來放置 (與 SimpleStrategy 一樣)
2) 剩餘的複本會順時針走訪 ring 的每個節點,直到發現有不同的 rack 的節點。如果沒有這樣的節點,那麼就會放在同一個 rack 裡的其它節點。 - 這個策略會嘗試將複本放到不同的 rack,因為在同一個 rack 的節點,有可能會同時失效 (斷電, 網路中斷)。
- NetworkTopologyStrategy 仰賴 snitch 的設定,以正確地放置複本到 rack 和 data center。慎重的為你的叢集選擇 snitch 的類型,這個重要性可以讓你的叢集能夠正確地決定節點在你網路的位置。
- OldNetworkTopologyStrategy 設定上的限制,只能夠指定多少複本要存到分別存到不同的 data center,不能夠像 NetworkTopologyStrategy 那樣可以個別設定哪一個 data center 可以存多少複本。
- Snitch
- Snitch 將 IP 對應到 rack 和 data center。
- 它定義了,節點如何在整個網路拓撲裡,被群集在一起。
- Cassandra 使用了這個資訊,盡可能以高效的方式,繞送 (route) 節點間的請求。
- 在同一個叢集內的節點應該都要使用相同的 snitch 設定。
- Snitch 的類型:
1) SimpleSnitch不去識別 data center 或 rack 資訊,用在 single-data center 佈署。
2) RackInferringSnitch
RackInferringSnitch 猜測網路拓撲是依據八進位的節點 IP 位址。
當定義你的 keyspace 的 strategy_options 時,使用第二個八進位的 IP 當做你 data center 的名稱。
左圖表示,data center 的名稱是 100。
3) PropertyFileSnitch
透過 rack 和 data center 決定節點的位置。透過 cassandra-topology.properties 來設定。
使用這個 snitch 的原因有:節點 IP 會改變、有複雜的複本群組需求。
4) EC2Snitch
提供 Amazon EC2 佈署(single region)
5) ECMRegionSnitch
提供 Amazon EC2 佈署(multiple region)。 - Dynamic Snitching
- 預設上是啟用的,所有的 snitch 都使用一個動態 snitch 層,這個 snitch 監控讀的延遲 (read latency)以及試著不要將請求送到效能低下的節點。
- 可以在 cassandra.yaml 針對每個節點,設定 dynamic snitch threshold 。
Cient Requests in Cassandra
- 所有 Cassandra 節點的地位都是相同的,同一個 Client 的讀或寫的請求可以送到任何一個節點處理,當一個 Client 連到一個節點並且發起一個讀或寫的請求,這個節點就成為協調者 (coordinator) 用來處理特定客戶端操作。
- 協調者的工作是:
- 扮演代理 (proxy) 的角色,做為 Client 與複本節點 (replicas) 之間的代理人。
- 決定 ring 裡的哪些節點應該取得請求,這必須依據 partitioner 和 replica placement strategy。
- Write Requests:
- Coordinator 將寫的請求發送給所有的 replicas,只要所有的複本節點是正常運作的,它們就會收到寫的請求,不論 Client 是否有指定的一致性等級 (consistency level)。
- 在一個單一 data center 裡有一個帶有複本因子為 3 並擁有 10 個節點的 Cassandra 叢集,一個在進行的寫的請求將會跑到 3 個節點,以讓它們擁有那筆資料列的請求。
- 如果 Client 指定這個寫入的 consistency level 是 ONE,第一個節點完成寫入後,回應了 coodinator,那麼隨後就會把成功訊息送回 Client。
- 如果一個複本節點錯過了一個寫入的請求,那麼這筆資料列將會在後續,以 Cassandra 所提供的修復方式:hinted handoff、read repair、anti-entropy node repair,來修復。
- Multi-Data Center Write Requests
- 多 data center 佈署上,Cassandra 最佳化寫的效能上,在每個 data center 各選擇了一個 coordinator,透過它來處理寫入複本的請求,這樣的話,只要把寫入的請求送給每一個 data center 的 coordinator 就行了。.
- 使用的 consistency level 是 ONE 或是 LOCAL_QUORUM,只有在同一個 data center 作為 coordinator 的節點必須要回應 client 的請求,以表示成功。這個方法,可以壁開地理性延遲的衝擊,影響回應 client 的時間。
- Read Requests
- 有兩種讀的類型:直接讀的請求、背景 read repair 請求。
- 要聯繫多個 replicas,由 Client 指定 consistency level 直接請求。而背景 read repair 請求則不需要一個直接的請求。
- Read repair 請求確保所請求的資料列必須一致性的存在於所有的複本。
- 如果 replicas 的節點都聯繫到了,從每個 replica 來的資料列就會在記憶體內做一致性的比較。若有 replica 是不一致的,接著就會發生一些事情:
1) 不管 read_repair_chance 是否有設定,一個在前景的 read repair 會發生在資料上。
2) Coordinator 使用擁最新資料的 replica (基於 timestamp) 並把結果傳遞到 client。
3) 背景裡,coordinator 會比較剩下擁有那筆資料列的 replicas。
4) 如果有不一致的 replica,會更新那筆資料列以反映最新寫入的資料值 (coordinator 會負責發起 "更新過期的 replica" 的操作)。 - 這個程序就是 read repair。我們能夠針對每個列族設定 read repair (使用 read_repair_chance 預設上是開啟的)。
Planning a Cassandra Cluster Deployment
- Hardware recommendations
- Memory
1) 最少要 8GB (最普遍)
2) DataStax 建議大約要 16GB ~ 32GB
3) Java heap size 最大值應該設定 8GB 或是你記憶體的一半 (要注意GC的衝擊)
4) 若是再虛擬環境,至少設定 4GB - Disk
1) 要考慮兩部份: 容量 (capacity) ─ 足夠的空間 和 I/O ─ 讀和寫的吞吐量。2) 大部份工作量,使用不昂貴的 SATA 硬碟以擴充容量,並且透過增加節點的方式,來擴充更多 I/O 的能力 (更多的 RAM)3) Solid-state drives (SSDs) 也是一個有效的替代品。4) 理想上,Cassandra 需要兩顆硬碟,一個提供 commit log,另一個提供 data directories。最低限度,commit log 應該在自己的 partition。5) Commit log disk ─ 這個硬碟不需要太大,但至少可以快到足以接收所有的寫入 (appends; sequential I/O)6) Data disks ─ 使用一個以上的硬碟以確保足夠大到能夠儲存 data volume,而且夠快以滿足資料不在記憶體快取的讀取,並且可以跟上 compaction。
7) RAID ─ 在單一 data directory volume,compaction 會暫時的需要 100% 的硬碟剩餘空間。這意味,當將達到硬碟的 50% 容量時,你應該使用 RAID 或 RAID 10 提供給 data directory volumes 使用。RAID 也能在 I/O 熱點情況下,對使用單一 SSTable 有所幫助8) 擴充檔案系統 ─ 到 ext2 或 ext3, 最小的檔案大小是 2TB ( 64-bit kernel 情況下也是)。ext4 可達 16TB - Number of Nodes
使用大量但資料少的節點比起使用少量但資料量大的節點好,這是因為在 compaction 期間,資料量大的節點會有潛在的瓶頸。 - Network
1) Cassandra 是一個分散式的儲存體,在網路上的負載就是 read/write 請求的處理和複本資料分散的處理。
2) 網路必須要能夠容納你需要的流量。
3) 頻寬至少要 1Gbits。
4) 將以下兩個介面分開設給不同 NIC
- Thrift interface (listen_address )
- RPC server interface (rpc_address)
5) Cassandra 盡可能的會向同一個 rack 的節點抓 replica,選擇同一個 rack 的節點,勝過於遠端的 data center 的節點。 - Node Configuration Options
- Storage Settings
在 Production 環境必須讓 commitlog_directory 與 data_file_directories 不同硬碟。 - Gossip Settings
控制節點如何參與叢集,以及節點如何被其它節點知道,設定有
- cluster_name:要加入這個叢集的節點都要設定一樣。- listen_address:Cassandra 的 IP 位址。
- seeds:這個設定用來啟動 gossip 程序,每個節點都要有同樣的 IP 位址列表。
- storage_port:預設是 7000,在叢集內每一個節點都要一樣,這是用來做內部溝通的 port。
- initial_token:用來決定這個節點所負責的資料範圍。 - Purging Gosssip State On a Node
Gossip 資訊會被 persisted 在本地端用來立即重啟,而不用等待 gossip。 - Partitioner Setting
1) 你必須要確保每一個節點能夠大約有相同量的資料 (負載平衡)。2) 為每個節點,設定 partitioner ,並且設定一個 initial_token 值。3) Datastax 建議對所有叢集的佈署,使用 RandomPartitioner (default)。4) 在單一 data center,你可以分別為叢集內的每個節點計算 token。5) 再多個 data center,token 應該被分開計算,如同每個 data center 的負載平衡都是獨立的。 - Snitch Settings1) Snitch 的責任,是在了解在你的網路拓撲裡的所有節點的位置。
2) 他的影響是複本要放在哪裡,以及請求要怎麼在 replicas 之間繞送。
3) endpoint_snitch 用來設定節點的 snitch,每個節點都應該要有確切的 snitch 設定。
4) 在單一個 data center,使用 SimpleSnitch 通常就夠用。
5) 如果之後你想要延伸到多個 rack 和多個 data center,也是很簡單的,只要在一開始的時候就選一個可以感知 rack 和 data center 的 snitch 就行。 - Configuring the PropertyFileSnitch
1) PropertyFileSnitch 允許你去定義自己想要的 data center 和 rack 名稱。
2) 在 cassandra-topology.properties 檔裡,可以定義網路細節,叢集內的每一個節點應該都要被描述在這個檔案裡,而且這個檔案在每個節點應該都要一樣。 - Choosing Keyspace Replication Options
- DataStax 建議,不管是單一 data center 或是多個 data center,選擇 NetworkTopologyStrategy 就好。它跟 SimpleStrategy 一樣容易使用,而且在未來也可以擴充到多個 data center。
- 設定 NetworkTopologyStrategy 有一些選項,如在每個資料中心的複本數。
即使是單一 data center 仍可使用。 UPDATE KEYSPACE gamecloud WITH placement_strategy =
'org.apache.cassandra.locator.NetworkTopologyStrategy'
AND strategy_options={DC1:3};- Data center 的名稱應該要參考 snitch 的設定。
- 一般的規則是,複本數不應該超過在複本群組內的節點數。然而,增加複本數,之後再增加節點數也是有可能的。當複本因子超過節點數,寫就會被拒絕,但是,讀取只要符合 consistency level 仍然沒有問題。當複本因子超過節點數,寫就會被拒絕,但是,讀取只要符合 consistency level 仍然沒有問題的。
2014-11-09
Effective Java - 覆寫 equals() 時,總是一併覆寫 hashCode()
"覆寫 equals() 時,總是一併覆寫 hashCode()"
無論何時,對同一個物件 (Object) 呼叫 hashCode() 都應該生成同樣的值。
一、Hash code用途:
- 任何兩物件 hash code 不相同,則這兩個物件必定不同。(但,不同物件仍可能產生相同 hash code)。
- 運用在雜湊集合 (HashMap, HashTable, HashSet...etc) 中來判斷是不是有該物件,集合中不允許相同物件的存在。
※ 可參考 HashMap.put(K key, V value) 和 HashMap.get(K key):Object 的實作
二、equals() 用途:
- 判斷兩物件是否相同。
- 若為 true 兩物件 hash code 值必定相同;為 false 兩物件值不一定不同。
- Object 預設的行為是比較兩個物件的記憶體位址。
public boolean equals(Object obj) {
return (this == obj);
}
三、Java.lang.Integer 為例:
java.lang.Integer 物件, 改寫了 equals() 和 hashCode() 方法,如下:
public boolean equals(Object obj) {
if (obj instanceof Integer) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
public int hashCode() {
return value;
}
public static void main(String[] args) {
Integer i = 10;
HashMap<Integer, String> map = new HashMap<Integer, String<>();
map.put(i, "10");
System.out.println(map.get(new Integer(10)));
}
---------------------------------------------------------------------------------------------
輸出結果:
10
結論:
HashMap.get(K key):Object 的實作上,會先找取得 key 的 hashcode (key.hashCode()) 來計算出對映於 hash table 真正的 key,找到物件後,再將兩個物件作 equals() 比較。
所以就算是這行這麼寫:
System.out.println(map.get(new Integer(10)));
經過 override equals() 和 hashCode() 後仍然能取得 "10" 的值。
----------------------------------------------------------------------------------------------
四、寫一個 FakeInteger 來測試看看:
接著,我們做一個對照組,建立一個 FakeInteger 物件,這個物件的作用與 Integer 相同,同樣以這個物件當作 HashMap 的 Key ,一開始我們只覆寫了 equals() 這個方法。
程式如下:
class FakeInteger {
private int value;
public FakeInteger(int i) {
this.value = i;
}
public int getValue() {
return value;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof FakeInteger) {
return value == ((FakeInteger) obj).getValue();
}
return false;
}
}
public static void main(String[] args) {
FakeInteger i = new FakeInteger(10);
HashMap<FakeInteger, String> map = new HashMap<FakeInteger, String<>();
map.put(i, "10");
System.out.println(map.get(new FakeInteger(10)));
}
---------------------------------------------------------------------------------------------
輸出結果:
null
結論:
依照上面的結論來看,兩個物件有不同的 hash code,當然在 hash table 裡找不到。
----------------------------------------------------------------------------------------------
因此,我們再覆寫 hashCode() 這個方法,然後測測看:
class FakeInteger {
private int value;
public FakeInteger(int i) {
this.value = i;
}
public int getValue() {
return value;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof FakeInteger) {
return value == ((FakeInteger) obj).getValue();
}
return false;
}
@Override
public int hashCode() {
return FakeInteger.class.getName().hashCode() + value;
}
}
public static void main(String[] args) {
FakeInteger i = new FakeInteger(10);
HashMap<FakeInteger, String> map = new HashMap<FakeInteger, String<>();
map.put(i, "10");
System.out.println(map.get(new FakeInteger(10)));
}
---------------------------------------------------------------------------------------------
輸出結果:
10
結論:
所以,如果想要讓物件成為雜湊資料結構的 Key,那麼在覆寫 equals() 的同時,就應該要覆寫 hashCode(),這樣才能夠達成你想要的結果。
----------------------------------------------------------------------------------------------
五、覆寫 hashCode() 方法應注意
- 一個好的雜湊函數通常是,為不相同的物件產出不相等的 hash code。
- 一個雜湊函式應該把在一個集合中所有不相等的實例,盡可能均勻分佈至所有可能的雜湊值。
- 底下是一個簡單的步驟,用來達成以上兩點:
- 將一個非 0 的整數儲存到一個變數 result,我們隨機一個 17 整數
- 對於每個物件的 significant field f 做以下的計算:
- 若 field 是 boolean,則計算 (f ? 1 : 0)
- 若 field 是 byte, char, short, int,則計算 (int) f
- 若 field 是 long,則計算 (int) ( f ^ ( f >>> 32))
- 若 field 是 float,則計算 Float.floatToIntBits(f)
- 若 field 是 double,則計算 Double.doubleToLongBits(f),然後再計算 (int) ( f ^ ( f >>> 32))
- 若 field 是 instance,透過遞迴呼叫 class 的 equals() 與 field 比較,那麼 field 的 hashCode 就會被遞迴呼叫到。若需要更複雜的計算,那麼就為該 field 計算出 canonical representation,並呼叫 canonical representation 的 hashCode。若 field 的值是 null,就返回 0 。
- 若 field 是 array,則計算
- 組合計算的結果:result = 37 * result + c
- 返回 result
- 如果一個類別是不可變的,計算 hash code 的成本又很高,那麼可以考慮將 hash code 暫存起來,而不是每次請求都重新計算。
- 如果物件被建立後,很容易被變成 hash key,那麼物件被建立的時候就應該計算出它的 hash code,否則延遲計算它 (第一次被呼叫時)。
- 不要在計算 hash code 時,排除物件關鍵的部分,以提高計算的效能,這樣反而會導致 hash table 不可用。
六、 總結
當類別 需要放在 HashTable、HashMap、HashSet 等等,雜湊結構的集合時才需要覆寫 hashCode(),Hash 集合利用 hash table 來查找對應於 Object Key 的資料,所以會先利用 hash code 來查找 hash table 真正的 key,然後再透過這個 key 找出資料。
所以,當 equal 相等時,hashCode 必須相等,而且如果是 Object 就必須覆寫 hashCode() 和equal() 方法。
一般來說,不管什麼情況下,只要覆寫 equals() 就覆寫 hashCode() 這樣可以省去往後當需要這個物件當 hash key 時的麻煩。
注意第五點,這樣可以讓你在撰寫 hashCode() 方法時,有一個遵循的依歸。
2014-08-28
Java Performance - Tuning the JVM step by step - 2
Book
Addison Wesley - Java Performance (2012)
Chapter 7 - Tuning the JVM step by step
前情提要
上一篇跑下來,我們首先決定了對系統效能的要求,選擇好JVM的佈屬方式、使用的JVM Runtime。
接著我們首先找出 application的 Live Data Size,根據Live Data Size 調整出初期的記憶體設定
然後就是tuning latency,latency主要是GC造成的,所以tuning的重點在於減少GC的頻率或是持續時間,調整YoungGen的大小來控制 minor GC的頻率與持續時間,然後調整OldGen的大小來控制OldGen/FullGC的頻率,並且在這過程中可能會考慮改為使用CMS GC。
Setp 6: Tune Application Throughput
- Throughput對每個application的定義不同,所以請先修改你的 application或測試程式,讓他們有辦法算出得到throughput。
- CMS throughput tuning
- 如果在上一步切換成使用CMS GC 的話
CMS GC調整throughput的重點在於降低GC造成的CPU的使用,可能的做法如下:
- 降低minor GC的頻率 >>> 加大YoungGen
- 降低CMS Cycle或FullGC的頻率 >>> 加大OldGen或是調整eden或survivor 的空間,讓promote rate下降
* 調整過程,注意所有的GC加起來的時間,1%~3%以內算是很不錯的範圍,最糟不要超過10%
- Throughput Garbage Collector Tuning
目標:減少FullGC的次數
Throughput GC預設會啟用 "adaptive sizing" ,這功能讓它可以自動調整YoungGen/OldGen的大小,adaptive sizing可以適用大部份的情況,但若想壓榨出更多的throughput,手動調整有可能可以再進一步得到更多的throughput。
-XX:-UseAdaptiveSizePolicy | - 用來停用adaptive sizing(注意 "-Use"代表停用 - adaptive sizing 預設是啟用的 |
-XX:+PrintAdaptiveSizePolicy | - 設定印出更多的 survived/promoted/overflow 的資訊 |
PrintAdaptiveSizePolicy範例:
[GCAdaptiveSizePolicy::compute_survivor_space_size_and_thresh: survived: 224408984 promoted: 10904856 overflow: false |
tuning的方式:
- 先找到一個steady state時發生的Full GC
- 首先確保OldGen的size 至少要 Live Data Size 的 1.5倍
- 接著觀察minor GC,看有沒有 survivor space overflow,如果有的話就加大survivor space的size。接著我們會再探討survivor space的tuning方式
- Tuning Survivor Spaces
目標:儘可能讓只有需要長期存活的物件被promote到oldGen,減少FullGC
- 觀察survivor log,在兩次FullGC中間的minor GC裡找到survived的最大值
- 根據找到的survived最大值調整survivor space的大小
*注意,survivor space的大小改變可能會間接造成eden的改變;若YoungGen的size改變的話,OldGen也會改變,所以調整時要注意它們之間的連動。理想狀態是讓survivor space變大而不改變其他space,但是這會需要更多的記憶體,假設真的沒辦法的話,另一種可能是調整 TargetSurvivorRatio,這樣可以在不改變其他space的size的情況下,讓survivor space有更多可用的空間
範例:
原設定如下:
-Xmx13g -Xms13g -Xmn4g -XX:SurvivorRatio=6 -XX:+UseParallelOldGC -XX:-UseAdaptiveSizePolicy -XX:PrintGCDateStamps -XX:+PrintGCDetails -XX:-PrintAdaptiveSizePolicy |
heap total 13g YG = 4g OG = 13-4 = 9g
survivor size = 4/(6+2) == 0.5g(each)
eden size = 4-(0.5*2) = 3g
預設的 TargetSurvivorRatio == 50%,也就是說survived size 超過256MB就會promote
若從觀察中發現最大的 survived size 是 473MB
- 每個 survivor space要到 473 / 50% = 946MB ~= 1G 才會夠
- 因為eden的大小不想改變,因此YoungGen總大小要變成 5g才夠: -Xmn5g
- 因為YoungGen總大小變5g了,所以SurvivorRatio要變: -XX:SurvivorRatio=3
(survivor = 5/(3+2) == 1g, eden = 5-(1*2) == 3g)
- OldGen size不想改變: -Xmx14g -Xms14g
新的設定如下:
-Xmx14g -Xms14g -Xmn5g -XX:SurvivorRatio=3 -XX:+UseParallelOldGC -XX:-UseAdaptiveSizePolicy -XX:PrintGCDateStamps -XX:+PrintGCDetails -XX:-PrintAdaptiveSizePolicy |
*若無更多記憶體給heap,另一個方法可以設定 TargetSurvivorRatio == 90%,但請進一步確認不會有突然allocate大量object 的情況,不然可能會造成overflow
- Tuning Parallel GC Threads
-XX:ParallelGCThreads=<n> | - 設定GC使用的thread數量 |
ParallelGCThreads預設值受到 Runtime.availableProcessors() 的影響
假設 P = Runtime.availableProcessors()
ParallelGCThreads = P <=8? P: 5/8p
若同一台機器有超過一個application的話,可能會需要設定 ParallelGCThreads,以防止單一個JVM gc影響到同一台機器的其他程式。
- Deploying on NUMA Systems
若你的JVM運行於支援NUMA的系統上,可以考慮加上 -XX:+UseNUMA 以得到更好的效能
參考
Edge case
某些極端的情況,tuning的方向可能會與前述差很多:
- 如果application常常會一次allocate 大量的object,但是卻只有很少的object會長期存活,這種情況適合將YoungGen調大,大於OldGen
- 有些application 每次promote都很少,那其實OldGen可以不必比Live Data Size大多少
- 有些需要低latency的application,若使用CMS GC,可以考慮將YoungGen調小,讓minor GC更常發生,但是每次minor GC的時間更短,配合大一點的OldGen讓CMS Cycle更少發生,有可能因此latency反而較低
其他額外的Performance相關Command line
- -XX:+AggressiveOpts
AggressiveOpts這個Option可以用來設定讓JVM使用一些新的performance option,這些option可能還不夠穩定,但是可以得到更好的效能,如果你可以接受一些小風險換來效能的增長的話,可以考慮使用這個Option。
其他可能的設定:
-XX:+DoEscapeAnalysis | - JDK6U23 之後預設啟用 - escape指的是若某個Object可以給多個thread存取的話,代表這個 object是 escaped - Escape analysis可以分析object的使用,如果是從生到死都 non-escaped 的object的話,JVM可以對這個Object的使用做更多的最佳化,以得到更好的效能 |
-XX:+UseBiasedLocking | - 在 JDK6預設啟用 - 同一個object會優先被前一個lock此object的thread取用,以降低lock的overhead |
-XX:+UseLargePages | - JVM使用 TLB: translation lookaside buffer 來快取 virtual memory與實際 memory的mapping - UseLargePages 可以支援更大的TLB,降低TLB missing - Linux可能不一定支援此Option [參考] - Windows需要額外的設定才能使用Large Page [參考] |
-XX:LargePageSizeInBytes=<n>[g|m|k] | - 在Solaris下,可以使用此Option來指定page size |
其他
GCViewer: 可用來幫助分析 GC log [GCViewer]
2014-08-24
Java Performance - Tuning the JVM step by step - 1
Book
Addison Wesley - Java Performance (2012)
Chapter 7 - Tuning the JVM step by step
開始前
- 最好可以先解一下第三章的GC或JIT的部份
- 每個部份的調整可以分開來看,但是了解整個process會有更多幫助
方法流程
假設一個軟體的執行分成三個phase:
- initialization: startup, initial some data structures
- steady state: the normal work time
- optional phase: maybe generate report or benchmark
其中我們最感興趣的會是 steady state,大部份的調整是針對steady state。
大至過程如下,過程中若有任何一個phase的目標無法達成,可能會回到上一步或是更早的phase,甚至是重新評估一開始所訂的目標,或是對程式做修改,然後再重覆調整,直到所有的目標都可以滿足。整個過程的圖可以參考圖 7-1 。
- Prioritize application systemic requirement: 主要因為不同的需求可能是互斥的,所以會需要先排出優先順序,後續所有的步驟的目標都是要滿足這個需求,如果無法滿足,可能就要修正需求,或是修改程式。
- choose JVM deployment model: single JVM or multiple JVM
- choose JVM runtime
- tuning application memory footprint
- tuning latency
- tuning throughput
Step 1: Application Systemic Requirements
可能的requirement如下表:
Availability | application持續處於可使用的狀態的能力,一般可以透過將application佈屬至更多的JVM來達成較好的availability,但是如此也會直接地增加management成本。 |
Manageability | 維持與監控application的運作所需的人、物力,一般來說,越少的JVM佈屬,越低的管理成本與需求。 |
Throughput | 每單位時間可處理的工作量,一般情況,throughput的增加可能會伴隨著latency或memory footprint的增加 |
Latency and Responsiveness | 收到request到處理完、回應所需的時間。一般來說,降低latency可能造成throughput的下降,或是增加memory footprint。 |
Memory Footprint | 所需的memory用量,增加可用的memory通常可以改善其他的問題,但是memory受限於實體機器的記憶體總量。 |
Startup Time | 程式啟始所需要的時間。可能跟起動程式時所需要load的class量、初始化的方式、或是所選的JVM runtime有關… |
針對上述requirement根據重要性給予排序,此過程可以邀請較高層級的管理者來參與,以幫助訂出最重要的需求
Step 2: Choose JVM deployment Model
可能的選項: Single JVM、Multiple JVM
Single JVM | Multiple JVM | |
---|---|---|
Availability | 差 | 佳 |
Manageability | 成本較低 | 成本相對高 |
Memory footprint | 較低的usage overhead | 每個單一JVM可用的memory量較少 |
Throughput | - | scale-out 可能可以增加throughput |
Latency | - | 每個單一JVM的heap可能比較小,gc造成的latency有可能會因此降低 |
Step 3: Choose JVM runtime
- client or server (or tiered) runtime
client | 啟動較快,相對較小的記憶體用量,相對較快的JIT(但最佳化的效果相對差) 64bit JVM 沒有client runtime |
server | 啟動較慢,JIT可產生更佳的的結果(但是相對需要更多的時間處理) |
tiered | 號稱結合 client與server runtime的好處,可以考慮用來取代client runtime。 需 J6U25+ or J7 command line: -server-XX:+TieredCompilation |
- 32bit or 64bit runtime
- 64bit JVM 無 client runtime
- 所使用的 3rd party 軟體是否可與64bit JVM相容
- 所使用的 JNI程式是否有64bit版
- 是否需要更多的heap memory?
根據不同的作業系統,與不同的記憶體需求,表7-1整理了可能的決定方式
- 決定初始的GC方式
番外篇: GC Tuning Fundamentals
接下來的Tuning主要是從GC的tuning來調整throughput、latency、memory footprint,加強任何一項都可能使另一項或兩項的效能下降(此時 Step 1 的決定就可以幫助我們做取捨),這邊我們先針對GC Tuning的基本做一些介紹
- Command Line Options and GC Logging
指令 | 效果 |
---|---|
-Xloggc:<file> | 指定輸出的log檔案 此為輸出log所需的最小設定之一 |
-XX:+PrintGCTimeStamps | 印出log的timestamp 格式是JVM啟動之後所經過的時間(秒) 此為輸出log所需的最小設定之一 |
-XX:+PrintGCDetails | 印出GC的統計資訊 此為輸出log所需的最小設定之一 |
-XX:+PrintGCDateStamps | 印出日期、時間格式的timestamp |
-XX:+PrintGCApplicationStoppedTime | 輸出GC safepoint的時間 tuning latency 所需 |
-XX:+PrintGCApplicationConcurrentTime | 輸出兩次GC safepoint經過的時間 tuning latency 所需 |
-XX:+PrintSafepointStatistics | 輸出所有的JVM safepoint事件的資訊,搭配-XX:+PrintGCApplicationStoppedTime可用來濾出GC造成的 safepoint tuning latency 所需 |
- gc log的解讀方式 (接下來的tuning大多需要從GC log了解狀況)
45.152: [GC [PSYoungGen: 295648K->32968K(306432K)] 296198K->33518K(1006848K), 0.1083183 secs] [Times: user=1.83 sys=0.01, real=0.11 secs] |
以上log代表
- 這個GC是在JVM啟動之後過了 45.152 秒後發生的
- 這是個minor GC,其中的PSYoungGen這個gc(這是個Young Generation gc),讓YoungGeneration的space size從 從295648K減少成 32968K,而這個Young Generation的總空間是 306432K (Young Generation的總空間 ==> eden space 加上兩個 survivor space)
- 這個JVM的 Young Generation + Old Generation的heap size從總共有 1006848K,並且GC後,size從 296198K 變成 33518K。
- 這個GC共花了 0.1083183秒
- 這個GC共花了user mode的CPU time約 1.83秒,system mode約0.01秒cpu time,而真實時間約是 0.11秒
- 因為eden會在gc後清空,所以可以知道此GC完之後,survivor space的size是 32968K
- Old generation 的總空間則是 1106848 - 306432 = 700416
- 296198 - 295648 = 550K == GC前Old generation 的size
- 33518-32968 = 550K == GC後Old generation的size
- 由前兩項可知此次的minor GC,並沒有任何object被promote到 Old Generation
- 目標:
- deploy model的影響:
- 相關的JVM command line指令
指令 | 效果 |
---|---|
-Xms | 指定 java heap(YoungGen+OldGen)的初始總量 |
-Xmx | 指定java heap的最大值,java heap不會超過這個值 |
-XX:NewSize=<n>[g|m|k] | 指定java heap的Young Generation的初始大小 ([g|m|k]是單位) |
-XX:MaxNewSize=<n>[g|m|k] | 指定java heap的Young Generation的最大值 ([g|m|k]是單位) |
-Xmn<n>[g|m|k] | 指定java heap的Young Generation的初始值與最大值([g|m|k]是單位) 這個指令等於前兩個指令同時使用並且設定一樣的大小 |
-XX:PermSize=<n>[g|m|k] | 指定java Permanent generation的初始大小 ([g|m|k]是單位) |
-XX:MaxPermSize=<n>[g|m|k] | 指定java Permanent generation的最大值 ([g|m|k]是單位) |
*一般在Production的環境,-Xmx 與-Xms 會設成一樣大,因為調整heap size需要先經過一次full gc
*一般在Production的環境,-XX:PermSize與-XX:MaxPermSize會設成一樣大,因為調整Permanent Generation 的容量需要先經過一次full gc
*若沒有給定以上任何的指令,則JVM會自動所需的值 [See Ch3 HotSpot VM Adaptive Tuning]
- 準備開始tuning
- 開始時可以先把heap size設大一點,用 -Xms -Xmx 來設定heap size,設定成你認為的application的需求大小,或是也可以不設定,交給jvm自動判斷
- 開始時,先用 throughput gc (-XX:+UseParallelOldGC)
- 打開 gc log
- 你必需有相關的工具讓你可以用產生類似Production環境時的的工作,並且持續運作,直到application運行到進入 steady state。
- 如果遇到任何 OutOfMemoryErrors ,找出他的原因(是Old GC fail還是 Perm GC fail?)
- 針對OutOfMemoryError的space做加大的動作
- 重覆以上動作,直到不會再遇到 OutOfMemoryError
2010-11-25T18:51:03.895-0600: [Full GC [PSYoungGen: 279700K->267300K(358400K)] [ParOldGen: 685165K->685165K(685170K)] 964865K->964865K(1043570K) [PSPermGen: 32390K->32390K(65536K)], 0.2499342 secs] [Times: user=0.08 sys=0.00, real=0.05 secs] Exception in thread "main" java.lang.OutOfMemoryError: Java heap space |
- 計算 Live Data Size
- tuning oldGen 的 Live Data Size
- tuning PermGen的 Live Data Size
- 計算出 full GC造成的 latency
- 當程式進入 steady state,經過幾次的 full GC,full GC後的 heap size就會是Live Data Size
- 可以多收集幾次的gc的數據
- 若GC不常發生,可以透過人工誘發的方式,例如 jconsole/ jvisualVM
- command line強制gc的方式: jmap -histo:live <pid>
例子:假設這是app處於steady state後的一次FullGC的結果
[Full GC [PSYoungGen: 273724K->0K(358400K)] [ParOldGen: 426892K->295111K(685170K)] [700616K->295111K(1048570K) [PSPermGen: 32390K->32390K(65536K)], 0.2499342 secs] [Times: user=0.08 sys=0.00 real=0.05 secs] |
- PermGen的 Live Data Size是32390K(約32MB)
- 根據 Live Data Size來調整設定
指令 | 設定 |
---|---|
-Xms -Xmx | 3x~4x OldGen的 Live Data Size |
-XX:PermSize -XX:MaxPermSize | 1.2x~1.5x PermGen 的Live Data Size |
-XX:NewSize -XX:MaxNewSize -Xmn | 約 1x~1.5x OldGen的LiveDataSize |
範例:
[Full GC [PSYoungGen: 273724K->0K(358400K)] [ParOldGen: 426892K->295111K(685170K)] [700616K->295111K(1048570K) [PSPermGen: 32390K->32390K(65536K)], 0.2499342 secs] [Times: user=0.08 sys=0.00 real=0.05 secs] |
- PermGen的Live Data Size是 32390K(32MB)
- 可算出 -Xms -Xmx的建議範圍是 885MB~1180MB,例子中目前是1048MB,在範圍內
- 算出PermGen的建議值是 38MB~48MB,例子中是64MB,雖然超過建議值,但比起1048MB並不算佔用很大,要不要調整都行
- YoungGen的建議大小是 295MB ~ 442MB,目前的設定是 358400K(358MB)
- 從以上計算的結果,可以得到建議的command line:
-Xms1180m -Xmx1180m -Xmn295m -XX:PermSize=48m -XX:MaxPermSize=48m |
- 其他注意事項
- 可以使用 OS提供的工具程式來了解實際的記憶體用量
ex: Solaris 的 "prstat", Linux 的 "top", windows的 "TaskManager"
- 假設調整完發現機器的實體記憶體無法滿足需求時,可能會需要降低一些其他需求,或是修改程式(可能的做法是做 heap profiling,並且修改程式,降低 Live Data Size)
- 這邊只是初始的設定,後續的調整也可能會修改到目前的結果
Step 5: Tune Latency/Responsiveness
目標: tuning 由 GC 所造成的 latency
- 測量
- minor GC的所需時間
- minor GC的頻率
- full GC所需時間
- full GC的頻率
*3跟4可以用來幫間調整OldGen ,也可能會因此換別種GC方式
- 調整Young Generation GC
- 降低YoungGen的大小可以有效減少GC所需的時間,但是會使GC的頻率上升;反之則會增加GC時間,但是降低頻率。
- 根據所訂的目標latency與發生頻率,重複測試、調整,直到符合需求。
- 注意,過程中只調整YoungGen的size,old gen的size必需固定。
範例:
假設目標 minor GC所需時間少於 50ms,發生頻率低於 次/5秒
初始時 -Xmn=2048m,-Xms=6144m
範例1: 若minor GC時間是60ms,可考慮減少10% -Xmn,試試 -Xmn=1843m -Xms=5939m
範例2: 若minor GC頻率是 次/2秒,則考慮增加兩倍的-Xmn,用 -Xmn=4096m -Xms=8192m
*以上兩範例都只調整YoungGen的大小,沒有改變OldGen的大小
*反複調整以得到最後的結果
*注意,調整時YoungGen的size請不要低於 -Xms的 10%,否則gc的頻率會太高
*調整時要注意實體記憶體大小,若heap 調太大可能會導至程式運行時會使用到OS的swap,那會使效能大幅下降
*假設最終無法達到需求,那就必需考慮修改程式、改變佈屬的JVM的數量,或是降低需求
- 調整Old Generation GC
- 若真的沒有FullGC發生,那就用工具來強制執行FullGC
- 根據minor GC的統計數據,可以算出每次minor GC平均增加的OldGen heap size,搭配minor GC的頻率與OldGen的size,我們可以預測FullGC的頻率
- 調整方式類似Young Generation(增加size降低頻率,增加花費時間)
- 注意此時只調整OldGen的size
若在這個階段,經過調整之後可以達成需求,那就可以跳往下個步驟。
若無法達成需求,那可能可以考慮改成使用 Concurrent GC (CMS)。
接下來會介紹CMS GC的tuning
- Concurrent Mark Sweep
- promote Old Generation較慢,所以如果minor GC需要promote OldGen,會需要較長時間
- throughput下降,因為在程式執行中會concurrent對OldGen做gc,但因此fullGC頻率會較低
- 只有在 OldGen不足時,才會做compacting,compacting GC會造成stop-the-world
- compacting 速度很慢
- OldGen的size會調高 20%~30%
- tuning 目標:避免 stop-the-world compacting GC
- Tuning CMS gc
- promote rate
- concurrent OldGen gc rate
- fragmentation of OldGen
- Tuning Promote Rate
一般的Promote流程:
- 新allocate的Object會放進eden
- 若eden滿了會發生 minor GC
- minor GC會清空eden與一個survivor space,將生下來的object放在另一個survivor space
- 若survivor space不足以放下minor GC的結果(稱為Overflow),則會發生promote OldGen
Command Line:
指令 | 解說 |
---|---|
-XX:SurvivorRatio=<ratio> | - survivor size == -Xmn<value>/(-XX:SurvivorRatio=<ratio> + 2) - 所以ratio越大,survivor space越小 - 在其他條件不變的情況下,ratio設定越小,survivor空間越大較不易發生Overflow,但是相對來說,eden越小所以minor GC的機會增加,反而會造成object Age增加的速度變快 - 範例: -Xmn512m -XX:SurvivorRatio=2 則 eden = 256m, Survivor is 128+128 |
-XX:TargetSurvivorRatio=<percent> | - 預設 50 - 在allocate survivor space時的比例,若50%的話,那survivor就只會有一半的size - 範例: -Xmn512m -XX:SurvivorRatio=2 則 eden = 256m, Survivor is 128+128 但因預設 -XX:TargetSurvivorRatio=50,所以實際運行時survivor space只會有 64mb |
-XX:MaxTenuringThreshold=<n> | - n= 0-15 - 用來設定 survivor object的max age - ParNew gc會自己計算 Tenuring Threshold,不管ParNew算出來的Tenuring Threshold多少,object的age都不會超這邊設定的max值 - 當 survivor object的age大於 Tenuring Threshold,就會被promote到 OldGen - 設成0的話,每次minor GC都會發生Promote - Tenuring Threshold太大的話,object會存活太久,Overflow的機率增加 |
-XX:+PrintTenuringDistribution | 設定輸出 survivor space的資訊 |
調整範例:
A.
Desired survivor size 8388608 bytes, new threshold 1 (max 15) - age 1: 16690480 bytes, 16690480 total |
- 若 -XX:TargetSurvivorRatio=50,可推出原設定是 -Xmn512 -XX:SurvivorRatio=30 ((512/32)*0.5 = 8)
- 此次GC的結果,age 1的 object有 16mb,超出了Survivor的大小,因此此次GC會有overflow產生,會有8MB的object被promote到OldGen
- 則若要調整讓此次GC不做Promote的話,survivor space的大小就要有 32m(32*0.5=16),那新的設定會是 -Xmn544 -XX:SurvivorRatio=15 (注意,-Xmn變大是因為要保持eden不變)
- 若heap size不能變的話,那eden就需要變小,那設定會是 Xmn512m -XX:SurvivorRatio=14
B
Desired survivor size 16777216 bytes, new threshold 15 (max 15) - age 1: 6115072 bytes, 6115072 total - age 2: 286672 bytes, 6401744 total - age 3: 115704 bytes, 6517448 total - age 4: 95932 bytes, 6613380 total - age 5: 89465 bytes, 6702845 total - age 6: 88322 bytes, 6791167 total - age 7: 88201 bytes, 6879368 total - age 8: 88176 bytes, 6967544 total |
調整注意事項
- Promote rate主要是調整YoungGen,但還是要注意,若YoungGen調太大,則minor GC的所需時間可能會上升,若因此造成latency太長,那就必需做出取捨,可能要為了latency調低YoungGen的大小,雖然Promote Rate會因此上升
- 若無法得到滿意的結果,那就必需修改程式或是降低需求或佈屬更多的JVM
- tuning concurrent OldGen gc rate
- 讓CMS的OldGen space能維持足夠的空間,防止因為OldGen空間不足而造成的Stop-the-world compacting GC。
- 若collection cycle太慢,則來不及清理OldGen,會造成Stop-the-world compacting GC;反之,若太快,則會造成不必要的效能損失,降低throughput,但通常這總比Stop-the-world好
先產生一次 stop-the-world
這次的tuning從一次stop-the-world gc的發生開始,log如下:
174.445: [GC 174.446: [ParNew: 66408K->66408K(66416K), 0.0000618 secs]174.446: [CMS (concurrent mode failure): 161928K->162118K(175104K), 4.0975124 secs] 228336K->162118K(241520K) |
調整 CMSInitiatingOccupancyFraction
-XX:CMSInitiatingOccupancyFraction=<percent> | - 設定CMS collection cycle的門檻 - 代表當OldGen超過該百分比時,會發生 CMS cycle - 可以先從 Live Data Size的 1.5倍開始調整 |
-XX:+UseCMSInitiatingOccupancyOnly | - 若沒加上此command的話,CMSInitiatingOccupancyFraction 只會發生一次 |
* 若調整時發現算完 CMSInitiatingOccupancyFraction 的空間小於Live Data Size,代表需要增加OldGen的容量
*反複調整 CMSInitiatingOccupancyFraction,直到可以符合需求
範例:
原設定: -Xmx1536m -Xms1536 -Xmn512m
OldGen size == 1024,推算 Live Data Size約 350m
CMSInitiatingOccupancyFraction先從1.5倍Live Data Size開始,所以是350*1.5=525m, 525m/1024m = 51%
結果:-Xmx1536m -Xms1536m -Xmn512m -XX:CMSInitiatingOccupancyFraction=51 -XX:+UseCMSInitiatingOccupancyOnly
範例A:CMS cycle太晚
[ParNew 742993K->648506K(773376K), 0.1688876 secs] [ParNew 753466K->659042K(773376K), 0.1695921 secs] [CMS-initial-mark 661142K(773376K), 0.0861029 secs] [Full GC 645986K->234335K(655360K), 8.9112629 secs] [ParNew 339295K->247490K(773376K), 0.0230993 secs] [ParNew 352450K->259959K(773376K), 0.1933945 secs] |
範例B:CMS cycle太早
[ParNew 390868K->296358K(773376K), 0.1882258 secs] [CMS-initial-mark 298458K(773376K), 0.0847541 secs] [ParNew 401318K->306863K(773376K), 0.1933159 secs] [CMS-concurrent-mark: 0.787/0.981 secs] [CMS-concurrent-preclean: 0.149/0.152 secs] [CMS-concurrent-abortable-preclean: 0.105/0.183 secs] [CMS-remark 374049K(773376K), 0.0353394 secs] [ParNew 407285K->312829K(773376K), 0.1969370 secs] [ParNew 405554K->311100K(773376K), 0.1922082 secs] [ParNew 404913K->310361K(773376K), 0.1909849 secs] [ParNew 406005K->311878K(773376K), 0.2012884 secs] [CMS-concurrent-sweep: 2.179/2.963 secs] [CMS-concurrent-reset: 0.010/0.010 secs] |
* 這個範例中,從 CMS-initial-mark 298458K(773376K) 可知道 CMSInitiatingOccupancyFraction 約是 35%~40% (因為 298458/773376 = 38.5),我們可以將 CMSInitiatingOccupancyFraction設高點(ex: 50%),來減慢CMS Cycle的週期
範例C: 一個正常運作的CMS GC回收了大量object而不必stop-the-world
[ParNew 640710K->546360K(773376K), 0.1839508 secs] [CMS-initial-mark 548460K(773376K), 0.0883685 secs] [ParNew 651320K->556690K(773376K), 0.2052309 secs] [CMS-concurrent-mark: 0.832/1.038 secs] [CMS-concurrent-preclean: 0.146/0.151 secs] [CMS-concurrent-abortable-preclean: 0.181/0.181 secs] [CMS-remark 623877K(773376K), 0.0328863 secs] [ParNew 655656K->561336K(773376K), 0.2088224 secs] [ParNew 648882K->554390K(773376K), 0.2053158 secs] [ParNew 489586K->395012K(773376K), 0.2050494 secs] [ParNew 463096K->368901K(773376K), 0.2137257 secs] [CMS-concurrent-sweep: 4.873/6.745 secs] [CMS-concurrent-reset: 0.010/0.010 secs] [ParNew 445124K->350518K(773376K), 0.1800791 secs] |
- 其他:Explicit Garbage Collections
2010-12-16T23:04:39.452-0600: [Full GC (System) [CMS: 418061K->428608K(16384K), 0.2539726 secs] |
當發生此類事件事,可能需要思考他發生的時機是不是正確的,有沒有必要禁止這類的行為…
與Explicit Garbage Collections有關的Command:
-XX:+ExplicitGCInvokesConcurrent | - 需要JDK6+ - 讓Explicit GC也能Concurrent進行 |
-XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses | - 需要JDK6U4+ - 讓Explicit GC也能Concurrent進行 - 較建議使用 |
-XX:+DisableExplicitGC | - 禁止 Explicit GC |
- Concurrent Permanent Generation Garbage Collection
範例:
[Full GC [CMS: 95401K->287072K(1048576K), 0.5317934 secs] 482111K->287072K(5190464K), [CMS Perm : 65534K->58281K(65536K)], 0.5319635 secs] |
一般情況,CMS不會對PermGen做GC,可以考慮以下Command:
-XX:+CMSClassUnloadingEnabled | 加上此選項讓CMS對PermGen做GC |
-XX:+CMSPermGenSweepingEnabled | 在J6U3以前,要額外加上此參數 |
-XX:CMSInitiatingPermOccupancyFraction=<percent> | 類似CMSInitiatingOccupancyFraction,當PermGen的使用達到此百分比時,就會對PermGen做GC |
-XX:+UseCMSInitiatingOccupancyOnly | 類似UseCMSInitiatingOccupancyOnly,若沒加上此設定的話,CMSInitiatingPermOccupancyFraction只會作用一次 |
- CMS 停止時間的 tuning
- initial mark phase : 通常不會很久
- remark phase : 通常會花較多時間,多執行緒
-XX:ParallelGCThreads=<n> | - 可用來指定 remark phase的thread數 - 若Runtime.availableProcessors() 小於8,則預設值等於 Runtime.availableProcessors() - 若 Runtime.availableProcessors() 大於8,則預設值等於 (5/8)*Runtime.availableProcessors() - 若系統上有其他的app的話,建議將此數值調低於預設值,否則此application在GC時,會造成其他的application變慢 |
-XX:+CMSScavengeBeforeRemark | 強制在 CMS remark之前做一次 minor GC,可減少 CMS remark的工作量,降低所需時間 |
-XX:+ParallelRefProcEnabled | - 使用多執行緒來查找 finalizable object - 這邊的多執行緒只針對尋找,並不用來執行 finalizer - 適合用在可能有大量 reference與 finalizable object的情況 - 可以任何GC搭配使用,而不止能用在CMS |
- 結果
如果沒辦法滿足需求,那可能會需要降低需求、修改程式,或是將程式佈屬到更多的JVM上
訂閱:
文章
(
Atom
)