Skip to content

Commit d1672b9

Browse files
committed
Merge branch 'main' into nslookup-v1
2 parents 4d9b802 + 7994e6d commit d1672b9

File tree

5 files changed

+227
-6
lines changed

5 files changed

+227
-6
lines changed

docs/caches.rst

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
..
2+
Copyright (c) 2022, 2024, Oracle and/or its affiliates.
3+
Licensed under the Universal Permissive License v 1.0 as shown at
4+
https://oss.oracle.com/licenses/upl.
5+
6+
Caches
7+
======
8+
9+
Once a Session has been obtained, it is now possible to begin working with
10+
`NamedMap` and/or `NamedCache` instances. This is done by calling either
11+
`Session.get_map(name: str, cache_options: Optional[CacheOptions] = None)` or
12+
`Session.get_cache(name: str, cache_options: Optional[CacheOptions] = None)`.
13+
The `name` argument is the logical name for this cache. The optional `cache_options`
14+
argument accepts a `CacheOptions` to configure the default time-to-live (ttl)
15+
for entries placed in the cache as well as allowing the configuration of near
16+
caching (discussed later in this section).
17+
18+
Here are some examples:
19+
20+
.. code-block:: python
21+
22+
# obtain NamedCache 'person'
23+
cache: NamedCache[int, Person] = await session.get_cache("person")
24+
25+
.. code-block:: python
26+
27+
# obtain NamedCache 'person' with a default ttl of 2000 millis
28+
# any entry inserted into this cache, unless overridden by a put call
29+
# with a custom ttl, will have a default ttl of 2000
30+
options: CacheOptions = CacheOptions(2000)
31+
cache: NamedCache[int, Person] = await session.get_cache("person", options)
32+
33+
Near Caches
34+
===========
35+
36+
Near caches are a local cache within the `NamedMap` or `NamedCache`
37+
that will store entries as they are obtained from the remote cache. By doing
38+
so, it is possible to reduce the number of remote calls made to the Coherence
39+
cluster by returning the locally cached value. This local cache also
40+
ensures updates made to, or removal of, an entry are properly
41+
reflected thus ensuring stale data isn't mistakenly returned.
42+
43+
.. note::
44+
Near caching will only work with Coherence CE `24.09` or later. Attempting
45+
to use near caching features with older versions will have no effect.
46+
47+
A near cache is configured via `NearCacheOptions` which provides several
48+
options for controlling how entries will be cached locally.
49+
50+
- `ttl` - configures the time-to-live of locally cached entries (this has no
51+
impact on entries stored within Coherence). If not specified, or the
52+
`ttl` is `0`, entries in the near cache will not expire
53+
- `high_units` - configures the max number of entries that may be locally
54+
cached. Once the number of locally cached entries exceeds the configured
55+
value, the cache will be pruned down (least recently used entries first)
56+
to a target size based on the configured `prune_factor`
57+
(defaults to `0.80` meaning the prune operation would retain 80% of
58+
the entries)
59+
- `high_units_memory` - configures the maximum memory size, in bytes, the
60+
locally cached entries may use. If total memory exceeds the configured
61+
value, the cache will be pruned down (least recently used entries first)
62+
to a target size based on the configured `prune_factor` (defaults to
63+
`0.80` meaning the prune operation would retain 80% the cache memory)
64+
- `prune_factor` - configures the target near cache size after exceeding
65+
either `high_units` or `high_units_memory` high-water marks
66+
67+
.. note::
68+
`high_units` and `high_units_memory` are mutually exclusive
69+
70+
Examples of configuring near caching:
71+
72+
.. code-block:: python
73+
74+
# obtain NamedCache 'person' and configure near caching with a local
75+
# ttl of 20_000 millis
76+
near_options: NearCacheOptions = NearCacheOptions(20_000)
77+
cache_options: CacheOptions = CacheOptions(near_cache_options=near_options)
78+
cache: NamedCache[int, Person] = await session.get_cache("person", options)
79+
80+
81+
.. code-block:: python
82+
83+
# obtain NamedCache 'person' and configure near caching with a max
84+
# number of entries of 1_000 and when pruned, it will be reduced
85+
# to 20%
86+
near_options: NearCacheOptions = NearCacheOptions(high_units=1_000, prune_factor=0.20)
87+
cache_options: CacheOptions = CacheOptions(near_cache_options=near_options)
88+
cache: NamedCache[int, Person] = await session.get_cache("person", options)
89+
90+
To verify the effectiveness of a near cache, several statistics are monitored
91+
and may be obtained from the `CacheStats` instance returned by the
92+
`near_cache_stats` property of the `NamedMap` or `NamedCache`
93+
94+
The following statistics are available (the statistic name given is the same
95+
property name on the `CacheStats` instance)
96+
97+
- hits - the number of times an entry was found in the near cache
98+
- misses - the number of times an entry was not found in the near cache
99+
- misses_duration - The accumulated time, in millis, spent for a cache miss
100+
(i.e., having to make a remote call and update the local cache)
101+
- hit_rate - the ratio of hits to misses
102+
- puts - the total number of puts that have been made against the near cache
103+
- gets - the total number of gets that have been made against the near cache
104+
- prunes - the number of times the cache was pruned due to exceeding the
105+
configured `high_units` or `high_units_memory` high-water marks
106+
- expires - the number of times the near cache's expiry logic expired entries
107+
- num_pruned - the total number of entries that were removed due to exceeding the
108+
configured `high_units` or `high_units_memory` high-water marks
109+
- num_expired - the total number of entries that were removed due to
110+
expiration
111+
- prunes_duration - the accumulated time, in millis, spent pruning
112+
the near cache
113+
- expires_duration - the accumulated time, in millis, removing
114+
expired entries from the near cache
115+
- size - the total number of entries currently held by the near cache
116+
- bytes - the total bytes the near cache entries consume
117+
118+
.. note::
119+
The `near_cache_stats` option will return `None` if near caching isn't
120+
configured or available
121+
122+
The following example demonstrates the value that near caching can provide:
123+
124+
.. literalinclude:: ../examples/near_caching.py
125+
:language: python
126+
:linenos:

docs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
installation
1111
sessions
12+
caches
1213
basics
1314
querying
1415
aggregation

docs/sessions.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
..
2-
Copyright (c) 2022, 2023, Oracle and/or its affiliates.
2+
Copyright (c) 2022, 2024, Oracle and/or its affiliates.
33
Licensed under the Universal Permissive License v 1.0 as shown at
44
https://oss.oracle.com/licenses/upl.
55
@@ -37,7 +37,7 @@ The currently supported arguments foe `Options` are:
3737
import asyncio
3838
3939
# create a new Session to the Coherence server
40-
session: Session = Session(None)
40+
session: Session = await Session.create()
4141
4242
This is the simplest invocation which assumes the following defaults:
4343
- `address` is `localhost:1408`
@@ -55,7 +55,7 @@ and pass it to the constructor of the `Session`:
5555
# create a new Session to the Coherence server
5656
addr: str = 'example.com:4444'
5757
opt: Options = Options(addr, default_scope, default_request_timeout, default_format)
58-
session: Session = Session(opt)
58+
session: Session = await Session.create(opt)
5959
6060
It's also possible to control the default address the session will bind to by providing
6161
an address via the `COHERENCE_SERVER_ADDRESS` environment variable. The format of the value would

examples/near_caching.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# Copyright (c) 2024, Oracle and/or its affiliates.
2+
# Licensed under the Universal Permissive License v 1.0 as shown at
3+
# https://oss.oracle.com/licenses/upl.
4+
5+
import asyncio
6+
import time
7+
from functools import reduce
8+
9+
from coherence import CacheOptions, CacheStats, NamedCache, NearCacheOptions, Session
10+
11+
12+
async def do_run() -> None:
13+
session: Session = await Session.create()
14+
15+
# obtain the basic cache and configure near caching with
16+
# all defaults; meaning no expiry or pruning
17+
cache_remote: NamedCache[str, str] = await session.get_cache("remote")
18+
cache_near: NamedCache[str, str] = await session.get_cache(
19+
"near", CacheOptions(near_cache_options=NearCacheOptions(ttl=0))
20+
)
21+
22+
await cache_remote.clear()
23+
await cache_near.clear()
24+
stats: CacheStats = cache_near.near_cache_stats
25+
26+
# these knobs control:
27+
# - how many current tasks to run
28+
# - how many entries will be inserted and queried
29+
# - how many times the calls will be invoked
30+
task_count: int = 25
31+
num_entries: int = 1_000
32+
iterations: int = 4
33+
34+
# seed data to populate the cache
35+
cache_seed: dict[str, str] = {str(x): str(x) for x in range(num_entries)}
36+
cache_seed_keys: set[str] = {key for key in cache_seed.keys()}
37+
print()
38+
39+
# task calling get_all() for 1_000 keys
40+
async def get_all_task(task_cache: NamedCache[str, str]) -> int:
41+
begin = time.time_ns()
42+
43+
for _ in range(iterations):
44+
async for _ in await task_cache.get_all(cache_seed_keys):
45+
continue
46+
47+
return (time.time_ns() - begin) // 1_000_000
48+
49+
await cache_remote.put_all(cache_seed)
50+
await cache_near.put_all(cache_seed)
51+
52+
print("Run without near caching ...")
53+
begin_outer: int = time.time_ns()
54+
results: list[int] = await asyncio.gather(*[get_all_task(cache_remote) for _ in range(task_count)])
55+
end_outer: int = time.time_ns()
56+
total_time = end_outer - begin_outer
57+
task_time = reduce(lambda first, second: first + second, results)
58+
59+
# Example output
60+
# Run without near caching ...
61+
# [remote] 25 tasks completed!
62+
# [remote] Total time: 4246ms
63+
# [remote] Tasks completion average: 3755.6
64+
65+
print(f"[remote] {task_count} tasks completed!")
66+
print(f"[remote] Total time: {total_time // 1_000_000}ms")
67+
print(f"[remote] Tasks completion average: {task_time / task_count}")
68+
69+
print()
70+
print("Run with near caching ...")
71+
begin_outer = time.time_ns()
72+
results = await asyncio.gather(*[get_all_task(cache_near) for _ in range(task_count)])
73+
end_outer = time.time_ns()
74+
total_time = end_outer - begin_outer
75+
task_time = reduce(lambda first, second: first + second, results)
76+
77+
# Run with near caching ...
78+
# [near] 25 tasks completed!
79+
# [near] Total time: 122ms
80+
# [near] Tasks completion average: 113.96
81+
# [near] Near cache statistics: CacheStats(puts=1000, gets=100000, hits=99000,
82+
# misses=1000, misses-duration=73ms, hit-rate=0.99, prunes=0,
83+
# num-pruned=0, prunes-duration=0ms, size=1000, expires=0,
84+
# num-expired=0, expires-duration=0ms, memory-bytes=681464)
85+
86+
print(f"[near] {task_count} tasks completed!")
87+
print(f"[near] Total time: {total_time // 1_000_000}ms")
88+
print(f"[near] Tasks completion average: {task_time / task_count}")
89+
print(f"[near] Near cache statistics: {stats}")
90+
91+
await session.close()
92+
93+
94+
asyncio.run(do_run())

pyproject.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@ classifiers = [
2323
[tool.poetry.dependencies]
2424
python = "^3.9"
2525
protobuf = "5.29.2"
26-
grpcio = "1.68.1"
26+
grpcio = "1.69.0"
2727
grpcio-tools = "1.68.1"
2828
jsonpickle = ">=3.0,<4.1"
29-
pymitter = ">=0.4,<0.6"
29+
pymitter = ">=0.4,<1.1"
3030
typing-extensions = ">=4.11,<4.13"
3131
types-protobuf = "5.29.1.20241207"
3232
pympler = "1.1"
@@ -37,7 +37,7 @@ pytest-asyncio = "~0.25"
3737
pytest-cov = "~6.0"
3838
pytest-unordered = "~0.6"
3939
pre-commit = "~4.0"
40-
Sphinx = "~7.1"
40+
Sphinx = "~7.4"
4141
sphinx-rtd-theme = "~3.0"
4242
sphinxcontrib-napoleon = "~0.7"
4343
m2r = "~0.3"

0 commit comments

Comments
 (0)