반응형
들어가며
Sharding이란?
- 한 RDBMS에 데이터가 많아질 경우 용량 초과 이슈가 발생할 수 있고, 데이터 조회 속도가 느려질 수 있다.
- 이를 해결하기 위해 데이터를 여러 RDBMS에 쪼개 관리하는 파티셔닝 기술을 샤딩이라고 한다.
sharding-jdbc-core
설명
- com.dangdang:sharding-jdbc-core 라이브러리를 활용하여 샤딩 처리를 수행하는 예제
- MySQL과 JPA를 활용하므로 필요하면 아래 링크 참고
테이블 추가
DROP TABLE IF EXISTS User;
CREATE TABLE User
(
userSeq BIGINT UNSIGNED NOT NULL,
name VARCHAR(200),
type CHAR(10),
PRIMARY KEY (userSeq)
);
User
@AllArgsConstructor
@NoArgsConstructor
@Data
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long userSeq;
private String name;
@Enumerated(EnumType.STRING)
private Type type;
public enum Type {
KAKAO, GMAIL, BASIC
}
}
UserRepository
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
}
build.gradle
dependencies {
...
implementation 'com.dangdang:sharding-jdbc-core:1.5.4.1'
}
ShardDBConfig
@Configuration
public class ShardDBConfig {
@Bean
public DataSource shardDataSource() throws SQLException {
Map<String, DataSource> dataSources = Map.of(
"shard-0", createDataSource("jdbc:mysql://localhost:13306/test", "ubuntu", "123456"),
"shard-1", createDataSource("jdbc:mysql://localhost:23306/test", "ubuntu", "123456")
);
DataSourceRule dataSourceRule = new DataSourceRule(dataSources);
SingleKeyDatabaseShardingAlgorithm<?> shardingAlgorithm = new CustomShardingAlgorithm<>(dataSources.size());
ShardingRule shardingRule = new ShardingRule.ShardingRuleBuilder()
.dataSourceRule(dataSourceRule)
.tableRules(Arrays.asList(
createTableRule("User", "userSeq", "type", dataSourceRule, shardingAlgorithm)
))
.build();
return new ShardingDataSource(shardingRule);
}
private DataSource createDataSource(String url, String username, String password) {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setDriverClassName(com.mysql.jdbc.Driver.class.getName());
dataSource.setJdbcUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
private TableRule createTableRule(String tableName, String primaryKeyName, String shardingKeyName, DataSourceRule dataSourceRule, SingleKeyDatabaseShardingAlgorithm<?> shardingAlgorithm) {
return TableRule.builder(tableName)
.generateKeyColumn(primaryKeyName)
.databaseShardingStrategy(new DatabaseShardingStrategy(shardingKeyName, shardingAlgorithm))
.dataSourceRule(dataSourceRule)
.build();
}
@Slf4j
@RequiredArgsConstructor
private static class CustomShardingAlgorithm<T extends Comparable<?>> implements SingleKeyDatabaseShardingAlgorithm<T> {
private final int shardSize;
@Override
public String doEqualSharding(Collection<String> shardNames, ShardingValue<T> shardingValue) {
String shardName = "shard-" + Math.abs(crc32(String.valueOf(shardingValue.getValue()))) % shardSize;
log.info("[DO_SHARDING] shardName : {}, shardingValue : {}", shardName, shardingValue);
if (shardNames.contains(shardName)) {
return shardName;
}
throw new UnsupportedOperationException();
}
@Override
public Collection<String> doInSharding(Collection<String> shardNames, ShardingValue<T> shardingValue) {
return null;
}
@Override
public Collection<String> doBetweenSharding(Collection<String> shardNames, ShardingValue<T> shardingValue) {
return null;
}
// 간단하게 value.hashCode()를 사용할 수도 있지만
// 환경에 따라 hashCode() 값을 동일하게 반환해주지 않을 수 있으므로 샤딩에서 사용하지 않을 것을 권장
// 따라서 여기서는 CRC32를 활용하여 hash 계산
private long crc32(String value) {
CRC32 crc32 = new CRC32();
crc32.update(value.getBytes());
return crc32.getValue();
}
}
}
Test
@Rollback(false)
@SpringBootTest
public class ShardingTest {
@Autowired
private UserRepository userRepository;
@Transactional
@Test
public void insertUser() {
IntStream.range(0, 100).forEach(i -> userRepository.save(new User(null, "john-" + i, User.Type.values()[i % User.Type.values().length])));
}
@Transactional(readOnly = true)
@Test
public void selectUsers() {
System.out.println(userRepository.findAll(PageRequest.of(0, 5)).getContent());
System.out.println(userRepository.findAll(PageRequest.of(5, 5)).getContent());
}
}
ShardingSphere
설명
- ShardingSphere란 RDBMS의 샤딩 처리를 지원하는 아파치 오픈소스 프로젝트를 말한다.
- 위의 sharding-jdbc-core 예제와 거의 동일하게 진행하고, 디펜던시와 설정만 조금 다르므로 아래를 참고한다.
build.gradle
dependencies {
...
implementation 'org.apache.shardingsphere:sharding-jdbc-core:4.1.1'
}
ShardDBConfig
@Configuration
public class ShardDBConfig {
@Bean
public DataSource shardDataSource() throws SQLException {
Map<String, DataSource> dataSources = Map.of(
"shard-0", createDataSource("jdbc:mysql://localhost:13306/test", "ubuntu", "123456"),
"shard-1", createDataSource("jdbc:mysql://localhost:23306/test", "ubuntu", "123456")
);
PreciseShardingAlgorithm<?> shardingAlgorithm = new CustomShardingAlgorithm(dataSources.size());
ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration();
shardingRuleConfig.setTableRuleConfigs(Arrays.asList(
createTableRuleConfig("User", "userSeq", "type", shardingAlgorithm)
));
return ShardingDataSourceFactory.createDataSource(dataSources, shardingRuleConfig, new Properties());
}
private DataSource createDataSource(String url, String username, String password) {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setDriverClassName(com.mysql.jdbc.Driver.class.getName());
dataSource.setJdbcUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
private TableRuleConfiguration createTableRuleConfig(String tableName, String primaryKeyName, String shardingKeyName, PreciseShardingAlgorithm shardingAlgorithm) {
Properties properties = new Properties();
properties.setProperty("worker.id", "123"); // 프로세스별로 다른 ID값으로 지정
TableRuleConfiguration tableRuleConfig = new TableRuleConfiguration(tableName);
tableRuleConfig.setKeyGeneratorConfig(new KeyGeneratorConfiguration("SNOWFLAKE", primaryKeyName, properties));
tableRuleConfig.setDatabaseShardingStrategyConfig(new StandardShardingStrategyConfiguration(shardingKeyName, shardingAlgorithm));
return tableRuleConfig;
}
@Slf4j
@RequiredArgsConstructor
public static class CustomShardingAlgorithm<T extends Comparable<?>> implements PreciseShardingAlgorithm<T> {
private final int shardSize;
@Override
public String doSharding(final Collection<String> shardNames, final PreciseShardingValue<T> shardingValue) {
String shardName = "shard-" + Math.abs(crc32(String.valueOf(shardingValue.getValue()))) % shardSize;
log.info("[DO_SHARDING] shardName : {}, shardingValue : {}", shardName, shardingValue);
if (shardNames.contains(shardName)) {
return shardName;
}
throw new UnsupportedOperationException();
}
private long crc32(String value) {
CRC32 crc32 = new CRC32();
crc32.update(value.getBytes());
return crc32.getValue();
}
}
}
참고
반응형
'Development > Spring' 카테고리의 다른 글
[Spring] @ConfigurationProperties (0) | 2021.08.02 |
---|---|
[Spring] @Value (0) | 2021.08.02 |
[Spring] Thymeleaf (0) | 2021.06.26 |
[Spring] Model Mapping (0) | 2021.06.13 |
[Spring] Spring Security (0) | 2021.05.29 |