Skip to content

Commit 00d1b01

Browse files
Programming Challenge redislabs-training#2 and redislabs-training#3 refactoring
1 parent cf0b231 commit 00d1b01

File tree

2 files changed

+168
-182
lines changed

2 files changed

+168
-182
lines changed

src/main/java/com/redislabs/university/RU102J/dao/MetricDaoRedisZsetImpl.java

Lines changed: 158 additions & 171 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,18 @@
33
import com.redislabs.university.RU102J.api.Measurement;
44
import com.redislabs.university.RU102J.api.MeterReading;
55
import com.redislabs.university.RU102J.api.MetricUnit;
6-
import redis.clients.jedis.Jedis;
7-
import redis.clients.jedis.JedisPool;
8-
import redis.clients.jedis.Pipeline;
9-
import redis.clients.jedis.Tuple;
106

117
import java.text.DecimalFormat;
128
import java.time.ZoneOffset;
139
import java.time.ZonedDateTime;
14-
import java.util.*;
10+
import java.util.ArrayList;
11+
import java.util.Collections;
12+
import java.util.List;
13+
import java.util.Set;
14+
15+
import redis.clients.jedis.Jedis;
16+
import redis.clients.jedis.JedisPool;
17+
import redis.clients.jedis.Tuple;
1518

1619
/**
1720
* Retain metrics using Redis sorted sets.
@@ -22,170 +25,154 @@
2225
*
2326
*/
2427
public class MetricDaoRedisZsetImpl implements MetricDao {
25-
static private final Integer MAX_METRIC_RETENTION_DAYS = 30;
26-
static private final Integer MAX_DAYS_TO_RETURN = 7;
27-
static private final Integer METRICS_PER_DAY = 60 * 24;
28-
static private final Integer METRIC_EXPIRATION_SECONDS =
29-
60 * 60 * 24 * MAX_METRIC_RETENTION_DAYS + 1;
30-
private final JedisPool jedisPool;
31-
32-
public MetricDaoRedisZsetImpl(JedisPool jedisPool) {
33-
this.jedisPool = jedisPool;
34-
}
35-
36-
@Override
37-
public void insert(MeterReading reading) {
38-
try (Jedis jedis = jedisPool.getResource()) {
39-
insertMetric(jedis, reading.getSiteId(), reading.getWhGenerated(),
40-
MetricUnit.WHGenerated, reading.getDateTime());
41-
insertMetric(jedis, reading.getSiteId(), reading.getWhUsed(),
42-
MetricUnit.WHUsed, reading.getDateTime());
43-
insertMetric(jedis, reading.getSiteId(), reading.getTempC(),
44-
MetricUnit.TemperatureCelsius, reading.getDateTime());
45-
}
46-
}
47-
48-
// Challenge #2
49-
private void insertMetric(Jedis jedis, long siteId, double value, MetricUnit unit,
50-
ZonedDateTime dateTime) {
51-
String metricKey = RedisSchema.getDayMetricKey(siteId, unit, dateTime);
52-
Integer minuteOfDay = getMinuteOfDay(dateTime);
53-
54-
jedis.zadd( metricKey, minuteOfDay, String.format( "%s:%s", value, minuteOfDay ) );
55-
}
56-
57-
/**
58-
* Return the N most-recent minute-level measurements starting at the
59-
* provided day.
60-
* TODO: Watch out for large data structures when sharding
61-
* TODO: Or implement your own expiry with zremrange
62-
*/
63-
@Override
64-
public List<Measurement> getRecent(Long siteId, MetricUnit unit,
65-
ZonedDateTime time, Integer limit) {
66-
if (limit > METRICS_PER_DAY * MAX_METRIC_RETENTION_DAYS) {
67-
throw new IllegalArgumentException("Cannot request more than two weeks" +
68-
"of minute-level data");
69-
}
70-
71-
List<Measurement> measurements = new ArrayList<>();
72-
ZonedDateTime currentDate = time;
73-
Integer count = limit;
74-
Integer iterations = 0;
75-
76-
// This loop extracts the elements of successive
77-
// sorted sets until it reaches the requested limit.
78-
do {
79-
List<Measurement> ms = getMeasurementsForDate(siteId, currentDate,
80-
unit, count);
81-
measurements.addAll(0, ms);
82-
count -= ms.size();
83-
currentDate = currentDate.minusDays(1);
84-
iterations += 1;
85-
} while (count > 0 && iterations < MAX_DAYS_TO_RETURN);
86-
87-
return measurements;
88-
}
89-
90-
/**
91-
* Return up to `count` elements from the sorted set corresponding to
92-
* the siteId, date, and metric unit specified here.
93-
*/
94-
private List<Measurement> getMeasurementsForDate(Long siteId,
95-
ZonedDateTime date,
96-
MetricUnit unit,
97-
Integer count) {
98-
// A list of Measurement objects to return.
99-
List<Measurement> measurements = new ArrayList<>();
100-
101-
try (Jedis jedis = jedisPool.getResource()) {
102-
// Get the metric key for the day implied by the date.
103-
// metric:[unit-name]:[year-month-day]:[site-id]
104-
// e.g.: metrics:whU:2020-01-01:1
105-
String metricKey = RedisSchema.getDayMetricKey(siteId, unit, date);
106-
107-
// Return a reverse range so that we're always consuming from the end
108-
// of the sorted set.
109-
Set<Tuple> metrics = jedis.zrevrangeWithScores(metricKey, 0, count - 1);
110-
for (Tuple minuteValue : metrics) {
111-
// Elements of the set are of the form [measurement]:[minute]
112-
// The MeasurementMinute class abstracts this for us.
113-
MeasurementMinute mm = MeasurementMinute.fromZSetValue(
114-
minuteValue.getElement());
115-
116-
// Derive the dateTime for the measurement using the date and
117-
// the minute of the day.
118-
ZonedDateTime dateTime = getDateFromDayMinute(date,
119-
mm.getMinuteOfDay());
120-
121-
// Add a new measurement to the list of measurements.
122-
measurements.add(new Measurement(siteId, unit, dateTime,
123-
mm.getMeasurement()));
124-
}
125-
}
126-
127-
Collections.reverse(measurements);
128-
return measurements;
129-
}
130-
131-
private ZonedDateTime getDateFromDayMinute(ZonedDateTime dateTime,
132-
Integer dayMinute) {
133-
int minute = dayMinute % 60;
134-
int hour = dayMinute / 60;
135-
return dateTime.withHour(hour).withMinute(minute).
136-
withZoneSameInstant(ZoneOffset.UTC);
137-
}
138-
139-
// Return the minute of the day. For example:
140-
// 01:12 is the 72nd minute of the day
141-
// 5:00 is the 300th minute of the day
142-
public Integer getMinuteOfDay(ZonedDateTime dateTime) {
143-
int hour = dateTime.getHour();
144-
int minute = dateTime.getMinute();
145-
return hour * 60 + minute;
146-
}
147-
148-
/**
149-
* Utility class to convert between our sorted set members and their
150-
* constituent measurement and minute values.
151-
*
152-
* Also rounds decimals before storing them.
153-
*/
154-
public static class MeasurementMinute {
155-
private final Double measurement;
156-
private final Integer minuteOfDay;
157-
private final DecimalFormat decimalFormat;
158-
159-
// For a sorted set value of "22.0:1", this classes provides
160-
// the measurement value of 22.0 and the minuteOfDay value of 1.
161-
public static MeasurementMinute fromZSetValue(String zSetValue) {
162-
String[] parts = zSetValue.split(":");
163-
if (parts.length == 2) {
164-
return new MeasurementMinute(Double.valueOf(parts[0]),
165-
Integer.valueOf(parts[1]));
166-
} else {
167-
throw new IllegalArgumentException("Cannot convert zSetValue " +
168-
zSetValue + " into MeasurementMinute");
169-
}
170-
}
171-
172-
public MeasurementMinute(Double measurement, Integer minuteOfDay) {
173-
this.measurement = measurement;
174-
this.minuteOfDay = minuteOfDay;
175-
this.decimalFormat = new DecimalFormat("#.##");
176-
}
177-
178-
public Integer getMinuteOfDay() {
179-
return minuteOfDay;
180-
}
181-
182-
public Double getMeasurement() {
183-
return measurement;
184-
}
185-
186-
public String toString() {
187-
return decimalFormat.format(measurement) + ':' +
188-
String.valueOf(minuteOfDay);
189-
}
190-
}
28+
29+
static private final Integer MAX_METRIC_RETENTION_DAYS = 30;
30+
static private final Integer MAX_DAYS_TO_RETURN = 7;
31+
static private final Integer METRICS_PER_DAY = 60 * 24;
32+
static private final Integer METRIC_EXPIRATION_SECONDS = 60 * 60 * 24 * MAX_METRIC_RETENTION_DAYS + 1;
33+
private final JedisPool jedisPool;
34+
35+
public MetricDaoRedisZsetImpl( JedisPool jedisPool ) {
36+
this.jedisPool = jedisPool;
37+
}
38+
39+
@Override
40+
public void insert( MeterReading reading ) {
41+
try ( Jedis jedis = jedisPool.getResource() ) {
42+
insertMetric( jedis, reading.getSiteId(), reading.getWhGenerated(), MetricUnit.WHGenerated, reading.getDateTime() );
43+
insertMetric( jedis, reading.getSiteId(), reading.getWhUsed(), MetricUnit.WHUsed, reading.getDateTime() );
44+
insertMetric( jedis, reading.getSiteId(), reading.getTempC(), MetricUnit.TemperatureCelsius, reading.getDateTime() );
45+
}
46+
}
47+
48+
// Challenge #2
49+
private void insertMetric( Jedis jedis, long siteId, double value, MetricUnit unit, ZonedDateTime dateTime ) {
50+
String metricKey = RedisSchema.getDayMetricKey( siteId, unit, dateTime );
51+
Integer minuteOfDay = getMinuteOfDay( dateTime );
52+
53+
jedis.zadd( metricKey, minuteOfDay, new MeasurementMinute( value, minuteOfDay ).toString() );
54+
jedis.expire( metricKey, METRIC_EXPIRATION_SECONDS );
55+
}
56+
57+
/**
58+
* Return the N most-recent minute-level measurements starting at the
59+
* provided day.
60+
* TODO: Watch out for large data structures when sharding
61+
* TODO: Or implement your own expiry with zremrange
62+
*/
63+
@Override
64+
public List<Measurement> getRecent( Long siteId, MetricUnit unit, ZonedDateTime time, Integer limit ) {
65+
if ( limit > METRICS_PER_DAY * MAX_METRIC_RETENTION_DAYS ) {
66+
throw new IllegalArgumentException( "Cannot request more than two weeks" + "of minute-level data" );
67+
}
68+
69+
List<Measurement> measurements = new ArrayList<>();
70+
ZonedDateTime currentDate = time;
71+
Integer count = limit;
72+
Integer iterations = 0;
73+
74+
// This loop extracts the elements of successive
75+
// sorted sets until it reaches the requested limit.
76+
do {
77+
List<Measurement> ms = getMeasurementsForDate( siteId, currentDate, unit, count );
78+
measurements.addAll( 0, ms );
79+
count -= ms.size();
80+
currentDate = currentDate.minusDays( 1 );
81+
iterations += 1;
82+
} while ( count > 0 && iterations < MAX_DAYS_TO_RETURN );
83+
84+
return measurements;
85+
}
86+
87+
/**
88+
* Return up to `count` elements from the sorted set corresponding to
89+
* the siteId, date, and metric unit specified here.
90+
*/
91+
private List<Measurement> getMeasurementsForDate( Long siteId, ZonedDateTime date, MetricUnit unit, Integer count ) {
92+
// A list of Measurement objects to return.
93+
List<Measurement> measurements = new ArrayList<>();
94+
95+
try ( Jedis jedis = jedisPool.getResource() ) {
96+
// Get the metric key for the day implied by the date.
97+
// metric:[unit-name]:[year-month-day]:[site-id]
98+
// e.g.: metrics:whU:2020-01-01:1
99+
String metricKey = RedisSchema.getDayMetricKey( siteId, unit, date );
100+
101+
// Return a reverse range so that we're always consuming from the end
102+
// of the sorted set.
103+
Set<Tuple> metrics = jedis.zrevrangeWithScores( metricKey, 0, count - 1 );
104+
for ( Tuple minuteValue : metrics ) {
105+
// Elements of the set are of the form [measurement]:[minute]
106+
// The MeasurementMinute class abstracts this for us.
107+
MeasurementMinute mm = MeasurementMinute.fromZSetValue( minuteValue.getElement() );
108+
109+
// Derive the dateTime for the measurement using the date and
110+
// the minute of the day.
111+
ZonedDateTime dateTime = getDateFromDayMinute( date, mm.getMinuteOfDay() );
112+
113+
// Add a new measurement to the list of measurements.
114+
measurements.add( new Measurement( siteId, unit, dateTime, mm.getMeasurement() ) );
115+
}
116+
}
117+
118+
Collections.reverse( measurements );
119+
return measurements;
120+
}
121+
122+
private ZonedDateTime getDateFromDayMinute( ZonedDateTime dateTime, Integer dayMinute ) {
123+
int minute = dayMinute % 60;
124+
int hour = dayMinute / 60;
125+
return dateTime.withHour( hour ).withMinute( minute ).withZoneSameInstant( ZoneOffset.UTC );
126+
}
127+
128+
// Return the minute of the day. For example:
129+
// 01:12 is the 72nd minute of the day
130+
// 5:00 is the 300th minute of the day
131+
public Integer getMinuteOfDay( ZonedDateTime dateTime ) {
132+
int hour = dateTime.getHour();
133+
int minute = dateTime.getMinute();
134+
return hour * 60 + minute;
135+
}
136+
137+
/**
138+
* Utility class to convert between our sorted set members and their
139+
* constituent measurement and minute values.
140+
*
141+
* Also rounds decimals before storing them.
142+
*/
143+
public static class MeasurementMinute {
144+
145+
private final Double measurement;
146+
private final Integer minuteOfDay;
147+
private final DecimalFormat decimalFormat;
148+
149+
// For a sorted set value of "22.0:1", this classes provides
150+
// the measurement value of 22.0 and the minuteOfDay value of 1.
151+
public static MeasurementMinute fromZSetValue( String zSetValue ) {
152+
String[] parts = zSetValue.split( ":" );
153+
if ( parts.length == 2 ) {
154+
return new MeasurementMinute( Double.valueOf( parts[0] ), Integer.valueOf( parts[1] ) );
155+
} else {
156+
throw new IllegalArgumentException( "Cannot convert zSetValue " + zSetValue + " into MeasurementMinute" );
157+
}
158+
}
159+
160+
public MeasurementMinute( Double measurement, Integer minuteOfDay ) {
161+
this.measurement = measurement;
162+
this.minuteOfDay = minuteOfDay;
163+
this.decimalFormat = new DecimalFormat( "#.##" );
164+
}
165+
166+
public Integer getMinuteOfDay() {
167+
return minuteOfDay;
168+
}
169+
170+
public Double getMeasurement() {
171+
return measurement;
172+
}
173+
174+
public String toString() {
175+
return decimalFormat.format( measurement ) + ':' + String.valueOf( minuteOfDay );
176+
}
177+
}
191178
}

src/main/java/com/redislabs/university/RU102J/dao/SiteStatsDaoRedisImpl.java

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -78,20 +78,19 @@ private void updateBasic( Jedis jedis, String key, MeterReading reading ) {
7878

7979
// Challenge #3
8080
private void updateOptimized( Jedis jedis, String key, MeterReading reading ) {
81-
String reportingTime = ZonedDateTime.now( ZoneOffset.UTC ).toString();
82-
83-
jedis.hset( key, SiteStats.reportingTimeField, reportingTime );
84-
jedis.hincrBy( key, SiteStats.countField, 1 );
85-
jedis.expire( key, weekSeconds );
81+
try ( Transaction t = jedis.multi() ) {
82+
String reportingTime = ZonedDateTime.now( ZoneOffset.UTC ).toString();
8683

87-
Transaction transaction = jedis.multi();
84+
t.hset( key, SiteStats.reportingTimeField, reportingTime );
85+
t.hincrBy( key, SiteStats.countField, 1 );
86+
t.expire( key, weekSeconds );
8887

89-
compareAndUpdateScript.updateIfGreater( transaction, key, SiteStats.maxWhField, reading.getWhGenerated() );
90-
compareAndUpdateScript.updateIfLess( transaction, key, SiteStats.minWhField, reading.getWhGenerated() );
91-
compareAndUpdateScript.updateIfGreater( transaction, key, SiteStats.maxCapacityField, getCurrentCapacity( reading ) );
88+
compareAndUpdateScript.updateIfGreater( t, key, SiteStats.maxWhField, reading.getWhGenerated() );
89+
compareAndUpdateScript.updateIfLess( t, key, SiteStats.minWhField, reading.getWhGenerated() );
90+
compareAndUpdateScript.updateIfGreater( t, key, SiteStats.maxCapacityField, getCurrentCapacity( reading ) );
9291

93-
transaction.exec();
94-
transaction.close();
92+
t.exec();
93+
}
9594
}
9695

9796
private Double getCurrentCapacity( MeterReading reading ) {

0 commit comments

Comments
 (0)