-
Notifications
You must be signed in to change notification settings - Fork 337
RED-134847 support for active defrag for RedisJSON. #1262
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
84606d2
f95ca41
06d186f
d4fafb4
c5edf29
b7d17ca
43d445f
428eb85
2c78c3e
cded029
2dede4b
b223cc9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
use std::{ | ||
alloc::Layout, | ||
os::raw::{c_int, c_void}, | ||
}; | ||
|
||
use ijson::{Defrag, DefragAllocator}; | ||
use lazy_static::lazy_static; | ||
use redis_module::{ | ||
defrag::DefragContext, raw, redisvalue::RedisValueKey, Context, RedisGILGuard, RedisResult, | ||
RedisValue, | ||
}; | ||
use redis_module_macros::{defrag_end_function, defrag_start_function}; | ||
|
||
use crate::redisjson::RedisJSON; | ||
|
||
#[derive(Default)] | ||
pub(crate) struct DefragStats { | ||
defrag_started: usize, | ||
defrag_ended: usize, | ||
keys_defrag: usize, | ||
} | ||
iddm marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
lazy_static! { | ||
pub(crate) static ref DEFRAG_STATS: RedisGILGuard<DefragStats> = RedisGILGuard::default(); | ||
} | ||
|
||
struct DefragCtxAllocator<'dc> { | ||
defrag_ctx: &'dc DefragContext, | ||
} | ||
|
||
impl<'dc> DefragAllocator for DefragCtxAllocator<'dc> { | ||
unsafe fn realloc_ptr<T>(&mut self, ptr: *mut T, _layout: Layout) -> *mut T { | ||
self.defrag_ctx.defrag_realloc(ptr) | ||
} | ||
|
||
/// Allocate memory for defrag | ||
unsafe fn alloc(&mut self, layout: Layout) -> *mut u8 { | ||
self.defrag_ctx.defrag_alloc(layout) | ||
} | ||
iddm marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
/// Free memory for defrag | ||
unsafe fn free<T>(&mut self, ptr: *mut T, layout: Layout) { | ||
self.defrag_ctx.defrag_dealloc(ptr, layout) | ||
} | ||
} | ||
|
||
#[defrag_start_function] | ||
fn defrag_start(defrag_ctx: &DefragContext) { | ||
let mut defrag_stats = DEFRAG_STATS.lock(defrag_ctx); | ||
defrag_stats.defrag_started += 1; | ||
ijson::reinit_shared_string_cache(); | ||
} | ||
|
||
#[defrag_end_function] | ||
fn defrag_end(defrag_ctx: &DefragContext) { | ||
let mut defrag_stats = DEFRAG_STATS.lock(defrag_ctx); | ||
defrag_stats.defrag_ended += 1; | ||
} | ||
|
||
#[allow(non_snake_case, unused)] | ||
pub unsafe extern "C" fn defrag( | ||
ctx: *mut raw::RedisModuleDefragCtx, | ||
key: *mut raw::RedisModuleString, | ||
value: *mut *mut c_void, | ||
) -> c_int { | ||
let defrag_ctx = DefragContext::new(ctx); | ||
|
||
let mut defrag_stats = DEFRAG_STATS.lock(&defrag_ctx); | ||
defrag_stats.keys_defrag += 1; | ||
|
||
let mut defrag_allocator = DefragCtxAllocator { | ||
defrag_ctx: &defrag_ctx, | ||
}; | ||
let value = value.cast::<*mut RedisJSON<ijson::IValue>>(); | ||
let new_val = defrag_allocator.realloc_ptr(*value, Layout::new::<RedisJSON<ijson::IValue>>()); | ||
if !new_val.is_null() { | ||
iddm marked this conversation as resolved.
Show resolved
Hide resolved
|
||
*value = new_val; | ||
ephraimfeldblum marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
} | ||
std::ptr::write( | ||
&mut (**value).data as *mut ijson::IValue, | ||
ephraimfeldblum marked this conversation as resolved.
Show resolved
Hide resolved
|
||
std::ptr::read(*value).data.defrag(&mut defrag_allocator), | ||
); | ||
iddm marked this conversation as resolved.
Show resolved
Hide resolved
|
||
0 | ||
} | ||
|
||
pub(crate) fn defrag_info(ctx: &Context) -> RedisResult { | ||
let defrag_stats = DEFRAG_STATS.lock(ctx); | ||
Ok(RedisValue::OrderedMap( | ||
[ | ||
( | ||
RedisValueKey::String("defrag_started".to_owned()), | ||
RedisValue::Integer(defrag_stats.defrag_started as i64), | ||
), | ||
( | ||
RedisValueKey::String("defrag_ended".to_owned()), | ||
RedisValue::Integer(defrag_stats.defrag_ended as i64), | ||
), | ||
( | ||
RedisValueKey::String("keys_defrag".to_owned()), | ||
RedisValue::Integer(defrag_stats.keys_defrag as i64), | ||
), | ||
] | ||
.into_iter() | ||
.collect(), | ||
)) | ||
} | ||
iddm marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
import time | ||
import json | ||
from RLTest import Defaults | ||
|
||
Defaults.decode_responses = True | ||
|
||
def enableDefrag(env): | ||
# make defrag as aggressive as possible | ||
env.cmd('CONFIG', 'SET', 'hz', '100') | ||
env.cmd('CONFIG', 'SET', 'active-defrag-ignore-bytes', '1') | ||
env.cmd('CONFIG', 'SET', 'active-defrag-threshold-lower', '0') | ||
env.cmd('CONFIG', 'SET', 'active-defrag-cycle-min', '99') | ||
|
||
try: | ||
env.cmd('CONFIG', 'SET', 'activedefrag', 'yes') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @MeirShpilraien i understand that unlike the redis defrag tests, this one isn't actually creating fragmentation and waiting for it to go down, which is ok. maybe in addition to these, add one test that create holes and waits for them to get filled? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Modify to tests to actually cause fragmentation and verify that defrag actually took place. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if i read it correctly, you created a lot of jason fields, and then deleted all of them.
see memefficiency.tcl in redis. |
||
except Exception: | ||
# If active defrag is not supported by the current Redis, simply skip the test. | ||
env.skip() | ||
|
||
def defragOnObj(env, obj): | ||
enableDefrag(env) | ||
env.expect('JSON.SET', 'test', '$', json.dumps(obj)).ok() | ||
_, _, _, _, _, keysDefrag = env.cmd('JSON.DEBUG', 'DEFRAG_INFO') | ||
startTime = time.time() | ||
# Wait for at least 2 defrag full cycles | ||
# We verify only the 'keysDefrag' value because the other values | ||
# are not promised to be updated. It depends if Redis support | ||
# the start/end defrag callbacks. | ||
while keysDefrag < 2: | ||
time.sleep(0.1) | ||
_, _, _, _, _, keysDefrag = env.cmd('JSON.DEBUG', 'DEFRAG_INFO') | ||
if time.time() - startTime > 30: | ||
# We will wait for up to 30 seconds and then we consider it a failure | ||
env.assertTrue(False, message='Failed waiting for defrag to run') | ||
return | ||
# make sure json is still valid. | ||
res = json.loads(env.cmd('JSON.GET', 'test', '$'))[0] | ||
env.assertEqual(res, obj) | ||
|
||
def testDefragNumber(env): | ||
defragOnObj(env, 1) | ||
|
||
def testDefragBigNumber(env): | ||
defragOnObj(env, 100000000000000000000) | ||
|
||
def testDefragDouble(env): | ||
defragOnObj(env, 1.111111111111) | ||
|
||
def testDefragNegativeNumber(env): | ||
defragOnObj(env, -100000000000000000000) | ||
|
||
def testDefragNegativeDouble(env): | ||
defragOnObj(env, -1.111111111111) | ||
|
||
def testDefragTrue(env): | ||
defragOnObj(env, True) | ||
|
||
def testDefragFalse(env): | ||
defragOnObj(env, True) | ||
|
||
def testDefragNone(env): | ||
defragOnObj(env, None) | ||
|
||
def testDefragEmptyString(env): | ||
defragOnObj(env, "") | ||
|
||
def testDefragString(env): | ||
defragOnObj(env, "foo") | ||
|
||
def testDefragEmptyArray(env): | ||
defragOnObj(env, []) | ||
|
||
def testDefragArray(env): | ||
defragOnObj(env, [1, 2, 3]) | ||
|
||
def testDefragEmptyObject(env): | ||
defragOnObj(env, {}) | ||
|
||
def testDefragObject(env): | ||
defragOnObj(env, {"foo": "bar"}) | ||
|
||
def testDefragComplex(env): | ||
defragOnObj(env, {"foo": ["foo", 1, None, True, False, {}, {"foo": [], "bar": 1}]}) |
Uh oh!
There was an error while loading. Please reload this page.