-
[ClickHouse] 클릭하우스 자바 클라이언트 만들어 데이터 다루기 예제! ClickHouse + Java + Http Client Exam스터디 노트 2023. 11. 21. 22:22
📌 들어가며
이번 게시글에서는 ClickHouse를 도커로 띄우는 방법에 대해 알아보았습니다.
이번에는 이미 도커에 떠있는 ClickHouse에 HTTP Client를 만들어 데이터를 송신하는 샘플 예제를 만들어보도록 하겠습니다.
ClickHouse HttpClient 연결부터 테이블 만들고 INSERT 및 SELECT 쿼리까지 한 번에 확인해보겠습니다!
실습 코드는 아래 깃헙에서 다운로드 받으실 수 있습니다 :D도움이 되셨다면 팔로우와 스타도 부탁드립니다😊
📌 실습하기
프로젝트는 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