點陣圖索引的工作原理 - Richard

banq發表於2021-11-29

點陣圖索引用於各種資料技術以實現高效的查詢處理。在高層次上,點陣圖索引可以被認為是一組謂詞在資料集上的物理具體化,它自然是列式的,特別適合多維布林查詢處理。  當有多個屬性受查詢約束時(例如在複合 where 子句中),PostgreSQL 會根據查詢謂詞動態實現點陣圖索引。ElasticSearch 和 Solr 中的過濾器快取 被實現為文件 ID 上過濾器謂詞的點陣圖索引。Pilosa是一個用 Go 構建的分散式點陣圖索引查詢引擎,帶有一個 Java 客戶端庫。

點陣圖索引不是一刀切的資料結構,在退化的情況下,它會佔用比資料本身更多的空間;在主鍵上使用點陣圖索引來支援 B 樹變體應該被視為濫用。存在各種風格的點陣圖實現,但新興的事實上的標準是 由Daniel Lemire領導的RoaringBitmap

 

樸素點陣圖索引

讓我們構建一個簡單的未壓縮點陣圖索引。假設您有一個資料集和某種從現在開始為每條記錄分配整數索引或記錄索引的方法(最簡單的例子是 CSV 檔案中的行號),並且已經選擇了一些要索引的屬性。對於資料的每個索引屬性的每個不同值,計算與謂詞匹配的記錄索引集。對於每個屬性,建立從屬性值到相應記錄索引集的對映。

用於集合的格式尚不重要,但要麼是int[] 要麼BitSet 根據資料和謂詞的屬性(例如,資料集的基數、資料集的排序順序、與謂詞匹配的記錄的基數)都將是合適的。

下表展示了資料結構。第一個表代表一個簡單的資料集。

Record Indices
    Country  Sector 
0             GB    Financials
1             DE    Manufacturing
2             FR    Agriculturals
3             FR    Financials
4             GB    Energies

第二個和第三個表代表資料集上的點陣圖索引,索引Country 和Sector屬性。

Country :

      Record Indices            Bitmap
GB    0,4                   0b10001
DE    1                      0b10
FR    2,3                    0b1100

Sector:

         Record Indices            Bitmap
Financials    0,3                  0b1001
Manufacturing   1                  0b10
Agriculturals   2                  0b100
Energies      4                   0b10000

值得注意的是上表中的三種模式。

  1. 一個屬性的點陣圖數量是該屬性的不同計數。
  2. 通常有零或一的執行,這些執行的長度取決於記錄索引屬性的排序順序。
  3. 記錄索引屬性本身上的點陣圖索引將與資料本身一樣大,並且表示形式要少得多。點陣圖索引不與 B 樹索引競爭主鍵屬性。

這種簡單的方案有效地將謂詞對資料的結果具體化,並且特別有吸引力,因為可以通過對點陣圖執行有效的邏輯操作來組合這些謂詞。當點陣圖的數量和每個點陣圖的大小都儘可能小時,查詢評估是最有效的。無論點陣圖大小如何,高效的查詢計劃都會盡可能少地接觸點陣圖。

無論點陣圖大小如何,高效的查詢計劃都會盡可能少地接觸點陣圖。以下是一些查詢示例及其評估計劃。

  • 單屬性聯合

select *
from records
where country = "GB" or country = "FR"

  1. 訪問國家索引,讀取值“FR”和“GB”的點陣圖。
  2. 應用按位邏輯 OR 以獲取新點陣圖。
  3. 使用檢索到的索引訪問由記錄 id 儲存的資料。

 

  • 多屬性交集

select *
from records
where country = "GB" and sector = "Energies"

  1. 訪問國家索引,並讀取值“GB”的點陣圖。
  2. 訪問扇區索引,並讀取值“能源”的點陣圖。
  3. 應用按位邏輯 AND 以獲取新點陣圖。
  4. 使用檢索到的索引訪問由記錄 id 儲存的資料。

....

 

壓縮

到目前為止提出的方案可作為玩具模型使用,但點陣圖太大而無法實用。單個屬性上的點陣圖索引在m由n記錄組成的資料集上具有不同的值,使用 一個BitSet將消耗mn位,使用 一個int[]將消耗32mn位。因此,需要某種壓縮才能使該方法可行。

RoaringBitmap 是一種動態資料結構,旨在成為適用於所有場景的通用解決方案。

RoaringBitmap 應該被認為是一組無符號整數,由覆蓋不相交子集的容器組成。每個子集可以包含大小範圍為 2^16 的值,並且該子集由 16 位鍵索引。這意味著在最壞的情況下,它只需要 16 位來表示一個 32 位值,因此無符號 32 位整數可以儲存為 Java shorts。容器大小的選擇也意味著在最壞的情況下,容器仍將適合現代處理器的 L1 快取。

下面是範圍編碼位片索引的實現,作為如何使用 RoaringBitmaps 的示例。

public class RangeEncodedOptBitSliceIndex implements RoaringIndex {

  private final int[] basis;
  private final int[] cumulativeBasis;
  private final RoaringBitmap[][] bitslice;

  public RangeEncodedOptBitSliceIndex(ProjectionIndex projectionIndex, int[] basis) {
    this.basis = basis;
    this.cumulativeBasis = accumulateBasis(basis);
    this.bitslice = BitSlices.createRangeEncodedBitSlice(projectionIndex, basis);
  }

  @Override
  public RoaringBitmap whereEqual(int code, RoaringBitmap existence) {
    RoaringBitmap result = existence.clone();
    int[] expansion = expand(code, cumulativeBasis);
    for(int i = 0; i < cumulativeBasis.length; ++i) {
      int component = expansion[i];
      if(component == 0) {
        result.and(bitslice[i][0]);
      }
      else if(component == basis[i] - 1) {
        result.andNot(bitslice[i][basis[i] - 2]);
      }
      else {
        result.and(FastAggregation.xor(bitslice[i][component], bitslice[i][component - 1]));
      }
    }
    return result;
  }

  @Override
  public RoaringBitmap whereNotEqual(int code, RoaringBitmap existence) {
    RoaringBitmap inequality = existence.clone();
    inequality.andNot(whereEqual(code, existence));
    return inequality;
  }

  @Override
  public RoaringBitmap whereLessThan(int code, RoaringBitmap existence) {
    return whereLessThanOrEqual(code - 1, existence);
  }

  @Override
  public RoaringBitmap whereLessThanOrEqual(int code, RoaringBitmap existence) {
    final int[] expansion = expand(code, cumulativeBasis);
    final int firstIndex = cumulativeBasis.length - 1;
    int component = expansion[firstIndex];
    int threshold = basis[firstIndex] - 1;
    RoaringBitmap result = component < threshold ? bitslice[firstIndex][component].clone() : existence.clone();     for(int i = firstIndex - 1; i >= 0; --i) {
      component = expansion[i];
      threshold = basis[i] - 1;
      if(component != threshold) {
        result.and(bitslice[i][component]);
      }
      if(component != 0) {
        result.or(bitslice[i][component - 1]);
      }
    }
    return result;
  }

  @Override
  public RoaringBitmap whereGreaterThan(int code, RoaringBitmap existence) {
    RoaringBitmap result = existence.clone();
    result.andNot(whereLessThanOrEqual(code, existence));
    return result;
  }

  @Override
  public RoaringBitmap whereGreaterThanOrEqual(int code, RoaringBitmap existence) {
    RoaringBitmap result = existence.clone();
    result.andNot(whereLessThan(code, existence));
    return result;
  }
}

 

相關文章