Skip to content

Commit

Permalink
Cassandra Compaction Monitoring Tools (#78) (#81)
Browse files Browse the repository at this point in the history
  • Loading branch information
seungh0 authored Jul 25, 2024
1 parent bc56c91 commit 987712d
Show file tree
Hide file tree
Showing 12 changed files with 320 additions and 32 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package kr.hakdang.cassdio.core.domain.cluster.compaction;

import com.datastax.oss.driver.api.core.cql.Row;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Map;
import java.util.TimeZone;
import java.util.UUID;

/**
* CompactionHistory
*
* @author seungh0
* @since 2024-07-25
*/
@ToString
@Getter
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class CompactionHistory {

private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

private UUID id;
private long bytesIn;
private long bytesOut;
private String columnFamilyName;
private LocalDateTime compactedAt;
private String keyspaceName;
private Map<Integer, Long> rowMerged;

@Builder
private CompactionHistory(UUID id, long bytesIn, long bytesOut, String columnFamilyName, LocalDateTime compactedAt, String keyspaceName, Map<Integer, Long> rowMerged) {
this.id = id;
this.bytesIn = bytesIn;
this.bytesOut = bytesOut;
this.columnFamilyName = columnFamilyName;
this.compactedAt = compactedAt;
this.keyspaceName = keyspaceName;
this.rowMerged = rowMerged;
}

public static CompactionHistory from(Row row) {
return CompactionHistory.builder()
.id(row.getUuid("id"))
.bytesIn(row.getLong("bytes_in"))
.bytesOut(row.getLong("bytes_out"))
.compactedAt(LocalDateTime.ofInstant(row.get("compacted_at", Instant.class), TimeZone.getDefault().toZoneId()))
.columnFamilyName(row.getString("columnfamily_name"))
.keyspaceName(row.getString("keyspace_name"))
.rowMerged(row.getMap("rows_merged", Integer.class, Long.class))
.build();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package kr.hakdang.cassdio.core.domain.cluster.compaction;

import com.datastax.oss.driver.api.core.CqlSession;
import com.datastax.oss.driver.api.core.cql.ResultSet;
import com.datastax.oss.driver.api.core.cql.SimpleStatement;
import com.datastax.oss.driver.api.querybuilder.QueryBuilder;
import kr.hakdang.cassdio.core.domain.cluster.BaseClusterCommander;
import kr.hakdang.cassdio.core.domain.cluster.keyspace.CassandraSystemKeyspace;
import kr.hakdang.cassdio.core.domain.cluster.keyspace.table.CassandraSystemTable;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;

import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

/**
* CompactionHistoryListCommander
*
* @author seungh0
* @since 2024-07-25
*/
@Service
public class CompactionHistoryListCommander extends BaseClusterCommander {

public CompactionHistoryListResult listCompactionHistory(CqlSession session, String keyspace) {
SimpleStatement statement = QueryBuilder
.selectFrom(CassandraSystemKeyspace.SYSTEM.getKeyspaceName(), CassandraSystemTable.SYSTEM_COMPACTION_HISTORY.getTableName())
.all()
.build();

ResultSet rs = session.execute(statement);

List<CompactionHistory> historyList = StreamSupport.stream(rs.spliterator(), false)
.map(CompactionHistory::from)
.filter(history -> StringUtils.isBlank(keyspace) || history.getKeyspaceName().equals(keyspace))
.sorted(Comparator.comparing(CompactionHistory::getCompactedAt).reversed())
.collect(Collectors.toList());

return CompactionHistoryListResult.builder()
.histories(historyList)
.build();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package kr.hakdang.cassdio.core.domain.cluster.compaction;

import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;

import java.util.List;

/**
* ClusterTable
*
* @author seungh0
* @since 2024-07-01
*/
@ToString
@Getter
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class CompactionHistoryListResult {

private List<CompactionHistory> histories;

@Builder
public CompactionHistoryListResult(List<CompactionHistory> histories) {
this.histories = histories;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import kr.hakdang.cassdio.core.domain.cluster.keyspace.CassandraSystemKeyspace;
import lombok.Getter;

import static kr.hakdang.cassdio.core.domain.cluster.keyspace.CassandraSystemKeyspace.SYSTEM;
import static kr.hakdang.cassdio.core.domain.cluster.keyspace.CassandraSystemKeyspace.SYSTEM_SCHEMA;

/**
Expand All @@ -18,6 +19,7 @@ public enum CassandraSystemTable {
SYSTEM_SCHEMA_TABLES(SYSTEM_SCHEMA, "tables"),
SYSTEM_SCHEMA_COLUMNS(SYSTEM_SCHEMA, "columns"),
SYSTEM_SCHEMA_TYPES(SYSTEM_SCHEMA, "types"),
SYSTEM_COMPACTION_HISTORY(SYSTEM, "compaction_history"),
;

private final CassandraSystemKeyspace keyspace;
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public enum CassandraSystemTablesColumn {
TYPES_TYPE_NAME(CassandraSystemTable.SYSTEM_SCHEMA_TYPES, "type_name"),
TYPES_FIELD_NAMES(CassandraSystemTable.SYSTEM_SCHEMA_TYPES, "field_names"),
TYPES_FIELD_TYPES(CassandraSystemTable.SYSTEM_SCHEMA_TYPES, "field_types"),
COMPACTION_HISTORY_KEYSPACE_NAME(CassandraSystemTable.SYSTEM_COMPACTION_HISTORY, "keyspace_name"),
;

private final CassandraSystemTable table;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package kr.hakdang.cassdio.web.route.cluster.compaction;

import com.datastax.oss.driver.api.core.CqlSession;
import kr.hakdang.cassdio.core.domain.cluster.ClusterConnector;
import kr.hakdang.cassdio.core.domain.cluster.compaction.CompactionHistoryListCommander;
import kr.hakdang.cassdio.core.domain.cluster.compaction.CompactionHistoryListResult;
import kr.hakdang.cassdio.web.common.dto.response.ApiResponse;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
* CompactionHistoryListApi
*
* @author seungh0
* @since 2024-07-25
*/
@RequestMapping("/api/cassandra/cluster/{clusterId}")
@RestController
public class CompactionHistoryListApi {

private final ClusterConnector clusterConnector;
private final CompactionHistoryListCommander compactionHistoryListCommander;

public CompactionHistoryListApi(ClusterConnector clusterConnector, CompactionHistoryListCommander compactionHistoryListCommander) {
this.clusterConnector = clusterConnector;
this.compactionHistoryListCommander = compactionHistoryListCommander;
}

@GetMapping("/compaction-history")
public ApiResponse<CompactionHistoryListResult> getCompactionHistories(
@PathVariable String clusterId,
@RequestParam(required = false) String keyspace
) {
try (CqlSession session = clusterConnector.makeSession(clusterId)) {
CompactionHistoryListResult result = compactionHistoryListCommander.listCompactionHistory(session, keyspace);
return ApiResponse.ok(result);
}
}

}
4 changes: 4 additions & 0 deletions cassdio-web/src/main/webapp/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import useCassdio from "./commons/hooks/useCassdio";
import {useCassdioState} from "./commons/context/cassdioContext";
import NodesHome from "./pages/cluster/components/nodes-home";
import CassdioToast from "./components/cassdio-toast";
import CompactionHome from "./pages/cluster/components/compaction-home";

function App() {
const {doBootstrap} = useCassdio();
Expand Down Expand Up @@ -50,6 +51,9 @@ function App() {
<Route path="/cluster/:clusterId/nodes" element={
<ClusterMainView><NodesHome/></ClusterMainView>
}></Route>
<Route path="/cluster/:clusterId/keyspace/:keyspaceName/compaction" element={
<ClusterMainView><CompactionHome/></ClusterMainView>
}></Route>
<Route path="/cluster/:clusterId/query" element={
<ClusterMainView><QueryHome/></ClusterMainView>
}></Route>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,13 @@ const ClusterMainView = (props) => {
<i className="bi bi-server"></i> Node List
</Link>
</li>
<li className="nav-item">
<Link
className={`nav-link d-flex align-items-center gap-2 link-body-emphasis text-decoration-none`}
to={`/cluster/${routeParams.clusterId}/client`}>
<i className="bi bi-easel3"></i> Clients (v4+ only)
</Link>
</li>
<li className="nav-item">
<Link
className={`nav-link d-flex align-items-center gap-2 link-body-emphasis text-decoration-none`}
Expand All @@ -67,7 +74,7 @@ const ClusterMainView = (props) => {
{/*</a>*/}
</h6>
<ul className="nav flex-column">
{
{
keyspaceNamesLoading ?
<li className="nav-item text-center">
<div className="spinner-border text-danger" role="status">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import {useParams} from "react-router-dom";
import {useEffect, useState} from "react";
import axios from "axios";
import Spinner from "components/spinner";
import useCassdio from "commons/hooks/useCassdio";
import {ByteFormatUtils} from "../../../utils/byteFormat";

const CompactionHome = () => {

const routeParams = useParams();
const {errorCatch} = useCassdio();

const [loading, setLoading] = useState(false);
const [histories, setHistories] = useState([]);

useEffect(() => {
console.log(routeParams.keyspaceName)
setLoading(true)
axios({
method: "GET",
url: `/api/cassandra/cluster/${routeParams.clusterId}/compaction-history`,
params: {
keyspace: routeParams.keyspaceName
}
}).then((response) => {
setHistories(response.data.result.histories)
}).catch((error) => {
errorCatch(error)
}).finally(() => {
setLoading(false)
});

return () => {
};
}, [routeParams.clusterId, routeParams.keyspaceName]);

return (
<>
<div
className="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h2 className="h2">
Compaction History
</h2>
</div>

<Spinner loading={loading}>
<div className="table-responsive small">
<table className="table table-sm table-hover">
<thead>
<tr className={"table-dark"}>
<th className={"text-center"} scope="col">Keyspace</th>
<th className={"text-center"} scope="col">Table</th>
<th className={"text-center"} scope="col">Compacted At</th>
<th className={"text-center"} scope="col">Bytes In</th>
<th className={"text-center"} scope="col">Bytes Out</th>
<th className={"text-center"} scope="col">Row Merged</th>
</tr>
</thead>
<tbody className="table-group-divider">
{
histories && histories.length > 0 && histories.map((compaction, infoIndex) => {
return (
<tr key={infoIndex}>
<td className={"text-center"}>
{compaction.keyspaceName}
</td>
<td className={"text-center"}>
{compaction.columnFamilyName}
</td>
<td className={"text-center"}>
{compaction.compactedAt}
</td>
<td className={"text-center"}>
{ByteFormatUtils.formatBytes(compaction.bytesIn)}
</td>
<td className={"text-center"}>
{ByteFormatUtils.formatBytes(compaction.bytesOut)}
</td>
<td className={"text-center"}>
{JSON.stringify(compaction.rowMerged)}
</td>
</tr>
)
})
}
</tbody>
</table>
</div>
</Spinner>
</>
)
}

export default CompactionHome;
Loading

0 comments on commit 987712d

Please sign in to comment.