-
[ClickHouse] 클릭하우스 자바 클라이언트 만들어 데이터 다루기 예제! ClickHouse + Java + Http Client Exam스터디 노트 2023. 11. 21. 22:22
📌 들어가며
이번 게시글에서는 ClickHouse를 도커로 띄우는 방법에 대해 알아보았습니다.
[ClickHouse] 클릭하우스를 도커로 띄워보기! ClickHouse with Docker Container
📌 들어가며 어제 깔짝 클릭하우스(ClickHouse)라는 플랫폼에 대해 스터디를 해봤지요. 컬럼 기반 DB로 엄청나게 빠른 데이터 조회 속도와 효율적인 저장구조를 보유하고 있다는 장점이 있었습니
deguruv.tistory.com
이번에는 이미 도커에 떠있는 ClickHouse에 HTTP Client를 만들어 데이터를 송신하는 샘플 예제를 만들어보도록 하겠습니다.
ClickHouse HttpClient 연결부터 테이블 만들고 INSERT 및 SELECT 쿼리까지 한 번에 확인해보겠습니다!
실습 코드는 아래 깃헙에서 다운로드 받으실 수 있습니다 :DGitHub - timulys/clickhouse-demo: Clickhouse with java(spring boot) sample project code
Clickhouse with java(spring boot) sample project code - GitHub - timulys/clickhouse-demo: Clickhouse with java(spring boot) sample project code
github.com
도움이 되셨다면 팔로우와 스타도 부탁드립니다😊
📌 실습하기
프로젝트는 Spring BOOT로 만들어주시면 됩니다. 시작에는 가볍게 web과 lombok 의존성만 추가해주었습니다.
사실 둘 다 필요없긴 합니다만, 그냥..추가해줬습니다. 나중에 Controller를 만들어보고 싶어질 수도 있으니까요😁
Java Version은 17을 사용했고, 부트 버전은 3.1.5를 사용하였습니다.
다음은 pom.xml파일의 일부입니다. ClickHouse의 의존성도 함께 추가합니다.... <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.1.5</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>clickhouse-demo</artifactId> <version>0.0.1-SNAPSHOT</version> <name>clickhouse-demo</name> <description>clickhouse-demo</description> <properties> <java.version>17</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!-- ClickHouse Dependency Injection --> <dependency> <groupId>com.clickhouse</groupId> <artifactId>clickhouse-http-client</artifactId> <version>0.5.0</version> </dependency> <!-- ClickHouse Dependency Injection --> <!-- lz4 Dependency Injection --> <dependency> <groupId>org.lz4</groupId> <artifactId>lz4-java</artifactId> <version>1.8.0</version> </dependency> <dependency> <groupId>org.lz4</groupId> <artifactId>lz4-pure-java</artifactId> <version>1.8.0</version> <scope>runtime</scope> </dependency> <!-- lz4 Dependency Injection --> <!-- Apache Http Client5 Dependency Injection --> <dependency> <groupId>org.apache.httpcomponents.client5</groupId> <artifactId>httpclient5</artifactId> <version>5.2.1</version> </dependency> <dependency> <groupId>org.apache.httpcomponents.core5</groupId> <artifactId>httpcore5</artifactId> <version>5.2.1</version> </dependency> <dependency> <groupId>org.apache.httpcomponents.core5</groupId> <artifactId>httpcore5-h2</artifactId> <version>5.2.1</version> </dependency> <!-- Apache Http Client5 Dependency Injection --> ...
이렇게 ClickHouse의 Http-Client만 의존성으로 추가해주면 프로젝트 구성 준비는 모두 끝났습니다.
그 외 추가적으로 Lz4와 Apache HttpClient도 함께 필요하니, 해당 의존성도 함께 추가해주도록 하겠습니다.
진짜 준비가 끝났습니다.
이제 개발을 해볼까요? 우선 ClickHouse 설정을 위해 application.yml파일을 확인해봅니다.spring: datasource: clickhouse: url: localhost port: 8123 database: default username: default password: server: port: 9999
데이터베이스는 default로 선언하였습니다. server.port는 제가 떠있는 프로젝트들이 많아서 9999로 강제로 변경하였습니다.
그리고 @Configuration 클래스를 만들어줍니다.
여기서 @Value 어노테이션을 통해 application.yml파일 내 String 데이터를 불러올 것입니다.
그리고 ClickHouse에 Request를 보낼 수 있는 Client를 만들어주는 Bean을 등록해 그것을 통해 ClickHouse와 통신을 할 것입니다.ClickHouse의 Client는 항상 자신이 바라보고 쿼리해야하는 server정보를 보유하고 있어야 합니다.
그래서 Server Node를 만들어준 뒤 Client를 return하기 전 read로 server정보를 명시해줍니다.
이렇게 하면 Client로 Server에 질의할 준비는 모두 끝났습니다.
정말 간단하죠?
서비스를 한 번 구성해보겠습니다.
우선 Configuration을 살펴볼까요? ClickHouseServer 클래스입니다.@Setter @Configuration public class ClickHouseServer { @Value("${spring.datasource.clickhouse.url}") public String url; @Value("${spring.datasource.clickhouse.port}") public Integer port; @Value("${spring.datasource.clickhouse.database}") public String database; @Value("${spring.datasource.clickhouse.username}") public String username; @Value("${spring.datasource.clickhouse.password}") public String password; @Bean public ClickHouseRequest clickHouseClient() { ClickHouseNode server = ClickHouseNode.builder() .host(System.getProperty("chHost", url)) .port(ClickHouseProtocol.HTTP, Integer.getInteger("chPort", port)) .database(database).credentials(ClickHouseCredentials.fromUserAndPassword( System.getProperty("chUser", username), System.getProperty("chPassword", password))) .build(); return ClickHouseClient.newInstance(server.getProtocol()).read(server); } }
url과 port, database, username, password가 properties로 사용되며 이를 가지고 ClickHouseNode Server를 만들어줍니다.
여기서 ClickHouseClient의 newInstance에 .read() 를 호출해주었는데, Service에서 사용하는 대부분의 질의 함수가 ClickHouseRequest의 것이기 때문이지요.
실질적은 테이블 삭제 및 생성과 Insert, Select를 하는 Service를 보겠습니다.@Service @RequiredArgsConstructor public class ClickHouseService { // Request Client Audotwired private final ClickHouseRequest client; // Drop table and create table public void dropAndCreateTable(String table) { try { ClickHouseRequest<?> request = client; request.query("drop table if exists " + table).execute().get(); request.query("create table " + table + "(a String, b Nullable(String)) engine=MergeTree() order by a") .execute().get(); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } // Data insert public long insert(String table) { try { ClickHouseRequest.Mutation request = client.write().table(table).format(ClickHouseFormat.RowBinary); ClickHouseConfig config = request.getConfig(); CompletableFuture<ClickHouseResponse> future; try (ClickHousePipedOutputStream stream = ClickHouseDataStreamFactory.getInstance().createPipedOutputStream(config, (Runnable) null)) { future = request.data(stream.getInputStream()).execute(); // 10000 Rows 데이터 insert for (int i = 0; i < 10000; i++) { BinaryStreamUtils.writeString(stream, String.valueOf(i % 16)); BinaryStreamUtils.writeNonNull(stream); BinaryStreamUtils.writeString(stream, UUID.randomUUID().toString()); } } // CompletableFuture 를 활용한 비동기 콜백 try (ClickHouseResponse response = future.get()) { ClickHouseResponseSummary summary = response.getSummary(); return summary.getWrittenRows(); } } catch (InterruptedException e) { throw new RuntimeException(e); } catch (ExecutionException | IOException e) { throw new RuntimeException(e); } } // Data select public int query(String table) { try (ClickHouseResponse response = (ClickHouseResponse) client.format(ClickHouseFormat.RowBinaryWithNamesAndTypes) .query("select * from " + table).execute().get()) { int count = 0; for (ClickHouseRecord record : response.records()) { count++; } return count; } catch (InterruptedException e) { throw new RuntimeException(e); } catch (ExecutionException e) { throw new RuntimeException(e); } } }
Table을 만들고 데이터를 삽입하고 Count를 조회해보았습니다.
결과는 Row Count = 10000 이 출력됩니다.
실제 ClickHouse에 들어가서 보도록 할까요?> docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 71e8a4902ea6 yandex/clickhouse-server "/entrypoint.sh" 4 minutes ago Up 4 minutes 9000/tcp, 0.0.0.0:8123->8123/tcp, 9009/tcp clickhouse-server f6f1ef3ee13c yandex/clickhouse-client "/bin/sleep infinity" 4 minutes ago Up 4 minutes clickhouse-client > docker exec -it clickhouse-server /bin/bash root@71e8a4902ea6:/# clickhouse-client ClickHouse client version 22.1.3.7 (official build). Connecting to localhost:9000 as user default. Connected to ClickHouse server version 22.1.3 revision 54455. 71e8a4902ea6 :) show tables; SHOW TABLES Query id: 1f8c6f0e-0be6-4fa2-9290-770ded9a6469 ┌─name───────┐ │ demo_table │ └────────────┘ 1 rows in set. Elapsed: 0.001 sec. 71e8a4902ea6 :) select * from demo_table; ... │ 1 │ 29fe507f-2f04-498d-849a-5063d25d369a │ │ 1 │ b2457860-ba1b-499d-8271-25b71fee789f │ │ 1 │ 64c13cf6-d715-4303-ad2f-0cb192a7c290 │ │ 1 │ 0fd6db66-de5e-4d7a-aa6a-3e22c29928e1 │ │ 1 │ d3f6e183-fa27-4123-b2e5-6758c64bc4f0 │ │ 1 │ 2dd676c3-71f7-4ddb-a4ef-ff40198a1acc │ │ 1 │ 37618353-7160-42d5-97c7-c49ec713883c │ │ 1 │ 7140742b-f11b-4673-b009-61e159db6569 │ ... Showed first 10000. 10000 rows in set. Elapsed: 0.028 sec. Processed 10.00 thousand rows, 563.75 KB (362.52 thousand rows/s., 20.44 MB/s.)
실제로 ClickHouse에 쿼리를 해보시려면, ClickHouse Server 컨테이너에 접속하시어 clickhouse-client를 입력하시면 client로 접속할 수가 있습니다.
그럼 거기서 show tables로 table의 목록을 조회하고 select * from demo_table로 실제 입력된 데이터를 볼 수 있지요.
ANSI Query 호환이 가능하기에 Query를 쉽게 작성할 수 있습니다.
가볍게 테스트하기 위해 만든 main 메소드입니다.@SpringBootApplication public class ClickhouseDemoApplication { public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(ClickhouseDemoApplication.class, args); ClickHouseService service = context.getBean(ClickHouseService.class); String table = "demo_table"; // 기존 테이블이 있다면 테이블을 삭제 후 다시 생성합니다. service.dropAndCreateTable(table); // 테이블에 데이터 10000 Row Insert service.insert(table); System.out.println("Row Count = " + service.query(table)); } }
main 메소드에서 Service Bean을 활용하기 위해서 application context에서 직접 bean을 꺼내서 사용합니다.
📌 마치며
ClickHouse와 Spring Boot with Java에 대한 데모 프로젝트를 완성하였습니다.
가볍게 만들어보았는데 확실히 성능이 빠르기는 하네요.
세부적으로 추후에 사용할 때 더 디테일하게 파악해보긴 하겠지만, 구현 난이도는 평이한 수준인 것 같습니👍'스터디 노트' 카테고리의 다른 글
모놀리식 아키텍쳐 vs 마이크로 서비스 아키텍쳐 (Monolithic Architecture vs Micro Service Architecture) (1) 2023.11.23 [Java] CompletableFuture 에 대하여 (0) 2023.11.22 [ClickHouse] 클릭하우스를 도커로 띄워보기! ClickHouse with Docker Container (2) 2023.11.21 [ClickHouse] 클릭하우스란 무엇일까? (0) 2023.11.20 [SQL] JOIN이란? JOIN의 종류(INNER, LEFT, RIGHT, FULL OUTER JOIN) (1) 2023.11.17