Skip to content

Commit ebf27bd

Browse files
authored
[FEATURE][BC-BREAK] Add possibility to cache schema IDs by hash (#11)
This is needed because when running serializers the cost of hashing the schemas' string representations is way less than going to the registry over HTTP. This adds 3 new methods to the `CacheAdapter` API: `cacheSchemaIdByHash(int $schemaId, string $schemaHash)` `getIdWithHash(string $hash)` `hasSchemaIdForHash(string $schemaHash)` Unfortunately this is a BC breaking change which requires clients that possibly have own implementations of the `CacheAdapterInterface` to implement those 3 methods. Clients that use the default shipped adapters are OK. Implements the new caching features in the `CachedRegistry`
1 parent 3e842d0 commit ebf27bd

File tree

8 files changed

+312
-172
lines changed

8 files changed

+312
-172
lines changed

src/Registry/Cache/AvroObjectCacheAdapter.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ class AvroObjectCacheAdapter implements CacheAdapter
1717
*/
1818
private $idToSchema = [];
1919

20+
/**
21+
* @var int[]
22+
*/
23+
private $hashToSchemaId = [];
24+
2025
/**
2126
* @var AvroSchema[]
2227
*/
@@ -30,6 +35,14 @@ public function cacheSchemaWithId(AvroSchema $schema, int $schemaId)
3035
$this->idToSchema[$schemaId] = $schema;
3136
}
3237

38+
/**
39+
* {@inheritdoc}
40+
*/
41+
public function cacheSchemaIdByHash(int $schemaId, string $schemaHash)
42+
{
43+
$this->hashToSchemaId[$schemaHash] = $schemaId;
44+
}
45+
3346
/**
3447
* {@inheritdoc}
3548
*/
@@ -50,6 +63,15 @@ public function getWithId(int $schemaId)
5063
return $this->idToSchema[$schemaId];
5164
}
5265

66+
public function getIdWithHash(string $hash)
67+
{
68+
if (!$this->hasSchemaIdForHash($hash)) {
69+
return null;
70+
}
71+
72+
return $this->hashToSchemaId[$hash];
73+
}
74+
5375
/**
5476
* {@inheritdoc}
5577
*/
@@ -72,6 +94,14 @@ public function hasSchemaForId(int $schemaId): bool
7294
return array_key_exists($schemaId, $this->idToSchema);
7395
}
7496

97+
/**
98+
* {@inheritdoc}
99+
*/
100+
public function hasSchemaIdForHash(string $schemaHash): bool
101+
{
102+
return array_key_exists($schemaHash, $this->hashToSchemaId);
103+
}
104+
75105
/**
76106
* {@inheritdoc}
77107
*/

src/Registry/Cache/DoctrineCacheAdapter.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ public function cacheSchemaWithId(AvroSchema $schema, int $schemaId)
3131
$this->doctrineCache->save($schemaId, (string) $schema);
3232
}
3333

34+
public function cacheSchemaIdByHash(int $schemaId, string $schemaHash)
35+
{
36+
$this->doctrineCache->save($schemaHash, $schemaId);
37+
}
38+
3439
/**
3540
* {@inheritdoc}
3641
*/
@@ -56,6 +61,20 @@ public function getWithId(int $schemaId)
5661
return AvroSchema::parse($rawSchema);
5762
}
5863

64+
/**
65+
* {@inheritdoc}
66+
*/
67+
public function getIdWithHash(string $hash)
68+
{
69+
$schemaId = $this->doctrineCache->fetch($hash);
70+
71+
if (!$schemaId) {
72+
return null;
73+
}
74+
75+
return $schemaId;
76+
}
77+
5978
/**
6079
* {@inheritdoc}
6180
*/
@@ -82,6 +101,14 @@ public function hasSchemaForId(int $schemaId): bool
82101
return $this->doctrineCache->contains($schemaId);
83102
}
84103

104+
/**
105+
* {@inheritdoc}
106+
*/
107+
public function hasSchemaIdForHash(string $schemaHash): bool
108+
{
109+
return $this->doctrineCache->contains($schemaHash);
110+
}
111+
85112
/**
86113
* {@inheritdoc}
87114
*/

src/Registry/CacheAdapter.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,16 @@ public function cacheSchemaWithId(AvroSchema $schema, int $schemaId);
3232
*/
3333
public function cacheSchemaWithSubjectAndVersion(AvroSchema $schema, string $subject, int $version);
3434

35+
/**
36+
* Caches a schema id by a hash (i.e. the hash of the Avro schema string representation)
37+
*
38+
* @param int $schemaId
39+
* @param string $schemaHash
40+
*
41+
* @return void
42+
*/
43+
public function cacheSchemaIdByHash(int $schemaId, string $schemaHash);
44+
3545
/**
3646
* Tries to fetch a cache with the global schema id.
3747
* Returns either the AvroSchema when found or `null` when not.
@@ -42,6 +52,16 @@ public function cacheSchemaWithSubjectAndVersion(AvroSchema $schema, string $sub
4252
*/
4353
public function getWithId(int $schemaId);
4454

55+
/**
56+
* Tries to fetch a cached schema id with a given hash.
57+
* Either returns the schema id as int or `null` when none is found.
58+
*
59+
* @param string $hash
60+
*
61+
* @return int|null
62+
*/
63+
public function getIdWithHash(string $hash);
64+
4565
/**
4666
* Tries to fetch a cache with a given subject and version.
4767
* Returns either the AvroSchema when found or `null` when not.
@@ -62,6 +82,15 @@ public function getWithSubjectAndVersion(string $subject, int $version);
6282
*/
6383
public function hasSchemaForId(int $schemaId): bool;
6484

85+
/**
86+
* Checks if a schema id exists for the given hash
87+
*
88+
* @param string $schemaHash
89+
*
90+
* @return bool
91+
*/
92+
public function hasSchemaIdForHash(string $schemaHash): bool;
93+
6594
/**
6695
* Checks if the cache engine has a cached schema for a given subject and version.
6796
*

src/Registry/CachedRegistry.php

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,23 @@ class CachedRegistry implements Registry
2323
*/
2424
private $cacheAdapter;
2525

26-
public function __construct(Registry $registry, CacheAdapter $cacheAdapter)
26+
/**
27+
* @var callable
28+
*/
29+
private $hashAlgoFunc;
30+
31+
public function __construct(Registry $registry, CacheAdapter $cacheAdapter, callable $hashAlgoFunc = null)
2732
{
2833
$this->registry = $registry;
2934
$this->cacheAdapter = $cacheAdapter;
35+
36+
if (!$hashAlgoFunc) {
37+
$hashAlgoFunc = function (AvroSchema $schema) {
38+
return md5((string) $schema);
39+
};
40+
}
41+
42+
$this->hashAlgoFunc = $hashAlgoFunc;
3043
}
3144

3245
/**
@@ -36,6 +49,7 @@ public function register(string $subject, AvroSchema $schema, callable $requestC
3649
{
3750
$closure = function (int $schemaId) use ($schema) {
3851
$this->cacheAdapter->cacheSchemaWithId($schema, $schemaId);
52+
$this->cacheAdapter->cacheSchemaIdByHash($schemaId, $this->getSchemaHash($schema));
3953

4054
return $schemaId;
4155
};
@@ -74,8 +88,15 @@ function (PromiseInterface $promise) use ($closure) {
7488
*/
7589
public function schemaId(string $subject, AvroSchema $schema, callable $requestCallback = null)
7690
{
77-
$closure = function (int $schemaId) use ($schema) {
91+
$schemaHash = $this->getSchemaHash($schema);
92+
93+
if ($this->cacheAdapter->hasSchemaIdForHash($schemaHash)) {
94+
return $this->cacheAdapter->getIdWithHash($schemaHash);
95+
}
96+
97+
$closure = function (int $schemaId) use ($schema, $schemaHash) {
7898
$this->cacheAdapter->cacheSchemaWithId($schema, $schemaId);
99+
$this->cacheAdapter->cacheSchemaIdByHash($schemaId, $schemaHash);
79100

80101
return $schemaId;
81102
};
@@ -100,6 +121,7 @@ public function schemaForId(int $schemaId, callable $requestCallback = null)
100121

101122
$closure = function (AvroSchema $schema) use ($schemaId) {
102123
$this->cacheAdapter->cacheSchemaWithId($schema, $schemaId);
124+
$this->cacheAdapter->cacheSchemaIdByHash($schemaId, $this->getSchemaHash($schema));
103125

104126
return $schema;
105127
};
@@ -159,4 +181,9 @@ private function applyValueHandlers($value, callable $promiseHandler, callable $
159181

160182
return $valueHandler($value);
161183
}
184+
185+
private function getSchemaHash(AvroSchema $schema): string
186+
{
187+
return call_user_func($this->hashAlgoFunc, $schema);
188+
}
162189
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace FlixTech\SchemaRegistryApi\Test\Registry\Cache;
6+
7+
use AvroSchema;
8+
use FlixTech\SchemaRegistryApi\Registry\CacheAdapter;
9+
use PHPUnit\Framework\TestCase;
10+
11+
abstract class AbstractCacheAdapterTestCase extends TestCase
12+
{
13+
abstract protected function getAdapter(): CacheAdapter;
14+
15+
/**
16+
* @var CacheAdapter
17+
*/
18+
protected $cacheAdapter;
19+
20+
protected function setUp()
21+
{
22+
$this->cacheAdapter = $this->getAdapter();
23+
}
24+
25+
/**
26+
* @test
27+
*/
28+
public function it_should_store_and_fetch_schemas_with_ids()
29+
{
30+
$schemaId = 3;
31+
$invalidSchemaId = 2;
32+
$schema = AvroSchema::parse('{"type": "string"}');
33+
34+
$this->cacheAdapter->cacheSchemaWithId($schema, $schemaId);
35+
36+
$this->assertFalse($this->cacheAdapter->hasSchemaForId($invalidSchemaId));
37+
$this->assertTrue($this->cacheAdapter->hasSchemaForId($schemaId));
38+
39+
$this->assertNull($this->cacheAdapter->getWithId($invalidSchemaId));
40+
$this->assertEquals($schema, $this->cacheAdapter->getWithId($schemaId));
41+
}
42+
43+
/**
44+
* @test
45+
*/
46+
public function it_should_store_and_fetch_schemas_with_subject_and_version()
47+
{
48+
$subject = 'test';
49+
$version = 2;
50+
$invalidSubject = 'none';
51+
$schema = AvroSchema::parse('{"type": "string"}');
52+
53+
$this->cacheAdapter->cacheSchemaWithSubjectAndVersion($schema, $subject, $version);
54+
55+
$this->assertFalse($this->cacheAdapter->hasSchemaForSubjectAndVersion($invalidSubject, $version));
56+
$this->assertTrue($this->cacheAdapter->hasSchemaForSubjectAndVersion($subject, $version));
57+
58+
$this->assertNull($this->cacheAdapter->getWithSubjectAndVersion($invalidSubject, $version));
59+
$this->assertEquals($schema, $this->cacheAdapter->getWithSubjectAndVersion($subject, $version));
60+
}
61+
62+
/**
63+
* @test
64+
*/
65+
public function it_should_store_and_fetch_schema_ids_with_schema_hashes()
66+
{
67+
$schemaId = 3;
68+
$hash = 'hash';
69+
$anotherHash = 'another';
70+
71+
$this->assertFalse($this->cacheAdapter->hasSchemaIdForHash($hash));
72+
$this->assertFalse($this->cacheAdapter->hasSchemaIdForHash($anotherHash));
73+
74+
$this->cacheAdapter->cacheSchemaIdByHash($schemaId, $hash);
75+
76+
$this->assertTrue($this->cacheAdapter->hasSchemaIdForHash($hash));
77+
$this->assertFalse($this->cacheAdapter->hasSchemaIdForHash($anotherHash));
78+
79+
$this->assertNull($this->cacheAdapter->getIdWithHash($anotherHash));
80+
$this->assertSame($schemaId, $this->cacheAdapter->getIdWithHash($hash));
81+
}
82+
}

test/Registry/Cache/AvroObjectCacheAdapterTest.php

Lines changed: 4 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -4,56 +4,13 @@
44

55
namespace FlixTech\SchemaRegistryApi\Test\Registry\Cache;
66

7-
use AvroSchema;
87
use FlixTech\SchemaRegistryApi\Registry\Cache\AvroObjectCacheAdapter;
9-
use PHPUnit\Framework\TestCase;
8+
use FlixTech\SchemaRegistryApi\Registry\CacheAdapter;
109

11-
class AvroObjectCacheAdapterTest extends TestCase
10+
class AvroObjectCacheAdapterTest extends AbstractCacheAdapterTestCase
1211
{
13-
/**
14-
* @var \FlixTech\SchemaRegistryApi\Registry\Cache\AvroObjectCacheAdapter
15-
*/
16-
private $cacheAdapter;
17-
18-
protected function setUp()
19-
{
20-
$this->cacheAdapter = new AvroObjectCacheAdapter();
21-
}
22-
23-
/**
24-
* @test
25-
*/
26-
public function it_should_store_and_fetch_schemas_with_ids()
12+
protected function getAdapter(): CacheAdapter
2713
{
28-
$schemaId = 3;
29-
$invalidSchemaId = 2;
30-
$schema = AvroSchema::parse('{"type": "string"}');
31-
32-
$this->cacheAdapter->cacheSchemaWithId($schema, $schemaId);
33-
34-
$this->assertFalse($this->cacheAdapter->hasSchemaForId($invalidSchemaId));
35-
$this->assertTrue($this->cacheAdapter->hasSchemaForId($schemaId));
36-
37-
$this->assertNull($this->cacheAdapter->getWithId($invalidSchemaId));
38-
$this->assertEquals($schema, $this->cacheAdapter->getWithId($schemaId));
39-
}
40-
41-
/**
42-
* @test
43-
*/
44-
public function it_should_store_and_fetch_schemas_with_subject_and_version()
45-
{
46-
$subject = 'test';
47-
$version = 2;
48-
$invalidSubject = 'none';
49-
$schema = AvroSchema::parse('{"type": "string"}');
50-
51-
$this->cacheAdapter->cacheSchemaWithSubjectAndVersion($schema, $subject, $version);
52-
53-
$this->assertFalse($this->cacheAdapter->hasSchemaForSubjectAndVersion($invalidSubject, $version));
54-
$this->assertTrue($this->cacheAdapter->hasSchemaForSubjectAndVersion($subject, $version));
55-
56-
$this->assertNull($this->cacheAdapter->getWithSubjectAndVersion($invalidSubject, $version));
57-
$this->assertEquals($schema, $this->cacheAdapter->getWithSubjectAndVersion($subject, $version));
14+
return new AvroObjectCacheAdapter();
5815
}
5916
}

0 commit comments

Comments
 (0)